diff options
author | Jonathan Matthew <jmatthew@cvs.openbsd.org> | 2014-02-12 23:04:27 +0000 |
---|---|---|
committer | Jonathan Matthew <jmatthew@cvs.openbsd.org> | 2014-02-12 23:04:27 +0000 |
commit | 8f9e686858f4682ec19c2e1376d89724109b4f98 (patch) | |
tree | 736298879a3c911fbc1561d11a964a8b9ba099ac /sys/dev | |
parent | 78f2b693db4507bf54fd839a0d4a8a8628e99625 (diff) |
Introduce qle(4), a new driver for QLogic ISP24xx fibre channel HBAs.
While this looks a lot like qla(4), there were a lot of minor changes between
the 23xx and 24xx generations that mean that there isn't really all that much
shared code.
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/pci/files.pci | 7 | ||||
-rw-r--r-- | sys/dev/pci/qle.c | 2614 | ||||
-rw-r--r-- | sys/dev/pci/qlereg.h | 610 |
3 files changed, 3230 insertions, 1 deletions
diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci index 0ac12c2997d..4383bfe9cb7 100644 --- a/sys/dev/pci/files.pci +++ b/sys/dev/pci/files.pci @@ -1,4 +1,4 @@ -# $OpenBSD: files.pci,v 1.302 2014/01/21 21:14:58 sf Exp $ +# $OpenBSD: files.pci,v 1.303 2014/02/12 23:04:26 jmatthew Exp $ # $NetBSD: files.pci,v 1.20 1996/09/24 17:47:15 christos Exp $ # # Config file and device description for machine-independent PCI code. @@ -217,6 +217,11 @@ file dev/pci/isp_pci.c isp_pci attach qla at pci with qla_pci file dev/pci/qla_pci.c qla_pci +# QLogic ISP24xx FC Controllers +device qle: scsi +attach qle at pci +file dev/pci/qle.c qle + # LSI Logic Fusion-MPT Message Passing Interface attach mpi at pci with mpi_pci file dev/pci/mpi_pci.c mpi_pci diff --git a/sys/dev/pci/qle.c b/sys/dev/pci/qle.c new file mode 100644 index 00000000000..8a8f286aae2 --- /dev/null +++ b/sys/dev/pci/qle.c @@ -0,0 +1,2614 @@ +/* $OpenBSD: qle.c,v 1.1 2014/02/12 23:04:26 jmatthew Exp $ */ + +/* + * Copyright (c) 2013, 2014 Jonathan Matthew <jmatthew@openbsd.org> + * + * 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 "bio.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/device.h> +#include <sys/sensors.h> +#include <sys/rwlock.h> +#include <sys/task.h> + +#include <machine/bus.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> + +#ifdef __sparc64__ +#include <dev/ofw/openfirm.h> +#endif + +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> + +#include <dev/pci/qlereg.h> + +/* firmware */ +#include <dev/microcode/isp/asm_2400.h> + +#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 + +/* 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 16 + +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 scsi_link sc_link; + + struct scsibus_softc *sc_scsibus; + + enum qle_isp_type sc_isp_type; + enum qle_isp_gen sc_isp_gen; + int sc_port; + + int sc_mbox_base; + 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; + int sc_update; + int sc_update_tasks; +#define QLE_UPDATE_TASK_CLEAR_ALL 0x00000001 +#define QLE_UPDATE_TASK_SOFTRESET 0x00000002 +#define QLE_UPDATE_TASK_DETACH_TARGET 0x00000004 +#define QLE_UPDATE_TASK_ATTACH_TARGET 0x00000008 +#define QLE_UPDATE_TASK_UPDATE_TOPO 0x00000010 +#define QLE_UPDATE_TASK_SCAN_LOOP 0x00000020 +#define QLE_UPDATE_TASK_SCANNING_LOOP 0x00000040 +#define QLE_UPDATE_TASK_SCAN_FABRIC 0x00000080 +#define QLE_UPDATE_TASK_SCANNING_FABRIC 0x00000100 +#define QLE_UPDATE_TASK_FABRIC_LOGIN 0x00000200 +#define QLE_UPDATE_TASK_FABRIC_RELOGIN 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; + + 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); + +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 *); +struct qle_ccb *qle_scsi_cmd_poll(struct qle_softc *); +int qle_scsi_probe(struct scsi_link *); + + +struct scsi_adapter qle_switch = { + qle_scsi_cmd, + scsi_minphys, + qle_scsi_probe, + NULL, /* scsi_free */ + NULL /* ioctl */ +}; + +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); +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_int16_t); +void qle_put_data_seg(struct qle_iocb_seg *, bus_dmamap_t, int); + +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_add_loop_port(struct qle_softc *, u_int16_t); +int qle_add_fabric_port(struct qle_softc *, struct qle_fc_port *); +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); +void qle_clear_port_lists(struct qle_softc *); +void qle_ports_gone(struct qle_softc *, u_int32_t); +int qle_softreset(struct qle_softc *); +void qle_update_topology(struct qle_softc *); +int qle_update_fabric(struct qle_softc *); +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_done(struct qle_softc *, int); +void qle_do_update(void *, void *); +int qle_async(struct qle_softc *, u_int16_t); + +int qle_load_fwchunk_2400(struct qle_softc *, + struct qle_dmamem *, const u_int32_t *); +int qle_load_firmware_2400(struct qle_softc *); +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)) * 2); +} + +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; + u_int32_t pcictl; + struct scsibus_attach_args saa; + struct qle_init_cb *icb; + + pcireg_t bars[] = { QLE_PCI_MEM_BAR, QLE_PCI_IO_BAR }; + pcireg_t memtype; + int r, i, rv; + + 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(pa, &ih)) { + printf(": unable to map interrupt\n"); + goto unmap; + } + printf(": %s\n", pci_intr_string(sc->sc_pc, ih)); + + sc->sc_ih = pci_intr_establish(sc->sc_pc, ih, IPL_BIO, + qle_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf("%s: unable to establish interrupt\n"); + goto deintr; + } + + 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; + sc->sc_mbox_base = QLE_MBOX_BASE_24XX; + break; + case PCI_PRODUCT_QLOGIC_ISP2432: + sc->sc_isp_type = QLE_ISP2432; + sc->sc_isp_gen = QLE_GEN_ISP24XX; + sc->sc_mbox_base = QLE_MBOX_BASE_24XX; + 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; + } + + 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 */ + printf("%s: bad startup mboxes: %x %x\n", DEVNAME(sc), + qle_read_mbox(sc, 1), qle_read_mbox(sc, 2)); + 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; + + if (qle_load_firmware_2400(sc)) { + printf("firmware load failed\n"); + goto deintr; + } + + /* execute firmware */ + sc->sc_mbox[0] = QLE_MBOX_EXEC_FIRMWARE; + sc->sc_mbox[1] = QLE_2400_CODE_ORG >> 16; + sc->sc_mbox[2] = QLE_2400_CODE_ORG & 0xffff; + sc->sc_mbox[3] = 0; + if (qle_mbox(sc, 0x000f, 0x0001)) { + 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, QLE_MBOX_ABOUT_FIRMWARE_IN, + QLE_MBOX_ABOUT_FIRMWARE_OUT)) { + printf("ISP not talking after firmware exec: %x\n", + sc->sc_mbox[0]); + goto deintr; + } + printf("firmware v%d.%d.%d, attrs %x\n", 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) */ + icb->icb_max_frame_len = htole16(1024); + icb->icb_exec_throttle = htole16(16); + icb->icb_portname = htobe64(QLE_DEFAULT_PORT_NAME); + icb->icb_nodename = 0; + icb->icb_login_retry = 3; + + icb->icb_fwoptions1 = htole16(QLE_ICB_FW1_FAIRNESS | + QLE_ICB_FW1_HARD_ADDR | + QLE_ICB_FW1_FULL_DUPLEX); + icb->icb_fwoptions2 = htole16(QLE_ICB_FW2_LOOP_PTP); + icb->icb_fwoptions3 = htole16(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; + icb->icb_req_queue_len = htole16(sc->sc_maxcmds); + icb->icb_resp_queue_len = htole16(sc->sc_maxcmds); + icb->icb_pri_req_queue_len = htole16(8); /* apparently the minimum */ + icb->icb_req_queue_addr = htole64(QLE_DMA_DVA(sc->sc_requests)); + icb->icb_resp_queue_addr = htole64(QLE_DMA_DVA(sc->sc_responses)); + icb->icb_pri_req_queue_addr = + htole64(QLE_DMA_DVA(sc->sc_pri_requests)); + + icb->icb_link_down_nos = htole16(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, QLE_MBOX_INIT_FIRMWARE_IN, 0x0001); + 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, QLE_MBOX_SET_FIRMWARE_OPTIONS_IN, 0x0001)) { + 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); + task_set(&sc->sc_update_task, qle_do_update, sc, NULL); + + /* wait a bit for link to come up so we can scan and attach devices */ + for (i = 0; i < QLE_WAIT_FOR_LOOP * 10000; i++) { + u_int16_t isr, info; + + delay(100); + + if (qle_read_isr(sc, &isr, &info) == 0) + continue; + + qle_handle_intr(sc, isr, info); + + if (sc->sc_loop_up) + break; + } + + if (sc->sc_loop_up) { + qle_do_update(sc, NULL); + } else { + printf("%s: loop still down, giving up\n", DEVNAME(sc)); + } + + /* we should be good to go now, attach scsibus */ + sc->sc_link.adapter = &qle_switch; + sc->sc_link.adapter_softc = sc; + sc->sc_link.adapter_target = QLE_MAX_TARGETS; + sc->sc_link.adapter_buswidth = QLE_MAX_TARGETS; + sc->sc_link.openings = sc->sc_maxcmds; + sc->sc_link.pool = &sc->sc_iopool; + if (sc->sc_nvram_valid) { + sc->sc_link.port_wwn = betoh64(sc->sc_nvram.port_name); + sc->sc_link.node_wwn = betoh64(sc->sc_nvram.node_name); + } else { + sc->sc_link.port_wwn = QLE_DEFAULT_PORT_NAME; + sc->sc_link.node_wwn = 0; + } + 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; + +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 didnt 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_gone, update) { + 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 i, last; + + i = QLE_MIN_HANDLE; + last = QLE_MAX_HANDLE; + 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, 0x0001)) { + printf("%s: get port db for %x 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_add_loop_port(struct qle_softc *sc, u_int16_t loopid) +{ + struct qle_get_port_db *pdb; + struct qle_fc_port *port, *pport; + int disp; + + if (qle_get_port_db(sc, loopid, sc->sc_scratch) != 0) { + return (1); + } + pdb = QLE_DMA_KVA(sc->sc_scratch); + + port = malloc(sizeof(*port), M_DEVBUF, M_ZERO | M_NOWAIT); + if (port == NULL) { + printf("%s: failed to allocate a port structure\n", + DEVNAME(sc)); + return (1); + } + + if (letoh16(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->location = QLE_LOCATION_LOOP_ID(loopid); + port->loopid = loopid; + 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[loopid] = port; + break; + case QLE_PORT_DISP_DUP: + free(port, M_DEVBUF); + break; + case QLE_PORT_DISP_SAME: + TAILQ_REMOVE(&sc->sc_ports_gone, pport, update); + free(port, M_DEVBUF); + break; + } + mtx_leave(&sc->sc_port_mtx); + + switch (disp) { + case QLE_PORT_DISP_CHANGED: + case QLE_PORT_DISP_MOVED: + case QLE_PORT_DISP_NEW: + printf("%s: %s %d; name %llx\n", + DEVNAME(sc), ISSET(port->flags, QLE_PORT_FLAG_IS_TARGET) ? + "target" : "non-target", 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); + return (1); + } + pdb = QLE_DMA_KVA(sc->sc_scratch); + + if (letoh16(pdb->prli_svc_word3) & QLE_SVC3_TARGET_ROLE) + port->flags |= QLE_PORT_FLAG_IS_TARGET; + + /* compare port and node name with what's in the port db now */ + + 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); + + printf("%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); +} + +struct qle_ccb * +qle_handle_resp(struct qle_softc *sc, u_int16_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) { + printf("%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); + } + /*qle_dump_iocb(sc, status);*/ + + 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, + 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) & 0x0f; + completion = letoh16(status->completion); + switch (completion) { + case QLE_IOCB_STATUS_DATA_OVERRUN: + case QLE_IOCB_STATUS_DATA_UNDERRUN: + case QLE_IOCB_STATUS_COMPLETE: + if (completion == QLE_IOCB_STATUS_COMPLETE) { + xs->resid = 0; + } else { + xs->resid = letoh32(status->resid); + } + + if (letoh16(status->scsi_status) & + QLE_SCSI_STATUS_SENSE_VALID) { + u_int32_t *pp; + int sr; + data = status->data + + letoh32(status->fcp_rsp_len); + memcpy(&xs->sense, data, + letoh32(status->fcp_sense_len)); + 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: + printf("%s: dma error\n", DEVNAME(sc)); + /* set resid apparently? */ + break; + + case QLE_IOCB_STATUS_RESET: + printf("%s: reset destroyed command\n", DEVNAME(sc)); + sc->sc_marker_required = 1; + xs->error = XS_RESET; + break; + + case QLE_IOCB_STATUS_ABORTED: + printf("%s: aborted\n", DEVNAME(sc)); + sc->sc_marker_required = 1; + xs->error = XS_DRIVER_STUFFUP; + break; + + case QLE_IOCB_STATUS_TIMEOUT: + printf("%s: command timed out\n", DEVNAME(sc)); + xs->error = XS_TIMEOUT; + break; + + case QLE_IOCB_STATUS_QUEUE_FULL: + printf("%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: + printf("%s: dev gone\n", DEVNAME(sc)); + xs->error = XS_SELTIMEOUT; + break; + + default: + printf("%s: unexpected completion status %x\n", + DEVNAME(sc), status->completion); + xs->error = XS_DRIVER_STUFFUP; + break; + } + break; + + case QLE_IOCB_STATUS_CONT: + printf("%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);*/ + sc->sc_fabric_pending = 2; + wakeup(sc->sc_scratch); + } else { + printf("%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: + printf("%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: + printf("%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_int16_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) { + /* isp(4) has some weird magic for this case */ + printf("%s: nonsense interrupt (%x)\n", DEVNAME(sc), + rspin); + } else { + while (sc->sc_last_resp_id != rspin) { + ccb = qle_handle_resp(sc, sc->sc_last_resp_id); + if (ccb) + scsi_done(ccb->ccb_xs); + + sc->sc_last_resp_id++; + if (sc->sc_last_resp_id == sc->sc_maxcmds) + sc->sc_last_resp_id = 0; + } + + 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) { + sc->sc_mbox[0] = info; + if (info == QLE_MBOX_COMPLETE) { + for (i = 1; 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); + printf("%s: unexpected mbox interrupt: %x\n", + DEVNAME(sc), info); + } + break; + + default: + /* maybe log something? */ + 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->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->adapter_softc; + struct qle_ccb *ccb; + void *iocb; + struct qle_ccb_list list; + u_int16_t req; + u_int32_t portid; + int offset, error; + bus_dmamap_t dmap; + + if (xs->cmdlen > 16) { + printf("%s: too fat (%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; + + printf("%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; + } + + SIMPLEQ_INIT(&list); + do { + ccb = qle_scsi_cmd_poll(sc); + SIMPLEQ_INSERT_TAIL(&list, ccb, ccb_link); + } while (xs->io != ccb); + + mtx_leave(&sc->sc_queue_mtx); + + while ((ccb = SIMPLEQ_FIRST(&list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&list, ccb_link); + scsi_done(ccb->ccb_xs); + } +} + +struct qle_ccb * +qle_scsi_cmd_poll(struct qle_softc *sc) +{ + u_int16_t rspin; + struct qle_ccb *ccb = NULL; + + while (ccb == NULL) { + u_int16_t isr, info; + + 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); + if (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; + + qle_write(sc, QLE_RESP_OUT, sc->sc_last_resp_id); + } + + qle_clear_isr(sc, isr); + } + + return (ccb); +} + +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 = sc->sc_mbox_base + (mbox * 2); + 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 +qle_write_mbox(struct qle_softc *sc, int mbox, u_int16_t value) +{ + bus_size_t offset = sc->sc_mbox_base + (mbox * 2); + 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); +} + +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 maskout) +{ + 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) { + 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; + } + } + } else { + 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, + "qlembox", 0); + } + result = sc->sc_mbox[0]; + sc->sc_mbox_pending = 0; + mtx_leave(&sc->sc_mbox_mtx); + return (result == QLE_MBOX_COMPLETE ? 0 : result); + } + + switch (result) { + case QLE_MBOX_COMPLETE: + for (i = 1; i < nitems(sc->sc_mbox); i++) { + sc->sc_mbox[i] = (maskout & (1 << i)) ? + qle_read_mbox(sc, i) : 0; + } + rv = 0; + break; + + case 0: + /* timed out; do something? */ + printf("mbox timed out\n"); + rv = 1; + break; + + default: + /* log a thing? */ + sc->sc_mbox[0] = result; + rv = result; + break; + } + + 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: + 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_start(struct qle_softc *sc, int task) +{ + atomic_setbits_int(&sc->sc_update_tasks, task); + task_add(sc->sc_update_taskq, &sc->sc_update_task); +} + +void +qle_clear_port_lists(struct qle_softc *sc) +{ + struct qle_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); + } + + 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); + } + + while (!TAILQ_EMPTY(&sc->sc_ports_gone)) { + p = TAILQ_FIRST(&sc->sc_ports_gone); + TAILQ_REMOVE(&sc->sc_ports_gone, p, update); + } +} + +void +qle_ports_gone(struct qle_softc *sc, u_int32_t location) +{ + struct qle_fc_port *port; + TAILQ_FOREACH(port, &sc->sc_ports, ports) { + if ((port->location & location) != 0) + TAILQ_INSERT_TAIL(&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) { + printf("%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, 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, QLE_MBOX_GET_LOOP_ID_OUT)) { + printf("%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: + printf("%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); + printf("%s: fabric port id %06x\n", DEVNAME(sc), + sc->sc_port_id); + break; + + case QLE_TOPO_N_PORT_NO_TARGET: + default: + printf("%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, 0x0001)) { + printf("%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); + printf("%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)); + rft->subcmd = htole16(QLE_SNS_RFT_ID); + rft->max_word = htole16(sizeof(struct qle_sns_req_hdr) / 4); + rft->port_id = htole32(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; + iocb->req_nport_handle = htole16(port_handle); + iocb->req_dsd_count = htole16(1); + iocb->req_resp_dsd_count = htole16(1); + iocb->req_cmd_byte_count = htole32(req_size); + iocb->req_resp_byte_count = htole32(resp_size); + iocb->req_cmd_seg.seg_addr = htole64(QLE_DMA_DVA(mem)); + iocb->req_cmd_seg.seg_len = htole32(req_size); + iocb->req_resp_seg.seg_addr = htole64(QLE_DMA_DVA(mem) + req_size); + iocb->req_resp_seg.seg_len = htole32(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(sc->sc_scratch, PRIBIO, "qle_fabric", 100); + } + } + 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) { + printf("%s: GA_NXT %x 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) { + printf("%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) { + printf("%s: got %06x again\n", DEVNAME(sc), *lastport); + *lastport = 0xffffffff; + return (NULL); + } + if (*firstport == 0xffffffff) + *firstport = *lastport; + + printf("%s: GA_NXT: port type/id: %x, 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_plogi(struct qle_softc *sc, struct qle_fc_port *port) +{ + struct qle_iocb_plogx *iocb; + u_int16_t req; + u_int64_t offset; + int rv; + int loopid; + + mtx_enter(&sc->sc_port_mtx); + loopid = qle_get_loop_id(sc); + mtx_leave(&sc->sc_port_mtx); + if (loopid == -1) { + printf("%s: ran out of loop ids\n", DEVNAME(sc)); + return (1); + } + + 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; + iocb->req_nport_handle = htole16(loopid); + iocb->req_port_id_lo = htole16(port->portid & 0xffff); + iocb->req_port_id_hi = htole16(port->portid >> 16); + iocb->req_flags = 0; + + /*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(sc->sc_scratch, PRIBIO, "qle_fabric", 100); + } + } + sc->sc_fabric_pending = 0; + + port->loopid = loopid; + return (rv); +} + +void +qle_fabric_plogo(struct qle_softc *sc, struct qle_fc_port *port) +{ +#if 0 + sc->sc_mbox[0] = QLE_MBOX_FABRIC_PLOGO; + sc->sc_mbox[1] = port->loopid; + sc->sc_mbox[10] = 0; + + if (qle_mbox(sc, 0x0403, 0x03)) + printf("%s: PLOGO %x failed\n", DEVNAME(sc), port->loopid); +#endif +} + +void +qle_do_update(void *xsc, void *x) +{ + struct qle_softc *sc = xsc; + int step, firstport, lastport; + struct qle_fc_port *port; + + printf("%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; + printf("%s: detaching everything\n", DEVNAME(sc)); + + mtx_enter(&sc->sc_port_mtx); + qle_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); + if (port->flags & QLE_PORT_FLAG_IS_TARGET) { + sc->sc_targets[port->loopid] = NULL; + } + } + 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, -1); + sc->sc_targets[port->loopid] = NULL; + } + if (port->location & QLE_LOCATION_FABRIC) + qle_fabric_plogo(sc, port); + + free(port, M_DEVBUF); + } + + qle_update_done(sc, QLE_UPDATE_TASK_CLEAR_ALL); + continue; + } + + if (sc->sc_update_tasks & QLE_UPDATE_TASK_SOFTRESET) { + printf("%s: attempting softreset\n", DEVNAME(sc)); + if (qle_softreset(sc) != 0) { + printf("%s: couldn't softreset\n", DEVNAME(sc)); + } + qle_update_done(sc, QLE_UPDATE_TASK_SOFTRESET); + 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) { + printf("%s: detaching port %06x\n", DEVNAME(sc), + port->portid); + if (sc->sc_scsibus != NULL) + scsi_detach_target(sc->sc_scsibus, + port->loopid, -1); + + if (port->location & QLE_LOCATION_FABRIC) + qle_fabric_plogo(sc, port); + + free(port, M_DEVBUF); + } else { + printf("%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; + } + + if (sc->sc_update_tasks & QLE_UPDATE_TASK_UPDATE_TOPO) { + printf("%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_SCAN_LOOP) { + printf("%s: starting loop scan\n", DEVNAME(sc)); + qle_clear_port_lists(sc); + qle_update_start(sc, QLE_UPDATE_TASK_SCANNING_LOOP); + qle_update_done(sc, QLE_UPDATE_TASK_SCAN_LOOP); + step = 0; + continue; + } + + if (sc->sc_update_tasks & QLE_UPDATE_TASK_SCANNING_LOOP) { + printf("%s: scanning loop id %x\n", DEVNAME(sc), step); + qle_add_loop_port(sc, step); + if (step == sc->sc_loop_max_id) { + qle_update_done(sc, + QLE_UPDATE_TASK_SCANNING_LOOP); + qle_update_start(sc, + QLE_UPDATE_TASK_ATTACH_TARGET | + QLE_UPDATE_TASK_DETACH_TARGET); + } else { + step++; + } + continue; + } + + if (sc->sc_update_tasks & QLE_UPDATE_TASK_SCAN_FABRIC) { + printf("%s: starting fabric scan\n", DEVNAME(sc)); + qle_clear_port_lists(sc); + qle_ports_gone(sc, QLE_LOCATION_FABRIC); + lastport = 0; + firstport = 0xffffffff; + step = 0; + if (qle_update_fabric(sc)) + qle_update_start(sc, + QLE_UPDATE_TASK_SCANNING_FABRIC); + + qle_update_done(sc, QLE_UPDATE_TASK_SCAN_FABRIC); + continue; + } + + if (sc->sc_update_tasks & QLE_UPDATE_TASK_SCANNING_FABRIC) { + port = qle_next_fabric_port(sc, &firstport, &lastport); + if (port != NULL) { + struct qle_fc_port *pport = NULL; + int disp; + + 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: + /* pport cleaned up later */ + case QLE_PORT_DISP_NEW: + printf("%s: new port %06x\n", + DEVNAME(sc), port->portid); + TAILQ_INSERT_TAIL(&sc->sc_ports_found, + port, update); + break; + case QLE_PORT_DISP_DUP: + free(port, M_DEVBUF); + port = NULL; + break; + case QLE_PORT_DISP_SAME: + printf("%s: existing port %06x\n", + DEVNAME(sc), port->portid); + TAILQ_REMOVE(&sc->sc_ports_gone, pport, + update); + free(port, M_DEVBUF); + port = NULL; + break; + } + mtx_leave(&sc->sc_port_mtx); + } + if (lastport == 0xffffffff) { + printf("%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) { + printf("%s: found port %06x\n", DEVNAME(sc), + port->portid); + if (qle_fabric_plogi(sc, port) == 0) { + qle_add_fabric_port(sc, port); + } else { + printf("%s: plogi %x failed\n", + DEVNAME(sc)); + free(port, M_DEVBUF); + } + } else { + printf("%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) { + /* loop across all fabric targets and redo login */ + qle_update_done(sc, QLE_UPDATE_TASK_FABRIC_RELOGIN); + continue; + } + } + + printf("%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: + printf("%s: lip occurred\n", DEVNAME(sc)); + break; + + case QLE_ASYNC_LOOP_UP: + printf("%s: loop up\n", DEVNAME(sc)); + sc->sc_loop_up = 1; + sc->sc_marker_required = 1; + qle_update_start(sc, QLE_UPDATE_TASK_UPDATE_TOPO | + QLE_UPDATE_TASK_SCAN_LOOP | + QLE_UPDATE_TASK_SCAN_FABRIC); + break; + + case QLE_ASYNC_LOOP_DOWN: + printf("%s: loop down\n", DEVNAME(sc)); + sc->sc_loop_up = 0; + qle_update_start(sc, QLE_UPDATE_TASK_CLEAR_ALL); + break; + + case QLE_ASYNC_LIP_RESET: + printf("%s: lip reset\n", DEVNAME(sc)); + sc->sc_marker_required = 1; + qle_update_start(sc, QLE_UPDATE_TASK_FABRIC_RELOGIN); + break; + + case QLE_ASYNC_PORT_DB_CHANGE: + printf("%s: port db changed %x\n", DEVNAME(sc), + qle_read_mbox(sc, 1)); + qle_update_start(sc, QLE_UPDATE_TASK_SCAN_LOOP); + break; + + case QLE_ASYNC_CHANGE_NOTIFY: + printf("%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_SCAN_FABRIC); + break; + + case QLE_ASYNC_LIP_F8: + printf("%s: lip f8\n", DEVNAME(sc)); + break; + + case QLE_ASYNC_LOOP_INIT_ERROR: + printf("%s: loop initialization error: %x", DEVNAME(sc), + qle_read_mbox(sc, 1)); + break; + + case QLE_ASYNC_POINT_TO_POINT: + printf("%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: + printf("%s: unknown async %x\n", DEVNAME(sc), info); + break; + } + return (1); +} + +void +qle_dump_stuff(struct qle_softc *sc, void *buf, int n) +{ + u_int8_t *d = buf; + int l; + + 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"); +} + +void +qle_dump_iocb(struct qle_softc *sc, void *buf) +{ + u_int8_t *iocb = buf; + int l; + int b; + + 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"); + } +} + +void +qle_dump_iocb_segs(struct qle_softc *sc, void *segs, int n) +{ + u_int8_t *buf = segs; + int s, b; + 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"); + } +} + +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_put_data_seg(struct qle_iocb_seg *seg, bus_dmamap_t dmap, int num) +{ + seg->seg_addr = htole64(dmap->dm_segs[num].ds_addr); + seg->seg_len = htole32(dmap->dm_segs[num].ds_len); +} + +void +qle_put_cmd(struct qle_softc *sc, void *buf, struct scsi_xfer *xs, + struct qle_ccb *ccb, u_int32_t target_port) +{ + 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; + + memset(req, 0, sizeof(*req)); + req->entry_type = QLE_IOCB_CMD_TYPE_6; + req->entry_count = 1; + + req->req_handle = ccb->ccb_id; + req->req_nport_handle = htole16(target); + + /* + * timeout is in seconds. make sure it's at least 1 if a timeout + * was specified in xs + */ + if (xs->timeout != 0) + req->req_timeout = htole16(MAX(1, xs->timeout/1000)); + + if (xs->datalen > 0) { + req->req_data_seg_count = htole16(ccb->ccb_dmamap->dm_nsegs); + req->req_ctrl_flags = htole16(xs->flags & SCSI_DATA_IN ? + QLE_IOCB_CTRL_FLAG_READ : QLE_IOCB_CTRL_FLAG_WRITE); + if (ccb->ccb_dmamap->dm_nsegs == 1) { + qle_put_data_seg(&req->req_data_seg, + ccb->ccb_dmamap, 0); + } else { + req->req_ctrl_flags |= + htole16(QLE_IOCB_CTRL_FLAG_EXT_SEG); + req->req_data_seg.seg_addr = + htole64(QLE_DMA_DVA(sc->sc_segments) + + ccb->ccb_seg_offset); + req->req_data_seg.seg_len = (ccb->ccb_dmamap->dm_nsegs + + 1) * sizeof(struct qle_iocb_seg); + for (seg = 0; seg < ccb->ccb_dmamap->dm_nsegs; seg++) { + qle_put_data_seg(&ccb->ccb_segs[seg], + ccb->ccb_dmamap, seg); + } + ccb->ccb_segs[ccb->ccb_dmamap->dm_nsegs].seg_addr = 0; + ccb->ccb_segs[ccb->ccb_dmamap->dm_nsegs].seg_len = 0; + 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, + BUS_DMASYNC_PREWRITE); + } + req->req_data_len = htole32(xs->datalen); + } + req->req_fcp_lun[0] = htole16((lun >> 16) & 0xffff); + req->req_fcp_lun[1] = htole16(lun & 0xffff); + + req->req_target_id = htole32(target_port & 0xffffff); + + fcp_cmnd_offset = ccb->ccb_id * sizeof(*cmnd); + req->req_fcp_cmnd_addr = htole64(QLE_DMA_DVA(sc->sc_fcp_cmnds) + + fcp_cmnd_offset); + + /* set up FCP_CMND */ + cmnd = (struct qle_fcp_cmnd *)QLE_DMA_KVA(sc->sc_fcp_cmnds) + + ccb->ccb_id; + + memset(cmnd, 0, sizeof(*cmnd)); + memcpy(cmnd->fcp_lun, req->req_fcp_lun, sizeof(cmnd->fcp_lun)); + /* 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) { + req->req_fcp_cmnd_len = htole16(12 + xs->cmdlen + 4); + cmnd->fcp_add_cdb_len = xs->cmdlen - 16; + memcpy(cmnd->fcp_cdb + xs->datalen, &fcp_dl, sizeof(fcp_dl)); + } else { + req->req_fcp_cmnd_len = htole16(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); +} + +int +qle_load_fwchunk_2400(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++) { + copy[i] = htole32(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, 0x0001)) { + 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; + } + + sc->sc_mbox[0] = QLE_MBOX_VERIFY_CSUM; + sc->sc_mbox[1] = src[2] >> 16; + sc->sc_mbox[2] = src[2]; + if (qle_mbox(sc, 0x0007, 0x0007)) { + printf("verification of chunk at %x failed: %x %x\n", src[2], + sc->sc_mbox[1], sc->sc_mbox[2]); + return (1); + } + + return (0); +} + +int +qle_load_firmware_2400(struct qle_softc *sc) +{ + struct qle_dmamem *mem; + const u_int32_t *fw = isp_2400_risc_code; + + mem = qle_dmamem_alloc(sc, 65536); + for (;;) { + qle_load_fwchunk_2400(sc, mem, fw); + if (fw[1] == 0) + break; + fw += fw[3]; + } + + qle_dmamem_free(sc, mem); + 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; + + base = 0x80 + (sc->sc_port * 0x100); + + csum = 0; + for (i = 0; i < nitems(data); i++) { + data[i] = 0xffffffff; + qle_write(sc, QLE_FLASH_NVRAM_ADDR, 0x7ffe0000 | (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); + + 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); +} + +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 = malloc(sizeof(struct qle_ccb) * sc->sc_maxcmds, + 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_pri; + } + 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_res; + } + + 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, 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); + + 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); +} + +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); +} diff --git a/sys/dev/pci/qlereg.h b/sys/dev/pci/qlereg.h new file mode 100644 index 00000000000..a1fda46cfb0 --- /dev/null +++ b/sys/dev/pci/qlereg.h @@ -0,0 +1,610 @@ +/* $OpenBSD: qlereg.h,v 1.1 2014/02/12 23:04:26 jmatthew Exp $ */ + +/* + * Copyright (c) 2013, 2014 Jonathan Matthew <jmatthew@openbsd.org> + * + * 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. + */ + +/* firmware loading */ +#define QLE_2400_CODE_ORG 0x00100000 + +/* interrupt types */ +#define QLE_INT_TYPE_MBOX 1 +#define QLE_INT_TYPE_ASYNC 2 +#define QLE_INT_TYPE_IO 3 +#define QLE_INT_TYPE_OTHER 4 + +/* 24xx interrupt status codes */ +#define QLE_24XX_INT_ROM_MBOX 0x01 +#define QLE_24XX_INT_ROM_MBOX_FAIL 0x02 +#define QLE_24XX_INT_MBOX 0x10 +#define QLE_24XX_INT_MBOX_FAIL 0x11 +#define QLE_24XX_INT_ASYNC 0x12 +#define QLE_24XX_INT_RSPQ 0x13 + +/* ISP registers */ +#define QLE_FLASH_NVRAM_ADDR 0x000 +#define QLE_FLASH_NVRAM_DATA 0x004 +#define QLE_CTRL_STATUS 0x008 +#define QLE_INT_CTRL 0x00C +#define QLE_INT_STATUS 0x010 +#define QLE_REQ_IN 0x01C +#define QLE_REQ_OUT 0x020 +#define QLE_RESP_IN 0x024 +#define QLE_RESP_OUT 0x028 +#define QLE_PRI_REQ_IN 0x02C +#define QLE_PRI_REQ_OUT 0x030 +#define QLE_RISC_STATUS 0x044 +#define QLE_HOST_CMD_CTRL 0x048 +#define QLE_GPIO_DATA 0x04C +#define QLE_GPIO_ENABLE 0x050 +#define QLE_HOST_SEMAPHORE 0x058 + +/* mailbox base moves around between generations */ +#define QLE_MBOX_BASE_24XX 0x080 + +/* QLE_CTRL_STATUS */ +#define QLE_CTRL_DMA_ACTIVE 0x00020000 +#define QLE_CTRL_DMA_SHUTDOWN 0x00010000 +#define QLE_CTRL_RESET 0x00000001 + +/* QLE_INT_STATUS */ +#define QLE_RISC_INT_REQ 0x00000008 + +/* QLE_INT_CTRL */ +#define QLE_INT_CTRL_ENABLE 0x00000008 + +/* QLE_RISC_STATUS */ +#define QLE_INT_INFO_SHIFT 16 +#define QLE_RISC_HOST_INT_REQ 0x00008000 +#define QLE_RISC_PAUSED 0x00000100 +#define QLE_INT_STATUS_MASK 0x000000FF + +/* QLE_HOST_CMD_CTRL write */ +#define QLE_HOST_CMD_SHIFT 28 +#define QLE_HOST_CMD_NOP 0x0 +#define QLE_HOST_CMD_RESET 0x1 +#define QLE_HOST_CMD_CLEAR_RESET 0x2 +#define QLE_HOST_CMD_PAUSE 0x3 +#define QLE_HOST_CMD_RELEASE 0x4 +#define QLE_HOST_CMD_SET_HOST_INT 0x5 +#define QLE_HOST_CMD_CLR_HOST_INT 0x6 +#define QLE_HOST_CMD_CLR_RISC_INT 0xA + +/* QLE_HOST_CMD_CTRL read */ +#define QLE_HOST_STATUS_ERROR_SHIFT 12 +#define QLE_HOST_STATUS_HOST_INT 0x00000040 +#define QLE_HOST_STATUS_RISC_RESET 0x00000020 + +/* QLE_HOST_SEMAPHORE */ +#define QLE_HOST_SEMAPHORE_LOCK 0x00000001 + + +/* QLE_MBOX_BASE (reg 0) read */ +#define QLE_MBOX_HAS_STATUS 0x4000 +#define QLE_MBOX_COMPLETE 0x4000 +#define QLE_MBOX_INVALID 0x4001 +#define QLE_MBOX_INTF_ERROR 0x4002 +#define QLE_MBOX_TEST_FAILED 0x4003 +#define QLE_MBOX_CMD_ERROR 0x4005 +#define QLE_MBOX_CMD_PARAM 0x4006 +#define QLE_MBOX_LINK_DOWN 0x400B +#define QLE_MBOX_DIAG_ERROR 0x400C +#define QLE_MBOX_CSUM_ERROR 0x4010 +#define QLE_ASYNC_SYSTEM_ERROR 0x8002 +#define QLE_ASYNC_REQ_XFER_ERROR 0x8003 +#define QLE_ASYNC_RSP_XFER_ERROR 0x8004 +#define QLE_ASYNC_LIP_OCCURRED 0x8010 +#define QLE_ASYNC_LOOP_UP 0x8011 +#define QLE_ASYNC_LOOP_DOWN 0x8012 +#define QLE_ASYNC_LIP_RESET 0x8013 +#define QLE_ASYNC_PORT_DB_CHANGE 0x8014 +#define QLE_ASYNC_CHANGE_NOTIFY 0x8015 +#define QLE_ASYNC_LIP_F8 0x8016 +#define QLE_ASYNC_LOOP_INIT_ERROR 0x8017 +#define QLE_ASYNC_POINT_TO_POINT 0x8030 +#define QLE_ASYNC_ZIO_RESP_UPDATE 0x8040 +#define QLE_ASYNC_RECV_ERROR 0x8048 +#define QLE_ASYNC_LOGIN_RJT_SENT 0x8049 + + +/* QLE_MBOX_BASE (reg 0) write */ +#define QLE_MBOX_NOP 0x0000 +#define QLE_MBOX_EXEC_FIRMWARE 0x0002 +#define QLE_MBOX_REGISTER_TEST 0x0006 +#define QLE_MBOX_VERIFY_CSUM 0x0007 +#define QLE_MBOX_ABOUT_FIRMWARE 0x0008 +#define QLE_MBOX_LOAD_RISC_RAM 0x000B +#define QLE_MBOX_INIT_RISC_RAM 0x000E +#define QLE_MBOX_GET_IO_STATUS 0x0012 +#define QLE_MBOX_STOP_FIRMWARE 0x0014 +#define QLE_MBOX_GET_ID 0x0020 +#define QLE_MBOX_SET_FIRMWARE_OPTIONS 0x0038 +#define QLE_MBOX_PLOGO 0x0056 +#define QLE_MBOX_DATA_RATE 0x005D +#define QLE_MBOX_INIT_FIRMWARE 0x0060 +#define QLE_MBOX_GET_INIT_CB 0x0061 +#define QLE_MBOX_GET_FC_AL_POS 0x0063 +#define QLE_MBOX_GET_PORT_DB 0x0064 +#define QLE_MBOX_GET_FIRMWARE_STATE 0x0069 +#define QLE_MBOX_GET_PORT_NAME 0x006A +#define QLE_MBOX_GET_LINK_STATUS 0x006B +#define QLE_MBOX_SEND_CHANGE_REQ 0x0070 +#define QLE_MBOX_LINK_INIT 0x0072 +#define QLE_MBOX_GET_PORT_NAME_LIST 0x0075 + +/* mailbox operation register bitfields */ +#define QLE_MBOX_ABOUT_FIRMWARE_IN 0x00000001 +#define QLE_MBOX_ABOUT_FIRMWARE_OUT 0x0000004f +#define QLE_MBOX_INIT_FIRMWARE_IN 0x000000fd +#define QLE_MBOX_SET_FIRMWARE_OPTIONS_IN 0x0000000f +#define QLE_MBOX_GET_LOOP_ID_OUT 0x000000cf + +#define QLE_MBOX_COUNT 32 + +/* nvram layout */ +struct qle_nvram { + u_int8_t id[4]; + u_int16_t nvram_version; + u_int16_t reserved_0; + + u_int16_t version; + u_int16_t reserved_1; + u_int16_t frame_payload_size; + u_int16_t execution_throttle; + u_int16_t exchg_count; + u_int16_t hard_address; + + u_int64_t port_name; + u_int64_t node_name; + + u_int16_t login_retry; + u_int16_t link_down_on_nos; + u_int16_t int_delay_timer; + u_int16_t login_timeout; + + u_int32_t fwoptions1; + u_int32_t fwoptions2; + u_int32_t fwoptions3; + + u_int16_t serial_options[4]; + + u_int16_t reserved_2[96]; + + u_int32_t host_p; + + u_int64_t alt_port_name; + u_int64_t alt_node_name; + + u_int64_t boot_port_name; + u_int16_t boot_lun; + u_int16_t reserved_3; + + u_int64_t alt1_boot_port_name; + u_int16_t alt1_boot_lun; + u_int16_t reserved_4; + + u_int64_t alt2_boot_port_name; + u_int16_t alt2_boot_lun; + u_int16_t reserved_5; + + u_int64_t alt3_boot_port_name; + u_int16_t alt3_boot_lun; + u_int16_t reserved_6; + + u_int32_t efi_param; + + u_int8_t reset_delay; + u_int8_t reserved_7; + u_int16_t reserved_8; + + u_int16_t boot_id_num; + u_int16_t reserved_9; + + u_int16_t max_luns_per_target; + u_int16_t reserved_10; + + u_int16_t port_down_retry_count; + u_int16_t link_down_timeout; + + u_int16_t fcode_param; + u_int16_t reserved_11[3]; + + u_int8_t prev_drv_ver_major; + u_int8_t prev_drv_ver_submajor; + u_int8_t prev_drv_ver_minor; + u_int8_t prev_drv_ver_subminor; + + u_int16_t prev_bios_ver_major; + u_int16_t prev_bios_ver_minor; + + u_int16_t prev_efi_ver_major; + u_int16_t prev_efi_ver_minor; + + u_int16_t prev_fw_ver_major; + u_int8_t prev_fw_ver_minor; + u_int8_t prev_fw_ver_subminor; + + u_int16_t reserved_12[56]; + + u_int8_t model_namep[16]; + + u_int16_t reserved_13[2]; + + u_int16_t pcie_table_sig; + u_int16_t pcie_table_offset; + u_int16_t subsystem_vendor_id; + u_int16_t subsystem_device_id; + + u_int32_t checksum; +} __packed; + +/* init firmware control block */ +#define QLE_ICB_VERSION 1 + +#define QLE_ICB_FW1_HARD_ADDR 0x0001 +#define QLE_ICB_FW1_FAIRNESS 0x0002 +#define QLE_ICB_FW1_FULL_DUPLEX 0x0004 +#define QLE_ICB_FW1_TARGET_MODE 0x0010 +#define QLE_ICB_FW1_DISABLE_INITIATOR 0x0020 +#define QLE_ICB_FW1_DISABLE_INIT_LIP 0x0200 +#define QLE_ICB_FW1_DESC_LOOP_ID 0x0400 +#define QLE_ICB_FW1_PREV_LOOP_ID 0x0800 +#define QLE_ICB_FW1_LOGIN_AFTER_LIP 0x2000 +#define QLE_ICB_FW1_NAME_OPTION 0x4000 + +#define QLE_ICB_FW2_LOOP_ONLY 0x0000 +#define QLE_ICB_FW2_PTP_ONLY 0x0010 +#define QLE_ICB_FW2_LOOP_PTP 0x0020 +#define QLE_ICB_FW2_ZIO_DISABLED 0x0000 +#define QLE_ICB_FW2_ZIO5_ENABLED 0x0005 +#define QLE_ICB_FW2_ZIO6_ENABLED 0x0006 +#define QLE_ICB_FW2_HARD_ADDR_ONLY 0x0080 + +#define QLE_ICB_FW3_SOFT_ID_ONLY 0x0002 +#define QLE_ICB_FW3_FCP_RSP_12_0 0x0010 +#define QLE_ICB_FW3_FCP_RSP_24_0 0x0020 +#define QLE_ICB_FW3_FCP_RSP_32_BYTES 0x0030 +#define QLE_ICB_FW3_ENABLE_OOO 0x0040 +#define QLE_ICB_FW3_NO_AUTO_PLOGI 0x0080 +#define QLE_ICB_FW3_ENABLE_OOO_RDY 0x0200 +#define QLE_ICB_FW3_1GBPS 0x0000 +#define QLE_ICB_FW3_2GBPS 0x2000 +#define QLE_ICB_FW3_AUTONEG 0x4000 +#define QLE_ICB_FW3_4GBPS 0x6000 +#define QLE_ICB_FW3_50_OHMS 0x8000 + + +struct qle_init_cb { + u_int16_t icb_version; + u_int16_t icb_reserved; + u_int16_t icb_max_frame_len; + u_int16_t icb_exec_throttle; + u_int16_t icb_exchange_count; + u_int16_t icb_hardaddr; + u_int64_t icb_portname; + u_int64_t icb_nodename; + u_int16_t icb_resp_in; + u_int16_t icb_req_out; + u_int16_t icb_login_retry; + u_int16_t icb_pri_req_out; + u_int16_t icb_resp_queue_len; + u_int16_t icb_req_queue_len; + u_int16_t icb_link_down_nos; + u_int16_t icb_pri_req_queue_len; + u_int64_t icb_req_queue_addr; + u_int64_t icb_resp_queue_addr; + u_int64_t icb_pri_req_queue_addr; + u_int8_t icb_reserved2[8]; + u_int16_t icb_atio_queue_in; + u_int16_t icb_atio_queue_len; + u_int64_t icb_atio_queue_addr; + u_int16_t icb_int_delay; + u_int16_t icb_login_timeout; + u_int32_t icb_fwoptions1; + u_int32_t icb_fwoptions2; + u_int32_t icb_fwoptions3; + u_int8_t icb_reserved3[24]; +} __packed; + +#define QLE_FW_OPTION1_ASYNC_LIP_F8 0x0001 +#define QLE_FW_OPTION1_ASYNC_LIP_RESET 0x0002 +#define QLE_FW_OPTION1_SYNC_LOSS_LIP 0x0010 +#define QLE_FW_OPTION1_ASYNC_LIP_ERROR 0x0080 +#define QLE_FW_OPTION1_ASYNC_LOGIN_RJT 0x0800 + +#define QLE_FW_OPTION3_EMERG_IOCB 0x0001 +#define QLE_FW_OPTION3_ASYNC_RND_ERROR 0x0002 + +/* topology types returned from QLE_MBOX_GET_LOOP_ID */ +#define QLE_TOPO_NL_PORT 0 +#define QLE_TOPO_FL_PORT 1 +#define QLE_TOPO_N_PORT 2 +#define QLE_TOPO_F_PORT 3 +#define QLE_TOPO_N_PORT_NO_TARGET 4 + + +struct qle_get_port_db { + u_int16_t flags; + u_int8_t current_login_state; + u_int8_t stable_login_state; + u_int8_t adisc_addr[3]; + u_int8_t reserved; + u_int8_t port_id[3]; + u_int8_t sequence_id; + u_int16_t retry_timer; + u_int16_t nport_handle; + u_int16_t recv_data_size; + u_int16_t reserved2; + u_int16_t prli_svc_word0; + u_int16_t prli_svc_word3; + u_int64_t port_name; + u_int64_t node_name; + u_int8_t reserved3[24]; +} __packed; + +#define QLE_SVC3_TARGET_ROLE 0x0010 + +/* fabric name server commands */ +#define QLE_SNS_GA_NXT 0x0100 +#define QLE_SNS_GID_FT 0x0171 +#define QLE_SNS_RFT_ID 0x0217 + +#define QLE_FC4_SCSI 8 + +#define QLE_LS_REJECT 0x8001 +#define QLE_LS_ACCEPT 0x8002 + +struct qle_ct_cmd_hdr { + u_int8_t ct_revision; + u_int8_t ct_id[3]; + u_int8_t ct_gs_type; + u_int8_t ct_gs_subtype; + u_int8_t ct_gs_options; + u_int8_t ct_gs_reserved; +} __packed; + +struct qle_ct_ga_nxt_req { + struct qle_ct_cmd_hdr header; + u_int16_t subcmd; + u_int16_t max_word; + u_int32_t reserved3; + u_int32_t port_id; +} __packed; + +struct qle_ct_ga_nxt_resp { + struct qle_ct_cmd_hdr header; + u_int16_t response; + u_int16_t residual; + u_int8_t fragment_id; + u_int8_t reason_code; + u_int8_t explanation_code; + u_int8_t vendor_unique; + + u_int32_t port_type_id; + u_int64_t port_name; + u_int8_t sym_port_name_len; + u_int8_t sym_port_name[255]; + u_int64_t node_name; + u_int8_t sym_node_name_len; + u_int8_t sym_node_name[255]; + u_int64_t initial_assoc; + u_int8_t node_ip_addr[16]; + u_int32_t cos; + u_int32_t fc4_types[8]; + u_int8_t ip_addr[16]; + u_int64_t fabric_port_name; + u_int32_t hard_address; +} __packed; + +struct qle_ct_rft_id_req { + struct qle_ct_cmd_hdr header; + u_int16_t subcmd; + u_int16_t max_word; + u_int32_t reserved3; + u_int32_t port_id; + u_int32_t fc4_types[8]; +} __packed; + +struct qle_ct_rft_id_resp { + struct qle_ct_cmd_hdr header; + u_int16_t response; + u_int16_t residual; + u_int8_t fragment_id; + u_int8_t reason_code; + u_int8_t explanation_code; + u_int8_t vendor_unique; +} __packed; + +/* available handle ranges */ +#define QLE_MIN_HANDLE 0x81 +#define QLE_MAX_HANDLE 0x7EF + +#define QLE_F_PORT_HANDLE 0x7FE +#define QLE_FABRIC_CTRL_HANDLE 0x7FD +#define QLE_SNS_HANDLE 0x7FC +#define QLE_IP_BCAST_HANDLE 0xFFF + +/* IOCB types */ +#define QLE_IOCB_STATUS 0x03 +#define QLE_IOCB_MARKER 0x04 +#define QLE_IOCB_STATUS_CONT 0x10 +#define QLE_IOCB_CMD_TYPE_7 0x18 +#define QLE_IOCB_CT_PASSTHROUGH 0x29 +#define QLE_IOCB_MAILBOX 0x39 +#define QLE_IOCB_CMD_TYPE_6 0x48 +#define QLE_IOCB_PLOGX 0x52 + +#define QLE_REQ_FLAG_CONT 0x01 +#define QLE_REQ_FLAG_FULL 0x02 +#define QLE_REQ_FLAG_BAD_HDR 0x04 +#define QLE_REQ_FLAG_BAD_PKT 0x08 + +#define QLE_RESP_FLAG_INVALID_COUNT 0x10 +#define QLE_RESP_FLAG_INVALID_ORDER 0x20 +#define QLE_RESP_FLAG_DMA_ERR 0x40 +#define QLE_RESP_FLAG_RESERVED 0x80 + +#define QLE_IOCB_CTRL_FLAG_WRITE 0x0001 +#define QLE_IOCB_CTRL_FLAG_READ 0x0002 +#define QLE_IOCB_CTRL_FLAG_EXT_SEG 0x0004 + +#define QLE_IOCB_SEGS_PER_CMD 2 + +#define QLE_IOCB_MARKER_SYNC_ALL 2 + +struct qle_iocb_seg { + u_int64_t seg_addr; + u_int32_t seg_len; +} __packed; + +struct qle_iocb_status { + u_int8_t entry_type; /* QLE_IOCB_STATUS */ + u_int8_t entry_count; + u_int8_t seqno; + u_int8_t flags; + + u_int32_t handle; + u_int16_t completion; + u_int16_t ox_id; + u_int32_t resid; + u_int16_t reserved; + u_int16_t state_flags; + u_int16_t reserved2; + u_int16_t scsi_status; + u_int32_t fcp_rsp_resid; + u_int32_t fcp_sense_len; + + u_int32_t fcp_rsp_len; + u_int8_t data[28]; +} __packed; + +/* completion */ +#define QLE_IOCB_STATUS_COMPLETE 0x0000 +#define QLE_IOCB_STATUS_DMA_ERROR 0x0002 +#define QLE_IOCB_STATUS_RESET 0x0004 +#define QLE_IOCB_STATUS_ABORTED 0x0005 +#define QLE_IOCB_STATUS_TIMEOUT 0x0006 +#define QLE_IOCB_STATUS_DATA_OVERRUN 0x0007 +#define QLE_IOCB_STATUS_DATA_UNDERRUN 0x0015 +#define QLE_IOCB_STATUS_QUEUE_FULL 0x001C +#define QLE_IOCB_STATUS_PORT_UNAVAIL 0x0028 +#define QLE_IOCB_STATUS_PORT_LOGGED_OUT 0x0029 +#define QLE_IOCB_STATUS_PORT_CHANGED 0x002A +#define QLE_IOCB_STATUS_PORT_BUSY 0x002B + +#define QLE_SCSI_STATUS_FCP_LEN_VALID 0x0100 +#define QLE_SCSI_STATUS_SENSE_VALID 0x0200 +#define QLE_SCSI_STATUS_RESID_OVER 0x0400 +#define QLE_SCSI_STATUS_RESID_UNDER 0x0800 + + +struct qle_iocb_marker { + u_int8_t entry_type; /* QLE_IOCB_MARKER */ + u_int8_t entry_count; + u_int8_t seqno; + u_int8_t flags; + + u_int32_t handle; + u_int8_t reserved; + u_int8_t target; + u_int8_t modifier; + u_int8_t vp_index; + u_int16_t marker_flags; + u_int16_t lun; + u_int8_t reserved2[48]; +} __packed; + +struct qle_iocb_status_cont { + u_int8_t entry_type; /* QLE_IOCB_STATUS_CONT */ + u_int8_t entry_count; + u_int8_t seqno; + u_int8_t flags; + + u_int8_t sense[44]; +} __packed; + +struct qle_iocb_req6 { + u_int8_t entry_type; /* QLE_IOCB_CMD_TYPE_6 */ + u_int8_t entry_count; + u_int8_t seqno; + u_int8_t flags; + + u_int32_t req_handle; + u_int16_t req_nport_handle; + u_int16_t req_timeout; + u_int16_t req_data_seg_count; + u_int16_t req_resp_seg_count; + + u_int16_t req_fcp_lun[4]; + u_int16_t req_ctrl_flags; + u_int16_t req_fcp_cmnd_len; + u_int64_t req_fcp_cmnd_addr; + u_int64_t req_resp_seg_addr; + u_int32_t req_data_len; + + u_int32_t req_target_id; + struct qle_iocb_seg req_data_seg; +} __packed; + +struct qle_fcp_cmnd { + u_int16_t fcp_lun[4]; + u_int8_t fcp_crn; + u_int8_t fcp_task_attr; + u_int8_t fcp_task_mgmt; + u_int8_t fcp_add_cdb_len; + + u_int8_t fcp_cdb[52]; + /* 64 bytes total */ +} __packed; + +struct qle_iocb_ct_passthrough { + u_int8_t entry_type; /* QLE_IOCB_CT_PASSTHROUGH */ + u_int8_t entry_count; + u_int8_t seqno; + u_int8_t flags; + + u_int32_t req_handle; + u_int16_t req_status; + u_int16_t req_nport_handle; + u_int16_t req_dsd_count; + u_int8_t req_vp_index; + u_int8_t req_reserved; + u_int16_t req_timeout; + u_int16_t req_reserved2; + u_int16_t req_resp_dsd_count; + u_int16_t req_reserved3[5]; + u_int32_t req_resp_byte_count; + u_int32_t req_cmd_byte_count; + struct qle_iocb_seg req_cmd_seg; + struct qle_iocb_seg req_resp_seg; +} __packed; + +struct qle_iocb_plogx { + u_int8_t entry_type; /* QLE_IOCB_PLOGX */ + u_int8_t entry_count; + u_int8_t seqno; + u_int8_t flags; + + u_int32_t req_handle; + u_int16_t req_status; + u_int16_t req_nport_handle; + u_int16_t req_flags; + u_int8_t req_vp_index; + u_int8_t req_reserved; + u_int16_t req_port_id_lo; + u_int8_t req_port_id_hi; + u_int8_t req_rspsize; + u_int16_t req_ioparms[22]; +} __packed; |