/* $OpenBSD: qle.c,v 1.66 2024/09/04 07:54:52 mglocker Exp $ */ /* * Copyright (c) 2013, 2014 Jonathan Matthew * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef QLE_DEBUG #define DPRINTF(m, f...) do { if ((qledebug & (m)) == (m)) printf(f); } \ while (0) #define QLE_D_MBOX 0x01 #define QLE_D_INTR 0x02 #define QLE_D_PORT 0x04 #define QLE_D_IO 0x08 #define QLE_D_IOCB 0x10 int qledebug = QLE_D_PORT; #else #define DPRINTF(m, f...) #endif #ifndef QLE_NOFIRMWARE #include #include #endif #define QLE_PCI_MEM_BAR 0x14 #define QLE_PCI_IO_BAR 0x10 #define QLE_DEFAULT_PORT_NAME 0x400000007F000003ULL /* from isp(4) */ #define QLE_WAIT_FOR_LOOP 10 /* seconds */ #define QLE_LOOP_SETTLE 200 /* ms */ /* rounded up range of assignable handles */ #define QLE_MAX_TARGETS 2048 /* maximum number of segments allowed for in a single io */ #define QLE_MAX_SEGS 32 enum qle_isp_gen { QLE_GEN_ISP24XX = 1, QLE_GEN_ISP25XX }; enum qle_isp_type { QLE_ISP2422 = 1, QLE_ISP2432, QLE_ISP2512, QLE_ISP2522, QLE_ISP2532 }; /* port database things */ #define QLE_SCRATCH_SIZE 0x1000 enum qle_port_disp { QLE_PORT_DISP_NEW, QLE_PORT_DISP_GONE, QLE_PORT_DISP_SAME, QLE_PORT_DISP_CHANGED, QLE_PORT_DISP_MOVED, QLE_PORT_DISP_DUP }; #define QLE_LOCATION_LOOP (1 << 24) #define QLE_LOCATION_FABRIC (2 << 24) #define QLE_LOCATION_LOOP_ID(l) (l | QLE_LOCATION_LOOP) #define QLE_LOCATION_PORT_ID(p) (p | QLE_LOCATION_FABRIC) struct qle_fc_port { TAILQ_ENTRY(qle_fc_port) ports; TAILQ_ENTRY(qle_fc_port) update; u_int64_t node_name; u_int64_t port_name; u_int32_t location; /* port id or loop id */ int flags; #define QLE_PORT_FLAG_IS_TARGET 1 #define QLE_PORT_FLAG_NEEDS_LOGIN 2 u_int32_t portid; u_int16_t loopid; }; /* request/response queue stuff */ #define QLE_QUEUE_ENTRY_SIZE 64 struct qle_ccb { struct qle_softc *ccb_sc; int ccb_id; struct scsi_xfer *ccb_xs; bus_dmamap_t ccb_dmamap; struct qle_iocb_seg *ccb_segs; u_int64_t ccb_seg_offset; SIMPLEQ_ENTRY(qle_ccb) ccb_link; }; SIMPLEQ_HEAD(qle_ccb_list, qle_ccb); struct qle_dmamem { bus_dmamap_t qdm_map; bus_dma_segment_t qdm_seg; size_t qdm_size; caddr_t qdm_kva; }; #define QLE_DMA_MAP(_qdm) ((_qdm)->qdm_map) #define QLE_DMA_LEN(_qdm) ((_qdm)->qdm_size) #define QLE_DMA_DVA(_qdm) ((u_int64_t)(_qdm)->qdm_map->dm_segs[0].ds_addr) #define QLE_DMA_KVA(_qdm) ((void *)(_qdm)->qdm_kva) struct qle_softc { struct device sc_dev; pci_chipset_tag_t sc_pc; pcitag_t sc_tag; void *sc_ih; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; bus_size_t sc_ios; bus_dma_tag_t sc_dmat; struct scsibus_softc *sc_scsibus; enum qle_isp_type sc_isp_type; enum qle_isp_gen sc_isp_gen; int sc_port; bus_space_handle_t sc_mbox_ioh; u_int16_t sc_mbox[QLE_MBOX_COUNT]; int sc_mbox_pending; struct mutex sc_mbox_mtx; int sc_loop_up; int sc_topology; int sc_loop_id; int sc_port_id; int sc_loop_max_id; u_int64_t sc_sns_port_name; struct mutex sc_port_mtx; TAILQ_HEAD(, qle_fc_port) sc_ports; TAILQ_HEAD(, qle_fc_port) sc_ports_new; TAILQ_HEAD(, qle_fc_port) sc_ports_gone; TAILQ_HEAD(, qle_fc_port) sc_ports_found; struct qle_fc_port *sc_targets[QLE_MAX_TARGETS]; struct taskq *sc_update_taskq; struct task sc_update_task; struct timeout sc_update_timeout; int sc_update; int sc_update_tasks; #define QLE_UPDATE_TASK_CLEAR_ALL 0x00000001 #define QLE_UPDATE_TASK_SOFTRESET 0x00000002 #define QLE_UPDATE_TASK_UPDATE_TOPO 0x00000004 #define QLE_UPDATE_TASK_GET_PORT_LIST 0x00000008 #define QLE_UPDATE_TASK_PORT_LIST 0x00000010 #define QLE_UPDATE_TASK_SCAN_FABRIC 0x00000020 #define QLE_UPDATE_TASK_SCANNING_FABRIC 0x00000040 #define QLE_UPDATE_TASK_FABRIC_LOGIN 0x00000080 #define QLE_UPDATE_TASK_FABRIC_RELOGIN 0x00000100 #define QLE_UPDATE_TASK_DETACH_TARGET 0x00000200 #define QLE_UPDATE_TASK_ATTACH_TARGET 0x00000400 int sc_maxcmds; struct qle_dmamem *sc_requests; struct qle_dmamem *sc_responses; struct qle_dmamem *sc_segments; struct qle_dmamem *sc_pri_requests; struct qle_dmamem *sc_scratch; struct qle_dmamem *sc_fcp_cmnds; struct qle_ccb *sc_ccbs; struct qle_ccb_list sc_ccb_free; struct mutex sc_ccb_mtx; struct mutex sc_queue_mtx; struct scsi_iopool sc_iopool; u_int32_t sc_next_req_id; u_int32_t sc_last_resp_id; int sc_marker_required; int sc_fabric_pending; u_int8_t sc_fabric_response[QLE_QUEUE_ENTRY_SIZE]; struct qle_nvram sc_nvram; int sc_nvram_valid; }; #define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname) int qle_intr(void *); int qle_match(struct device *, void *, void *); void qle_attach(struct device *, struct device *, void *); int qle_detach(struct device *, int); const struct cfattach qle_ca = { sizeof(struct qle_softc), qle_match, qle_attach, qle_detach }; struct cfdriver qle_cd = { NULL, "qle", DV_DULL }; void qle_scsi_cmd(struct scsi_xfer *); int qle_scsi_probe(struct scsi_link *); const struct scsi_adapter qle_switch = { qle_scsi_cmd, NULL, qle_scsi_probe, NULL, NULL }; u_int32_t qle_read(struct qle_softc *, int); void qle_write(struct qle_softc *, int, u_int32_t); void qle_host_cmd(struct qle_softc *sc, u_int32_t); int qle_mbox(struct qle_softc *, int); int qle_ct_pass_through(struct qle_softc *sc, u_int32_t port_handle, struct qle_dmamem *mem, size_t req_size, size_t resp_size); void qle_mbox_putaddr(u_int16_t *, struct qle_dmamem *); u_int16_t qle_read_mbox(struct qle_softc *, int); void qle_write_mbox(struct qle_softc *, int, u_int16_t); void qle_handle_intr(struct qle_softc *, u_int16_t, u_int16_t); void qle_set_ints(struct qle_softc *, int); int qle_read_isr(struct qle_softc *, u_int16_t *, u_int16_t *); void qle_clear_isr(struct qle_softc *, u_int16_t); void qle_put_marker(struct qle_softc *, void *); void qle_put_cmd(struct qle_softc *, void *, struct scsi_xfer *, struct qle_ccb *, u_int32_t); struct qle_ccb *qle_handle_resp(struct qle_softc *, u_int32_t); void qle_sge(struct qle_iocb_seg *, u_int64_t, u_int32_t); struct qle_fc_port *qle_next_fabric_port(struct qle_softc *, u_int32_t *, u_int32_t *); int qle_get_port_db(struct qle_softc *, u_int16_t, struct qle_dmamem *); int qle_get_port_name_list(struct qle_softc *sc, u_int32_t); int qle_add_loop_port(struct qle_softc *, struct qle_fc_port *); int qle_add_fabric_port(struct qle_softc *, struct qle_fc_port *); int qle_add_logged_in_port(struct qle_softc *, u_int16_t, u_int32_t); int qle_classify_port(struct qle_softc *, u_int32_t, u_int64_t, u_int64_t, struct qle_fc_port **); int qle_get_loop_id(struct qle_softc *sc, int); void qle_clear_port_lists(struct qle_softc *); int qle_softreset(struct qle_softc *); void qle_update_topology(struct qle_softc *); int qle_update_fabric(struct qle_softc *); int qle_fabric_plogx(struct qle_softc *, struct qle_fc_port *, int, u_int32_t *); int qle_fabric_plogi(struct qle_softc *, struct qle_fc_port *); void qle_fabric_plogo(struct qle_softc *, struct qle_fc_port *); void qle_update_start(struct qle_softc *, int); void qle_update_defer(struct qle_softc *, int); void qle_update_cancel(struct qle_softc *); void qle_update_done(struct qle_softc *, int); void qle_do_update(void *); void qle_deferred_update(void *); int qle_async(struct qle_softc *, u_int16_t); int qle_load_fwchunk(struct qle_softc *, struct qle_dmamem *, const u_int32_t *); u_int32_t qle_read_ram_word(struct qle_softc *, u_int32_t); int qle_verify_firmware(struct qle_softc *, u_int32_t); int qle_load_firmware_chunks(struct qle_softc *, const u_int32_t *); int qle_read_nvram(struct qle_softc *); struct qle_dmamem *qle_dmamem_alloc(struct qle_softc *, size_t); void qle_dmamem_free(struct qle_softc *, struct qle_dmamem *); int qle_alloc_ccbs(struct qle_softc *); void qle_free_ccbs(struct qle_softc *); void *qle_get_ccb(void *); void qle_put_ccb(void *, void *); void qle_dump_stuff(struct qle_softc *, void *, int); void qle_dump_iocb(struct qle_softc *, void *); void qle_dump_iocb_segs(struct qle_softc *, void *, int); static const struct pci_matchid qle_devices[] = { { PCI_VENDOR_QLOGIC, PCI_PRODUCT_QLOGIC_ISP2422 }, { PCI_VENDOR_QLOGIC, PCI_PRODUCT_QLOGIC_ISP2432 }, { PCI_VENDOR_QLOGIC, PCI_PRODUCT_QLOGIC_ISP2512 }, { PCI_VENDOR_QLOGIC, PCI_PRODUCT_QLOGIC_ISP2522 }, { PCI_VENDOR_QLOGIC, PCI_PRODUCT_QLOGIC_ISP2532 }, }; int qle_match(struct device *parent, void *match, void *aux) { return (pci_matchbyid(aux, qle_devices, nitems(qle_devices))); } void qle_attach(struct device *parent, struct device *self, void *aux) { struct qle_softc *sc = (void *)self; struct pci_attach_args *pa = aux; pci_intr_handle_t ih; const char *intrstr; u_int32_t pcictl; struct scsibus_attach_args saa; struct qle_init_cb *icb; bus_size_t mbox_base; u_int32_t firmware_addr; #ifndef QLE_NOFIRMWARE const u_int32_t *firmware = NULL; #endif pcireg_t bars[] = { QLE_PCI_MEM_BAR, QLE_PCI_IO_BAR }; pcireg_t memtype; int r, i, rv, loop_up = 0; sc->sc_pc = pa->pa_pc; sc->sc_tag = pa->pa_tag; sc->sc_ih = NULL; sc->sc_dmat = pa->pa_dmat; sc->sc_ios = 0; for (r = 0; r < nitems(bars); r++) { memtype = pci_mapreg_type(sc->sc_pc, sc->sc_tag, bars[r]); if (pci_mapreg_map(pa, bars[r], memtype, 0, &sc->sc_iot, &sc->sc_ioh, NULL, &sc->sc_ios, 0) == 0) break; sc->sc_ios = 0; } if (sc->sc_ios == 0) { printf(": unable to map registers\n"); return; } if (pci_intr_map_msi(pa, &ih) != 0 && pci_intr_map(pa, &ih) != 0) { printf(": unable to map interrupt\n"); goto unmap; } intrstr = pci_intr_string(sc->sc_pc, ih); sc->sc_ih = pci_intr_establish(sc->sc_pc, ih, IPL_BIO, qle_intr, sc, DEVNAME(sc)); if (sc->sc_ih == NULL) { printf(": unable to establish interrupt"); if (intrstr != NULL) printf(" at %s", intrstr); printf("\n"); goto deintr; } printf(": %s\n", intrstr); pcictl = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG); pcictl |= PCI_COMMAND_INVALIDATE_ENABLE | PCI_COMMAND_PARITY_ENABLE | PCI_COMMAND_SERR_ENABLE; pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, pcictl); pcictl = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG); pcictl &= ~(PCI_LATTIMER_MASK << PCI_LATTIMER_SHIFT); pcictl &= ~(PCI_CACHELINE_MASK << PCI_CACHELINE_SHIFT); pcictl |= (0x80 << PCI_LATTIMER_SHIFT); pcictl |= (0x10 << PCI_CACHELINE_SHIFT); pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG, pcictl); pcictl = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_ROM_REG); pcictl &= ~1; pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_ROM_REG, pcictl); switch (PCI_PRODUCT(pa->pa_id)) { case PCI_PRODUCT_QLOGIC_ISP2422: sc->sc_isp_type = QLE_ISP2422; sc->sc_isp_gen = QLE_GEN_ISP24XX; break; case PCI_PRODUCT_QLOGIC_ISP2432: sc->sc_isp_type = QLE_ISP2432; sc->sc_isp_gen = QLE_GEN_ISP24XX; break; case PCI_PRODUCT_QLOGIC_ISP2512: sc->sc_isp_type = QLE_ISP2512; sc->sc_isp_gen = QLE_GEN_ISP25XX; break; case PCI_PRODUCT_QLOGIC_ISP2522: sc->sc_isp_type = QLE_ISP2522; sc->sc_isp_gen = QLE_GEN_ISP25XX; break; case PCI_PRODUCT_QLOGIC_ISP2532: sc->sc_isp_type = QLE_ISP2532; sc->sc_isp_gen = QLE_GEN_ISP25XX; break; default: printf("unknown pci id %x", pa->pa_id); goto deintr; } /* these are the same for 24xx and 25xx but may vary later */ mbox_base = QLE_MBOX_BASE_24XX; firmware_addr = QLE_2400_CODE_ORG; if (bus_space_subregion(sc->sc_iot, sc->sc_ioh, mbox_base, sizeof(sc->sc_mbox), &sc->sc_mbox_ioh) != 0) { printf("%s: unable to map mbox registers\n", DEVNAME(sc)); goto deintr; } sc->sc_port = pa->pa_function; TAILQ_INIT(&sc->sc_ports); TAILQ_INIT(&sc->sc_ports_new); TAILQ_INIT(&sc->sc_ports_gone); TAILQ_INIT(&sc->sc_ports_found); /* after reset, mbox regs 1 and 2 contain the string "ISP " */ if (qle_read_mbox(sc, 1) != 0x4953 || qle_read_mbox(sc, 2) != 0x5020) { /* try releasing the risc processor */ qle_host_cmd(sc, QLE_HOST_CMD_RELEASE); } qle_host_cmd(sc, QLE_HOST_CMD_PAUSE); if (qle_softreset(sc) != 0) { printf("softreset failed\n"); goto deintr; } if (qle_read_nvram(sc) == 0) sc->sc_nvram_valid = 1; #ifdef QLE_NOFIRMWARE if (qle_verify_firmware(sc, firmware_addr)) { printf("%s: no firmware loaded\n", DEVNAME(sc)); goto deintr; } #else switch (sc->sc_isp_gen) { case QLE_GEN_ISP24XX: firmware = isp_2400_risc_code; break; case QLE_GEN_ISP25XX: firmware = isp_2500_risc_code; break; default: printf("%s: no firmware to load?\n", DEVNAME(sc)); goto deintr; } if (qle_load_firmware_chunks(sc, firmware)) { printf("%s: firmware load failed\n", DEVNAME(sc)); goto deintr; } #endif /* execute firmware */ sc->sc_mbox[0] = QLE_MBOX_EXEC_FIRMWARE; sc->sc_mbox[1] = firmware_addr >> 16; sc->sc_mbox[2] = firmware_addr & 0xffff; #ifdef QLE_NOFIRMWARE sc->sc_mbox[3] = 1; #else sc->sc_mbox[3] = 0; #endif sc->sc_mbox[4] = 0; if (qle_mbox(sc, 0x001f)) { printf("ISP couldn't exec firmware: %x\n", sc->sc_mbox[0]); goto deintr; } delay(250000); /* from isp(4) */ sc->sc_mbox[0] = QLE_MBOX_ABOUT_FIRMWARE; if (qle_mbox(sc, 0x0001)) { printf("ISP not talking after firmware exec: %x\n", sc->sc_mbox[0]); goto deintr; } printf("%s: firmware rev %d.%d.%d, attrs 0x%x\n", DEVNAME(sc), sc->sc_mbox[1], sc->sc_mbox[2], sc->sc_mbox[3], sc->sc_mbox[6]); sc->sc_maxcmds = 4096; /* reserve queue slots for markers and fabric ops */ sc->sc_maxcmds -= 2; if (qle_alloc_ccbs(sc)) { /* error already printed */ goto deintr; } sc->sc_scratch = qle_dmamem_alloc(sc, QLE_SCRATCH_SIZE); if (sc->sc_scratch == NULL) { printf("%s: unable to allocate scratch\n", DEVNAME(sc)); goto free_ccbs; } /* build init buffer thing */ icb = (struct qle_init_cb *)QLE_DMA_KVA(sc->sc_scratch); memset(icb, 0, sizeof(*icb)); icb->icb_version = QLE_ICB_VERSION; if (sc->sc_nvram_valid) { icb->icb_max_frame_len = sc->sc_nvram.frame_payload_size; icb->icb_exec_throttle = sc->sc_nvram.execution_throttle; icb->icb_hardaddr = sc->sc_nvram.hard_address; icb->icb_portname = sc->sc_nvram.port_name; icb->icb_nodename = sc->sc_nvram.node_name; icb->icb_login_retry = sc->sc_nvram.login_retry; icb->icb_login_timeout = sc->sc_nvram.login_timeout; icb->icb_fwoptions1 = sc->sc_nvram.fwoptions1; icb->icb_fwoptions2 = sc->sc_nvram.fwoptions2; icb->icb_fwoptions3 = sc->sc_nvram.fwoptions3; } else { /* defaults copied from isp(4) */ htolem16(&icb->icb_max_frame_len, 1024); htolem16(&icb->icb_exec_throttle, 16); icb->icb_portname = htobe64(QLE_DEFAULT_PORT_NAME); icb->icb_nodename = 0; icb->icb_login_retry = 3; htolem32(&icb->icb_fwoptions1, QLE_ICB_FW1_FAIRNESS | QLE_ICB_FW1_HARD_ADDR | QLE_ICB_FW1_FULL_DUPLEX); htolem32(&icb->icb_fwoptions2, QLE_ICB_FW2_LOOP_PTP); htolem32(&icb->icb_fwoptions3, QLE_ICB_FW3_FCP_RSP_24_0 | QLE_ICB_FW3_AUTONEG); } icb->icb_exchange_count = 0; icb->icb_req_out = 0; icb->icb_resp_in = 0; icb->icb_pri_req_out = 0; htolem16(&icb->icb_req_queue_len, sc->sc_maxcmds); htolem16(&icb->icb_resp_queue_len, sc->sc_maxcmds); htolem16(&icb->icb_pri_req_queue_len, 8); /* apparently the minimum */ htolem32(&icb->icb_req_queue_addr_lo, QLE_DMA_DVA(sc->sc_requests)); htolem32(&icb->icb_req_queue_addr_hi, QLE_DMA_DVA(sc->sc_requests) >> 32); htolem32(&icb->icb_resp_queue_addr_lo, QLE_DMA_DVA(sc->sc_responses)); htolem32(&icb->icb_resp_queue_addr_hi, QLE_DMA_DVA(sc->sc_responses) >> 32); htolem32(&icb->icb_pri_req_queue_addr_lo, QLE_DMA_DVA(sc->sc_pri_requests)); htolem32(&icb->icb_pri_req_queue_addr_hi, QLE_DMA_DVA(sc->sc_pri_requests) >> 32); htolem16(&icb->icb_link_down_nos, 200); icb->icb_int_delay = 0; icb->icb_login_timeout = 0; sc->sc_mbox[0] = QLE_MBOX_INIT_FIRMWARE; sc->sc_mbox[4] = 0; sc->sc_mbox[5] = 0; qle_mbox_putaddr(sc->sc_mbox, sc->sc_scratch); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0, sizeof(*icb), BUS_DMASYNC_PREWRITE); rv = qle_mbox(sc, 0x00fd); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0, sizeof(*icb), BUS_DMASYNC_POSTWRITE); if (rv != 0) { printf("%s: ISP firmware init failed: %x\n", DEVNAME(sc), sc->sc_mbox[0]); goto free_scratch; } /* enable some more notifications */ sc->sc_mbox[0] = QLE_MBOX_SET_FIRMWARE_OPTIONS; sc->sc_mbox[1] = QLE_FW_OPTION1_ASYNC_LIP_F8 | QLE_FW_OPTION1_ASYNC_LIP_RESET | QLE_FW_OPTION1_ASYNC_LIP_ERROR | QLE_FW_OPTION1_ASYNC_LOGIN_RJT; sc->sc_mbox[2] = 0; sc->sc_mbox[3] = 0; if (qle_mbox(sc, 0x000f)) { printf("%s: setting firmware options failed: %x\n", DEVNAME(sc), sc->sc_mbox[0]); goto free_scratch; } sc->sc_update_taskq = taskq_create(DEVNAME(sc), 1, IPL_BIO, 0); task_set(&sc->sc_update_task, qle_do_update, sc); timeout_set(&sc->sc_update_timeout, qle_deferred_update, sc); /* wait a bit for link to come up so we can scan and attach devices */ for (i = 0; i < QLE_WAIT_FOR_LOOP * 1000; i++) { u_int16_t isr, info; if (sc->sc_loop_up) { if (++loop_up == QLE_LOOP_SETTLE) break; } else loop_up = 0; delay(1000); if (qle_read_isr(sc, &isr, &info) == 0) continue; qle_handle_intr(sc, isr, info); } if (sc->sc_loop_up) { qle_do_update(sc); } else { DPRINTF(QLE_D_PORT, "%s: loop still down, giving up\n", DEVNAME(sc)); } saa.saa_adapter = &qle_switch; saa.saa_adapter_softc = sc; saa.saa_adapter_target = SDEV_NO_ADAPTER_TARGET; saa.saa_adapter_buswidth = QLE_MAX_TARGETS; saa.saa_luns = 8; saa.saa_openings = sc->sc_maxcmds; saa.saa_pool = &sc->sc_iopool; if (sc->sc_nvram_valid) { saa.saa_wwpn = betoh64(sc->sc_nvram.port_name); saa.saa_wwnn = betoh64(sc->sc_nvram.node_name); } else { saa.saa_wwpn = QLE_DEFAULT_PORT_NAME; saa.saa_wwnn = 0; } if (saa.saa_wwnn == 0) { /* * mask out the port number from the port name to get * the node name. */ saa.saa_wwnn = saa.saa_wwpn; saa.saa_wwnn &= ~(0xfULL << 56); } saa.saa_quirks = saa.saa_flags = 0; sc->sc_scsibus = (struct scsibus_softc *)config_found(&sc->sc_dev, &saa, scsiprint); return; free_scratch: qle_dmamem_free(sc, sc->sc_scratch); free_ccbs: qle_free_ccbs(sc); deintr: pci_intr_disestablish(sc->sc_pc, sc->sc_ih); sc->sc_ih = NULL; unmap: bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); sc->sc_ios = 0; } int qle_detach(struct device *self, int flags) { struct qle_softc *sc = (struct qle_softc *)self; if (sc->sc_ih == NULL) { /* we didn't attach properly, so nothing to detach */ return (0); } pci_intr_disestablish(sc->sc_pc, sc->sc_ih); sc->sc_ih = NULL; bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); sc->sc_ios = 0; return (0); } int qle_classify_port(struct qle_softc *sc, u_int32_t location, u_int64_t port_name, u_int64_t node_name, struct qle_fc_port **prev) { struct qle_fc_port *port, *locmatch, *wwnmatch; locmatch = NULL; wwnmatch = NULL; /* make sure we don't try to add a port or location twice */ TAILQ_FOREACH(port, &sc->sc_ports_new, update) { if ((port->port_name == port_name && port->node_name == node_name) || port->location == location) { *prev = port; return (QLE_PORT_DISP_DUP); } } /* if we're attaching, everything is new */ if (sc->sc_scsibus == NULL) { *prev = NULL; return (QLE_PORT_DISP_NEW); } TAILQ_FOREACH(port, &sc->sc_ports, ports) { if (port->location == location) locmatch = port; if (port->port_name == port_name && port->node_name == node_name) wwnmatch = port; } if (locmatch == NULL && wwnmatch == NULL) { *prev = NULL; return (QLE_PORT_DISP_NEW); } else if (locmatch == wwnmatch) { *prev = locmatch; return (QLE_PORT_DISP_SAME); } else if (wwnmatch != NULL) { *prev = wwnmatch; return (QLE_PORT_DISP_MOVED); } else { *prev = locmatch; return (QLE_PORT_DISP_CHANGED); } } int qle_get_loop_id(struct qle_softc *sc, int start) { int i, last; i = QLE_MIN_HANDLE; last = QLE_MAX_HANDLE; if (i < start) i = start; for (; i <= last; i++) { if (sc->sc_targets[i] == NULL) return (i); } return (-1); } int qle_get_port_db(struct qle_softc *sc, u_int16_t loopid, struct qle_dmamem *mem) { sc->sc_mbox[0] = QLE_MBOX_GET_PORT_DB; sc->sc_mbox[1] = loopid; qle_mbox_putaddr(sc->sc_mbox, mem); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, sizeof(struct qle_get_port_db), BUS_DMASYNC_PREREAD); if (qle_mbox(sc, 0x00cf)) { DPRINTF(QLE_D_PORT, "%s: get port db for %d failed: %x\n", DEVNAME(sc), loopid, sc->sc_mbox[0]); return (1); } bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, sizeof(struct qle_get_port_db), BUS_DMASYNC_POSTREAD); return (0); } int qle_get_port_name_list(struct qle_softc *sc, u_int32_t match) { struct qle_port_name_list *l; struct qle_fc_port *port; int i; sc->sc_mbox[0] = QLE_MBOX_GET_PORT_NAME_LIST; sc->sc_mbox[1] = 0; sc->sc_mbox[8] = QLE_DMA_LEN(sc->sc_scratch); sc->sc_mbox[9] = 0; qle_mbox_putaddr(sc->sc_mbox, sc->sc_scratch); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0, QLE_DMA_LEN(sc->sc_scratch), BUS_DMASYNC_PREREAD); if (qle_mbox(sc, 0x03cf)) { DPRINTF(QLE_D_PORT, "%s: get port name list failed: %x\n", DEVNAME(sc), sc->sc_mbox[0]); return (1); } bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0, sc->sc_mbox[1], BUS_DMASYNC_POSTREAD); i = 0; l = QLE_DMA_KVA(sc->sc_scratch); mtx_enter(&sc->sc_port_mtx); while (i * sizeof(*l) < sc->sc_mbox[1]) { u_int16_t loopid; u_int32_t loc; loopid = lemtoh16(&l[i].loopid) & 0xfff; /* skip special ports */ switch (loopid) { case QLE_F_PORT_HANDLE: case QLE_SNS_HANDLE: case QLE_FABRIC_CTRL_HANDLE: case QLE_IP_BCAST_HANDLE: loc = 0; break; default: if (loopid <= sc->sc_loop_max_id) { loc = QLE_LOCATION_LOOP_ID(loopid); } else { /* * we don't have the port id here, so just * indicate it's a fabric port. */ loc = QLE_LOCATION_FABRIC; } break; } if (match & loc) { port = malloc(sizeof(*port), M_DEVBUF, M_ZERO | M_NOWAIT); if (port == NULL) { printf("%s: failed to allocate port struct\n", DEVNAME(sc)); break; } port->location = loc; port->loopid = loopid; port->port_name = letoh64(l[i].port_name); DPRINTF(QLE_D_PORT, "%s: loop id %d, port name %llx\n", DEVNAME(sc), port->loopid, port->port_name); TAILQ_INSERT_TAIL(&sc->sc_ports_found, port, update); } i++; } mtx_leave(&sc->sc_port_mtx); return (0); } int qle_add_loop_port(struct qle_softc *sc, struct qle_fc_port *port) { struct qle_get_port_db *pdb; struct qle_fc_port *pport; int disp; if (qle_get_port_db(sc, port->loopid, sc->sc_scratch) != 0) { return (1); } pdb = QLE_DMA_KVA(sc->sc_scratch); if (lemtoh16(&pdb->prli_svc_word3) & QLE_SVC3_TARGET_ROLE) port->flags |= QLE_PORT_FLAG_IS_TARGET; port->port_name = betoh64(pdb->port_name); port->node_name = betoh64(pdb->node_name); port->portid = (pdb->port_id[0] << 16) | (pdb->port_id[1] << 8) | pdb->port_id[2]; mtx_enter(&sc->sc_port_mtx); disp = qle_classify_port(sc, port->location, port->port_name, port->node_name, &pport); switch (disp) { case QLE_PORT_DISP_CHANGED: case QLE_PORT_DISP_MOVED: case QLE_PORT_DISP_NEW: TAILQ_INSERT_TAIL(&sc->sc_ports_new, port, update); sc->sc_targets[port->loopid] = port; break; case QLE_PORT_DISP_DUP: free(port, M_DEVBUF, sizeof *port); break; case QLE_PORT_DISP_SAME: TAILQ_REMOVE(&sc->sc_ports_gone, pport, update); free(port, M_DEVBUF, sizeof *port); break; } mtx_leave(&sc->sc_port_mtx); switch (disp) { case QLE_PORT_DISP_CHANGED: case QLE_PORT_DISP_MOVED: case QLE_PORT_DISP_NEW: DPRINTF(QLE_D_PORT, "%s: %s %d; name %llx\n", DEVNAME(sc), ISSET(port->flags, QLE_PORT_FLAG_IS_TARGET) ? "target" : "non-target", port->loopid, betoh64(pdb->port_name)); break; default: break; } return (0); } int qle_add_fabric_port(struct qle_softc *sc, struct qle_fc_port *port) { struct qle_get_port_db *pdb; if (qle_get_port_db(sc, port->loopid, sc->sc_scratch) != 0) { free(port, M_DEVBUF, sizeof *port); return (1); } pdb = QLE_DMA_KVA(sc->sc_scratch); if (lemtoh16(&pdb->prli_svc_word3) & QLE_SVC3_TARGET_ROLE) port->flags |= QLE_PORT_FLAG_IS_TARGET; /* * if we only know about this port because qle_get_port_name_list * returned it, we don't have its port id or node name, so fill * those in and update its location. */ if (port->location == QLE_LOCATION_FABRIC) { port->node_name = betoh64(pdb->node_name); port->port_name = betoh64(pdb->port_name); port->portid = (pdb->port_id[0] << 16) | (pdb->port_id[1] << 8) | pdb->port_id[2]; port->location = QLE_LOCATION_PORT_ID(port->portid); } mtx_enter(&sc->sc_port_mtx); TAILQ_INSERT_TAIL(&sc->sc_ports_new, port, update); sc->sc_targets[port->loopid] = port; mtx_leave(&sc->sc_port_mtx); DPRINTF(QLE_D_PORT, "%s: %s %d; name %llx\n", DEVNAME(sc), ISSET(port->flags, QLE_PORT_FLAG_IS_TARGET) ? "target" : "non-target", port->loopid, port->port_name); return (0); } int qle_add_logged_in_port(struct qle_softc *sc, u_int16_t loopid, u_int32_t portid) { struct qle_fc_port *port; struct qle_get_port_db *pdb; u_int64_t node_name, port_name; int flags, ret; ret = qle_get_port_db(sc, loopid, sc->sc_scratch); mtx_enter(&sc->sc_port_mtx); if (ret != 0) { /* put in a fake port to prevent use of this loop id */ printf("%s: loop id %d used, but can't see what's using it\n", DEVNAME(sc), loopid); node_name = 0; port_name = 0; flags = 0; } else { pdb = QLE_DMA_KVA(sc->sc_scratch); node_name = betoh64(pdb->node_name); port_name = betoh64(pdb->port_name); flags = 0; if (lemtoh16(&pdb->prli_svc_word3) & QLE_SVC3_TARGET_ROLE) flags |= QLE_PORT_FLAG_IS_TARGET; /* see if we've already found this port */ TAILQ_FOREACH(port, &sc->sc_ports_found, update) { if ((port->node_name == node_name) && (port->port_name == port_name) && (port->portid == portid)) { mtx_leave(&sc->sc_port_mtx); DPRINTF(QLE_D_PORT, "%s: already found port " "%06x\n", DEVNAME(sc), portid); return (0); } } } port = malloc(sizeof(*port), M_DEVBUF, M_ZERO | M_NOWAIT); if (port == NULL) { mtx_leave(&sc->sc_port_mtx); printf("%s: failed to allocate a port structure\n", DEVNAME(sc)); return (1); } port->location = QLE_LOCATION_PORT_ID(portid); port->port_name = port_name; port->node_name = node_name; port->loopid = loopid; port->portid = portid; port->flags = flags; TAILQ_INSERT_TAIL(&sc->sc_ports, port, ports); sc->sc_targets[port->loopid] = port; mtx_leave(&sc->sc_port_mtx); DPRINTF(QLE_D_PORT, "%s: added logged in port %06x at %d\n", DEVNAME(sc), portid, loopid); return (0); } struct qle_ccb * qle_handle_resp(struct qle_softc *sc, u_int32_t id) { struct qle_ccb *ccb; struct qle_iocb_status *status; struct qle_iocb_req6 *req; struct scsi_xfer *xs; u_int32_t handle; u_int16_t completion; u_int8_t *entry; u_int8_t *data; ccb = NULL; entry = QLE_DMA_KVA(sc->sc_responses) + (id * QLE_QUEUE_ENTRY_SIZE); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_responses), id * QLE_QUEUE_ENTRY_SIZE, QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTREAD); qle_dump_iocb(sc, entry); switch(entry[0]) { case QLE_IOCB_STATUS: status = (struct qle_iocb_status *)entry; handle = status->handle; if (handle > sc->sc_maxcmds) { panic("bad completed command handle: %d (> %d)", handle, sc->sc_maxcmds); } ccb = &sc->sc_ccbs[handle]; xs = ccb->ccb_xs; if (xs == NULL) { DPRINTF(QLE_D_IO, "%s: got status for inactive ccb %d\n", DEVNAME(sc), handle); ccb = NULL; break; } if (xs->io != ccb) { panic("completed command handle doesn't match xs " "(handle %d, ccb %p, xs->io %p)", handle, ccb, xs->io); } if (xs->datalen > 0) { if (ccb->ccb_dmamap->dm_nsegs > QLE_IOCB_SEGS_PER_CMD) { bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_segments), ccb->ccb_seg_offset, sizeof(*ccb->ccb_segs) * ccb->ccb_dmamap->dm_nsegs + 1, BUS_DMASYNC_POSTWRITE); } bus_dmamap_sync(sc->sc_dmat, ccb->ccb_dmamap, 0, ccb->ccb_dmamap->dm_mapsize, (xs->flags & SCSI_DATA_IN) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmamap); } xs->status = lemtoh16(&status->scsi_status) & 0xff; xs->resid = 0; completion = lemtoh16(&status->completion); switch (completion) { case QLE_IOCB_STATUS_DATA_UNDERRUN: xs->resid = lemtoh32(&status->resid); /* FALLTHROUGH */ case QLE_IOCB_STATUS_DATA_OVERRUN: case QLE_IOCB_STATUS_COMPLETE: if (lemtoh16(&status->scsi_status) & QLE_SCSI_STATUS_SENSE_VALID) { u_int32_t *pp; int sr; data = status->data + lemtoh32(&status->fcp_rsp_len); sr = MIN(lemtoh32(&status->fcp_sense_len), sizeof(xs->sense)); memcpy(&xs->sense, data, sr); xs->error = XS_SENSE; pp = (u_int32_t *)&xs->sense; for (sr = 0; sr < sizeof(xs->sense)/4; sr++) { pp[sr] = swap32(pp[sr]); } } else { xs->error = XS_NOERROR; } break; case QLE_IOCB_STATUS_DMA_ERROR: DPRINTF(QLE_D_IO, "%s: dma error\n", DEVNAME(sc)); /* set resid apparently? */ break; case QLE_IOCB_STATUS_RESET: DPRINTF(QLE_D_IO, "%s: reset destroyed command\n", DEVNAME(sc)); sc->sc_marker_required = 1; xs->error = XS_RESET; break; case QLE_IOCB_STATUS_ABORTED: DPRINTF(QLE_D_IO, "%s: aborted\n", DEVNAME(sc)); sc->sc_marker_required = 1; xs->error = XS_DRIVER_STUFFUP; break; case QLE_IOCB_STATUS_TIMEOUT: DPRINTF(QLE_D_IO, "%s: command timed out\n", DEVNAME(sc)); xs->error = XS_TIMEOUT; break; case QLE_IOCB_STATUS_QUEUE_FULL: DPRINTF(QLE_D_IO, "%s: queue full\n", DEVNAME(sc)); xs->error = XS_BUSY; break; case QLE_IOCB_STATUS_PORT_UNAVAIL: case QLE_IOCB_STATUS_PORT_LOGGED_OUT: case QLE_IOCB_STATUS_PORT_CHANGED: DPRINTF(QLE_D_IO, "%s: dev gone\n", DEVNAME(sc)); xs->error = XS_SELTIMEOUT; /* mark port as needing relogin? */ break; default: DPRINTF(QLE_D_IO, "%s: unexpected completion status " "%x\n", DEVNAME(sc), status->completion); xs->error = XS_DRIVER_STUFFUP; break; } break; case QLE_IOCB_STATUS_CONT: DPRINTF(QLE_D_IO, "%s: ignoring status continuation iocb\n", DEVNAME(sc)); break; case QLE_IOCB_PLOGX: case QLE_IOCB_CT_PASSTHROUGH: if (sc->sc_fabric_pending) { qle_dump_iocb(sc, entry); memcpy(sc->sc_fabric_response, entry, QLE_QUEUE_ENTRY_SIZE); sc->sc_fabric_pending = 2; wakeup(sc->sc_scratch); } else { DPRINTF(QLE_D_IO, "%s: unexpected fabric response %x\n", DEVNAME(sc), entry[0]); } break; case QLE_IOCB_MARKER: break; case QLE_IOCB_CMD_TYPE_6: case QLE_IOCB_CMD_TYPE_7: DPRINTF(QLE_D_IO, "%s: request bounced back\n", DEVNAME(sc)); req = (struct qle_iocb_req6 *)entry; handle = req->req_handle; if (handle > sc->sc_maxcmds) { panic("bad bounced command handle: %d (> %d)", handle, sc->sc_maxcmds); } ccb = &sc->sc_ccbs[handle]; xs = ccb->ccb_xs; xs->error = XS_DRIVER_STUFFUP; break; default: DPRINTF(QLE_D_IO, "%s: unexpected response entry type %x\n", DEVNAME(sc), entry[0]); break; } return (ccb); } void qle_handle_intr(struct qle_softc *sc, u_int16_t isr, u_int16_t info) { int i; u_int32_t rspin; struct qle_ccb *ccb; switch (isr) { case QLE_INT_TYPE_ASYNC: qle_async(sc, info); break; case QLE_INT_TYPE_IO: rspin = qle_read(sc, QLE_RESP_IN); if (rspin == sc->sc_last_resp_id) break; do { ccb = qle_handle_resp(sc, sc->sc_last_resp_id); if (ccb) scsi_done(ccb->ccb_xs); sc->sc_last_resp_id++; sc->sc_last_resp_id %= sc->sc_maxcmds; } while (sc->sc_last_resp_id != rspin); qle_write(sc, QLE_RESP_OUT, sc->sc_last_resp_id); break; case QLE_INT_TYPE_MBOX: mtx_enter(&sc->sc_mbox_mtx); if (sc->sc_mbox_pending) { for (i = 0; i < nitems(sc->sc_mbox); i++) { sc->sc_mbox[i] = qle_read_mbox(sc, i); } sc->sc_mbox_pending = 2; wakeup(sc->sc_mbox); mtx_leave(&sc->sc_mbox_mtx); } else { mtx_leave(&sc->sc_mbox_mtx); DPRINTF(QLE_D_INTR, "%s: unexpected mbox interrupt: " "%x\n", DEVNAME(sc), info); } break; default: break; } qle_clear_isr(sc, isr); } int qle_intr(void *xsc) { struct qle_softc *sc = xsc; u_int16_t isr; u_int16_t info; if (qle_read_isr(sc, &isr, &info) == 0) return (0); qle_handle_intr(sc, isr, info); return (1); } int qle_scsi_probe(struct scsi_link *link) { struct qle_softc *sc = link->bus->sb_adapter_softc; int rv = 0; mtx_enter(&sc->sc_port_mtx); if (sc->sc_targets[link->target] == NULL) rv = ENXIO; else if (!ISSET(sc->sc_targets[link->target]->flags, QLE_PORT_FLAG_IS_TARGET)) rv = ENXIO; mtx_leave(&sc->sc_port_mtx); return (rv); } void qle_scsi_cmd(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct qle_softc *sc = link->bus->sb_adapter_softc; struct qle_ccb *ccb; void *iocb; struct qle_ccb_list list; u_int16_t req; u_int32_t portid; int offset, error, done; bus_dmamap_t dmap; if (xs->cmdlen > 16) { DPRINTF(QLE_D_IO, "%s: cmd too big (%d)\n", DEVNAME(sc), xs->cmdlen); memset(&xs->sense, 0, sizeof(xs->sense)); xs->sense.error_code = SSD_ERRCODE_VALID | SSD_ERRCODE_CURRENT; xs->sense.flags = SKEY_ILLEGAL_REQUEST; xs->sense.add_sense_code = 0x20; xs->error = XS_SENSE; scsi_done(xs); return; } portid = 0xffffffff; mtx_enter(&sc->sc_port_mtx); if (sc->sc_targets[xs->sc_link->target] != NULL) { portid = sc->sc_targets[xs->sc_link->target]->portid; } mtx_leave(&sc->sc_port_mtx); if (portid == 0xffffffff) { xs->error = XS_DRIVER_STUFFUP; scsi_done(xs); return; } ccb = xs->io; dmap = ccb->ccb_dmamap; if (xs->datalen > 0) { error = bus_dmamap_load(sc->sc_dmat, dmap, xs->data, xs->datalen, NULL, (xs->flags & SCSI_NOSLEEP) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK); if (error) { xs->error = XS_DRIVER_STUFFUP; scsi_done(xs); return; } bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize, (xs->flags & SCSI_DATA_IN) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); } mtx_enter(&sc->sc_queue_mtx); /* put in a sync marker if required */ if (sc->sc_marker_required) { req = sc->sc_next_req_id++; if (sc->sc_next_req_id == sc->sc_maxcmds) sc->sc_next_req_id = 0; DPRINTF(QLE_D_IO, "%s: writing marker at request %d\n", DEVNAME(sc), req); offset = (req * QLE_QUEUE_ENTRY_SIZE); iocb = QLE_DMA_KVA(sc->sc_requests) + offset; bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset, QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE); qle_put_marker(sc, iocb); qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id); sc->sc_marker_required = 0; } req = sc->sc_next_req_id++; if (sc->sc_next_req_id == sc->sc_maxcmds) sc->sc_next_req_id = 0; offset = (req * QLE_QUEUE_ENTRY_SIZE); iocb = QLE_DMA_KVA(sc->sc_requests) + offset; bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset, QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE); ccb->ccb_xs = xs; qle_put_cmd(sc, iocb, xs, ccb, portid); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset, QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_PREREAD); qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id); if (!ISSET(xs->flags, SCSI_POLL)) { mtx_leave(&sc->sc_queue_mtx); return; } done = 0; SIMPLEQ_INIT(&list); do { u_int16_t isr, info; u_int32_t rspin; delay(100); if (qle_read_isr(sc, &isr, &info) == 0) { continue; } if (isr != QLE_INT_TYPE_IO) { qle_handle_intr(sc, isr, info); continue; } rspin = qle_read(sc, QLE_RESP_IN); while (rspin != sc->sc_last_resp_id) { ccb = qle_handle_resp(sc, sc->sc_last_resp_id); sc->sc_last_resp_id++; if (sc->sc_last_resp_id == sc->sc_maxcmds) sc->sc_last_resp_id = 0; if (ccb != NULL) SIMPLEQ_INSERT_TAIL(&list, ccb, ccb_link); if (ccb == xs->io) done = 1; } qle_write(sc, QLE_RESP_OUT, sc->sc_last_resp_id); qle_clear_isr(sc, isr); } while (done == 0); mtx_leave(&sc->sc_queue_mtx); while ((ccb = SIMPLEQ_FIRST(&list)) != NULL) { SIMPLEQ_REMOVE_HEAD(&list, ccb_link); scsi_done(ccb->ccb_xs); } } u_int32_t qle_read(struct qle_softc *sc, int offset) { u_int32_t v; v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, offset); bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 4, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); return (v); } void qle_write(struct qle_softc *sc, int offset, u_int32_t value) { bus_space_write_4(sc->sc_iot, sc->sc_ioh, offset, value); bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 4, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); } u_int16_t qle_read_mbox(struct qle_softc *sc, int mbox) { u_int16_t v; bus_size_t offset = mbox * 2; v = bus_space_read_2(sc->sc_iot, sc->sc_mbox_ioh, offset); bus_space_barrier(sc->sc_iot, sc->sc_mbox_ioh, offset, 2, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); return (v); } void qle_write_mbox(struct qle_softc *sc, int mbox, u_int16_t value) { bus_size_t offset = (mbox * 2); bus_space_write_2(sc->sc_iot, sc->sc_mbox_ioh, offset, value); bus_space_barrier(sc->sc_iot, sc->sc_mbox_ioh, offset, 2, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); } void qle_host_cmd(struct qle_softc *sc, u_int32_t cmd) { qle_write(sc, QLE_HOST_CMD_CTRL, cmd << QLE_HOST_CMD_SHIFT); } #define MBOX_COMMAND_TIMEOUT 400000 int qle_mbox(struct qle_softc *sc, int maskin) { int i; int result = 0; int rv; for (i = 0; i < nitems(sc->sc_mbox); i++) { if (maskin & (1 << i)) { qle_write_mbox(sc, i, sc->sc_mbox[i]); } } qle_host_cmd(sc, QLE_HOST_CMD_SET_HOST_INT); if (sc->sc_scsibus != NULL) { mtx_enter(&sc->sc_mbox_mtx); sc->sc_mbox_pending = 1; while (sc->sc_mbox_pending == 1) { msleep_nsec(sc->sc_mbox, &sc->sc_mbox_mtx, PRIBIO, "qlembox", INFSLP); } result = sc->sc_mbox[0]; sc->sc_mbox_pending = 0; mtx_leave(&sc->sc_mbox_mtx); return (result == QLE_MBOX_COMPLETE ? 0 : result); } for (i = 0; i < MBOX_COMMAND_TIMEOUT && result == 0; i++) { u_int16_t isr, info; delay(100); if (qle_read_isr(sc, &isr, &info) == 0) continue; switch (isr) { case QLE_INT_TYPE_MBOX: result = info; break; default: qle_handle_intr(sc, isr, info); break; } } if (result == 0) { /* timed out; do something? */ DPRINTF(QLE_D_MBOX, "%s: mbox timed out\n", DEVNAME(sc)); rv = 1; } else { for (i = 0; i < nitems(sc->sc_mbox); i++) { sc->sc_mbox[i] = qle_read_mbox(sc, i); } rv = (result == QLE_MBOX_COMPLETE ? 0 : result); } qle_clear_isr(sc, QLE_INT_TYPE_MBOX); return (rv); } void qle_mbox_putaddr(u_int16_t *mbox, struct qle_dmamem *mem) { mbox[2] = (QLE_DMA_DVA(mem) >> 16) & 0xffff; mbox[3] = (QLE_DMA_DVA(mem) >> 0) & 0xffff; mbox[6] = (QLE_DMA_DVA(mem) >> 48) & 0xffff; mbox[7] = (QLE_DMA_DVA(mem) >> 32) & 0xffff; } void qle_set_ints(struct qle_softc *sc, int enabled) { u_int32_t v = enabled ? QLE_INT_CTRL_ENABLE : 0; qle_write(sc, QLE_INT_CTRL, v); } int qle_read_isr(struct qle_softc *sc, u_int16_t *isr, u_int16_t *info) { u_int32_t v; switch (sc->sc_isp_gen) { case QLE_GEN_ISP24XX: case QLE_GEN_ISP25XX: if ((qle_read(sc, QLE_INT_STATUS) & QLE_RISC_INT_REQ) == 0) return (0); v = qle_read(sc, QLE_RISC_STATUS); switch (v & QLE_INT_STATUS_MASK) { case QLE_24XX_INT_ROM_MBOX: case QLE_24XX_INT_ROM_MBOX_FAIL: case QLE_24XX_INT_MBOX: case QLE_24XX_INT_MBOX_FAIL: *isr = QLE_INT_TYPE_MBOX; break; case QLE_24XX_INT_ASYNC: *isr = QLE_INT_TYPE_ASYNC; break; case QLE_24XX_INT_RSPQ: *isr = QLE_INT_TYPE_IO; break; default: *isr = QLE_INT_TYPE_OTHER; break; } *info = (v >> QLE_INT_INFO_SHIFT); return (1); default: return (0); } } void qle_clear_isr(struct qle_softc *sc, u_int16_t isr) { qle_host_cmd(sc, QLE_HOST_CMD_CLR_RISC_INT); } void qle_update_done(struct qle_softc *sc, int task) { atomic_clearbits_int(&sc->sc_update_tasks, task); } void qle_update_cancel(struct qle_softc *sc) { atomic_swap_uint(&sc->sc_update_tasks, 0); timeout_del(&sc->sc_update_timeout); task_del(sc->sc_update_taskq, &sc->sc_update_task); } void qle_update_start(struct qle_softc *sc, int task) { atomic_setbits_int(&sc->sc_update_tasks, task); if (!timeout_pending(&sc->sc_update_timeout)) task_add(sc->sc_update_taskq, &sc->sc_update_task); } void qle_update_defer(struct qle_softc *sc, int task) { atomic_setbits_int(&sc->sc_update_tasks, task); timeout_del(&sc->sc_update_timeout); task_del(sc->sc_update_taskq, &sc->sc_update_task); timeout_add_msec(&sc->sc_update_timeout, QLE_LOOP_SETTLE); } void qle_clear_port_lists(struct qle_softc *sc) { struct qle_fc_port *port; while (!TAILQ_EMPTY(&sc->sc_ports_found)) { port = TAILQ_FIRST(&sc->sc_ports_found); TAILQ_REMOVE(&sc->sc_ports_found, port, update); free(port, M_DEVBUF, sizeof *port); } while (!TAILQ_EMPTY(&sc->sc_ports_new)) { port = TAILQ_FIRST(&sc->sc_ports_new); TAILQ_REMOVE(&sc->sc_ports_new, port, update); free(port, M_DEVBUF, sizeof *port); } while (!TAILQ_EMPTY(&sc->sc_ports_gone)) { port = TAILQ_FIRST(&sc->sc_ports_gone); TAILQ_REMOVE(&sc->sc_ports_gone, port, update); } } int qle_softreset(struct qle_softc *sc) { int i; qle_set_ints(sc, 0); /* set led control bits, stop dma */ qle_write(sc, QLE_GPIO_DATA, 0); qle_write(sc, QLE_CTRL_STATUS, QLE_CTRL_DMA_SHUTDOWN); while (qle_read(sc, QLE_CTRL_STATUS) & QLE_CTRL_DMA_ACTIVE) { DPRINTF(QLE_D_IO, "%s: dma still active\n", DEVNAME(sc)); delay(100); } /* reset */ qle_write(sc, QLE_CTRL_STATUS, QLE_CTRL_RESET | QLE_CTRL_DMA_SHUTDOWN); delay(100); /* clear data and control dma engines? */ /* wait for soft reset to clear */ for (i = 0; i < 1000; i++) { if (qle_read_mbox(sc, 0) == 0x0000) break; delay(100); } if (i == 1000) { printf("%s: reset mbox didn't clear\n", DEVNAME(sc)); qle_set_ints(sc, 0); return (ENXIO); } for (i = 0; i < 500000; i++) { if ((qle_read(sc, QLE_CTRL_STATUS) & QLE_CTRL_RESET) == 0) break; delay(5); } if (i == 500000) { printf("%s: reset status didn't clear\n", DEVNAME(sc)); return (ENXIO); } /* reset risc processor */ qle_host_cmd(sc, QLE_HOST_CMD_RESET); qle_host_cmd(sc, QLE_HOST_CMD_RELEASE); qle_host_cmd(sc, QLE_HOST_CMD_CLEAR_RESET); /* wait for reset to clear */ for (i = 0; i < 1000; i++) { if (qle_read_mbox(sc, 0) == 0x0000) break; delay(100); } if (i == 1000) { printf("%s: risc not ready after reset\n", DEVNAME(sc)); return (ENXIO); } /* reset queue pointers */ qle_write(sc, QLE_REQ_IN, 0); qle_write(sc, QLE_REQ_OUT, 0); qle_write(sc, QLE_RESP_IN, 0); qle_write(sc, QLE_RESP_OUT, 0); qle_set_ints(sc, 1); /* do a basic mailbox operation to check we're alive */ sc->sc_mbox[0] = QLE_MBOX_NOP; if (qle_mbox(sc, 0x0001)) { printf("ISP not responding after reset\n"); return (ENXIO); } return (0); } void qle_update_topology(struct qle_softc *sc) { sc->sc_mbox[0] = QLE_MBOX_GET_ID; if (qle_mbox(sc, 0x0001)) { DPRINTF(QLE_D_PORT, "%s: unable to get loop id\n", DEVNAME(sc)); sc->sc_topology = QLE_TOPO_N_PORT_NO_TARGET; } else { sc->sc_topology = sc->sc_mbox[6]; sc->sc_loop_id = sc->sc_mbox[1]; switch (sc->sc_topology) { case QLE_TOPO_NL_PORT: case QLE_TOPO_N_PORT: DPRINTF(QLE_D_PORT, "%s: loop id %d\n", DEVNAME(sc), sc->sc_loop_id); break; case QLE_TOPO_FL_PORT: case QLE_TOPO_F_PORT: sc->sc_port_id = sc->sc_mbox[2] | (sc->sc_mbox[3] << 16); DPRINTF(QLE_D_PORT, "%s: fabric port id %06x\n", DEVNAME(sc), sc->sc_port_id); break; case QLE_TOPO_N_PORT_NO_TARGET: default: DPRINTF(QLE_D_PORT, "%s: not useful\n", DEVNAME(sc)); break; } switch (sc->sc_topology) { case QLE_TOPO_NL_PORT: case QLE_TOPO_FL_PORT: sc->sc_loop_max_id = 126; break; case QLE_TOPO_N_PORT: sc->sc_loop_max_id = 2; break; default: sc->sc_loop_max_id = 0; break; } } } int qle_update_fabric(struct qle_softc *sc) { /*struct qle_sns_rft_id *rft;*/ switch (sc->sc_topology) { case QLE_TOPO_F_PORT: case QLE_TOPO_FL_PORT: break; default: return (0); } /* get the name server's port db entry */ sc->sc_mbox[0] = QLE_MBOX_GET_PORT_DB; sc->sc_mbox[1] = QLE_F_PORT_HANDLE; qle_mbox_putaddr(sc->sc_mbox, sc->sc_scratch); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0, sizeof(struct qle_get_port_db), BUS_DMASYNC_PREREAD); if (qle_mbox(sc, 0x00cf)) { DPRINTF(QLE_D_PORT, "%s: get port db for SNS failed: %x\n", DEVNAME(sc), sc->sc_mbox[0]); sc->sc_sns_port_name = 0; } else { struct qle_get_port_db *pdb; bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0, sizeof(struct qle_get_port_db), BUS_DMASYNC_POSTREAD); pdb = QLE_DMA_KVA(sc->sc_scratch); DPRINTF(QLE_D_PORT, "%s: SNS port name %llx\n", DEVNAME(sc), betoh64(pdb->port_name)); sc->sc_sns_port_name = betoh64(pdb->port_name); } /* * register fc4 types with the fabric * some switches do this automatically, but apparently * some don't. */ /* rft = QLE_DMA_KVA(sc->sc_scratch); memset(rft, 0, sizeof(*rft) + sizeof(struct qle_sns_req_hdr)); htolem16(&rft->subcmd, QLE_SNS_RFT_ID); htolem16(&rft->max_word, sizeof(struct qle_sns_req_hdr) / 4); htolem32(&rft->port_id, sc->sc_port_id); rft->fc4_types[0] = (1 << QLE_FC4_SCSI); if (qle_sns_req(sc, sc->sc_scratch, sizeof(*rft))) { printf("%s: RFT_ID failed\n", DEVNAME(sc)); / * we might be able to continue after this fails * / } */ return (1); } int qle_ct_pass_through(struct qle_softc *sc, u_int32_t port_handle, struct qle_dmamem *mem, size_t req_size, size_t resp_size) { struct qle_iocb_ct_passthrough *iocb; u_int16_t req; u_int64_t offset; int rv; mtx_enter(&sc->sc_queue_mtx); req = sc->sc_next_req_id++; if (sc->sc_next_req_id == sc->sc_maxcmds) sc->sc_next_req_id = 0; offset = (req * QLE_QUEUE_ENTRY_SIZE); iocb = QLE_DMA_KVA(sc->sc_requests) + offset; bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset, QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE); memset(iocb, 0, QLE_QUEUE_ENTRY_SIZE); iocb->entry_type = QLE_IOCB_CT_PASSTHROUGH; iocb->entry_count = 1; iocb->req_handle = 9; htolem16(&iocb->req_nport_handle, port_handle); htolem16(&iocb->req_dsd_count, 1); htolem16(&iocb->req_resp_dsd_count, 1); htolem32(&iocb->req_cmd_byte_count, req_size); htolem32(&iocb->req_resp_byte_count, resp_size); qle_sge(&iocb->req_cmd_seg, QLE_DMA_DVA(mem), req_size); qle_sge(&iocb->req_resp_seg, QLE_DMA_DVA(mem) + req_size, resp_size); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, QLE_DMA_LEN(mem), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id); sc->sc_fabric_pending = 1; mtx_leave(&sc->sc_queue_mtx); /* maybe put a proper timeout on this */ rv = 0; while (sc->sc_fabric_pending == 1) { if (sc->sc_scsibus == NULL) { u_int16_t isr, info; delay(100); if (qle_read_isr(sc, &isr, &info) != 0) qle_handle_intr(sc, isr, info); } else { tsleep_nsec(sc->sc_scratch, PRIBIO, "qle_fabric", SEC_TO_NSEC(1)); } } if (rv == 0) bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, QLE_DMA_LEN(mem), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); sc->sc_fabric_pending = 0; return (rv); } struct qle_fc_port * qle_next_fabric_port(struct qle_softc *sc, u_int32_t *firstport, u_int32_t *lastport) { struct qle_ct_ga_nxt_req *ga; struct qle_ct_ga_nxt_resp *gar; struct qle_fc_port *fport; int result; /* get the next port from the fabric nameserver */ ga = QLE_DMA_KVA(sc->sc_scratch); memset(ga, 0, sizeof(*ga) + sizeof(*gar)); ga->header.ct_revision = 0x01; ga->header.ct_gs_type = 0xfc; ga->header.ct_gs_subtype = 0x02; ga->subcmd = htobe16(QLE_SNS_GA_NXT); ga->max_word = htobe16((sizeof(*gar) - 16) / 4); ga->port_id = htobe32(*lastport); result = qle_ct_pass_through(sc, QLE_SNS_HANDLE, sc->sc_scratch, sizeof(*ga), sizeof(*gar)); if (result) { DPRINTF(QLE_D_PORT, "%s: GA_NXT %06x failed: %x\n", DEVNAME(sc), *lastport, result); *lastport = 0xffffffff; return (NULL); } gar = (struct qle_ct_ga_nxt_resp *)(ga + 1); /* if the response is all zeroes, try again */ if (gar->port_type_id == 0 && gar->port_name == 0 && gar->node_name == 0) { DPRINTF(QLE_D_PORT, "%s: GA_NXT returned junk\n", DEVNAME(sc)); return (NULL); } /* are we back at the start? */ *lastport = betoh32(gar->port_type_id) & 0xffffff; if (*lastport == *firstport) { *lastport = 0xffffffff; return (NULL); } if (*firstport == 0xffffffff) *firstport = *lastport; DPRINTF(QLE_D_PORT, "%s: GA_NXT: port id: %06x, wwpn %llx, wwnn %llx\n", DEVNAME(sc), *lastport, betoh64(gar->port_name), betoh64(gar->node_name)); /* don't try to log in to ourselves */ if (*lastport == sc->sc_port_id) { return (NULL); } fport = malloc(sizeof(*fport), M_DEVBUF, M_ZERO | M_NOWAIT); if (fport == NULL) { printf("%s: failed to allocate a port struct\n", DEVNAME(sc)); *lastport = 0xffffffff; return (NULL); } fport->port_name = betoh64(gar->port_name); fport->node_name = betoh64(gar->node_name); fport->location = QLE_LOCATION_PORT_ID(*lastport); fport->portid = *lastport; return (fport); } int qle_fabric_plogx(struct qle_softc *sc, struct qle_fc_port *port, int flags, u_int32_t *info) { struct qle_iocb_plogx *iocb; u_int16_t req; u_int64_t offset; int rv; mtx_enter(&sc->sc_queue_mtx); req = sc->sc_next_req_id++; if (sc->sc_next_req_id == sc->sc_maxcmds) sc->sc_next_req_id = 0; offset = (req * QLE_QUEUE_ENTRY_SIZE); iocb = QLE_DMA_KVA(sc->sc_requests) + offset; bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset, QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE); memset(iocb, 0, QLE_QUEUE_ENTRY_SIZE); iocb->entry_type = QLE_IOCB_PLOGX; iocb->entry_count = 1; iocb->req_handle = 7; htolem16(&iocb->req_nport_handle, port->loopid); htolem16(&iocb->req_port_id_lo, port->portid); iocb->req_port_id_hi = port->portid >> 16; htolem16(&iocb->req_flags, flags); DPRINTF(QLE_D_PORT, "%s: plogx loop id %d port %06x, flags %x\n", DEVNAME(sc), port->loopid, port->portid, flags); qle_dump_iocb(sc, iocb); qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id); sc->sc_fabric_pending = 1; mtx_leave(&sc->sc_queue_mtx); /* maybe put a proper timeout on this */ rv = 0; while (sc->sc_fabric_pending == 1) { if (sc->sc_scsibus == NULL) { u_int16_t isr, info; delay(100); if (qle_read_isr(sc, &isr, &info) != 0) qle_handle_intr(sc, isr, info); } else { tsleep_nsec(sc->sc_scratch, PRIBIO, "qle_fabric", SEC_TO_NSEC(1)); } } sc->sc_fabric_pending = 0; iocb = (struct qle_iocb_plogx *)&sc->sc_fabric_response; rv = lemtoh16(&iocb->req_status); if (rv == QLE_PLOGX_ERROR) { rv = lemtoh32(&iocb->req_ioparms[0]); *info = lemtoh32(&iocb->req_ioparms[1]); } return (rv); } int qle_fabric_plogi(struct qle_softc *sc, struct qle_fc_port *port) { u_int32_t info; int err, loopid; loopid = 0; retry: if (port->loopid == 0) { mtx_enter(&sc->sc_port_mtx); loopid = qle_get_loop_id(sc, loopid); mtx_leave(&sc->sc_port_mtx); if (loopid == -1) { printf("%s: ran out of loop ids\n", DEVNAME(sc)); return (1); } port->loopid = loopid; } err = qle_fabric_plogx(sc, port, QLE_PLOGX_LOGIN, &info); switch (err) { case 0: DPRINTF(QLE_D_PORT, "%s: logged in to %06x as %d\n", DEVNAME(sc), port->portid, port->loopid); port->flags &= ~QLE_PORT_FLAG_NEEDS_LOGIN; return (0); case QLE_PLOGX_ERROR_PORT_ID_USED: DPRINTF(QLE_D_PORT, "%s: already logged in to %06x as %d\n", DEVNAME(sc), port->portid, info); port->loopid = info; port->flags &= ~QLE_PORT_FLAG_NEEDS_LOGIN; return (0); case QLE_PLOGX_ERROR_HANDLE_USED: if (qle_add_logged_in_port(sc, loopid, info)) { return (1); } port->loopid = 0; loopid++; goto retry; default: DPRINTF(QLE_D_PORT, "%s: error %x logging in to port %06x\n", DEVNAME(sc), err, port->portid); port->loopid = 0; return (1); } } void qle_fabric_plogo(struct qle_softc *sc, struct qle_fc_port *port) { int err; u_int32_t info; /* * we only log out if we can't see the port any more, so we always * want to do an explicit logout and free the n-port handle. */ err = qle_fabric_plogx(sc, port, QLE_PLOGX_LOGOUT | QLE_PLOGX_LOGOUT_EXPLICIT | QLE_PLOGX_LOGOUT_FREE_HANDLE, &info); if (err == 0) { DPRINTF(QLE_D_PORT, "%s: logged out of port %06x\n", DEVNAME(sc), port->portid); } else { DPRINTF(QLE_D_PORT, "%s: failed to log out of port %06x: " "%x %x\n", DEVNAME(sc), port->portid, err, info); } } void qle_deferred_update(void *xsc) { struct qle_softc *sc = xsc; task_add(sc->sc_update_taskq, &sc->sc_update_task); } void qle_do_update(void *xsc) { struct qle_softc *sc = xsc; int firstport, lastport; struct qle_fc_port *port, *fport; DPRINTF(QLE_D_PORT, "%s: updating\n", DEVNAME(sc)); while (sc->sc_update_tasks != 0) { if (sc->sc_update_tasks & QLE_UPDATE_TASK_CLEAR_ALL) { TAILQ_HEAD(, qle_fc_port) detach; DPRINTF(QLE_D_PORT, "%s: detaching everything\n", DEVNAME(sc)); mtx_enter(&sc->sc_port_mtx); qle_clear_port_lists(sc); TAILQ_INIT(&detach); TAILQ_CONCAT(&detach, &sc->sc_ports, ports); mtx_leave(&sc->sc_port_mtx); while (!TAILQ_EMPTY(&detach)) { port = TAILQ_FIRST(&detach); TAILQ_REMOVE(&detach, port, ports); if (port->flags & QLE_PORT_FLAG_IS_TARGET) { scsi_detach_target(sc->sc_scsibus, port->loopid, DETACH_FORCE | DETACH_QUIET); sc->sc_targets[port->loopid] = NULL; } if (port->location & QLE_LOCATION_FABRIC) qle_fabric_plogo(sc, port); free(port, M_DEVBUF, sizeof *port); } qle_update_done(sc, QLE_UPDATE_TASK_CLEAR_ALL); continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_SOFTRESET) { DPRINTF(QLE_D_IO, "%s: attempting softreset\n", DEVNAME(sc)); if (qle_softreset(sc) != 0) { DPRINTF(QLE_D_IO, "%s: couldn't softreset\n", DEVNAME(sc)); } qle_update_done(sc, QLE_UPDATE_TASK_SOFTRESET); continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_UPDATE_TOPO) { DPRINTF(QLE_D_PORT, "%s: updating topology\n", DEVNAME(sc)); qle_update_topology(sc); qle_update_done(sc, QLE_UPDATE_TASK_UPDATE_TOPO); continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_GET_PORT_LIST) { DPRINTF(QLE_D_PORT, "%s: getting port name list\n", DEVNAME(sc)); mtx_enter(&sc->sc_port_mtx); qle_clear_port_lists(sc); mtx_leave(&sc->sc_port_mtx); qle_get_port_name_list(sc, QLE_LOCATION_LOOP | QLE_LOCATION_FABRIC); mtx_enter(&sc->sc_port_mtx); TAILQ_FOREACH(port, &sc->sc_ports, ports) { TAILQ_INSERT_TAIL(&sc->sc_ports_gone, port, update); if (port->location & QLE_LOCATION_FABRIC) { port->flags |= QLE_PORT_FLAG_NEEDS_LOGIN; } } /* take care of ports that haven't changed first */ TAILQ_FOREACH(fport, &sc->sc_ports_found, update) { port = sc->sc_targets[fport->loopid]; if (port == NULL || fport->port_name != port->port_name) { /* new or changed port, handled later */ continue; } /* * the port hasn't been logged out, which * means we don't need to log in again, and, * for loop ports, that the port still exists */ port->flags &= ~QLE_PORT_FLAG_NEEDS_LOGIN; if (port->location & QLE_LOCATION_LOOP) TAILQ_REMOVE(&sc->sc_ports_gone, port, update); fport->location = 0; } mtx_leave(&sc->sc_port_mtx); qle_update_start(sc, QLE_UPDATE_TASK_PORT_LIST); qle_update_done(sc, QLE_UPDATE_TASK_GET_PORT_LIST); continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_PORT_LIST) { mtx_enter(&sc->sc_port_mtx); fport = TAILQ_FIRST(&sc->sc_ports_found); if (fport != NULL) { TAILQ_REMOVE(&sc->sc_ports_found, fport, update); } mtx_leave(&sc->sc_port_mtx); if (fport == NULL) { DPRINTF(QLE_D_PORT, "%s: done with ports\n", DEVNAME(sc)); qle_update_done(sc, QLE_UPDATE_TASK_PORT_LIST); qle_update_start(sc, QLE_UPDATE_TASK_SCAN_FABRIC); } else if (fport->location & QLE_LOCATION_LOOP) { DPRINTF(QLE_D_PORT, "%s: loop port %04x\n", DEVNAME(sc), fport->loopid); if (qle_add_loop_port(sc, fport) != 0) free(fport, M_DEVBUF, sizeof *port); } else if (fport->location & QLE_LOCATION_FABRIC) { qle_add_fabric_port(sc, fport); } else { /* already processed */ free(fport, M_DEVBUF, sizeof *port); } continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_SCAN_FABRIC) { DPRINTF(QLE_D_PORT, "%s: starting fabric scan\n", DEVNAME(sc)); lastport = sc->sc_port_id; firstport = 0xffffffff; if (qle_update_fabric(sc)) qle_update_start(sc, QLE_UPDATE_TASK_SCANNING_FABRIC); else qle_update_start(sc, QLE_UPDATE_TASK_ATTACH_TARGET | QLE_UPDATE_TASK_DETACH_TARGET); qle_update_done(sc, QLE_UPDATE_TASK_SCAN_FABRIC); continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_SCANNING_FABRIC) { fport = qle_next_fabric_port(sc, &firstport, &lastport); if (fport != NULL) { int disp; mtx_enter(&sc->sc_port_mtx); disp = qle_classify_port(sc, fport->location, fport->port_name, fport->node_name, &port); switch (disp) { case QLE_PORT_DISP_CHANGED: case QLE_PORT_DISP_MOVED: /* we'll log out the old port later */ case QLE_PORT_DISP_NEW: DPRINTF(QLE_D_PORT, "%s: new port " "%06x\n", DEVNAME(sc), fport->portid); TAILQ_INSERT_TAIL(&sc->sc_ports_found, fport, update); break; case QLE_PORT_DISP_DUP: free(fport, M_DEVBUF, sizeof *port); break; case QLE_PORT_DISP_SAME: DPRINTF(QLE_D_PORT, "%s: existing port " " %06x\n", DEVNAME(sc), fport->portid); TAILQ_REMOVE(&sc->sc_ports_gone, port, update); free(fport, M_DEVBUF, sizeof *port); break; } mtx_leave(&sc->sc_port_mtx); } if (lastport == 0xffffffff) { DPRINTF(QLE_D_PORT, "%s: finished\n", DEVNAME(sc)); qle_update_done(sc, QLE_UPDATE_TASK_SCANNING_FABRIC); qle_update_start(sc, QLE_UPDATE_TASK_FABRIC_LOGIN); } continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_FABRIC_LOGIN) { mtx_enter(&sc->sc_port_mtx); port = TAILQ_FIRST(&sc->sc_ports_found); if (port != NULL) { TAILQ_REMOVE(&sc->sc_ports_found, port, update); } mtx_leave(&sc->sc_port_mtx); if (port != NULL) { DPRINTF(QLE_D_PORT, "%s: found port %06x\n", DEVNAME(sc), port->portid); if (qle_fabric_plogi(sc, port) == 0) { qle_add_fabric_port(sc, port); } else { DPRINTF(QLE_D_PORT, "%s: plogi %06x " "failed\n", DEVNAME(sc), port->portid); free(port, M_DEVBUF, sizeof *port); } } else { DPRINTF(QLE_D_PORT, "%s: done with logins\n", DEVNAME(sc)); qle_update_done(sc, QLE_UPDATE_TASK_FABRIC_LOGIN); qle_update_start(sc, QLE_UPDATE_TASK_ATTACH_TARGET | QLE_UPDATE_TASK_DETACH_TARGET); } continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_FABRIC_RELOGIN) { TAILQ_FOREACH(port, &sc->sc_ports, ports) { if (port->flags & QLE_PORT_FLAG_NEEDS_LOGIN) { qle_fabric_plogi(sc, port); break; } } if (port == NULL) qle_update_done(sc, QLE_UPDATE_TASK_FABRIC_RELOGIN); continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_DETACH_TARGET) { mtx_enter(&sc->sc_port_mtx); port = TAILQ_FIRST(&sc->sc_ports_gone); if (port != NULL) { sc->sc_targets[port->loopid] = NULL; TAILQ_REMOVE(&sc->sc_ports_gone, port, update); TAILQ_REMOVE(&sc->sc_ports, port, ports); } mtx_leave(&sc->sc_port_mtx); if (port != NULL) { DPRINTF(QLE_D_PORT, "%s: detaching port %06x\n", DEVNAME(sc), port->portid); if (sc->sc_scsibus != NULL) scsi_detach_target(sc->sc_scsibus, port->loopid, DETACH_FORCE | DETACH_QUIET); if (port->location & QLE_LOCATION_FABRIC) qle_fabric_plogo(sc, port); free(port, M_DEVBUF, sizeof *port); } else { DPRINTF(QLE_D_PORT, "%s: nothing to detach\n", DEVNAME(sc)); qle_update_done(sc, QLE_UPDATE_TASK_DETACH_TARGET); } continue; } if (sc->sc_update_tasks & QLE_UPDATE_TASK_ATTACH_TARGET) { mtx_enter(&sc->sc_port_mtx); port = TAILQ_FIRST(&sc->sc_ports_new); if (port != NULL) { TAILQ_REMOVE(&sc->sc_ports_new, port, update); TAILQ_INSERT_TAIL(&sc->sc_ports, port, ports); } mtx_leave(&sc->sc_port_mtx); if (port != NULL) { if (sc->sc_scsibus != NULL) scsi_probe_target(sc->sc_scsibus, port->loopid); } else { qle_update_done(sc, QLE_UPDATE_TASK_ATTACH_TARGET); } continue; } } DPRINTF(QLE_D_PORT, "%s: done updating\n", DEVNAME(sc)); } int qle_async(struct qle_softc *sc, u_int16_t info) { switch (info) { case QLE_ASYNC_SYSTEM_ERROR: qle_update_start(sc, QLE_UPDATE_TASK_SOFTRESET); break; case QLE_ASYNC_REQ_XFER_ERROR: qle_update_start(sc, QLE_UPDATE_TASK_SOFTRESET); break; case QLE_ASYNC_RSP_XFER_ERROR: qle_update_start(sc, QLE_UPDATE_TASK_SOFTRESET); break; case QLE_ASYNC_LIP_OCCURRED: DPRINTF(QLE_D_INTR, "%s: lip occurred\n", DEVNAME(sc)); break; case QLE_ASYNC_LOOP_UP: DPRINTF(QLE_D_PORT, "%s: loop up\n", DEVNAME(sc)); sc->sc_loop_up = 1; sc->sc_marker_required = 1; qle_update_defer(sc, QLE_UPDATE_TASK_UPDATE_TOPO | QLE_UPDATE_TASK_GET_PORT_LIST); break; case QLE_ASYNC_LOOP_DOWN: DPRINTF(QLE_D_PORT, "%s: loop down\n", DEVNAME(sc)); sc->sc_loop_up = 0; qle_update_cancel(sc); qle_update_start(sc, QLE_UPDATE_TASK_CLEAR_ALL); break; case QLE_ASYNC_LIP_RESET: DPRINTF(QLE_D_PORT, "%s: lip reset\n", DEVNAME(sc)); sc->sc_marker_required = 1; qle_update_defer(sc, QLE_UPDATE_TASK_FABRIC_RELOGIN); break; case QLE_ASYNC_PORT_DB_CHANGE: DPRINTF(QLE_D_PORT, "%s: port db changed %x\n", DEVNAME(sc), qle_read_mbox(sc, 1)); qle_update_start(sc, QLE_UPDATE_TASK_GET_PORT_LIST); break; case QLE_ASYNC_CHANGE_NOTIFY: DPRINTF(QLE_D_PORT, "%s: name server change (%02x:%02x)\n", DEVNAME(sc), qle_read_mbox(sc, 1), qle_read_mbox(sc, 2)); qle_update_start(sc, QLE_UPDATE_TASK_GET_PORT_LIST); break; case QLE_ASYNC_LIP_F8: DPRINTF(QLE_D_INTR, "%s: lip f8\n", DEVNAME(sc)); break; case QLE_ASYNC_LOOP_INIT_ERROR: DPRINTF(QLE_D_PORT, "%s: loop initialization error: %x\n", DEVNAME(sc), qle_read_mbox(sc, 1)); break; case QLE_ASYNC_POINT_TO_POINT: DPRINTF(QLE_D_PORT, "%s: connected in point-to-point mode\n", DEVNAME(sc)); break; case QLE_ASYNC_ZIO_RESP_UPDATE: /* shouldn't happen, we don't do zio */ break; default: DPRINTF(QLE_D_INTR, "%s: unknown async %x\n", DEVNAME(sc), info); break; } return (1); } void qle_dump_stuff(struct qle_softc *sc, void *buf, int n) { #ifdef QLE_DEBUG u_int8_t *d = buf; int l; if ((qledebug & QLE_D_IOCB) == 0) return; printf("%s: stuff\n", DEVNAME(sc)); for (l = 0; l < n; l++) { printf(" %2.2x", d[l]); if (l % 16 == 15) printf("\n"); } if (n % 16 != 0) printf("\n"); #endif } void qle_dump_iocb(struct qle_softc *sc, void *buf) { #ifdef QLE_DEBUG u_int8_t *iocb = buf; int l; int b; if ((qledebug & QLE_D_IOCB) == 0) return; printf("%s: iocb:\n", DEVNAME(sc)); for (l = 0; l < 4; l++) { for (b = 0; b < 16; b++) { printf(" %2.2x", iocb[(l*16)+b]); } printf("\n"); } #endif } void qle_dump_iocb_segs(struct qle_softc *sc, void *segs, int n) { #ifdef QLE_DEBUG u_int8_t *buf = segs; int s, b; if ((qledebug & QLE_D_IOCB) == 0) return; printf("%s: iocb segs:\n", DEVNAME(sc)); for (s = 0; s < n; s++) { for (b = 0; b < sizeof(struct qle_iocb_seg); b++) { printf(" %2.2x", buf[(s*(sizeof(struct qle_iocb_seg))) + b]); } printf("\n"); } #endif } void qle_put_marker(struct qle_softc *sc, void *buf) { struct qle_iocb_marker *marker = buf; marker->entry_type = QLE_IOCB_MARKER; marker->entry_count = 1; marker->seqno = 0; marker->flags = 0; /* could be more specific here; isp(4) isn't */ marker->target = 0; marker->modifier = QLE_IOCB_MARKER_SYNC_ALL; } void qle_sge(struct qle_iocb_seg *seg, u_int64_t addr, u_int32_t len) { htolem32(&seg->seg_addr_lo, addr); htolem32(&seg->seg_addr_hi, addr >> 32); htolem32(&seg->seg_len, len); } void qle_put_cmd(struct qle_softc *sc, void *buf, struct scsi_xfer *xs, struct qle_ccb *ccb, u_int32_t target_port) { bus_dmamap_t dmap = ccb->ccb_dmamap; struct qle_iocb_req6 *req = buf; struct qle_fcp_cmnd *cmnd; u_int64_t fcp_cmnd_offset; u_int32_t fcp_dl; int seg; int target = xs->sc_link->target; int lun = xs->sc_link->lun; u_int16_t flags; memset(req, 0, sizeof(*req)); req->entry_type = QLE_IOCB_CMD_TYPE_6; req->entry_count = 1; req->req_handle = ccb->ccb_id; htolem16(&req->req_nport_handle, target); /* * timeout is in seconds. make sure it's at least 1 if a timeout * was specified in xs */ if (xs->timeout != 0) htolem16(&req->req_timeout, MAX(1, xs->timeout/1000)); if (xs->datalen > 0) { flags = (xs->flags & SCSI_DATA_IN) ? QLE_IOCB_CTRL_FLAG_READ : QLE_IOCB_CTRL_FLAG_WRITE; if (dmap->dm_nsegs == 1) { qle_sge(&req->req_data_seg, dmap->dm_segs[0].ds_addr, dmap->dm_segs[0].ds_len); } else { flags |= QLE_IOCB_CTRL_FLAG_EXT_SEG; for (seg = 0; seg < dmap->dm_nsegs; seg++) { qle_sge(&ccb->ccb_segs[seg], dmap->dm_segs[seg].ds_addr, dmap->dm_segs[seg].ds_len); } qle_sge(&ccb->ccb_segs[seg++], 0, 0); bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_segments), ccb->ccb_seg_offset, seg * sizeof(*ccb->ccb_segs), BUS_DMASYNC_PREWRITE); qle_sge(&req->req_data_seg, QLE_DMA_DVA(sc->sc_segments) + ccb->ccb_seg_offset, seg * sizeof(struct qle_iocb_seg)); } htolem16(&req->req_data_seg_count, dmap->dm_nsegs); htolem32(&req->req_data_len, xs->datalen); htolem16(&req->req_ctrl_flags, flags); } htobem16(&req->req_fcp_lun[0], lun); htobem16(&req->req_fcp_lun[1], lun >> 16); htolem32(&req->req_target_id, target_port & 0xffffff); fcp_cmnd_offset = ccb->ccb_id * sizeof(*cmnd); /* set up FCP_CMND */ cmnd = (struct qle_fcp_cmnd *)QLE_DMA_KVA(sc->sc_fcp_cmnds) + ccb->ccb_id; memset(cmnd, 0, sizeof(*cmnd)); htobem16(&cmnd->fcp_lun[0], lun); htobem16(&cmnd->fcp_lun[1], lun >> 16); /* cmnd->fcp_task_attr = TSK_SIMPLE; */ /* cmnd->fcp_task_mgmt = 0; */ memcpy(cmnd->fcp_cdb, &xs->cmd, xs->cmdlen); /* FCP_DL goes after the cdb */ fcp_dl = htobe32(xs->datalen); if (xs->cmdlen > 16) { htolem16(&req->req_fcp_cmnd_len, 12 + xs->cmdlen + 4); cmnd->fcp_add_cdb_len = xs->cmdlen - 16; memcpy(cmnd->fcp_cdb + xs->cmdlen, &fcp_dl, sizeof(fcp_dl)); } else { htolem16(&req->req_fcp_cmnd_len, 12 + 16 + 4); cmnd->fcp_add_cdb_len = 0; memcpy(cmnd->fcp_cdb + 16, &fcp_dl, sizeof(fcp_dl)); } if (xs->datalen > 0) cmnd->fcp_add_cdb_len |= (xs->flags & SCSI_DATA_IN) ? 2 : 1; bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_fcp_cmnds), fcp_cmnd_offset, sizeof(*cmnd), BUS_DMASYNC_PREWRITE); /* link req to cmnd */ fcp_cmnd_offset += QLE_DMA_DVA(sc->sc_fcp_cmnds); htolem32(&req->req_fcp_cmnd_addr_lo, fcp_cmnd_offset); htolem32(&req->req_fcp_cmnd_addr_hi, fcp_cmnd_offset >> 32); } int qle_load_fwchunk(struct qle_softc *sc, struct qle_dmamem *mem, const u_int32_t *src) { u_int32_t dest, done, total; int i; dest = src[2]; done = 0; total = src[3]; while (done < total) { u_int32_t *copy; u_int32_t words; /* limit transfer size otherwise it just doesn't work */ words = MIN(total - done, 1 << 10); copy = QLE_DMA_KVA(mem); for (i = 0; i < words; i++) { htolem32(©[i], src[done++]); } bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, words * 4, BUS_DMASYNC_PREWRITE); sc->sc_mbox[0] = QLE_MBOX_LOAD_RISC_RAM; sc->sc_mbox[1] = dest; sc->sc_mbox[4] = words >> 16; sc->sc_mbox[5] = words & 0xffff; sc->sc_mbox[8] = dest >> 16; qle_mbox_putaddr(sc->sc_mbox, mem); if (qle_mbox(sc, 0x01ff)) { printf("firmware load failed\n"); return (1); } bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, words * 4, BUS_DMASYNC_POSTWRITE); dest += words; } return (qle_verify_firmware(sc, src[2])); } int qle_load_firmware_chunks(struct qle_softc *sc, const u_int32_t *fw) { struct qle_dmamem *mem; int res = 0; mem = qle_dmamem_alloc(sc, 65536); for (;;) { if (qle_load_fwchunk(sc, mem, fw)) { res = 1; break; } if (fw[1] == 0) break; fw += fw[3]; } qle_dmamem_free(sc, mem); return (res); } u_int32_t qle_read_ram_word(struct qle_softc *sc, u_int32_t addr) { sc->sc_mbox[0] = QLE_MBOX_READ_RISC_RAM; sc->sc_mbox[1] = addr & 0xffff; sc->sc_mbox[8] = addr >> 16; if (qle_mbox(sc, 0x0103)) { return (0); } return ((sc->sc_mbox[3] << 16) | sc->sc_mbox[2]); } int qle_verify_firmware(struct qle_softc *sc, u_int32_t addr) { /* * QLE_MBOX_VERIFY_CSUM requires at least the firmware header * to be correct, otherwise it wanders all over ISP memory and * gets lost. Check that chunk address (addr+2) is right and * size (addr+3) is plausible first. */ if ((qle_read_ram_word(sc, addr+2) != addr) || (qle_read_ram_word(sc, addr+3) > 0xffff)) { return (1); } sc->sc_mbox[0] = QLE_MBOX_VERIFY_CSUM; sc->sc_mbox[1] = addr >> 16; sc->sc_mbox[2] = addr; if (qle_mbox(sc, 0x0007)) { return (1); } return (0); } int qle_read_nvram(struct qle_softc *sc) { u_int32_t data[sizeof(sc->sc_nvram) / 4]; u_int32_t csum, tmp, v; int i, base, l; switch (sc->sc_isp_gen) { case QLE_GEN_ISP24XX: base = 0x7ffe0080; break; case QLE_GEN_ISP25XX: base = 0x7ff48080; break; } base += sc->sc_port * 0x100; csum = 0; for (i = 0; i < nitems(data); i++) { data[i] = 0xffffffff; qle_write(sc, QLE_FLASH_NVRAM_ADDR, base + i); for (l = 0; l < 5000; l++) { delay(10); tmp = qle_read(sc, QLE_FLASH_NVRAM_ADDR); if (tmp & (1U << 31)) { v = qle_read(sc, QLE_FLASH_NVRAM_DATA); csum += v; data[i] = letoh32(v); break; } } } bcopy(data, &sc->sc_nvram, sizeof(sc->sc_nvram)); /* id field should be 'ISP' */ if (sc->sc_nvram.id[0] != 'I' || sc->sc_nvram.id[1] != 'S' || sc->sc_nvram.id[2] != 'P' || csum != 0) { printf("%s: nvram corrupt\n", DEVNAME(sc)); return (1); } return (0); } struct qle_dmamem * qle_dmamem_alloc(struct qle_softc *sc, size_t size) { struct qle_dmamem *m; int nsegs; m = malloc(sizeof(*m), M_DEVBUF, M_NOWAIT | M_ZERO); if (m == NULL) return (NULL); m->qdm_size = size; if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &m->qdm_map) != 0) goto qdmfree; if (bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &m->qdm_seg, 1, &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0) goto destroy; if (bus_dmamem_map(sc->sc_dmat, &m->qdm_seg, nsegs, size, &m->qdm_kva, BUS_DMA_NOWAIT) != 0) goto free; if (bus_dmamap_load(sc->sc_dmat, m->qdm_map, m->qdm_kva, size, NULL, BUS_DMA_NOWAIT) != 0) goto unmap; return (m); unmap: bus_dmamem_unmap(sc->sc_dmat, m->qdm_kva, m->qdm_size); free: bus_dmamem_free(sc->sc_dmat, &m->qdm_seg, 1); destroy: bus_dmamap_destroy(sc->sc_dmat, m->qdm_map); qdmfree: free(m, M_DEVBUF, sizeof *m); return (NULL); } void qle_dmamem_free(struct qle_softc *sc, struct qle_dmamem *m) { bus_dmamap_unload(sc->sc_dmat, m->qdm_map); bus_dmamem_unmap(sc->sc_dmat, m->qdm_kva, m->qdm_size); bus_dmamem_free(sc->sc_dmat, &m->qdm_seg, 1); bus_dmamap_destroy(sc->sc_dmat, m->qdm_map); free(m, M_DEVBUF, sizeof *m); } int qle_alloc_ccbs(struct qle_softc *sc) { struct qle_ccb *ccb; u_int8_t *cmd; int i; SIMPLEQ_INIT(&sc->sc_ccb_free); mtx_init(&sc->sc_ccb_mtx, IPL_BIO); mtx_init(&sc->sc_queue_mtx, IPL_BIO); mtx_init(&sc->sc_port_mtx, IPL_BIO); mtx_init(&sc->sc_mbox_mtx, IPL_BIO); sc->sc_ccbs = mallocarray(sc->sc_maxcmds, sizeof(struct qle_ccb), M_DEVBUF, M_WAITOK | M_CANFAIL | M_ZERO); if (sc->sc_ccbs == NULL) { printf("%s: unable to allocate ccbs\n", DEVNAME(sc)); return (1); } sc->sc_requests = qle_dmamem_alloc(sc, sc->sc_maxcmds * QLE_QUEUE_ENTRY_SIZE); if (sc->sc_requests == NULL) { printf("%s: unable to allocate ccb dmamem\n", DEVNAME(sc)); goto free_ccbs; } sc->sc_responses = qle_dmamem_alloc(sc, sc->sc_maxcmds * QLE_QUEUE_ENTRY_SIZE); if (sc->sc_responses == NULL) { printf("%s: unable to allocate rcb dmamem\n", DEVNAME(sc)); goto free_req; } sc->sc_pri_requests = qle_dmamem_alloc(sc, 8 * QLE_QUEUE_ENTRY_SIZE); if (sc->sc_pri_requests == NULL) { printf("%s: unable to allocate pri ccb dmamem\n", DEVNAME(sc)); goto free_res; } sc->sc_segments = qle_dmamem_alloc(sc, sc->sc_maxcmds * QLE_MAX_SEGS * sizeof(struct qle_iocb_seg)); if (sc->sc_segments == NULL) { printf("%s: unable to allocate iocb segments\n", DEVNAME(sc)); goto free_pri; } sc->sc_fcp_cmnds = qle_dmamem_alloc(sc, sc->sc_maxcmds * sizeof(struct qle_fcp_cmnd)); if (sc->sc_fcp_cmnds == NULL) { printf("%s: unable to allocate FCP_CMNDs\n", DEVNAME(sc)); goto free_seg; } cmd = QLE_DMA_KVA(sc->sc_requests); memset(cmd, 0, QLE_QUEUE_ENTRY_SIZE * sc->sc_maxcmds); for (i = 0; i < sc->sc_maxcmds; i++) { ccb = &sc->sc_ccbs[i]; if (bus_dmamap_create(sc->sc_dmat, MAXPHYS, QLE_MAX_SEGS-1, MAXPHYS, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ccb->ccb_dmamap) != 0) { printf("%s: unable to create dma map\n", DEVNAME(sc)); goto free_maps; } ccb->ccb_sc = sc; ccb->ccb_id = i; ccb->ccb_seg_offset = i * QLE_MAX_SEGS * sizeof(struct qle_iocb_seg); ccb->ccb_segs = QLE_DMA_KVA(sc->sc_segments) + ccb->ccb_seg_offset; qle_put_ccb(sc, ccb); } scsi_iopool_init(&sc->sc_iopool, sc, qle_get_ccb, qle_put_ccb); return (0); free_maps: while ((ccb = qle_get_ccb(sc)) != NULL) bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap); qle_dmamem_free(sc, sc->sc_fcp_cmnds); free_seg: qle_dmamem_free(sc, sc->sc_segments); free_pri: qle_dmamem_free(sc, sc->sc_pri_requests); free_res: qle_dmamem_free(sc, sc->sc_responses); free_req: qle_dmamem_free(sc, sc->sc_requests); free_ccbs: free(sc->sc_ccbs, M_DEVBUF, 0); return (1); } void qle_free_ccbs(struct qle_softc *sc) { struct qle_ccb *ccb; scsi_iopool_destroy(&sc->sc_iopool); while ((ccb = qle_get_ccb(sc)) != NULL) bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap); qle_dmamem_free(sc, sc->sc_segments); qle_dmamem_free(sc, sc->sc_responses); qle_dmamem_free(sc, sc->sc_requests); free(sc->sc_ccbs, M_DEVBUF, 0); } void * qle_get_ccb(void *xsc) { struct qle_softc *sc = xsc; struct qle_ccb *ccb; mtx_enter(&sc->sc_ccb_mtx); ccb = SIMPLEQ_FIRST(&sc->sc_ccb_free); if (ccb != NULL) { SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_free, ccb_link); } mtx_leave(&sc->sc_ccb_mtx); return (ccb); } void qle_put_ccb(void *xsc, void *io) { struct qle_softc *sc = xsc; struct qle_ccb *ccb = io; ccb->ccb_xs = NULL; mtx_enter(&sc->sc_ccb_mtx); SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_free, ccb, ccb_link); mtx_leave(&sc->sc_ccb_mtx); }