/* $NetBSD: aac.c,v 1.8 2003/01/31 00:26:25 thorpej Exp $ */ /*- * Copyright (c) 2002 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Andrew Doran. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /*- * Copyright (c) 2001 Scott Long * Copyright (c) 2001 Adaptec, Inc. * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * Copyright (c) 2000 Niklas Hallqvist * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Driver for the Adaptec 'FSA' family of PCI/SCSI RAID adapters. * * TODO: * * o Management interface. * o Look again at some of the portability issues. * o Handle various AIFs (e.g., notification that a container is going away). */ #include __KERNEL_RCSID(0, "$NetBSD: aac.c,v 1.8 2003/01/31 00:26:25 thorpej Exp $"); #include "locators.h" #include #include #include #include #include #include #include #include #include #include #include int aac_check_firmware(struct aac_softc *); void aac_describe_controller(struct aac_softc *); int aac_dequeue_fib(struct aac_softc *, int, u_int32_t *, struct aac_fib **); int aac_enqueue_fib(struct aac_softc *, int, struct aac_fib *); void aac_host_command(struct aac_softc *); void aac_host_response(struct aac_softc *); int aac_init(struct aac_softc *); int aac_print(void *, const char *); void aac_shutdown(void *); void aac_startup(struct aac_softc *); int aac_sync_command(struct aac_softc *, u_int32_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t *); int aac_sync_fib(struct aac_softc *, u_int32_t, u_int32_t, void *, u_int16_t, void *, u_int16_t *); int aac_submatch(struct device *, struct cfdata *, void *); #ifdef AAC_DEBUG void aac_print_fib(struct aac_softc *, struct aac_fib *, char *); #endif /* * Adapter-space FIB queue manipulation. * * Note that the queue implementation here is a little funky; neither the PI or * CI will ever be zero. This behaviour is a controller feature. */ static struct { int size; int notify; } const aac_qinfo[] = { { AAC_HOST_NORM_CMD_ENTRIES, AAC_DB_COMMAND_NOT_FULL }, { AAC_HOST_HIGH_CMD_ENTRIES, 0 }, { AAC_ADAP_NORM_CMD_ENTRIES, AAC_DB_COMMAND_READY }, { AAC_ADAP_HIGH_CMD_ENTRIES, 0 }, { AAC_HOST_NORM_RESP_ENTRIES, AAC_DB_RESPONSE_NOT_FULL }, { AAC_HOST_HIGH_RESP_ENTRIES, 0 }, { AAC_ADAP_NORM_RESP_ENTRIES, AAC_DB_RESPONSE_READY }, { AAC_ADAP_HIGH_RESP_ENTRIES, 0 } }; #ifdef AAC_DEBUG int aac_debug = AAC_DEBUG; #endif void *aac_sdh; extern struct cfdriver aac_cd; int aac_attach(struct aac_softc *sc) { struct aac_attach_args aaca; int nsegs, i, rv, state, size; struct aac_ccb *ac; struct aac_fib *fib; bus_addr_t fibpa; SIMPLEQ_INIT(&sc->sc_ccb_free); SIMPLEQ_INIT(&sc->sc_ccb_queue); SIMPLEQ_INIT(&sc->sc_ccb_complete); /* * Disable interrupts before we do anything. */ AAC_MASK_INTERRUPTS(sc); /* * Initialise the adapter. */ if (aac_check_firmware(sc)) return (EINVAL); if ((rv = aac_init(sc)) != 0) return (rv); aac_startup(sc); /* * Print a little information about the controller. */ aac_describe_controller(sc); /* * Initialize the ccbs. */ sc->sc_ccbs = malloc(sizeof(*ac) * AAC_NCCBS, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_ccbs == NULL) { aprint_error("%s: memory allocation failure\n", sc->sc_dv.dv_xname); return (ENOMEM); } state = 0; size = sizeof(*fib) * AAC_NCCBS; if ((rv = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_fibs_dmamap)) != 0) { aprint_error("%s: cannot create fibs dmamap\n", sc->sc_dv.dv_xname); goto bail_out; } state++; if ((rv = bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &sc->sc_fibs_seg, 1, &nsegs, BUS_DMA_NOWAIT)) != 0) { aprint_error("%s: can't allocate fibs structure\n", sc->sc_dv.dv_xname); goto bail_out; } state++; if ((rv = bus_dmamem_map(sc->sc_dmat, &sc->sc_fibs_seg, nsegs, size, (caddr_t *)&sc->sc_fibs, 0)) != 0) { aprint_error("%s: can't map fibs structure\n", sc->sc_dv.dv_xname); goto bail_out; } state++; if ((rv = bus_dmamap_load(sc->sc_dmat, sc->sc_fibs_dmamap, sc->sc_fibs, size, NULL, BUS_DMA_NOWAIT)) != 0) { aprint_error("%s: cannot load fibs dmamap\n", sc->sc_dv.dv_xname); goto bail_out; } state++; memset(sc->sc_fibs, 0, size); fibpa = sc->sc_fibs_seg.ds_addr; fib = sc->sc_fibs; for (i = 0, ac = sc->sc_ccbs; i < AAC_NCCBS; i++, ac++) { rv = bus_dmamap_create(sc->sc_dmat, AAC_MAX_XFER, AAC_MAX_SGENTRIES, AAC_MAX_XFER, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ac->ac_dmamap_xfer); if (rv) { while (--ac >= sc->sc_ccbs) bus_dmamap_destroy(sc->sc_dmat, ac->ac_dmamap_xfer); aprint_error("%s: cannot create ccb dmamap (%d)", sc->sc_dv.dv_xname, rv); goto bail_out; } ac->ac_fib = fib++; ac->ac_fibphys = fibpa; fibpa += sizeof(*fib); aac_ccb_free(sc, ac); } /* * Attach devices. */ for (i = 0; i < AAC_MAX_CONTAINERS; i++) { if (!sc->sc_hdr[i].hd_present) continue; aaca.aaca_unit = i; config_found_sm(&sc->sc_dv, &aaca, aac_print, aac_submatch); } /* * Enable interrupts, and register our shutdown hook. */ sc->sc_flags |= AAC_ONLINE; AAC_UNMASK_INTERRUPTS(sc); if (aac_sdh != NULL) shutdownhook_establish(aac_shutdown, NULL); return (0); bail_out: bus_dmamap_unload(sc->sc_dmat, sc->sc_common_dmamap); bus_dmamem_unmap(sc->sc_dmat, (caddr_t)sc->sc_common, sizeof(*sc->sc_common)); bus_dmamem_free(sc->sc_dmat, &sc->sc_common_seg, 1); bus_dmamap_destroy(sc->sc_dmat, sc->sc_common_dmamap); if (state > 3) bus_dmamap_unload(sc->sc_dmat, sc->sc_fibs_dmamap); if (state > 2) bus_dmamem_unmap(sc->sc_dmat, (caddr_t)sc->sc_fibs, size); if (state > 1) bus_dmamem_free(sc->sc_dmat, &sc->sc_fibs_seg, 1); if (state > 0) bus_dmamap_destroy(sc->sc_dmat, sc->sc_fibs_dmamap); free(sc->sc_ccbs, M_DEVBUF); return (rv); } /* * Print autoconfiguration message for a sub-device. */ int aac_print(void *aux, const char *pnp) { struct aac_attach_args *aaca; aaca = aux; if (pnp != NULL) aprint_normal("block device at %s", pnp); aprint_normal(" unit %d", aaca->aaca_unit); return (UNCONF); } /* * Match a sub-device. */ int aac_submatch(struct device *parent, struct cfdata *cf, void *aux) { struct aac_attach_args *aaca; aaca = aux; if (cf->aaccf_unit != AACCF_UNIT_DEFAULT && cf->aaccf_unit != aaca->aaca_unit) return (0); return (config_match(parent, cf, aux)); } /* * Look up a text description of a numeric error code and return a pointer to * same. */ const char * aac_describe_code(const struct aac_code_lookup *table, u_int32_t code) { int i; for (i = 0; table[i].string != NULL; i++) if (table[i].code == code) return (table[i].string); return (table[i + 1].string); } void aac_describe_controller(struct aac_softc *sc) { u_int8_t buf[AAC_FIB_DATASIZE]; u_int16_t bufsize; struct aac_adapter_info *info; u_int8_t arg; arg = 0; if (aac_sync_fib(sc, RequestAdapterInfo, 0, &arg, sizeof(arg), &buf, &bufsize)) { aprint_error("%s: RequestAdapterInfo failed\n", sc->sc_dv.dv_xname); return; } if (bufsize != sizeof(*info)) { aprint_error("%s: " "RequestAdapterInfo returned wrong data size (%d != %d)\n", sc->sc_dv.dv_xname, bufsize, sizeof(*info)); return; } info = (struct aac_adapter_info *)&buf[0]; aprint_normal("%s: %s at %dMHz, %dMB cache, %s, kernel %d.%d-%d\n", sc->sc_dv.dv_xname, aac_describe_code(aac_cpu_variant, le32toh(info->CpuVariant)), le32toh(info->ClockSpeed), le32toh(info->BufferMem) / (1024 * 1024), aac_describe_code(aac_battery_platform, le32toh(info->batteryPlatform)), info->KernelRevision.external.comp.major, info->KernelRevision.external.comp.minor, info->KernelRevision.external.comp.dash); /* Save the kernel revision structure for later use. */ sc->sc_revision = info->KernelRevision; } /* * Retrieve the firmware version numbers. Dell PERC2/QC cards with firmware * version 1.x are not compatible with this driver. */ int aac_check_firmware(struct aac_softc *sc) { u_int32_t major, minor; if ((sc->sc_quirks & AAC_QUIRK_PERC2QC) != 0) { if (aac_sync_command(sc, AAC_MONKER_GETKERNVER, 0, 0, 0, 0, NULL)) { aprint_error("%s: error reading firmware version\n", sc->sc_dv.dv_xname); return (1); } /* These numbers are stored as ASCII! */ major = (AAC_GETREG4(sc, AAC_SA_MAILBOX + 4) & 0xff) - 0x30; minor = (AAC_GETREG4(sc, AAC_SA_MAILBOX + 8) & 0xff) - 0x30; if (major == 1) { aprint_error( "%s: firmware version %d.%d not supported.\n", sc->sc_dv.dv_xname, major, minor); return (1); } } return (0); } int aac_init(struct aac_softc *sc) { int nsegs, i, rv, state, norm, high; struct aac_adapter_init *ip; u_int32_t code; u_int8_t *qaddr; state = 0; /* * First wait for the adapter to come ready. */ for (i = 0; i < AAC_BOOT_TIMEOUT * 1000; i++) { code = AAC_GET_FWSTATUS(sc); if ((code & AAC_SELF_TEST_FAILED) != 0) { aprint_error("%s: FATAL: selftest failed\n", sc->sc_dv.dv_xname); return (ENXIO); } if ((code & AAC_KERNEL_PANIC) != 0) { aprint_error("%s: FATAL: controller kernel panic\n", sc->sc_dv.dv_xname); return (ENXIO); } if ((code & AAC_UP_AND_RUNNING) != 0) break; DELAY(1000); } if (i == AAC_BOOT_TIMEOUT * 1000) { aprint_error( "%s: FATAL: controller not coming ready, status %x\n", sc->sc_dv.dv_xname, code); return (ENXIO); } if ((rv = bus_dmamap_create(sc->sc_dmat, sizeof(*sc->sc_common), 1, sizeof(*sc->sc_common), 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_common_dmamap)) != 0) { aprint_error("%s: cannot create common dmamap\n", sc->sc_dv.dv_xname); return (rv); } if ((rv = bus_dmamem_alloc(sc->sc_dmat, sizeof(*sc->sc_common), PAGE_SIZE, 0, &sc->sc_common_seg, 1, &nsegs, BUS_DMA_NOWAIT)) != 0) { aprint_error("%s: can't allocate common structure\n", sc->sc_dv.dv_xname); goto bail_out; } state++; if ((rv = bus_dmamem_map(sc->sc_dmat, &sc->sc_common_seg, nsegs, sizeof(*sc->sc_common), (caddr_t *)&sc->sc_common, 0)) != 0) { aprint_error("%s: can't map common structure\n", sc->sc_dv.dv_xname); goto bail_out; } state++; if ((rv = bus_dmamap_load(sc->sc_dmat, sc->sc_common_dmamap, sc->sc_common, sizeof(*sc->sc_common), NULL, BUS_DMA_NOWAIT)) != 0) { aprint_error("%s: cannot load common dmamap\n", sc->sc_dv.dv_xname); goto bail_out; } state++; memset(sc->sc_common, 0, sizeof(*sc->sc_common)); /* * Fill in the init structure. This tells the adapter about the * physical location of various important shared data structures. */ ip = &sc->sc_common->ac_init; ip->InitStructRevision = htole32(AAC_INIT_STRUCT_REVISION); ip->AdapterFibsPhysicalAddress = htole32(sc->sc_common_seg.ds_addr + offsetof(struct aac_common, ac_fibs)); ip->AdapterFibsVirtualAddress = htole32(&sc->sc_common->ac_fibs[0]); ip->AdapterFibsSize = htole32(AAC_ADAPTER_FIBS * sizeof(struct aac_fib)); ip->AdapterFibAlign = htole32(sizeof(struct aac_fib)); ip->PrintfBufferAddress = htole32(sc->sc_common_seg.ds_addr + offsetof(struct aac_common, ac_printf)); ip->PrintfBufferSize = htole32(AAC_PRINTF_BUFSIZE); ip->HostPhysMemPages = 0; /* not used? */ ip->HostElapsedSeconds = 0; /* reset later if invalid */ /* * Initialise FIB queues. Note that it appears that the layout of * the indexes and the segmentation of the entries is mandated by * the adapter, which is only told about the base of the queue index * fields. * * The initial values of the indices are assumed to inform the * adapter of the sizes of the respective queues. * * The Linux driver uses a much more complex scheme whereby several * header records are kept for each queue. We use a couple of * generic list manipulation functions which 'know' the size of each * list by virtue of a table. */ qaddr = &sc->sc_common->ac_qbuf[0] + AAC_QUEUE_ALIGN; qaddr -= (u_int32_t)qaddr % AAC_QUEUE_ALIGN; /* XXX not portable */ sc->sc_queues = (struct aac_queue_table *)qaddr; ip->CommHeaderAddress = htole32(sc->sc_common_seg.ds_addr + ((caddr_t)sc->sc_queues - (caddr_t)sc->sc_common)); memset(sc->sc_queues, 0, sizeof(struct aac_queue_table)); norm = htole32(AAC_HOST_NORM_CMD_ENTRIES); high = htole32(AAC_HOST_HIGH_CMD_ENTRIES); sc->sc_queues->qt_qindex[AAC_HOST_NORM_CMD_QUEUE][AAC_PRODUCER_INDEX] = norm; sc->sc_queues->qt_qindex[AAC_HOST_NORM_CMD_QUEUE][AAC_CONSUMER_INDEX] = norm; sc->sc_queues->qt_qindex[AAC_HOST_HIGH_CMD_QUEUE][AAC_PRODUCER_INDEX] = high; sc->sc_queues->qt_qindex[AAC_HOST_HIGH_CMD_QUEUE][AAC_CONSUMER_INDEX] = high; norm = htole32(AAC_ADAP_NORM_CMD_ENTRIES); high = htole32(AAC_ADAP_HIGH_CMD_ENTRIES); sc->sc_queues->qt_qindex[AAC_ADAP_NORM_CMD_QUEUE][AAC_PRODUCER_INDEX] = norm; sc->sc_queues->qt_qindex[AAC_ADAP_NORM_CMD_QUEUE][AAC_CONSUMER_INDEX] = norm; sc->sc_queues->qt_qindex[AAC_ADAP_HIGH_CMD_QUEUE][AAC_PRODUCER_INDEX] = high; sc->sc_queues->qt_qindex[AAC_ADAP_HIGH_CMD_QUEUE][AAC_CONSUMER_INDEX] = high; norm = htole32(AAC_HOST_NORM_RESP_ENTRIES); high = htole32(AAC_HOST_HIGH_RESP_ENTRIES); sc->sc_queues-> qt_qindex[AAC_HOST_NORM_RESP_QUEUE][AAC_PRODUCER_INDEX] = norm; sc->sc_queues-> qt_qindex[AAC_HOST_NORM_RESP_QUEUE][AAC_CONSUMER_INDEX] = norm; sc->sc_queues-> qt_qindex[AAC_HOST_HIGH_RESP_QUEUE][AAC_PRODUCER_INDEX] = high; sc->sc_queues-> qt_qindex[AAC_HOST_HIGH_RESP_QUEUE][AAC_CONSUMER_INDEX] = high; norm = htole32(AAC_ADAP_NORM_RESP_ENTRIES); high = htole32(AAC_ADAP_HIGH_RESP_ENTRIES); sc->sc_queues-> qt_qindex[AAC_ADAP_NORM_RESP_QUEUE][AAC_PRODUCER_INDEX] = norm; sc->sc_queues-> qt_qindex[AAC_ADAP_NORM_RESP_QUEUE][AAC_CONSUMER_INDEX] = norm; sc->sc_queues-> qt_qindex[AAC_ADAP_HIGH_RESP_QUEUE][AAC_PRODUCER_INDEX] = high; sc->sc_queues-> qt_qindex[AAC_ADAP_HIGH_RESP_QUEUE][AAC_CONSUMER_INDEX] = high; sc->sc_qentries[AAC_HOST_NORM_CMD_QUEUE] = &sc->sc_queues->qt_HostNormCmdQueue[0]; sc->sc_qentries[AAC_HOST_HIGH_CMD_QUEUE] = &sc->sc_queues->qt_HostHighCmdQueue[0]; sc->sc_qentries[AAC_ADAP_NORM_CMD_QUEUE] = &sc->sc_queues->qt_AdapNormCmdQueue[0]; sc->sc_qentries[AAC_ADAP_HIGH_CMD_QUEUE] = &sc->sc_queues->qt_AdapHighCmdQueue[0]; sc->sc_qentries[AAC_HOST_NORM_RESP_QUEUE] = &sc->sc_queues->qt_HostNormRespQueue[0]; sc->sc_qentries[AAC_HOST_HIGH_RESP_QUEUE] = &sc->sc_queues->qt_HostHighRespQueue[0]; sc->sc_qentries[AAC_ADAP_NORM_RESP_QUEUE] = &sc->sc_queues->qt_AdapNormRespQueue[0]; sc->sc_qentries[AAC_ADAP_HIGH_RESP_QUEUE] = &sc->sc_queues->qt_AdapHighRespQueue[0]; /* * Do controller-type-specific initialisation */ switch (sc->sc_hwif) { case AAC_HWIF_I960RX: AAC_SETREG4(sc, AAC_RX_ODBR, ~0); break; } bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, 0, sizeof(*sc->sc_common), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* * Give the init structure to the controller. */ if (aac_sync_command(sc, AAC_MONKER_INITSTRUCT, sc->sc_common_seg.ds_addr + offsetof(struct aac_common, ac_init), 0, 0, 0, NULL)) { aprint_error("%s: error establishing init structure\n", sc->sc_dv.dv_xname); rv = EIO; goto bail_out; } return (0); bail_out: if (state > 2) bus_dmamap_unload(sc->sc_dmat, sc->sc_common_dmamap); if (state > 1) bus_dmamem_unmap(sc->sc_dmat, (caddr_t)sc->sc_common, sizeof(*sc->sc_common)); if (state > 0) bus_dmamem_free(sc->sc_dmat, &sc->sc_common_seg, 1); bus_dmamap_destroy(sc->sc_dmat, sc->sc_common_dmamap); return (rv); } /* * Probe for containers, create disks. */ void aac_startup(struct aac_softc *sc) { struct aac_mntinfo mi; struct aac_mntinforesponse mir; struct aac_drive *hd; u_int16_t rsize; int i; /* * Loop over possible containers. */ mi.Command = htole32(VM_NameServe); mi.MntType = htole32(FT_FILESYS); hd = sc->sc_hdr; for (i = 0; i < AAC_MAX_CONTAINERS; i++, hd++) { /* * Request information on this container. */ mi.MntCount = htole32(i); if (aac_sync_fib(sc, ContainerCommand, 0, &mi, sizeof(mi), &mir, &rsize)) { aprint_error("%s: error probing container %d\n", sc->sc_dv.dv_xname, i); continue; } if (rsize != sizeof(mir)) { aprint_error("%s: container info response wrong size " "(%d should be %d)\n", sc->sc_dv.dv_xname, rsize, sizeof(mir)); continue; } /* * Check container volume type for validity. Note that many * of the possible types may never show up. */ if (le32toh(mir.Status) != ST_OK || le32toh(mir.MntTable[0].VolType) == CT_NONE) continue; hd->hd_present = 1; hd->hd_size = le32toh(mir.MntTable[0].Capacity); hd->hd_devtype = le32toh(mir.MntTable[0].VolType); hd->hd_size &= ~0x1f; sc->sc_nunits++; } } void aac_shutdown(void *cookie) { struct aac_softc *sc; struct aac_close_command cc; u_int32_t i; for (i = 0; i < aac_cd.cd_ndevs; i++) { if ((sc = device_lookup(&aac_cd, i)) == NULL) continue; if ((sc->sc_flags & AAC_ONLINE) == 0) continue; AAC_MASK_INTERRUPTS(sc); /* * Send a Container shutdown followed by a HostShutdown FIB * to the controller to convince it that we don't want to * talk to it anymore. We've been closed and all I/O * completed already */ cc.Command = htole32(VM_CloseAll); cc.ContainerId = 0xffffffff; if (aac_sync_fib(sc, ContainerCommand, 0, &cc, sizeof(cc), NULL, NULL)) { printf("%s: unable to halt controller\n", sc->sc_dv.dv_xname); continue; } /* * Note that issuing this command to the controller makes it * shut down but also keeps it from coming back up without a * reset of the PCI bus. */ if (aac_sync_fib(sc, FsaHostShutdown, AAC_FIBSTATE_SHUTDOWN, &i, sizeof(i), NULL, NULL)) printf("%s: unable to halt controller\n", sc->sc_dv.dv_xname); } } /* * Take an interrupt. */ int aac_intr(void *cookie) { struct aac_softc *sc; u_int16_t reason; int claimed; sc = cookie; claimed = 0; AAC_DPRINTF(AAC_D_INTR, ("aac_intr(%p) ", sc)); reason = AAC_GET_ISTATUS(sc); AAC_DPRINTF(AAC_D_INTR, ("istatus 0x%04x ", reason)); /* * Controller wants to talk to the log. XXX Should we defer this? */ if ((reason & AAC_DB_PRINTF) != 0) { if (sc->sc_common->ac_printf[0] != '\0') { printf("%s: WARNING: adapter logged message:\n", sc->sc_dv.dv_xname); printf("%s: %.*s", sc->sc_dv.dv_xname, AAC_PRINTF_BUFSIZE, sc->sc_common->ac_printf); sc->sc_common->ac_printf[0] = '\0'; } AAC_CLEAR_ISTATUS(sc, AAC_DB_PRINTF); AAC_QNOTIFY(sc, AAC_DB_PRINTF); claimed = 1; } /* * Controller has a message for us? */ if ((reason & AAC_DB_COMMAND_READY) != 0) { aac_host_command(sc); AAC_CLEAR_ISTATUS(sc, AAC_DB_COMMAND_READY); claimed = 1; } /* * Controller has a response for us? */ if ((reason & AAC_DB_RESPONSE_READY) != 0) { aac_host_response(sc); AAC_CLEAR_ISTATUS(sc, AAC_DB_RESPONSE_READY); claimed = 1; } /* * Spurious interrupts that we don't use - reset the mask and clear * the interrupts. */ if ((reason & (AAC_DB_SYNC_COMMAND | AAC_DB_COMMAND_NOT_FULL | AAC_DB_RESPONSE_NOT_FULL)) != 0) { AAC_UNMASK_INTERRUPTS(sc); AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND | AAC_DB_COMMAND_NOT_FULL | AAC_DB_RESPONSE_NOT_FULL); claimed = 1; } return (claimed); } /* * Handle notification of one or more FIBs coming from the controller. */ void aac_host_command(struct aac_softc *sc) { struct aac_fib *fib; u_int32_t fib_size; for (;;) { if (aac_dequeue_fib(sc, AAC_HOST_NORM_CMD_QUEUE, &fib_size, &fib)) break; /* nothing to do */ bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)fib - (caddr_t)sc->sc_common, sizeof(*fib), BUS_DMASYNC_POSTREAD); switch (le16toh(fib->Header.Command)) { case AifRequest: #ifdef notyet aac_handle_aif(sc, (struct aac_aif_command *)&fib->data[0]); #endif break; default: printf("%s: unknown command from controller\n", sc->sc_dv.dv_xname); AAC_PRINT_FIB(sc, fib); break; } bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)fib - (caddr_t)sc->sc_common, sizeof(*fib), BUS_DMASYNC_PREREAD); /* XXX reply to FIBs requesting responses ?? */ /* XXX how do we return these FIBs to the controller? */ } } /* * Handle notification of one or more FIBs completed by the controller */ void aac_host_response(struct aac_softc *sc) { struct aac_ccb *ac; struct aac_fib *fib; u_int32_t fib_size; /* * Look for completed FIBs on our queue. */ for (;;) { if (aac_dequeue_fib(sc, AAC_HOST_NORM_RESP_QUEUE, &fib_size, &fib)) break; /* nothing to do */ bus_dmamap_sync(sc->sc_dmat, sc->sc_fibs_dmamap, (caddr_t)fib - (caddr_t)sc->sc_fibs, sizeof(*fib), BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); if ((fib->Header.SenderData & 0x80000000) == 0) { /* Not valid; not sent by us. */ AAC_PRINT_FIB(sc, fib); } else { ac = (struct aac_ccb *)((caddr_t)sc->sc_ccbs + (fib->Header.SenderData & 0x7fffffff)); fib->Header.SenderData = 0; SIMPLEQ_INSERT_TAIL(&sc->sc_ccb_complete, ac, ac_chain); } } /* * Deal with any completed commands. */ while ((ac = SIMPLEQ_FIRST(&sc->sc_ccb_complete)) != NULL) { SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_complete, ac_chain); ac->ac_flags |= AAC_CCB_COMPLETED; if (ac->ac_intr != NULL) (*ac->ac_intr)(ac); } /* * Try to submit more commands. */ if (! SIMPLEQ_EMPTY(&sc->sc_ccb_queue)) aac_ccb_enqueue(sc, NULL); } /* * Send a synchronous command to the controller and wait for a result. */ int aac_sync_command(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3, u_int32_t *sp) { int i; u_int32_t status; int s; s = splbio(); /* Populate the mailbox. */ AAC_SET_MAILBOX(sc, command, arg0, arg1, arg2, arg3); /* Ensure the sync command doorbell flag is cleared. */ AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* ... then set it to signal the adapter. */ AAC_QNOTIFY(sc, AAC_DB_SYNC_COMMAND); DELAY(AAC_SYNC_DELAY); /* Spin waiting for the command to complete. */ for (i = 0; i < AAC_IMMEDIATE_TIMEOUT * 1000; i++) { if (AAC_GET_ISTATUS(sc) & AAC_DB_SYNC_COMMAND); break; DELAY(1000); } if (i == AAC_IMMEDIATE_TIMEOUT * 1000) { splx(s); return (EIO); } /* Clear the completion flag. */ AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* Get the command status. */ status = AAC_GET_MAILBOXSTATUS(sc); splx(s); if (sp != NULL) *sp = status; return (0); /* XXX Check command return status? */ } /* * Send a synchronous FIB to the controller and wait for a result. */ int aac_sync_fib(struct aac_softc *sc, u_int32_t command, u_int32_t xferstate, void *data, u_int16_t datasize, void *result, u_int16_t *resultsize) { struct aac_fib *fib; u_int32_t fibpa, status; fib = &sc->sc_common->ac_sync_fib; fibpa = sc->sc_common_seg.ds_addr + offsetof(struct aac_common, ac_sync_fib); if (datasize > AAC_FIB_DATASIZE) return (EINVAL); /* * Set up the sync FIB. */ fib->Header.XferState = htole32(AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | xferstate); fib->Header.Command = htole16(command); fib->Header.StructType = AAC_FIBTYPE_TFIB; fib->Header.Size = htole16(sizeof(*fib) + datasize); fib->Header.SenderSize = htole16(sizeof(*fib)); fib->Header.SenderFibAddress = htole32((u_int32_t)fib); /* XXX */ fib->Header.ReceiverFibAddress = htole32(fibpa); /* * Copy in data. */ if (data != NULL) { memcpy(fib->data, data, datasize); fib->Header.XferState |= htole32(AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_NORM); } bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)fib - (caddr_t)sc->sc_common, sizeof(*fib), BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); /* * Give the FIB to the controller, wait for a response. */ if (aac_sync_command(sc, AAC_MONKER_SYNCFIB, fibpa, 0, 0, 0, &status)) return (EIO); bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)fib - (caddr_t)sc->sc_common, sizeof(*fib), BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); /* * Copy out the result */ if (result != NULL) { *resultsize = le16toh(fib->Header.Size) - sizeof(fib->Header); memcpy(result, fib->data, *resultsize); } return (0); } struct aac_ccb * aac_ccb_alloc(struct aac_softc *sc, int flags) { struct aac_ccb *ac; int s; AAC_DPRINTF(AAC_D_QUEUE, ("aac_ccb_alloc(%p, 0x%x) ", sc, flags)); s = splbio(); ac = SIMPLEQ_FIRST(&sc->sc_ccb_free); #ifdef DIAGNOSTIC if (ac == NULL) panic("aac_ccb_get: no free CCBS"); #endif SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_free, ac_chain); splx(s); ac->ac_flags = flags; return (ac); } void aac_ccb_free(struct aac_softc *sc, struct aac_ccb *ac) { int s; AAC_DPRINTF(AAC_D_QUEUE, ("aac_ccb_free(%p, %p) ", sc, ac)); ac->ac_flags = 0; ac->ac_intr = NULL; ac->ac_fib->Header.XferState = htole32(AAC_FIBSTATE_EMPTY); ac->ac_fib->Header.StructType = AAC_FIBTYPE_TFIB; ac->ac_fib->Header.Flags = 0; ac->ac_fib->Header.SenderSize = htole16(sizeof(*ac->ac_fib)); #ifdef AAC_DEBUG /* * These are duplicated in aac_ccb_submit() to cover the case where * an intermediate stage may have destroyed them. They're left * initialised here for debugging purposes only. */ ac->ac_fib->Header.SenderFibAddress = htole32((u_int32_t)ac->ac_fib); ac->ac_fib->Header.ReceiverFibAddress = htole32(ac->ac_fibphys); #endif s = splbio(); SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_free, ac, ac_chain); splx(s); } int aac_ccb_map(struct aac_softc *sc, struct aac_ccb *ac) { int error; AAC_DPRINTF(AAC_D_QUEUE, ("aac_ccb_map(%p, %p) ", sc, ac)); #ifdef DIAGNOSTIC if ((ac->ac_flags & AAC_CCB_MAPPED) != 0) panic("aac_ccb_map: already mapped"); #endif error = bus_dmamap_load(sc->sc_dmat, ac->ac_dmamap_xfer, ac->ac_data, ac->ac_datalen, NULL, BUS_DMA_NOWAIT | BUS_DMA_STREAMING | ((ac->ac_flags & AAC_CCB_DATA_IN) ? BUS_DMA_READ : BUS_DMA_WRITE)); if (error) { printf("%s: aac_ccb_map: ", sc->sc_dv.dv_xname); if (error == EFBIG) printf("more than %d dma segs\n", AAC_MAX_SGENTRIES); else printf("error %d loading dma map\n", error); return (error); } bus_dmamap_sync(sc->sc_dmat, ac->ac_dmamap_xfer, 0, ac->ac_datalen, (ac->ac_flags & AAC_CCB_DATA_IN) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); #ifdef DIAGNOSTIC ac->ac_flags |= AAC_CCB_MAPPED; #endif return (0); } void aac_ccb_unmap(struct aac_softc *sc, struct aac_ccb *ac) { AAC_DPRINTF(AAC_D_QUEUE, ("aac_ccb_unmap(%p, %p) ", sc, ac)); #ifdef DIAGNOSTIC if ((ac->ac_flags & AAC_CCB_MAPPED) == 0) panic("aac_ccb_unmap: not mapped"); #endif bus_dmamap_sync(sc->sc_dmat, ac->ac_dmamap_xfer, 0, ac->ac_datalen, (ac->ac_flags & AAC_CCB_DATA_IN) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, ac->ac_dmamap_xfer); #ifdef DIAGNOSTIC ac->ac_flags &= ~AAC_CCB_MAPPED; #endif } void aac_ccb_enqueue(struct aac_softc *sc, struct aac_ccb *ac) { int s; AAC_DPRINTF(AAC_D_QUEUE, ("aac_ccb_enqueue(%p, %p) ", sc, ac)); s = splbio(); if (ac != NULL) SIMPLEQ_INSERT_TAIL(&sc->sc_ccb_queue, ac, ac_chain); while ((ac = SIMPLEQ_FIRST(&sc->sc_ccb_queue)) != NULL) { if (aac_ccb_submit(sc, ac)) break; SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_queue, ac_chain); } splx(s); } int aac_ccb_submit(struct aac_softc *sc, struct aac_ccb *ac) { AAC_DPRINTF(AAC_D_QUEUE, ("aac_ccb_submit(%p, %p) ", sc, ac)); /* Fix up the address values. */ ac->ac_fib->Header.SenderFibAddress = htole32((u_int32_t)ac->ac_fib); ac->ac_fib->Header.ReceiverFibAddress = htole32(ac->ac_fibphys); /* Save a pointer to the command for speedy reverse-lookup. */ ac->ac_fib->Header.SenderData = (u_int32_t)((caddr_t)ac - (caddr_t)sc->sc_ccbs) | 0x80000000; bus_dmamap_sync(sc->sc_dmat, sc->sc_fibs_dmamap, (caddr_t)ac->ac_fib - (caddr_t)sc->sc_fibs, sizeof(*ac->ac_fib), BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); /* Put the FIB on the outbound queue. */ return (aac_enqueue_fib(sc, AAC_ADAP_NORM_CMD_QUEUE, ac->ac_fib)); } int aac_ccb_poll(struct aac_softc *sc, struct aac_ccb *ac, int timo) { int rv, s; AAC_DPRINTF(AAC_D_QUEUE, ("aac_ccb_poll(%p, %p, %d) ", sc, ac, timo)); s = splbio(); if ((rv = aac_ccb_submit(sc, ac)) != 0) { splx(s); return (rv); } for (timo *= 1000; timo != 0; timo--) { aac_intr(sc); if ((ac->ac_flags & AAC_CCB_COMPLETED) != 0) break; DELAY(100); } splx(s); return (timo == 0); } /* * Atomically insert an entry into the nominated queue, returns 0 on success * or EBUSY if the queue is full. * * XXX Note that it would be more efficient to defer notifying the * controller in the case where we may be inserting several entries in rapid * succession, but implementing this usefully is difficult. */ int aac_enqueue_fib(struct aac_softc *sc, int queue, struct aac_fib *fib) { u_int32_t fib_size, fib_addr, pi, ci; fib_size = le16toh(fib->Header.Size); fib_addr = le32toh(fib->Header.ReceiverFibAddress); bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)sc->sc_common->ac_qbuf - (caddr_t)sc->sc_common, sizeof(sc->sc_common->ac_qbuf), BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); /* Get the producer/consumer indices. */ pi = le32toh(sc->sc_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]); ci = le32toh(sc->sc_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]); /* Wrap the queue? */ if (pi >= aac_qinfo[queue].size) pi = 0; /* Check for queue full. */ if ((pi + 1) == ci) return (EAGAIN); /* Populate queue entry. */ (sc->sc_qentries[queue] + pi)->aq_fib_size = htole32(fib_size); (sc->sc_qentries[queue] + pi)->aq_fib_addr = htole32(fib_addr); /* Update producer index. */ sc->sc_queues->qt_qindex[queue][AAC_PRODUCER_INDEX] = htole32(pi + 1); bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)sc->sc_common->ac_qbuf - (caddr_t)sc->sc_common, sizeof(sc->sc_common->ac_qbuf), BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); /* Notify the adapter if we know how. */ if (aac_qinfo[queue].notify != 0) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); return (0); } /* * Atomically remove one entry from the nominated queue, returns 0 on success * or ENOENT if the queue is empty. */ int aac_dequeue_fib(struct aac_softc *sc, int queue, u_int32_t *fib_size, struct aac_fib **fib_addr) { u_int32_t pi, ci; int notify; bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)sc->sc_common->ac_qbuf - (caddr_t)sc->sc_common, sizeof(sc->sc_common->ac_qbuf), BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); /* Get the producer/consumer indices. */ pi = le32toh(sc->sc_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]); ci = le32toh(sc->sc_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]); /* Check for queue empty. */ if (ci == pi) return (ENOENT); notify = 0; if (ci == pi + 1) notify = 1; /* Wrap the queue? */ if (ci >= aac_qinfo[queue].size) ci = 0; /* Fetch the entry. */ *fib_size = le32toh((sc->sc_qentries[queue] + ci)->aq_fib_size); *fib_addr = le32toh((struct aac_fib *) (sc->sc_qentries[queue] + ci)->aq_fib_addr); /* Update consumer index. */ sc->sc_queues->qt_qindex[queue][AAC_CONSUMER_INDEX] = ci + 1; bus_dmamap_sync(sc->sc_dmat, sc->sc_common_dmamap, (caddr_t)sc->sc_common->ac_qbuf - (caddr_t)sc->sc_common, sizeof(sc->sc_common->ac_qbuf), BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); /* If we have made the queue un-full, notify the adapter. */ if (notify && (aac_qinfo[queue].notify != 0)) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); return (0); } #ifdef AAC_DEBUG /* * Print a FIB */ void aac_print_fib(struct aac_softc *sc, struct aac_fib *fib, char *caller) { struct aac_blockread *br; struct aac_blockwrite *bw; struct aac_sg_table *sg; char buf[512]; int i; printf("%s: FIB @ %p\n", caller, fib); bitmask_snprintf(le32toh(fib->Header.XferState), "\20" "\1HOSTOWNED" "\2ADAPTEROWNED" "\3INITIALISED" "\4EMPTY" "\5FROMPOOL" "\6FROMHOST" "\7FROMADAP" "\10REXPECTED" "\11RNOTEXPECTED" "\12DONEADAP" "\13DONEHOST" "\14HIGH" "\15NORM" "\16ASYNC" "\17PAGEFILEIO" "\20SHUTDOWN" "\21LAZYWRITE" "\22ADAPMICROFIB" "\23BIOSFIB" "\24FAST_RESPONSE" "\25APIFIB\n", buf, sizeof(buf)); printf(" XferState %s\n", buf); printf(" Command %d\n", le16toh(fib->Header.Command)); printf(" StructType %d\n", fib->Header.StructType); printf(" Flags 0x%x\n", fib->Header.Flags); printf(" Size %d\n", le16toh(fib->Header.Size)); printf(" SenderSize %d\n", le16toh(fib->Header.SenderSize)); printf(" SenderAddress 0x%x\n", le32toh(fib->Header.SenderFibAddress)); printf(" ReceiverAddress 0x%x\n", le32toh(fib->Header.ReceiverFibAddress)); printf(" SenderData 0x%x\n", fib->Header.SenderData); switch (fib->Header.Command) { case ContainerCommand: { br = (struct aac_blockread *)fib->data; bw = (struct aac_blockwrite *)fib->data; sg = NULL; if (le32toh(br->Command) == VM_CtBlockRead) { printf(" BlockRead: container %d 0x%x/%d\n", le32toh(br->ContainerId), le32toh(br->BlockNumber), le32toh(br->ByteCount)); sg = &br->SgMap; } if (le32toh(bw->Command) == VM_CtBlockWrite) { printf(" BlockWrite: container %d 0x%x/%d (%s)\n", le32toh(bw->ContainerId), le32toh(bw->BlockNumber), le32toh(bw->ByteCount), le32toh(bw->Stable) == CSTABLE ? "stable" : "unstable"); sg = &bw->SgMap; } if (sg != NULL) { printf(" %d s/g entries\n", le32toh(sg->SgCount)); for (i = 0; i < le32toh(sg->SgCount); i++) printf(" 0x%08x/%d\n", le32toh(sg->SgEntry[i].SgAddress), le32toh(sg->SgEntry[i].SgByteCount)); } break; } default: printf(" %16D\n", fib->data, " "); printf(" %16D\n", fib->data + 16, " "); break; } } #endif