/* $OpenBSD: qla.c,v 1.56 2017/06/05 04:57:37 dlg Exp $ */ /* * Copyright (c) 2011 David Gwynne * 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 #include #include #ifdef QLA_DEBUG #define DPRINTF(m, f...) do { if ((qladebug & (m)) == (m)) printf(f); } \ while (0) #define QLA_D_MBOX 0x01 #define QLA_D_INTR 0x02 #define QLA_D_PORT 0x04 #define QLA_D_IO 0x08 #define QLA_D_IOCB 0x10 int qladebug = QLA_D_PORT; #else #define DPRINTF(m, f...) #endif #ifndef ISP_NOFIRMWARE #include #include #include #endif struct cfdriver qla_cd = { NULL, "qla", DV_DULL }; void qla_scsi_cmd(struct scsi_xfer *); int qla_scsi_probe(struct scsi_link *); u_int16_t qla_read(struct qla_softc *, bus_size_t); void qla_write(struct qla_softc *, bus_size_t, u_int16_t); void qla_host_cmd(struct qla_softc *sc, u_int16_t); u_int16_t qla_read_queue_2100(struct qla_softc *, bus_size_t); int qla_mbox(struct qla_softc *, int); int qla_sns_req(struct qla_softc *, struct qla_dmamem *, int); void qla_mbox_putaddr(u_int16_t *, struct qla_dmamem *); u_int16_t qla_read_mbox(struct qla_softc *, int); void qla_write_mbox(struct qla_softc *, int, u_int16_t); void qla_handle_intr(struct qla_softc *, u_int16_t, u_int16_t); void qla_set_ints(struct qla_softc *, int); int qla_read_isr_1G(struct qla_softc *, u_int16_t *, u_int16_t *); int qla_read_isr_2G(struct qla_softc *, u_int16_t *, u_int16_t *); void qla_clear_isr(struct qla_softc *, u_int16_t); void qla_update_start(struct qla_softc *, int); void qla_update_done(struct qla_softc *, int); void qla_do_update(void *); void qla_put_marker(struct qla_softc *, void *); void qla_put_cmd(struct qla_softc *, void *, struct scsi_xfer *, struct qla_ccb *); struct qla_ccb *qla_handle_resp(struct qla_softc *, u_int16_t); int qla_get_port_name_list(struct qla_softc *, u_int32_t); struct qla_fc_port *qla_next_fabric_port(struct qla_softc *, u_int32_t *, u_int32_t *); int qla_get_port_db(struct qla_softc *c, u_int16_t, struct qla_dmamem *); int qla_add_loop_port(struct qla_softc *, struct qla_fc_port *); int qla_add_fabric_port(struct qla_softc *, struct qla_fc_port *); int qla_add_logged_in_port(struct qla_softc *, int, u_int32_t); int qla_classify_port(struct qla_softc *, u_int32_t, u_int64_t, u_int64_t, struct qla_fc_port **); int qla_get_loop_id(struct qla_softc *sc, int); void qla_clear_port_lists(struct qla_softc *); int qla_softreset(struct qla_softc *); void qla_update_topology(struct qla_softc *); int qla_update_fabric(struct qla_softc *); int qla_fabric_plogi(struct qla_softc *, struct qla_fc_port *); void qla_fabric_plogo(struct qla_softc *, struct qla_fc_port *); void qla_update_start(struct qla_softc *, int); int qla_async(struct qla_softc *, u_int16_t); int qla_verify_firmware(struct qla_softc *sc, u_int16_t); int qla_load_firmware_words(struct qla_softc *, const u_int16_t *, u_int16_t); int qla_load_firmware_2100(struct qla_softc *); int qla_load_firmware_2200(struct qla_softc *); int qla_load_fwchunk_2300(struct qla_softc *, struct qla_dmamem *, const u_int16_t *, u_int32_t); int qla_load_firmware_2300(struct qla_softc *); int qla_load_firmware_2322(struct qla_softc *); int qla_read_nvram(struct qla_softc *); struct qla_dmamem *qla_dmamem_alloc(struct qla_softc *, size_t); void qla_dmamem_free(struct qla_softc *, struct qla_dmamem *); int qla_alloc_ccbs(struct qla_softc *); void qla_free_ccbs(struct qla_softc *); void *qla_get_ccb(void *); void qla_put_ccb(void *, void *); void qla_dump_iocb(struct qla_softc *, void *); void qla_dump_iocb_segs(struct qla_softc *, void *, int); static const struct qla_regs qla_regs_2100 = { qla_read_queue_2100, qla_read_isr_1G, QLA_MBOX_BASE_2100 + 0x8, QLA_MBOX_BASE_2100 + 0x8, QLA_MBOX_BASE_2100 + 0xa, QLA_MBOX_BASE_2100 + 0xa }; static const struct qla_regs qla_regs_2200 = { qla_read, qla_read_isr_1G, QLA_MBOX_BASE_2200 + 0x8, QLA_MBOX_BASE_2200 + 0x8, QLA_MBOX_BASE_2200 + 0xa, QLA_MBOX_BASE_2200 + 0xa }; static const struct qla_regs qla_regs_23XX = { qla_read, qla_read_isr_2G, QLA_REQ_IN, QLA_REQ_OUT, QLA_RESP_IN, QLA_RESP_OUT }; #define qla_queue_read(_sc, _r) ((*(_sc)->sc_regs->read)((_sc), (_r))) #define qla_queue_write(_sc, _r, _v) qla_write((_sc), (_r), (_v)) #define qla_read_isr(_sc, _isr, _info) \ ((*(_sc)->sc_regs->read_isr)((_sc), (_isr), (_info))) struct scsi_adapter qla_switch = { qla_scsi_cmd, scsi_minphys, qla_scsi_probe, NULL, /* scsi_free */ NULL /* ioctl */ }; int qla_classify_port(struct qla_softc *sc, u_int32_t location, u_int64_t port_name, u_int64_t node_name, struct qla_fc_port **prev) { struct qla_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 (QLA_PORT_DISP_DUP); } } /* if we're attaching, everything is new */ if (sc->sc_scsibus == NULL) { *prev = NULL; return (QLA_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 (QLA_PORT_DISP_NEW); } else if (locmatch == wwnmatch) { *prev = locmatch; return (QLA_PORT_DISP_SAME); } else if (wwnmatch != NULL) { *prev = wwnmatch; return (QLA_PORT_DISP_MOVED); } else { *prev = locmatch; return (QLA_PORT_DISP_CHANGED); } } int qla_get_loop_id(struct qla_softc *sc, int start) { int i, last; if (sc->sc_2k_logins) { i = QLA_2KL_MIN_HANDLE; last = QLA_2KL_MAX_HANDLE; } else { /* if we're an F port, we can have two ranges, but meh */ i = QLA_MIN_HANDLE; last = QLA_MAX_HANDLE; } if (i < start) i = start; for (; i <= last; i++) { if (sc->sc_targets[i] == NULL) return (i); } return (-1); } int qla_get_port_db(struct qla_softc *sc, u_int16_t loopid, struct qla_dmamem *mem) { sc->sc_mbox[0] = QLA_MBOX_GET_PORT_DB; if (sc->sc_2k_logins) { sc->sc_mbox[1] = loopid; } else { sc->sc_mbox[1] = loopid << 8; } memset(QLA_DMA_KVA(mem), 0, sizeof(struct qla_get_port_db)); qla_mbox_putaddr(sc->sc_mbox, mem); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, sizeof(struct qla_get_port_db), BUS_DMASYNC_PREREAD); if (qla_mbox(sc, 0x00cf)) { DPRINTF(QLA_D_PORT, "%s: get port db %d failed: %x\n", DEVNAME(sc), loopid, sc->sc_mbox[0]); return (1); } bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, sizeof(struct qla_get_port_db), BUS_DMASYNC_POSTREAD); return (0); } int qla_add_loop_port(struct qla_softc *sc, struct qla_fc_port *port) { struct qla_get_port_db *pdb; struct qla_fc_port *pport = NULL; int disp; if (qla_get_port_db(sc, port->loopid, sc->sc_scratch)) { return (1); } pdb = QLA_DMA_KVA(sc->sc_scratch); if (letoh16(pdb->prli_svc_word3) & QLA_SVC3_TARGET_ROLE) port->flags |= QLA_PORT_FLAG_IS_TARGET; port->port_name = betoh64(pdb->port_name); port->node_name = betoh64(pdb->node_name); port->portid = (letoh16(pdb->port_id[0]) << 16) | letoh16(pdb->port_id[1]); mtx_enter(&sc->sc_port_mtx); disp = qla_classify_port(sc, port->location, port->port_name, port->node_name, &pport); switch (disp) { case QLA_PORT_DISP_CHANGED: case QLA_PORT_DISP_MOVED: case QLA_PORT_DISP_NEW: TAILQ_INSERT_TAIL(&sc->sc_ports_new, port, update); sc->sc_targets[port->loopid] = port; break; case QLA_PORT_DISP_DUP: free(port, M_DEVBUF, sizeof *port); break; case QLA_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 QLA_PORT_DISP_CHANGED: case QLA_PORT_DISP_MOVED: case QLA_PORT_DISP_NEW: DPRINTF(QLA_D_PORT, "%s: %s %d; name %llx, port %06x\n", DEVNAME(sc), ISSET(port->flags, QLA_PORT_FLAG_IS_TARGET) ? "target" : "non-target", port->loopid, port->port_name, port->portid); break; } return (0); } int qla_add_fabric_port(struct qla_softc *sc, struct qla_fc_port *port) { struct qla_get_port_db *pdb; if (qla_get_port_db(sc, port->loopid, sc->sc_scratch)) { return (1); } pdb = QLA_DMA_KVA(sc->sc_scratch); if (letoh16(pdb->prli_svc_word3) & QLA_SVC3_TARGET_ROLE) port->flags |= QLA_PORT_FLAG_IS_TARGET; /* * if we only know about this port because qla_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 == QLA_LOCATION_FABRIC) { port->node_name = betoh64(pdb->node_name); port->port_name = betoh64(pdb->port_name); port->portid = (letoh16(pdb->port_id[0]) << 16) | letoh16(pdb->port_id[1]); port->location = QLA_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(QLA_D_PORT, "%s: %s %d; name %llx\n", DEVNAME(sc), ISSET(port->flags, QLA_PORT_FLAG_IS_TARGET) ? "target" : "non-target", port->loopid, port->port_name); return (0); } int qla_add_logged_in_port(struct qla_softc *sc, int loopid, u_int32_t portid) { struct qla_fc_port *port; struct qla_get_port_db *pdb; u_int64_t node_name, port_name; int flags, ret; ret = qla_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 = QLA_DMA_KVA(sc->sc_scratch); node_name = betoh64(pdb->node_name); port_name = betoh64(pdb->port_name); flags = 0; if (letoh16(pdb->prli_svc_word3) & QLA_SVC3_TARGET_ROLE) flags |= QLA_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(QLA_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 = QLA_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(QLA_D_PORT, "%s: added logged in port %06x at %d\n", DEVNAME(sc), portid, loopid); return (0); } int qla_attach(struct qla_softc *sc) { struct scsibus_attach_args saa; struct qla_init_cb *icb; #ifndef ISP_NOFIRMWARE int (*loadfirmware)(struct qla_softc *) = NULL; #endif u_int16_t firmware_addr = 0; u_int64_t dva; int i, rv; TAILQ_INIT(&sc->sc_ports); TAILQ_INIT(&sc->sc_ports_new); TAILQ_INIT(&sc->sc_ports_gone); TAILQ_INIT(&sc->sc_ports_found); switch (sc->sc_isp_gen) { case QLA_GEN_ISP2100: sc->sc_mbox_base = QLA_MBOX_BASE_2100; sc->sc_regs = &qla_regs_2100; #ifndef ISP_NOFIRMWARE loadfirmware = qla_load_firmware_2100; #endif firmware_addr = QLA_2100_CODE_ORG; break; case QLA_GEN_ISP2200: sc->sc_mbox_base = QLA_MBOX_BASE_2200; sc->sc_regs = &qla_regs_2200; #ifndef ISP_NOFIRMWARE loadfirmware = qla_load_firmware_2200; #endif firmware_addr = QLA_2200_CODE_ORG; break; case QLA_GEN_ISP23XX: sc->sc_mbox_base = QLA_MBOX_BASE_23XX; sc->sc_regs = &qla_regs_23XX; #ifndef ISP_NOFIRMWARE if (sc->sc_isp_type != QLA_ISP2322) loadfirmware = qla_load_firmware_2300; #endif firmware_addr = QLA_2300_CODE_ORG; break; default: printf("unknown isp type\n"); return (ENXIO); } /* after reset, mbox registers 1-3 should contain the string "ISP " */ if (qla_read_mbox(sc, 1) != 0x4953 || qla_read_mbox(sc, 2) != 0x5020 || qla_read_mbox(sc, 3) != 0x2020) { /* try releasing the risc processor */ qla_host_cmd(sc, QLA_HOST_CMD_RELEASE); } qla_host_cmd(sc, QLA_HOST_CMD_PAUSE); if (qla_softreset(sc) != 0) { printf("softreset failed\n"); return (ENXIO); } if (qla_read_nvram(sc) == 0) { sc->sc_nvram_valid = 1; if (sc->sc_port_name == 0) sc->sc_port_name = betoh64(sc->sc_nvram.port_name); if (sc->sc_node_name == 0) sc->sc_node_name = betoh64(sc->sc_nvram.node_name); } if (sc->sc_port_name == 0) sc->sc_port_name = QLA_DEFAULT_PORT_NAME; #ifdef ISP_NOFIRMWARE if (qla_verify_firmware(sc, firmware_addr)) { printf("%s: no firmware loaded\n", DEVNAME(sc)); return (ENXIO); } #else if (loadfirmware && (loadfirmware)(sc)) { printf("%s: firmware load failed\n", DEVNAME(sc)); return (ENXIO); } #endif /* execute firmware */ sc->sc_mbox[0] = QLA_MBOX_EXEC_FIRMWARE; sc->sc_mbox[1] = firmware_addr; #ifdef ISP_NOFIRMWARE sc->sc_mbox[2] = 1; #else if (loadfirmware) sc->sc_mbox[2] = 0; else sc->sc_mbox[2] = 1; #endif if (qla_mbox(sc, 0x0007)) { printf("ISP couldn't exec firmware: %x\n", sc->sc_mbox[0]); return (ENXIO); } delay(250000); /* from isp(4) */ sc->sc_mbox[0] = QLA_MBOX_ABOUT_FIRMWARE; if (qla_mbox(sc, 0x0001)) { printf("ISP not talking after firmware exec: %x\n", sc->sc_mbox[0]); return (ENXIO); } 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]); if (sc->sc_mbox[6] & QLA_FW_ATTR_EXPANDED_LUN) sc->sc_expanded_lun = 1; if (sc->sc_mbox[6] & QLA_FW_ATTR_FABRIC) sc->sc_fabric = 1; if (sc->sc_mbox[6] & QLA_FW_ATTR_2K_LOGINS) sc->sc_2k_logins = 1; /* work out how many ccbs to allocate */ sc->sc_mbox[0] = QLA_MBOX_GET_FIRMWARE_STATUS; if (qla_mbox(sc, 0x0001)) { printf("couldn't get firmware status: %x\n", sc->sc_mbox[0]); return (ENXIO); } sc->sc_maxcmds = sc->sc_mbox[2]; if (qla_alloc_ccbs(sc)) { /* error already printed */ return (ENOMEM); } sc->sc_scratch = qla_dmamem_alloc(sc, QLA_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 qla_init_cb *)QLA_DMA_KVA(sc->sc_scratch); memset(icb, 0, sizeof(*icb)); icb->icb_version = QLA_ICB_VERSION; /* port and node names are big-endian in the icb */ htobem32(&icb->icb_portname_hi, sc->sc_port_name >> 32); htobem32(&icb->icb_portname_lo, sc->sc_port_name); htobem32(&icb->icb_nodename_hi, sc->sc_node_name >> 32); htobem32(&icb->icb_nodename_lo, sc->sc_node_name); if (sc->sc_nvram_valid) { icb->icb_fw_options = sc->sc_nvram.fw_options; icb->icb_max_frame_len = sc->sc_nvram.frame_payload_size; icb->icb_max_alloc = sc->sc_nvram.max_iocb_allocation; icb->icb_exec_throttle = sc->sc_nvram.execution_throttle; icb->icb_retry_count = sc->sc_nvram.retry_count; icb->icb_retry_delay = sc->sc_nvram.retry_delay; icb->icb_hardaddr = sc->sc_nvram.hard_address; icb->icb_inquiry_data = sc->sc_nvram.inquiry_data; icb->icb_login_timeout = sc->sc_nvram.login_timeout; icb->icb_xfwoptions = sc->sc_nvram.add_fw_options; icb->icb_zfwoptions = sc->sc_nvram.special_options; } else { /* defaults copied from isp(4) */ icb->icb_retry_count = 3; icb->icb_retry_delay = 5; icb->icb_exec_throttle = htole16(16); icb->icb_max_alloc = htole16(256); icb->icb_max_frame_len = htole16(1024); icb->icb_fw_options = htole16(QLA_ICB_FW_FAIRNESS | QLA_ICB_FW_ENABLE_PDB_CHANGED | QLA_ICB_FW_HARD_ADDR | QLA_ICB_FW_FULL_DUPLEX); } /* target mode stuff that we don't care about */ icb->icb_lun_enables = 0; icb->icb_cmd_count = 0; icb->icb_notify_count = 0; icb->icb_lun_timeout = 0; /* "zero interrupt operation" */ icb->icb_int_delaytimer = 0; icb->icb_req_out = 0; icb->icb_resp_in = 0; htolem16(&icb->icb_req_queue_len, sc->sc_maxcmds); htolem16(&icb->icb_resp_queue_len, sc->sc_maxcmds); dva = QLA_DMA_DVA(sc->sc_requests); htolem32(&icb->icb_req_queue_addr_lo, dva); htolem32(&icb->icb_req_queue_addr_hi, dva >> 32); dva = QLA_DMA_DVA(sc->sc_responses); htolem32(&icb->icb_resp_queue_addr_lo, dva); htolem32(&icb->icb_resp_queue_addr_hi, dva >> 32); /* adjust firmware options a bit */ icb->icb_fw_options |= htole16(QLA_ICB_FW_EXTENDED_INIT_CB); icb->icb_fw_options &= ~htole16(QLA_ICB_FW_FAST_POST); sc->sc_mbox[0] = QLA_MBOX_INIT_FIRMWARE; sc->sc_mbox[4] = 0; sc->sc_mbox[5] = 0; qla_mbox_putaddr(sc->sc_mbox, sc->sc_scratch); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0, sizeof(*icb), BUS_DMASYNC_PREWRITE); rv = qla_mbox(sc, 0x00fd); bus_dmamap_sync(sc->sc_dmat, QLA_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] = QLA_MBOX_SET_FIRMWARE_OPTIONS; sc->sc_mbox[1] = QLA_FW_OPTION1_ASYNC_LIP_F8 | QLA_FW_OPTION1_ASYNC_LIP_RESET | QLA_FW_OPTION1_ASYNC_LIP_ERROR | QLA_FW_OPTION1_ASYNC_LOGIN_RJT; sc->sc_mbox[2] = 0; sc->sc_mbox[3] = 0; if (qla_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, qla_do_update, sc); /* wait a bit for link to come up so we can scan and attach devices */ for (i = 0; i < QLA_WAIT_FOR_LOOP * 10000; i++) { u_int16_t isr, info; delay(100); if (qla_read_isr(sc, &isr, &info) == 0) continue; qla_handle_intr(sc, isr, info); if (sc->sc_loop_up) break; } if (sc->sc_loop_up) { qla_do_update(sc); } else { DPRINTF(QLA_D_PORT, "%s: loop still down, giving up\n", DEVNAME(sc)); } /* we should be good to go now, attach scsibus */ sc->sc_link.adapter = &qla_switch; sc->sc_link.adapter_softc = sc; if (sc->sc_2k_logins) { sc->sc_link.adapter_buswidth = QLA_2KL_BUSWIDTH; } else { sc->sc_link.adapter_buswidth = QLA_BUSWIDTH; } sc->sc_link.adapter_target = sc->sc_link.adapter_buswidth; sc->sc_link.openings = sc->sc_maxcmds; sc->sc_link.pool = &sc->sc_iopool; sc->sc_link.port_wwn = sc->sc_port_name; sc->sc_link.node_wwn = sc->sc_node_name; if (sc->sc_link.node_wwn == 0) { /* * mask out the port number from the port name to get * the node name. */ sc->sc_link.node_wwn = sc->sc_link.port_wwn; sc->sc_link.node_wwn &= ~(0xfULL << 56); } memset(&saa, 0, sizeof(saa)); saa.saa_sc_link = &sc->sc_link; /* config_found() returns the scsibus attached to us */ sc->sc_scsibus = (struct scsibus_softc *)config_found(&sc->sc_dev, &saa, scsiprint); return(0); free_scratch: qla_dmamem_free(sc, sc->sc_scratch); free_ccbs: qla_free_ccbs(sc); return (ENXIO); } int qla_detach(struct qla_softc *sc, int flags) { return (0); } struct qla_ccb * qla_handle_resp(struct qla_softc *sc, u_int16_t id) { struct qla_ccb *ccb; struct qla_iocb_status *status; struct scsi_xfer *xs; u_int32_t handle; u_int8_t *entry; ccb = NULL; entry = QLA_DMA_KVA(sc->sc_responses) + (id * QLA_QUEUE_ENTRY_SIZE); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_responses), id * QLA_QUEUE_ENTRY_SIZE, QLA_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTREAD); qla_dump_iocb(sc, entry); switch (entry[0]) { case QLA_IOCB_STATUS: status = (struct qla_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(QLA_D_INTR, "%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 > QLA_IOCB_SEGS_PER_CMD) { bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_segments), ccb->ccb_seg_offset, sizeof(*ccb->ccb_t4segs) * ccb->ccb_dmamap->dm_nsegs, 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 = letoh16(status->scsi_status); switch (letoh16(status->completion)) { case QLA_IOCB_STATUS_COMPLETE: if (letoh16(status->scsi_status) & QLA_SCSI_STATUS_SENSE_VALID) { memcpy(&xs->sense, status->sense_data, sizeof(xs->sense)); xs->error = XS_SENSE; } else { xs->error = XS_NOERROR; } xs->resid = 0; break; case QLA_IOCB_STATUS_DMA_ERROR: DPRINTF(QLA_D_INTR, "%s: dma error\n", DEVNAME(sc)); /* set resid apparently? */ break; case QLA_IOCB_STATUS_RESET: DPRINTF(QLA_D_IO, "%s: reset destroyed command\n", DEVNAME(sc)); sc->sc_marker_required = 1; xs->error = XS_RESET; break; case QLA_IOCB_STATUS_ABORTED: DPRINTF(QLA_D_IO, "%s: aborted\n", DEVNAME(sc)); sc->sc_marker_required = 1; xs->error = XS_DRIVER_STUFFUP; break; case QLA_IOCB_STATUS_TIMEOUT: DPRINTF(QLA_D_IO, "%s: command timed out\n", DEVNAME(sc)); xs->error = XS_TIMEOUT; break; case QLA_IOCB_STATUS_DATA_OVERRUN: case QLA_IOCB_STATUS_DATA_UNDERRUN: xs->resid = letoh32(status->resid); xs->error = XS_NOERROR; break; case QLA_IOCB_STATUS_QUEUE_FULL: DPRINTF(QLA_D_IO, "%s: queue full\n", DEVNAME(sc)); xs->error = XS_BUSY; break; case QLA_IOCB_STATUS_PORT_UNAVAIL: case QLA_IOCB_STATUS_PORT_LOGGED_OUT: case QLA_IOCB_STATUS_PORT_CHANGED: DPRINTF(QLA_D_IO, "%s: dev gone\n", DEVNAME(sc)); xs->error = XS_SELTIMEOUT; break; default: DPRINTF(QLA_D_INTR, "%s: unexpected completion" " status %x\n", DEVNAME(sc), status->completion); xs->error = XS_DRIVER_STUFFUP; break; } break; case QLA_IOCB_STATUS_CONT: DPRINTF(QLA_D_INTR, "%s: ignoring status continuation iocb\n", DEVNAME(sc)); break; /* check for requests that bounce back? */ default: DPRINTF(QLA_D_INTR, "%s: unexpected response entry type %x\n", DEVNAME(sc), entry[0]); break; } return (ccb); } void qla_handle_intr(struct qla_softc *sc, u_int16_t isr, u_int16_t info) { int i; u_int16_t rspin; struct qla_ccb *ccb; switch (isr) { case QLA_INT_TYPE_ASYNC: qla_async(sc, info); break; case QLA_INT_TYPE_IO: rspin = qla_queue_read(sc, sc->sc_regs->res_in); if (rspin == sc->sc_last_resp_id) { /* seems to happen a lot on 2200s when mbox commands * complete but it doesn't want to give us the register * semaphore, or something. * * if we're waiting on a mailbox command, don't ack * the interrupt yet. */ if (sc->sc_mbox_pending) { DPRINTF(QLA_D_MBOX, "%s: ignoring premature" " mbox int\n", DEVNAME(sc)); return; } break; } if (sc->sc_responses == NULL) break; DPRINTF(QLA_D_IO, "%s: response queue %x=>%x\n", DEVNAME(sc), sc->sc_last_resp_id, rspin); do { ccb = qla_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); qla_queue_write(sc, sc->sc_regs->res_out, rspin); break; case QLA_INT_TYPE_MBOX: mtx_enter(&sc->sc_mbox_mtx); if (sc->sc_mbox_pending) { DPRINTF(QLA_D_MBOX, "%s: mbox response %x\n", DEVNAME(sc), info); for (i = 0; i < nitems(sc->sc_mbox); i++) { sc->sc_mbox[i] = qla_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(QLA_D_MBOX, "%s: unexpected mbox interrupt:" " %x\n", DEVNAME(sc), info); } break; default: /* maybe log something? */ break; } qla_clear_isr(sc, isr); } int qla_intr(void *xsc) { struct qla_softc *sc = xsc; u_int16_t isr; u_int16_t info; if (qla_read_isr(sc, &isr, &info) == 0) return (0); qla_handle_intr(sc, isr, info); return (1); } int qla_scsi_probe(struct scsi_link *link) { struct qla_softc *sc = link->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, QLA_PORT_FLAG_IS_TARGET)) rv = ENXIO; else { link->port_wwn = sc->sc_targets[link->target]->port_name; link->node_wwn = sc->sc_targets[link->target]->node_name; } mtx_leave(&sc->sc_port_mtx); return (rv); } void qla_scsi_cmd(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct qla_softc *sc = link->adapter_softc; struct qla_ccb *ccb; struct qla_iocb_req34 *iocb; struct qla_ccb_list list; u_int16_t req, rspin; int offset, error, done; bus_dmamap_t dmap; if (xs->cmdlen > sizeof(iocb->req_cdb)) { DPRINTF(QLA_D_IO, "%s: cdb 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; } 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(QLA_D_IO, "%s: writing marker at request %d\n", DEVNAME(sc), req); offset = (req * QLA_QUEUE_ENTRY_SIZE); iocb = QLA_DMA_KVA(sc->sc_requests) + offset; bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_requests), offset, QLA_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE); qla_put_marker(sc, iocb); qla_queue_write(sc, sc->sc_regs->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 * QLA_QUEUE_ENTRY_SIZE); iocb = QLA_DMA_KVA(sc->sc_requests) + offset; bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_requests), offset, QLA_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE); ccb->ccb_xs = xs; DPRINTF(QLA_D_IO, "%s: writing cmd at request %d\n", DEVNAME(sc), req); qla_put_cmd(sc, iocb, xs, ccb); qla_queue_write(sc, sc->sc_regs->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; delay(100); if (qla_read_isr(sc, &isr, &info) == 0) { continue; } if (isr != QLA_INT_TYPE_IO) { qla_handle_intr(sc, isr, info); continue; } rspin = qla_queue_read(sc, sc->sc_regs->res_in); while (rspin != sc->sc_last_resp_id) { ccb = qla_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; } qla_queue_write(sc, sc->sc_regs->res_out, rspin); qla_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_int16_t qla_read(struct qla_softc *sc, bus_size_t offset) { u_int16_t v; v = bus_space_read_2(sc->sc_iot, sc->sc_ioh, offset); bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 2, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); return (v); } void qla_write(struct qla_softc *sc, bus_size_t offset, u_int16_t value) { bus_space_write_2(sc->sc_iot, sc->sc_ioh, offset, value); bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 2, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); } u_int16_t qla_read_mbox(struct qla_softc *sc, int mbox) { /* could range-check mboxes according to chip type? */ return (qla_read(sc, sc->sc_mbox_base + (mbox * 2))); } void qla_write_mbox(struct qla_softc *sc, int mbox, u_int16_t value) { qla_write(sc, sc->sc_mbox_base + (mbox * 2), value); } void qla_host_cmd(struct qla_softc *sc, u_int16_t cmd) { qla_write(sc, QLA_HOST_CMD_CTRL, cmd << QLA_HOST_CMD_SHIFT); } #define MBOX_COMMAND_TIMEOUT 4000 int qla_mbox(struct qla_softc *sc, int maskin) { int i; int result = 0; int rv; sc->sc_mbox_pending = 1; for (i = 0; i < nitems(sc->sc_mbox); i++) { if (maskin & (1 << i)) { qla_write_mbox(sc, i, sc->sc_mbox[i]); } } qla_host_cmd(sc, QLA_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(sc->sc_mbox, &sc->sc_mbox_mtx, PRIBIO, "qlambox", 0); } result = sc->sc_mbox[0]; sc->sc_mbox_pending = 0; mtx_leave(&sc->sc_mbox_mtx); return (result == QLA_MBOX_COMPLETE ? 0 : result); } for (i = 0; i < MBOX_COMMAND_TIMEOUT && result == 0; i++) { u_int16_t isr, info; delay(100); if (qla_read_isr(sc, &isr, &info) == 0) continue; switch (isr) { case QLA_INT_TYPE_MBOX: result = info; break; default: qla_handle_intr(sc, isr, info); break; } } if (result == 0) { /* timed out; do something? */ DPRINTF(QLA_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] = qla_read_mbox(sc, i); } rv = (result == QLA_MBOX_COMPLETE ? 0 : result); } qla_clear_isr(sc, QLA_INT_TYPE_MBOX); sc->sc_mbox_pending = 0; return (rv); } void qla_mbox_putaddr(u_int16_t *mbox, struct qla_dmamem *mem) { mbox[2] = (QLA_DMA_DVA(mem) >> 16) & 0xffff; mbox[3] = (QLA_DMA_DVA(mem) >> 0) & 0xffff; mbox[6] = (QLA_DMA_DVA(mem) >> 48) & 0xffff; mbox[7] = (QLA_DMA_DVA(mem) >> 32) & 0xffff; } int qla_sns_req(struct qla_softc *sc, struct qla_dmamem *mem, int reqsize) { struct qla_sns_req_hdr *header; uint64_t dva; int rv; memset(&sc->sc_mbox, 0, sizeof(sc->sc_mbox)); sc->sc_mbox[0] = QLA_MBOX_SEND_SNS; sc->sc_mbox[1] = reqsize / 2; qla_mbox_putaddr(sc->sc_mbox, mem); header = QLA_DMA_KVA(mem); htolem16(&header->resp_len, (QLA_DMA_LEN(mem) - reqsize) / 2); dva = QLA_DMA_DVA(mem) + reqsize; htolem32(&header->resp_addr_lo, dva); htolem32(&header->resp_addr_hi, dva >> 32); header->subcmd_len = htole16((reqsize - sizeof(*header)) / 2); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, QLA_DMA_LEN(mem), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); rv = qla_mbox(sc, 0x00cf); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, QLA_DMA_LEN(mem), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); return (rv); } void qla_set_ints(struct qla_softc *sc, int enabled) { u_int16_t v = enabled ? (QLA_INT_REQ | QLA_RISC_INT_REQ) : 0; qla_write(sc, QLA_INT_CTRL, v); } int qla_read_isr_1G(struct qla_softc *sc, u_int16_t *isr, u_int16_t *info) { u_int16_t int_status; if (qla_read(sc, QLA_SEMA) & QLA_SEMA_LOCK) { *info = qla_read_mbox(sc, 0); if (*info & QLA_MBOX_HAS_STATUS) *isr = QLA_INT_TYPE_MBOX; else *isr = QLA_INT_TYPE_ASYNC; } else { int_status = qla_read(sc, QLA_INT_STATUS); if ((int_status & QLA_INT_REQ) == 0) return (0); *isr = QLA_INT_TYPE_IO; } return (1); } int qla_read_isr_2G(struct qla_softc *sc, u_int16_t *isr, u_int16_t *info) { u_int32_t v; if ((qla_read(sc, QLA_INT_STATUS) & QLA_INT_REQ) == 0) return (0); v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, QLA_RISC_STATUS_LOW); bus_space_barrier(sc->sc_iot, sc->sc_ioh, QLA_RISC_STATUS_LOW, 4, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); switch (v & QLA_INT_STATUS_MASK) { case QLA_23XX_INT_ROM_MBOX: case QLA_23XX_INT_ROM_MBOX_FAIL: case QLA_23XX_INT_MBOX: case QLA_23XX_INT_MBOX_FAIL: *isr = QLA_INT_TYPE_MBOX; break; case QLA_23XX_INT_ASYNC: *isr = QLA_INT_TYPE_ASYNC; break; case QLA_23XX_INT_RSPQ: *isr = QLA_INT_TYPE_IO; break; default: *isr = QLA_INT_TYPE_OTHER; break; } *info = (v >> QLA_INT_INFO_SHIFT); return (1); } void qla_clear_isr(struct qla_softc *sc, u_int16_t isr) { qla_host_cmd(sc, QLA_HOST_CMD_CLR_RISC_INT); switch (isr) { case QLA_INT_TYPE_MBOX: case QLA_INT_TYPE_ASYNC: qla_write(sc, QLA_SEMA, 0); break; default: break; } } u_int16_t qla_read_queue_2100(struct qla_softc *sc, bus_size_t queue) { u_int16_t a, b, i; for (i = 0; i < 1000; i++) { a = qla_read(sc, queue); b = qla_read(sc, queue); if (a == b) return (a); } DPRINTF(QLA_D_INTR, "%s: queue ptr unstable\n", DEVNAME(sc)); return (a); } int qla_softreset(struct qla_softc *sc) { int i; qla_set_ints(sc, 0); /* reset */ qla_write(sc, QLA_CTRL_STATUS, QLA_CTRL_RESET); delay(100); /* clear data and control dma engines? */ /* wait for soft reset to clear */ for (i = 0; i < 1000; i++) { if ((qla_read(sc, QLA_CTRL_STATUS) & QLA_CTRL_RESET) == 0) break; delay(100); } if (i == 1000) { DPRINTF(QLA_D_INTR, "%s: reset didn't clear\n", DEVNAME(sc)); qla_set_ints(sc, 0); return (ENXIO); } /* reset FPM */ qla_write(sc, QLA_CTRL_STATUS, QLA_CTRL_FPM0_REGS); qla_write(sc, QLA_FPM_DIAG, QLA_FPM_RESET); qla_write(sc, QLA_FPM_DIAG, 0); /* isp(4) doesn't do this? */ qla_write(sc, QLA_CTRL_STATUS, QLA_CTRL_RISC_REGS); /* reset risc processor */ qla_host_cmd(sc, QLA_HOST_CMD_RESET); delay(100); qla_write(sc, QLA_SEMA, 0); qla_host_cmd(sc, QLA_HOST_CMD_MASK_PARITY); /* from isp(4) */ qla_host_cmd(sc, QLA_HOST_CMD_RELEASE); /* reset queue pointers */ qla_queue_write(sc, sc->sc_regs->req_in, 0); qla_queue_write(sc, sc->sc_regs->req_out, 0); qla_queue_write(sc, sc->sc_regs->res_in, 0); qla_queue_write(sc, sc->sc_regs->res_out, 0); qla_set_ints(sc, 1); /* isp(4) sends QLA_HOST_CMD_BIOS here.. not documented? */ /* do a basic mailbox operation to check we're alive */ sc->sc_mbox[0] = QLA_MBOX_NOP; if (qla_mbox(sc, 0x0001)) { DPRINTF(QLA_D_INTR, "%s: ISP not responding after reset\n", DEVNAME(sc)); return (ENXIO); } return (0); } void qla_update_topology(struct qla_softc *sc) { sc->sc_mbox[0] = QLA_MBOX_GET_LOOP_ID; if (qla_mbox(sc, 0x0001)) { DPRINTF(QLA_D_PORT, "%s: unable to get loop id\n", DEVNAME(sc)); sc->sc_topology = QLA_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 QLA_TOPO_NL_PORT: case QLA_TOPO_N_PORT: DPRINTF(QLA_D_PORT, "%s: loop id %d\n", DEVNAME(sc), sc->sc_loop_id); break; case QLA_TOPO_FL_PORT: case QLA_TOPO_F_PORT: sc->sc_port_id = sc->sc_mbox[2] | (sc->sc_mbox[3] << 16); DPRINTF(QLA_D_PORT, "%s: fabric port id %06x\n", DEVNAME(sc), sc->sc_port_id); break; case QLA_TOPO_N_PORT_NO_TARGET: default: DPRINTF(QLA_D_PORT, "%s: not connected\n", DEVNAME(sc)); break; } switch (sc->sc_topology) { case QLA_TOPO_NL_PORT: case QLA_TOPO_FL_PORT: sc->sc_loop_max_id = 126; break; case QLA_TOPO_N_PORT: sc->sc_loop_max_id = 2; break; default: sc->sc_loop_max_id = 0; break; } } } int qla_update_fabric(struct qla_softc *sc) { struct qla_sns_rft_id *rft; if (sc->sc_fabric == 0) return (0); switch (sc->sc_topology) { case QLA_TOPO_F_PORT: case QLA_TOPO_FL_PORT: break; default: return (0); } /* get the name server's port db entry */ sc->sc_mbox[0] = QLA_MBOX_GET_PORT_DB; if (sc->sc_2k_logins) { sc->sc_mbox[1] = QLA_F_PORT_HANDLE; } else { sc->sc_mbox[1] = QLA_F_PORT_HANDLE << 8; } qla_mbox_putaddr(sc->sc_mbox, sc->sc_scratch); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0, sizeof(struct qla_get_port_db), BUS_DMASYNC_PREREAD); if (qla_mbox(sc, 0x00cf)) { DPRINTF(QLA_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 qla_get_port_db *pdb; bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0, sizeof(struct qla_get_port_db), BUS_DMASYNC_POSTREAD); pdb = QLA_DMA_KVA(sc->sc_scratch); DPRINTF(QLA_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 = QLA_DMA_KVA(sc->sc_scratch); memset(rft, 0, sizeof(*rft) + sizeof(struct qla_sns_req_hdr)); rft->subcmd = htole16(QLA_SNS_RFT_ID); rft->max_word = htole16(sizeof(struct qla_sns_req_hdr) / 4); rft->port_id = htole32(sc->sc_port_id); rft->fc4_types[0] = htole32(1 << QLA_FC4_SCSI); if (qla_sns_req(sc, sc->sc_scratch, sizeof(*rft))) { DPRINTF(QLA_D_PORT, "%s: RFT_ID failed\n", DEVNAME(sc)); /* we might be able to continue after this fails */ } return (1); } int qla_get_port_name_list(struct qla_softc *sc, u_int32_t match) { int i; struct qla_port_name_list *l; struct qla_fc_port *port; sc->sc_mbox[0] = QLA_MBOX_GET_PORT_NAME_LIST; sc->sc_mbox[1] = 0x08; /* include initiators */ if (match & QLA_LOCATION_FABRIC) sc->sc_mbox[1] |= 0x02; /* return all loop ids */ qla_mbox_putaddr(sc->sc_mbox, sc->sc_scratch); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0, QLA_DMA_LEN(sc->sc_scratch), BUS_DMASYNC_PREREAD); if (qla_mbox(sc, 0x04f)) { DPRINTF(QLA_D_PORT, "%s: get port name list failed: %x\n", DEVNAME(sc), sc->sc_mbox[0]); return (1); } bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0, QLA_DMA_LEN(sc->sc_scratch), BUS_DMASYNC_PREREAD); i = 0; l = QLA_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 = letoh16(l[i].loop_id); /* skip special ports */ switch (loopid) { case QLA_F_PORT_HANDLE: case QLA_SNS_HANDLE: case QLA_FABRIC_CTRL_HANDLE: loc = 0; break; default: if (loopid <= sc->sc_loop_max_id) { loc = QLA_LOCATION_LOOP_ID(loopid); } else { /* * we don't have the port id here, so just * indicate it's a fabric port. */ loc = QLA_LOCATION_FABRIC; } } 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(QLA_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); } struct qla_fc_port * qla_next_fabric_port(struct qla_softc *sc, u_int32_t *firstport, u_int32_t *lastport) { struct qla_sns_ga_nxt *ga; struct qla_sns_ga_nxt_resp *gar; struct qla_fc_port *fport; int result; /* get the next port from the fabric nameserver */ ga = QLA_DMA_KVA(sc->sc_scratch); memset(ga, 0, sizeof(*ga) + sizeof(*gar)); ga->subcmd = htole16(QLA_SNS_GA_NXT); ga->max_word = htole16(sizeof(*gar) / 4); ga->port_id = htole32(*lastport); result = qla_sns_req(sc, sc->sc_scratch, sizeof(*ga)); if (result) { DPRINTF(QLA_D_PORT, "%s: GA_NXT %06x failed: %x\n", DEVNAME(sc), *lastport, result); *lastport = 0xffffffff; return (NULL); } gar = (struct qla_sns_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(QLA_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(QLA_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 = QLA_LOCATION_PORT_ID(*lastport); fport->portid = *lastport; return (fport); } int qla_fabric_plogi(struct qla_softc *sc, struct qla_fc_port *port) { int loopid, mboxin, err; u_int32_t id; loopid = 0; retry: if (port->loopid == 0) { mtx_enter(&sc->sc_port_mtx); loopid = qla_get_loop_id(sc, loopid); mtx_leave(&sc->sc_port_mtx); if (loopid == -1) { DPRINTF(QLA_D_PORT, "%s: ran out of loop ids\n", DEVNAME(sc)); return (1); } } mboxin = 0x000f; sc->sc_mbox[0] = QLA_MBOX_FABRIC_PLOGI; sc->sc_mbox[2] = (port->portid >> 16) & 0xff; sc->sc_mbox[3] = port->portid & 0xffff; if (sc->sc_2k_logins) { sc->sc_mbox[1] = loopid; sc->sc_mbox[10] = 0; mboxin |= (1 << 10); } else { sc->sc_mbox[1] = loopid << 8; } err = qla_mbox(sc, mboxin); switch (err) { case 0: DPRINTF(QLA_D_PORT, "%s: logged in to %06x as %d\n", DEVNAME(sc), port->portid, loopid); port->flags &= ~QLA_PORT_FLAG_NEEDS_LOGIN; port->loopid = loopid; return (0); case QLA_MBOX_PORT_USED: DPRINTF(QLA_D_PORT, "%s: already logged in to %06x as %d\n", DEVNAME(sc), port->portid, sc->sc_mbox[1]); port->flags &= ~QLA_PORT_FLAG_NEEDS_LOGIN; port->loopid = sc->sc_mbox[1]; return (0); case QLA_MBOX_LOOP_USED: id = (sc->sc_mbox[1] << 16) | sc->sc_mbox[2]; if (qla_add_logged_in_port(sc, loopid, id)) { return (1); } port->loopid = 0; loopid++; goto retry; default: DPRINTF(QLA_D_PORT, "%s: error %x logging in to port %06x\n", DEVNAME(sc), err, port->portid); port->loopid = 0; return (1); } } void qla_fabric_plogo(struct qla_softc *sc, struct qla_fc_port *port) { int mboxin = 0x0003; sc->sc_mbox[0] = QLA_MBOX_FABRIC_PLOGO; if (sc->sc_2k_logins) { sc->sc_mbox[1] = port->loopid; sc->sc_mbox[10] = 0; mboxin |= (1 << 10); } else { sc->sc_mbox[1] = port->loopid << 8; } if (qla_mbox(sc, mboxin)) DPRINTF(QLA_D_PORT, "%s: loop id %d logout failed\n", DEVNAME(sc), port->loopid); } void qla_update_done(struct qla_softc *sc, int task) { atomic_clearbits_int(&sc->sc_update_tasks, task); } void qla_update_start(struct qla_softc *sc, int task) { atomic_setbits_int(&sc->sc_update_tasks, task); task_add(sc->sc_update_taskq, &sc->sc_update_task); } void qla_clear_port_lists(struct qla_softc *sc) { struct qla_fc_port *p; while (!TAILQ_EMPTY(&sc->sc_ports_found)) { p = TAILQ_FIRST(&sc->sc_ports_found); TAILQ_REMOVE(&sc->sc_ports_found, p, update); free(p, M_DEVBUF, sizeof *p); } while (!TAILQ_EMPTY(&sc->sc_ports_new)) { p = TAILQ_FIRST(&sc->sc_ports_new); TAILQ_REMOVE(&sc->sc_ports_new, p, update); free(p, M_DEVBUF, sizeof *p); } while (!TAILQ_EMPTY(&sc->sc_ports_gone)) { p = TAILQ_FIRST(&sc->sc_ports_gone); TAILQ_REMOVE(&sc->sc_ports_gone, p, update); } } void qla_do_update(void *xsc) { struct qla_softc *sc = xsc; int firstport, lastport; struct qla_fc_port *port, *fport; DPRINTF(QLA_D_PORT, "%s: updating\n", DEVNAME(sc)); while (sc->sc_update_tasks != 0) { if (sc->sc_update_tasks & QLA_UPDATE_TASK_CLEAR_ALL) { TAILQ_HEAD(, qla_fc_port) detach; DPRINTF(QLA_D_PORT, "%s: detaching everything\n", DEVNAME(sc)); mtx_enter(&sc->sc_port_mtx); qla_clear_port_lists(sc); TAILQ_INIT(&detach); while (!TAILQ_EMPTY(&sc->sc_ports)) { port = TAILQ_FIRST(&sc->sc_ports); TAILQ_REMOVE(&sc->sc_ports, port, ports); TAILQ_INSERT_TAIL(&detach, port, ports); } mtx_leave(&sc->sc_port_mtx); while (!TAILQ_EMPTY(&detach)) { port = TAILQ_FIRST(&detach); TAILQ_REMOVE(&detach, port, ports); if (port->flags & QLA_PORT_FLAG_IS_TARGET) { scsi_detach_target(sc->sc_scsibus, port->loopid, -1); } sc->sc_targets[port->loopid] = NULL; if (port->location & QLA_LOCATION_FABRIC) qla_fabric_plogo(sc, port); free(port, M_DEVBUF, sizeof *port); } qla_update_done(sc, QLA_UPDATE_TASK_CLEAR_ALL); continue; } if (sc->sc_update_tasks & QLA_UPDATE_TASK_SOFTRESET) { /* what no */ qla_update_done(sc, QLA_UPDATE_TASK_SOFTRESET); continue; } if (sc->sc_update_tasks & QLA_UPDATE_TASK_UPDATE_TOPO) { DPRINTF(QLA_D_PORT, "%s: updating topology\n", DEVNAME(sc)); qla_update_topology(sc); qla_update_done(sc, QLA_UPDATE_TASK_UPDATE_TOPO); continue; } if (sc->sc_update_tasks & QLA_UPDATE_TASK_GET_PORT_LIST) { DPRINTF(QLA_D_PORT, "%s: getting port name list\n", DEVNAME(sc)); mtx_enter(&sc->sc_port_mtx); qla_clear_port_lists(sc); mtx_leave(&sc->sc_port_mtx); qla_get_port_name_list(sc, QLA_LOCATION_LOOP | QLA_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 & QLA_LOCATION_FABRIC) { port->flags |= QLA_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 &= ~QLA_PORT_FLAG_NEEDS_LOGIN; if (port->location & QLA_LOCATION_LOOP) TAILQ_REMOVE(&sc->sc_ports_gone, port, update); fport->location = 0; } mtx_leave(&sc->sc_port_mtx); qla_update_start(sc, QLA_UPDATE_TASK_PORT_LIST); qla_update_done(sc, QLA_UPDATE_TASK_GET_PORT_LIST); continue; } if (sc->sc_update_tasks & QLA_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(QLA_D_PORT, "%s: done with ports\n", DEVNAME(sc)); qla_update_done(sc, QLA_UPDATE_TASK_PORT_LIST); qla_update_start(sc, QLA_UPDATE_TASK_SCAN_FABRIC); } else if (fport->location & QLA_LOCATION_LOOP) { DPRINTF(QLA_D_PORT, "%s: loop port %d\n", DEVNAME(sc), fport->loopid); if (qla_add_loop_port(sc, fport) != 0) free(fport, M_DEVBUF, sizeof *fport); } else if (fport->location & QLA_LOCATION_FABRIC) { qla_add_fabric_port(sc, fport); } else { /* already processed */ free(fport, M_DEVBUF, sizeof *fport); } continue; } if (sc->sc_update_tasks & QLA_UPDATE_TASK_SCAN_FABRIC) { DPRINTF(QLA_D_PORT, "%s: starting fabric scan\n", DEVNAME(sc)); lastport = sc->sc_port_id; firstport = 0xffffffff; if (qla_update_fabric(sc)) qla_update_start(sc, QLA_UPDATE_TASK_SCANNING_FABRIC); qla_update_done(sc, QLA_UPDATE_TASK_SCAN_FABRIC); continue; } if (sc->sc_update_tasks & QLA_UPDATE_TASK_SCANNING_FABRIC) { fport = qla_next_fabric_port(sc, &firstport, &lastport); if (fport != NULL) { int disp; mtx_enter(&sc->sc_port_mtx); disp = qla_classify_port(sc, fport->location, fport->port_name, fport->node_name, &port); switch (disp) { case QLA_PORT_DISP_CHANGED: case QLA_PORT_DISP_MOVED: /* we'll log out the old port later */ case QLA_PORT_DISP_NEW: DPRINTF(QLA_D_PORT, "%s: new port " "%06x\n", DEVNAME(sc), fport->portid); TAILQ_INSERT_TAIL(&sc->sc_ports_found, fport, update); break; case QLA_PORT_DISP_DUP: free(fport, M_DEVBUF, sizeof *fport); break; case QLA_PORT_DISP_SAME: DPRINTF(QLA_D_PORT, "%s: existing port" " %06x\n", DEVNAME(sc), fport->portid); TAILQ_REMOVE(&sc->sc_ports_gone, port, update); free(fport, M_DEVBUF, sizeof *fport); break; } mtx_leave(&sc->sc_port_mtx); } if (lastport == 0xffffffff) { DPRINTF(QLA_D_PORT, "%s: finished\n", DEVNAME(sc)); qla_update_done(sc, QLA_UPDATE_TASK_SCANNING_FABRIC); qla_update_start(sc, QLA_UPDATE_TASK_FABRIC_LOGIN); } continue; } if (sc->sc_update_tasks & QLA_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(QLA_D_PORT, "%s: found port %06x\n", DEVNAME(sc), port->portid); if (qla_fabric_plogi(sc, port) == 0) { qla_add_fabric_port(sc, port); } else { free(port, M_DEVBUF, sizeof *port); } } else { DPRINTF(QLA_D_PORT, "%s: done with logins\n", DEVNAME(sc)); qla_update_done(sc, QLA_UPDATE_TASK_FABRIC_LOGIN); qla_update_start(sc, QLA_UPDATE_TASK_ATTACH_TARGET | QLA_UPDATE_TASK_DETACH_TARGET); } continue; } if (sc->sc_update_tasks & QLA_UPDATE_TASK_FABRIC_RELOGIN) { TAILQ_FOREACH(port, &sc->sc_ports, ports) { if (port->flags & QLA_PORT_FLAG_NEEDS_LOGIN) { qla_fabric_plogi(sc, port); break; } } if (port == NULL) qla_update_done(sc, QLA_UPDATE_TASK_FABRIC_RELOGIN); continue; } if (sc->sc_update_tasks & QLA_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(QLA_D_PORT, "%s: detaching target %d\n", DEVNAME(sc), port->loopid); if (sc->sc_scsibus != NULL) scsi_detach_target(sc->sc_scsibus, port->loopid, -1); if (port->location & QLA_LOCATION_FABRIC) qla_fabric_plogo(sc, port); free(port, M_DEVBUF, sizeof *port); } else { qla_update_done(sc, QLA_UPDATE_TASK_DETACH_TARGET); } continue; } if (sc->sc_update_tasks & QLA_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 { qla_update_done(sc, QLA_UPDATE_TASK_ATTACH_TARGET); } continue; } } DPRINTF(QLA_D_PORT, "%s: done updating\n", DEVNAME(sc)); } int qla_async(struct qla_softc *sc, u_int16_t info) { u_int16_t id, exp; switch (info) { case QLA_ASYNC_SYSTEM_ERROR: qla_update_start(sc, QLA_UPDATE_TASK_SOFTRESET); break; case QLA_ASYNC_REQ_XFER_ERROR: qla_update_start(sc, QLA_UPDATE_TASK_SOFTRESET); break; case QLA_ASYNC_RSP_XFER_ERROR: qla_update_start(sc, QLA_UPDATE_TASK_SOFTRESET); break; case QLA_ASYNC_LIP_OCCURRED: DPRINTF(QLA_D_PORT, "%s: lip occurred\n", DEVNAME(sc)); break; case QLA_ASYNC_LOOP_UP: DPRINTF(QLA_D_PORT, "%s: loop up\n", DEVNAME(sc)); sc->sc_loop_up = 1; sc->sc_marker_required = 1; qla_update_start(sc, QLA_UPDATE_TASK_UPDATE_TOPO | QLA_UPDATE_TASK_GET_PORT_LIST); break; case QLA_ASYNC_LOOP_DOWN: DPRINTF(QLA_D_PORT, "%s: loop down\n", DEVNAME(sc)); sc->sc_loop_up = 0; qla_update_start(sc, QLA_UPDATE_TASK_CLEAR_ALL); break; case QLA_ASYNC_LIP_RESET: DPRINTF(QLA_D_PORT, "%s: lip reset\n", DEVNAME(sc)); sc->sc_marker_required = 1; qla_update_start(sc, QLA_UPDATE_TASK_FABRIC_RELOGIN); break; case QLA_ASYNC_PORT_DB_CHANGE: DPRINTF(QLA_D_PORT, "%s: port db changed %x\n", DEVNAME(sc), qla_read_mbox(sc, 1)); qla_update_start(sc, QLA_UPDATE_TASK_GET_PORT_LIST); break; case QLA_ASYNC_CHANGE_NOTIFY: DPRINTF(QLA_D_PORT, "%s: name server change (%02x:%02x)\n", DEVNAME(sc), qla_read_mbox(sc, 1), qla_read_mbox(sc, 2)); qla_update_start(sc, QLA_UPDATE_TASK_GET_PORT_LIST); break; case QLA_ASYNC_LIP_F8: DPRINTF(QLA_D_PORT, "%s: lip f8\n", DEVNAME(sc)); break; case QLA_ASYNC_LOOP_INIT_ERROR: DPRINTF(QLA_D_PORT, "%s: loop initialization error: %x\n", DEVNAME(sc), qla_read_mbox(sc, 1)); break; case QLA_ASYNC_LOGIN_REJECT: id = qla_read_mbox(sc, 1); exp = qla_read_mbox(sc, 2); DPRINTF(QLA_D_PORT, "%s: login reject from %x (reason %d," " explanation %x)\n", DEVNAME(sc), id >> 8, id & 0xff, exp); break; case QLA_ASYNC_SCSI_CMD_COMPLETE: /* shouldn't happen, we disable fast posting */ break; case QLA_ASYNC_CTIO_COMPLETE: /* definitely shouldn't happen, we don't do target mode */ break; case QLA_ASYNC_POINT_TO_POINT: DPRINTF(QLA_D_PORT, "%s: connected in point-to-point mode\n", DEVNAME(sc)); /* we get stuck handling these if we have the wrong loop * topology; should somehow reinit with different things * somehow. */ break; case QLA_ASYNC_ZIO_RESP_UPDATE: /* shouldn't happen, we don't do zio */ break; case QLA_ASYNC_RND_ERROR: /* do nothing? */ break; case QLA_ASYNC_QUEUE_FULL: break; default: DPRINTF(QLA_D_INTR, "%s: unknown async %x\n", DEVNAME(sc), info); break; } return (1); } void qla_dump_iocb(struct qla_softc *sc, void *buf) { #ifdef QLA_DEBUG u_int8_t *iocb = buf; int l; int b; if ((qladebug & QLA_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 qla_dump_iocb_segs(struct qla_softc *sc, void *segs, int n) { #ifdef QLA_DEBUG u_int8_t *buf = segs; int s, b; if ((qladebug & QLA_D_IOCB) == 0) return; printf("%s: iocb segs:\n", DEVNAME(sc)); for (s = 0; s < n; s++) { for (b = 0; b < sizeof(struct qla_iocb_seg); b++) { printf(" %2.2x", buf[(s*(sizeof(struct qla_iocb_seg))) + b]); } printf("\n"); } #endif } void qla_put_marker(struct qla_softc *sc, void *buf) { struct qla_iocb_marker *marker = buf; marker->entry_type = QLA_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 = QLA_IOCB_MARKER_SYNC_ALL; qla_dump_iocb(sc, buf); } static inline void qla_put_data_seg(struct qla_iocb_seg *seg, bus_dmamap_t dmap, int num) { uint64_t addr = dmap->dm_segs[num].ds_addr; htolem32(&seg->seg_addr_lo, addr); htolem32(&seg->seg_addr_hi, addr >> 32); htolem32(&seg->seg_len, dmap->dm_segs[num].ds_len); } void qla_put_cmd(struct qla_softc *sc, void *buf, struct scsi_xfer *xs, struct qla_ccb *ccb) { struct qla_iocb_req34 *req = buf; u_int16_t dir; int seg; int target = xs->sc_link->target; req->seqno = 0; req->flags = 0; req->entry_count = 1; if (xs->datalen == 0) { dir = QLA_IOCB_CMD_NO_DATA; req->req_seg_count = 0; req->entry_type = QLA_IOCB_CMD_TYPE_3; } else { dir = xs->flags & SCSI_DATA_IN ? QLA_IOCB_CMD_READ_DATA : QLA_IOCB_CMD_WRITE_DATA; htolem16(&req->req_seg_count, ccb->ccb_dmamap->dm_nsegs); if (ccb->ccb_dmamap->dm_nsegs > QLA_IOCB_SEGS_PER_CMD) { req->entry_type = QLA_IOCB_CMD_TYPE_4; for (seg = 0; seg < ccb->ccb_dmamap->dm_nsegs; seg++) { qla_put_data_seg(&ccb->ccb_t4segs[seg], ccb->ccb_dmamap, seg); } req->req_type.req4.req4_seg_type = htole16(1); req->req_type.req4.req4_seg_base = 0; req->req_type.req4.req4_seg_addr = ccb->ccb_seg_dva; memset(req->req_type.req4.req4_reserved, 0, sizeof(req->req_type.req4.req4_reserved)); bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_segments), ccb->ccb_seg_offset, sizeof(*ccb->ccb_t4segs) * ccb->ccb_dmamap->dm_nsegs, BUS_DMASYNC_PREWRITE); } else { req->entry_type = QLA_IOCB_CMD_TYPE_3; for (seg = 0; seg < ccb->ccb_dmamap->dm_nsegs; seg++) { qla_put_data_seg(&req->req_type.req3_segs[seg], ccb->ccb_dmamap, seg); } } } /* isp(4) uses head of queue for 'request sense' commands */ htolem16(&req->req_flags, QLA_IOCB_CMD_SIMPLE_QUEUE | dir); /* * 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_time, MAX(1, xs->timeout/1000)); /* lun and target layout vary with firmware attributes */ if (sc->sc_expanded_lun) { if (sc->sc_2k_logins) { req->req_target = htole16(target); } else { req->req_target = htole16(target << 8); } req->req_scclun = htole16(xs->sc_link->lun); } else { req->req_target = htole16(target << 8 | xs->sc_link->lun); } memcpy(req->req_cdb, xs->cmd, xs->cmdlen); req->req_totalcnt = htole32(xs->datalen); req->req_handle = ccb->ccb_id; qla_dump_iocb(sc, buf); } int qla_verify_firmware(struct qla_softc *sc, u_int16_t addr) { sc->sc_mbox[0] = QLA_MBOX_VERIFY_CSUM; sc->sc_mbox[1] = addr; return (qla_mbox(sc, 0x0003)); } #ifndef ISP_NOFIRMWARE int qla_load_firmware_words(struct qla_softc *sc, const u_int16_t *src, u_int16_t dest) { u_int16_t i; for (i = 0; i < src[3]; i++) { sc->sc_mbox[0] = QLA_MBOX_WRITE_RAM_WORD; sc->sc_mbox[1] = i + dest; sc->sc_mbox[2] = src[i]; if (qla_mbox(sc, 0x07)) { printf("firmware load failed\n"); return (1); } } return (qla_verify_firmware(sc, dest)); } int qla_load_firmware_2100(struct qla_softc *sc) { return qla_load_firmware_words(sc, isp_2100_risc_code, QLA_2100_CODE_ORG); } int qla_load_firmware_2200(struct qla_softc *sc) { return qla_load_firmware_words(sc, isp_2200_risc_code, QLA_2200_CODE_ORG); } int qla_load_fwchunk_2300(struct qla_softc *sc, struct qla_dmamem *mem, const u_int16_t *src, u_int32_t dest) { u_int16_t origin, done, total; int i; origin = dest; done = 0; total = src[3]; while (done < total) { u_int16_t *copy; u_int32_t words; /* limit transfer size otherwise it just doesn't work */ words = MIN(total - done, 1 << 10); copy = QLA_DMA_KVA(mem); for (i = 0; i < words; i++) { copy[i] = htole16(src[done++]); } bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, words * 2, BUS_DMASYNC_PREWRITE); sc->sc_mbox[0] = QLA_MBOX_LOAD_RAM_EXT; sc->sc_mbox[1] = dest; sc->sc_mbox[4] = words; sc->sc_mbox[8] = dest >> 16; qla_mbox_putaddr(sc->sc_mbox, mem); if (qla_mbox(sc, 0x01ff)) { printf("firmware load failed\n"); return (1); } bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, words * 2, BUS_DMASYNC_POSTWRITE); dest += words; } return (qla_verify_firmware(sc, origin)); } int qla_load_firmware_2300(struct qla_softc *sc) { struct qla_dmamem *mem; const u_int16_t *fw = isp_2300_risc_code; int rv; mem = qla_dmamem_alloc(sc, 65536); rv = qla_load_fwchunk_2300(sc, mem, fw, QLA_2300_CODE_ORG); qla_dmamem_free(sc, mem); return (rv); } int qla_load_firmware_2322(struct qla_softc *sc) { /* we don't have the 2322 firmware image yet */ #if 0 struct qla_dmamem *mem; const u_int16_t *fw = isp_2322_risc_code; u_int32_t addr; int i; mem = qla_dmamem_alloc(sc, 65536); if (qla_load_fwchunk_2300(sc, mem, fw, QLA_2300_CODE_ORG)) { qla_dmamem_free(sc, mem); return (1); } for (i = 0; i < 2; i++) { fw += fw[3]; addr = fw[5] | ((fw[4] & 0x3f) << 16); if (qla_load_fwchunk_2300(sc, mem, fw, addr)) { qla_dmamem_free(sc, mem); return (1); } } qla_dmamem_free(sc, mem); #endif return (0); } #endif /* !ISP_NOFIRMWARE */ int qla_read_nvram(struct qla_softc *sc) { u_int16_t data[sizeof(sc->sc_nvram) >> 1]; u_int16_t req, cmd, val; u_int8_t csum; int i, base, bit; base = sc->sc_port * 0x80; qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL); delay(10); qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL | QLA_NVRAM_CLOCK); delay(10); for (i = 0; i < nitems(data); i++) { req = (i + base) | (QLA_NVRAM_CMD_READ << 8); /* write each bit out through the nvram register */ for (bit = 10; bit >= 0; bit--) { cmd = QLA_NVRAM_CHIP_SEL; if ((req >> bit) & 1) { cmd |= QLA_NVRAM_DATA_OUT; } qla_write(sc, QLA_NVRAM, cmd); delay(10); qla_read(sc, QLA_NVRAM); qla_write(sc, QLA_NVRAM, cmd | QLA_NVRAM_CLOCK); delay(10); qla_read(sc, QLA_NVRAM); qla_write(sc, QLA_NVRAM, cmd); delay(10); qla_read(sc, QLA_NVRAM); } /* read the result back */ val = 0; for (bit = 0; bit < 16; bit++) { val <<= 1; qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL | QLA_NVRAM_CLOCK); delay(10); if (qla_read(sc, QLA_NVRAM) & QLA_NVRAM_DATA_IN) val |= 1; delay(10); qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL); delay(10); qla_read(sc, QLA_NVRAM); } qla_write(sc, QLA_NVRAM, 0); delay(10); qla_read(sc, QLA_NVRAM); data[i] = letoh16(val); } csum = 0; for (i = 0; i < nitems(data); i++) { csum += data[i] & 0xff; csum += data[i] >> 8; } memcpy(&sc->sc_nvram, data, sizeof(sc->sc_nvram)); /* id field should be 'ISP ', version should be at least 1 */ if (sc->sc_nvram.id[0] != 'I' || sc->sc_nvram.id[1] != 'S' || sc->sc_nvram.id[2] != 'P' || sc->sc_nvram.id[3] != ' ' || sc->sc_nvram.nvram_version < 1 || (csum != 0)) { /* * onboard 2200s on Sun hardware don't have an nvram * fitted, but will provide us with node and port name * through Open Firmware; don't complain in that case. */ if (sc->sc_node_name == 0 || sc->sc_port_name == 0) printf("%s: nvram corrupt\n", DEVNAME(sc)); return (1); } return (0); } struct qla_dmamem * qla_dmamem_alloc(struct qla_softc *sc, size_t size) { struct qla_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 qla_dmamem_free(struct qla_softc *sc, struct qla_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 qla_alloc_ccbs(struct qla_softc *sc) { struct qla_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 qla_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 = qla_dmamem_alloc(sc, sc->sc_maxcmds * QLA_QUEUE_ENTRY_SIZE); if (sc->sc_requests == NULL) { printf("%s: unable to allocate ccb dmamem\n", DEVNAME(sc)); goto free_ccbs; } sc->sc_responses = qla_dmamem_alloc(sc, sc->sc_maxcmds * QLA_QUEUE_ENTRY_SIZE); if (sc->sc_responses == NULL) { printf("%s: unable to allocate rcb dmamem\n", DEVNAME(sc)); goto free_req; } sc->sc_segments = qla_dmamem_alloc(sc, sc->sc_maxcmds * QLA_MAX_SEGS * sizeof(struct qla_iocb_seg)); if (sc->sc_segments == NULL) { printf("%s: unable to allocate iocb segments\n", DEVNAME(sc)); goto free_res; } cmd = QLA_DMA_KVA(sc->sc_requests); memset(cmd, 0, QLA_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, QLA_MAX_SEGS, 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 * QLA_MAX_SEGS * sizeof(struct qla_iocb_seg); htolem64(&ccb->ccb_seg_dva, QLA_DMA_DVA(sc->sc_segments) + ccb->ccb_seg_offset); ccb->ccb_t4segs = QLA_DMA_KVA(sc->sc_segments) + ccb->ccb_seg_offset; qla_put_ccb(sc, ccb); } scsi_iopool_init(&sc->sc_iopool, sc, qla_get_ccb, qla_put_ccb); return (0); free_maps: while ((ccb = qla_get_ccb(sc)) != NULL) bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap); qla_dmamem_free(sc, sc->sc_segments); free_res: qla_dmamem_free(sc, sc->sc_responses); free_req: qla_dmamem_free(sc, sc->sc_requests); free_ccbs: free(sc->sc_ccbs, M_DEVBUF, 0); return (1); } void qla_free_ccbs(struct qla_softc *sc) { struct qla_ccb *ccb; scsi_iopool_destroy(&sc->sc_iopool); while ((ccb = qla_get_ccb(sc)) != NULL) bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap); qla_dmamem_free(sc, sc->sc_segments); qla_dmamem_free(sc, sc->sc_responses); qla_dmamem_free(sc, sc->sc_requests); free(sc->sc_ccbs, M_DEVBUF, 0); } void * qla_get_ccb(void *xsc) { struct qla_softc *sc = xsc; struct qla_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 qla_put_ccb(void *xsc, void *io) { struct qla_softc *sc = xsc; struct qla_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); }