/* $OpenBSD: qli_pci.c,v 1.20 2010/08/07 03:50:02 krw Exp $ */ /* * Copyright (c) 2007 Marco Peereboom * Copyright (c) 2007 David Collins * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if NBIO > 0 #include #include #endif /* NBIO > 0 */ #define DEVNAME(_s) ((_s)->sc_dev.dv_xname) struct qli_softc { struct device sc_dev; void *sc_ih; bus_space_tag_t sc_memt; bus_space_handle_t sc_memh; bus_size_t sc_memsize; bus_dma_tag_t sc_dmat; volatile struct qli_reg *sc_reg; /* pointer to registers */ /* scsi ioctl from sd device */ int (*sc_ioctl)(struct device *, u_long, caddr_t); int sc_ql4010; /* if set we are a QL4010 HBA */ u_int32_t sc_resource; /* nr for semaphores */ struct rwlock sc_lock; /* mailbox members */ struct rwlock sc_mbox_lock; u_int32_t sc_mbox[QLI_MBOX_SIZE]; int sc_mbox_flags; #define QLI_MBOX_F_INVALID (0x00) #define QLI_MBOX_F_PENDING (0x01) #define QLI_MBOX_F_WAKEUP (0x02) #define QLI_MBOX_F_POLL (0x04) /* firmware control block */ struct qli_mem *sc_fw_cb; /* queues */ unsigned int sc_queues_len; struct qli_mem *sc_queues; bus_addr_t sc_request_dva; struct qli_queue_entry *sc_request_ring; }; /* #define QLI_DEBUG */ #ifdef QLI_DEBUG #define DPRINTF(x...) do { if (qli_debug) printf(x); } while(0) #define DNPRINTF(n,x...) do { if (qli_debug & n) printf(x); } while(0) #define QLI_D_CMD 0x0001 #define QLI_D_INTR 0x0002 #define QLI_D_MISC 0x0004 #define QLI_D_DMA 0x0008 #define QLI_D_IOCTL 0x0010 #define QLI_D_RW 0x0020 #define QLI_D_MEM 0x0040 #define QLI_D_CCB 0x0080 #define QLI_D_SEM 0x0100 #define QLI_D_MBOX 0x0200 u_int32_t qli_debug = 0 | QLI_D_CMD | QLI_D_INTR | QLI_D_MISC | QLI_D_DMA | QLI_D_IOCTL | QLI_D_RW | QLI_D_MEM | QLI_D_CCB | QLI_D_SEM | QLI_D_MBOX ; void qli_dump_mbox(struct qli_softc *, u_int32_t *); #else #define DPRINTF(x...) #define DNPRINTF(n,x...) #define qli_dump_mbox(x, y) #endif /* QLI_DEBUG */ struct qli_mem { bus_dmamap_t am_map; bus_dma_segment_t am_seg; size_t am_size; caddr_t am_kva; }; #define QLIMEM_MAP(_am) ((_am)->am_map) #define QLIMEM_DVA(_am) ((_am)->am_map->dm_segs[0].ds_addr) #define QLIMEM_KVA(_am) ((void *)(_am)->am_kva) #define QLIMEM_ALIGN (MAX(QLI_REQUESTQ_DEPTH, QLI_RESPONSEQ_DEPTH) *\ sizeof(struct qli_queue_entry)) struct qli_mem *qli_allocmem(struct qli_softc *, size_t); void qli_freemem(struct qli_softc *, struct qli_mem *); void qli_scsi_cmd(struct scsi_xfer *); int qli_scsi_ioctl(struct scsi_link *, u_long, caddr_t, int, struct proc *); void qliminphys(struct buf *bp, struct scsi_link *sl); void qli_disable_interrupts(struct qli_softc *); void qli_enable_interrupts(struct qli_softc *); int qli_pci_find_device(void *); int qli_pci_match(struct device *, void *, void *); void qli_pci_attach(struct device *, struct device *, void *); int qli_ioctl(struct device *, u_long, caddr_t); int qli_lock_sem(struct qli_softc *, u_int32_t, u_int32_t); void qli_unlock_sem(struct qli_softc *, u_int32_t); void qli_eeprom_out(struct qli_softc *, u_int32_t); u_int16_t qli_read_nvram(struct qli_softc *, u_int32_t); int qli_validate_nvram(struct qli_softc *); int qli_lock_driver(struct qli_softc *); void qli_write(struct qli_softc *, volatile u_int32_t *, u_int32_t); u_int32_t qli_read(struct qli_softc *, volatile u_int32_t *); void qli_hw_reset(struct qli_softc *); int qli_soft_reset(struct qli_softc *); int qli_get_fw_state(struct qli_softc *, u_int32_t *); int qli_start_firmware(struct qli_softc *); int qli_mgmt(struct qli_softc *, int, u_int32_t *); int qli_intr(void *); int qli_attach(struct qli_softc *); #ifndef SMALL_KERNEL int qli_create_sensors(struct qli_softc *); #endif /* SMALL_KERNEL */ struct scsi_adapter qli_switch = { qli_scsi_cmd, qliminphys, 0, 0, qli_scsi_ioctl }; struct cfdriver qli_cd = { NULL, "qli", DV_DULL }; struct cfattach qli_pci_ca = { sizeof(struct qli_softc), qli_pci_match, qli_pci_attach }; struct qli_pci_device { pcireg_t qpd_vendor; pcireg_t qpd_product; pcireg_t qpd_subvendor; pcireg_t qpd_subproduct; char *qpd_model; uint32_t qpd_flags; } qli_pci_devices[] = { { PCI_VENDOR_QLOGIC, PCI_PRODUCT_QLOGIC_ISP4022_HBA, 0, 0, "", 0 }, { PCI_VENDOR_QLOGIC, PCI_PRODUCT_QLOGIC_ISP4010_HBA, 0, 0, "", 0 }, { 0 } }; int qli_pci_find_device(void *aux) { struct pci_attach_args *pa = aux; int i; for (i = 0; qli_pci_devices[i].qpd_vendor; i++) { if (qli_pci_devices[i].qpd_vendor == PCI_VENDOR(pa->pa_id) && qli_pci_devices[i].qpd_product == PCI_PRODUCT(pa->pa_id)) { DNPRINTF(QLI_D_MISC, "qli_pci_find_device: %i\n", i); return (i); } } return (-1); } int qli_pci_match(struct device *parent, void *match, void *aux) { int i; if ((i = qli_pci_find_device(aux)) != -1) { DNPRINTF(QLI_D_MISC, "qli_pci_match: vendor: %04x product: %04x\n", qli_pci_devices[i].qpd_vendor, qli_pci_devices[i].qpd_product); return (1); } return (0); } void qli_pci_attach(struct device *parent, struct device *self, void *aux) { struct qli_softc *sc = (struct qli_softc *)self; struct pci_attach_args *pa = aux; const char *intrstr; pci_intr_handle_t ih; pcireg_t memtype; int r; /* find the appropriate memory base */ for (r = PCI_MAPREG_START; r < PCI_MAPREG_END; r += sizeof(memtype)) { memtype = pci_mapreg_type(pa->pa_pc, pa->pa_tag, r); if ((memtype & PCI_MAPREG_TYPE_MASK) == PCI_MAPREG_TYPE_MEM) break; } if (r >= PCI_MAPREG_END) { printf(": unable to locate system interface registers\n"); return; } if (pci_mapreg_map(pa, r, memtype, BUS_SPACE_MAP_LINEAR, &sc->sc_memt, &sc->sc_memh, NULL, &sc->sc_memsize, 0)) { printf(": can't map controller pci space\n"); return; } sc->sc_dmat = pa->pa_dmat; /* establish interrupt */ if (pci_intr_map(pa, &ih)) { printf(": can't map interrupt\n"); goto unmap; } intrstr = pci_intr_string(pa->pa_pc, ih); sc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_BIO, qli_intr, sc, DEVNAME(sc)); if (!sc->sc_ih) { printf(": can't establish interrupt"); if (intrstr) printf(" at %s", intrstr); printf("\n"); goto unmap; } /* retrieve kva for register access */ sc->sc_reg = bus_space_vaddr(sc->sc_memt, sc->sc_memh); if (sc->sc_reg == NULL) { printf(": can't map registers into kernel\n"); goto intrdis; } printf(": %s\n", intrstr); sc->sc_ql4010 = PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_QLOGIC_ISP4010_HBA; if (qli_attach(sc)) { printf("%s: can't attach\n", DEVNAME(sc)); goto intrdis; } return; intrdis: pci_intr_disestablish(pa->pa_pc, sc->sc_ih); unmap: sc->sc_ih = NULL; bus_space_unmap(sc->sc_memt, sc->sc_memh, sc->sc_memsize); } struct qli_mem * qli_allocmem(struct qli_softc *sc, size_t size) { struct qli_mem *mm; int nsegs; DNPRINTF(QLI_D_MEM, "%s: qli_allocmem: %d\n", DEVNAME(sc), size); mm = malloc(sizeof(*mm), M_DEVBUF, M_NOWAIT | M_ZERO); if (mm == NULL) return (NULL); mm->am_size = size; if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &mm->am_map) != 0) goto amfree; if (bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &mm->am_seg, 1, &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0) goto destroy; if (bus_dmamem_map(sc->sc_dmat, &mm->am_seg, nsegs, size, &mm->am_kva, BUS_DMA_NOWAIT) != 0) goto free; if (bus_dmamap_load(sc->sc_dmat, mm->am_map, mm->am_kva, size, NULL, BUS_DMA_NOWAIT) != 0) goto unmap; DNPRINTF(QLI_D_MEM, " kva: %p dva: %p map: %p\n", mm->am_kva, mm->am_map->dm_segs[0].ds_addr, mm->am_map); return (mm); unmap: bus_dmamem_unmap(sc->sc_dmat, mm->am_kva, size); free: bus_dmamem_free(sc->sc_dmat, &mm->am_seg, 1); destroy: bus_dmamap_destroy(sc->sc_dmat, mm->am_map); amfree: free(mm, M_DEVBUF); return (NULL); } void qli_freemem(struct qli_softc *sc, struct qli_mem *mm) { DNPRINTF(QLI_D_MEM, "%s: qli_freemem: %p\n", DEVNAME(sc), mm); bus_dmamap_unload(sc->sc_dmat, mm->am_map); bus_dmamem_unmap(sc->sc_dmat, mm->am_kva, mm->am_size); bus_dmamem_free(sc->sc_dmat, &mm->am_seg, 1); bus_dmamap_destroy(sc->sc_dmat, mm->am_map); free(mm, M_DEVBUF); } void qliminphys(struct buf *bp, struct scsi_link *sl) { DNPRINTF(QLI_D_MISC, "qliminphys: %d\n", bp->b_bcount); if (bp->b_bcount > QLI_MAXFER) bp->b_bcount = QLI_MAXFER; minphys(bp); } void qli_disable_interrupts(struct qli_softc *sc) { DNPRINTF(QLI_D_INTR, "%s: qli_disable_interrupts\n", DEVNAME(sc)); if (sc->sc_ql4010) qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_CLR_MASK(QLI_REG_CTRLSTAT_SCSI_INTR_ENABLE)); else qli_write(sc, &sc->sc_reg->u1.isp4022.q22_intr_mask, QLI_CLR_MASK(QLI_REG_CTRLSTAT_SCSI_INTR_ENABLE_4022)); } void qli_enable_interrupts(struct qli_softc *sc) { DNPRINTF(QLI_D_INTR, "%s: qli_enable_interrupts\n", DEVNAME(sc)); if (sc->sc_ql4010) qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_SCSI_INTR_ENABLE)); else qli_write(sc, &sc->sc_reg->u1.isp4022.q22_intr_mask, QLI_SET_MASK(QLI_REG_CTRLSTAT_SCSI_INTR_ENABLE_4022)); } void qli_write(struct qli_softc *sc, volatile u_int32_t *p, u_int32_t v) { DNPRINTF(QLI_D_RW, "%s: qw 0x%x 0x%08x\n", DEVNAME(sc), (u_int8_t *)p - (u_int8_t *)sc->sc_reg, v); *p = letoh32(v); bus_space_barrier(sc->sc_memt, sc->sc_memh, (u_int8_t *)p - (u_int8_t *)sc->sc_reg, 4, BUS_SPACE_BARRIER_WRITE); } u_int32_t qli_read(struct qli_softc *sc, volatile u_int32_t *p) { u_int32_t v; bus_space_barrier(sc->sc_memt, sc->sc_memh, (u_int8_t *)p - (u_int8_t *)sc->sc_reg, 4, BUS_SPACE_BARRIER_READ); v = letoh32(*p); DNPRINTF(QLI_D_RW, "%s: qr 0x%x 0x%08x\n", DEVNAME(sc), (u_int8_t *)p - (u_int8_t *)sc->sc_reg, v); return (v); } void qli_hw_reset(struct qli_softc *sc) { u_int32_t s; DNPRINTF(QLI_D_MISC, "%s: qli_hw_reset\n", DEVNAME(sc)); /* clear scsi reset interrupt bit or soft reset won't work */ s = qli_read(sc, &sc->sc_reg->qlr_ctrl_status); if (s & QLI_REG_CTRLSTAT_SCSI_RESET_INTR) qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(s)); /* issue soft reset */ qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_SOFT_RESET)); } int qli_soft_reset(struct qli_softc *sc) { int rv = 1, i, failed; u_int32_t s; DNPRINTF(QLI_D_MISC, "%s: qli_soft_reset\n", DEVNAME(sc)); qli_hw_reset(sc); /* wait until net reset bit is cleared */ for (i = 0; i < QLI_SOFT_RESET_RETRIES; i++) { s = qli_read(sc, &sc->sc_reg->qlr_ctrl_status); if ((s & QLI_REG_CTRLSTAT_NET_RESET_INTR) == 0) break; delay(1000000); /* 1s */ } s = qli_read(sc, &sc->sc_reg->qlr_ctrl_status); if (s & QLI_REG_CTRLSTAT_NET_RESET_INTR) { printf("%s: qli_soft_reset: net reset intr bit not cleared\n", DEVNAME(sc)); /* XXX set the same bit per the linux driver */ qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_NET_RESET_INTR)); } /* wait for soft reset to complete */ for (i = 0, failed = 1; i < QLI_SOFT_RESET_RETRIES; i++) { s = qli_read(sc, &sc->sc_reg->qlr_ctrl_status); if ((s & QLI_REG_CTRLSTAT_SOFT_RESET) == 0) { failed = 0; break; } delay(1000000); /* 1s */ } /* check if scsi reset interrupt is cleared */ s = qli_read(sc, &sc->sc_reg->qlr_ctrl_status); if (s & QLI_REG_CTRLSTAT_SCSI_RESET_INTR) { printf("%s: qli_soft_reset: scsi reset intr bit not cleared\n", DEVNAME(sc)); /* XXX set the same bit per the linux driver */ qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_SCSI_RESET_INTR)); } if (failed) { /* force the soft reset */ printf("%s: qli_soft_reset: soft reset failed\n", DEVNAME(sc)); qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_FORCE_SOFT_RESET)); for (i = 0; i < QLI_SOFT_RESET_RETRIES; i++) { s = qli_read(sc, &sc->sc_reg->qlr_ctrl_status); if ((s & QLI_REG_CTRLSTAT_FORCE_SOFT_RESET) == 0) { rv = 0; break; } delay(1000000); /* 1s */ } } else rv = 0; return (rv); } int qli_get_fw_state(struct qli_softc *sc, u_int32_t *mbox) { int rv = 1; DNPRINTF(QLI_D_MISC, "%s: qli_get_fw_state\n", DEVNAME(sc)); mbox[0] = QLI_MBOX_OPC_GET_FW_STATE; if (qli_mgmt(sc, 1, mbox)) goto done; DNPRINTF(QLI_D_MISC, "%s: qli_get_fw_state: state: 0x%08x\n", DEVNAME(sc), mbox[1]); rv = 0; done: return (rv); } int qli_lock_driver(struct qli_softc *sc) { int i, rv = 1; DNPRINTF(QLI_D_SEM, "%s: qli_lock_driver\n", DEVNAME(sc)); for (i = 0; i < QLI_SEM_MAX_RETRIES; i++) { if (qli_lock_sem(sc, QLI_SEM_DRIVER(sc), QLI_SEM_DRIVER_MASK(sc))) { DNPRINTF(QLI_D_SEM, "%s: qli_lock_driver: semaphore" " not acquired, retry %d\n", DEVNAME(sc), i); if (cold) delay(1000000); /* 1s */ else while (tsleep(sc, PRIBIO + 1, "qlisem", hz) != EWOULDBLOCK) ; } else { DNPRINTF(QLI_D_SEM, "%s: qli_lock_driver: semaphore" " acquired\n", DEVNAME(sc)); rv = 0; break; } } return (rv); } void qli_unlock_sem(struct qli_softc *sc, u_int32_t mask) { DNPRINTF(QLI_D_SEM, "%s: qli_unlock_sem: 0x%08x released\n", DEVNAME(sc), mask); qli_write(sc, QLI_SEMAPHORE(sc), mask); } int qli_lock_sem(struct qli_softc *sc, u_int32_t shift, u_int32_t mask) { int rv = 1; u_int32_t v, s; s = sc->sc_resource << shift; qli_write(sc, QLI_SEMAPHORE(sc), s | mask); v = qli_read(sc, QLI_SEMAPHORE(sc)); if ((v & (mask >> 16)) == s) rv = 0; DNPRINTF(QLI_D_SEM, "%s: qli_lock_sem: mask: 0x%08x shift: 0x%08x " "s: 0x%08x v: 0x%08x did %sacquire semaphore \n", DEVNAME(sc), mask, shift, s, v, rv ? "not " : ""); return (rv); } void qli_eeprom_out(struct qli_softc *sc, u_int32_t data) { qli_write(sc, QLI_NVRAM(sc), data); delay(1); } u_int16_t qli_read_nvram(struct qli_softc *sc, u_int32_t offset) { int i; u_int32_t s, mask, data; u_int16_t val = 0; #ifdef QLI_DEBUG u_int32_t qli_debug_save = qli_debug; qli_debug = 0; #endif /* QLI_DEBUG */ /* select chip */ s = QLI_NVRAM_MASK | QLI_NVRAM_SELECT; qli_eeprom_out(sc, s); /* start bit */ qli_eeprom_out(sc, s | QLI_NVRAM_DATA_OUT); qli_eeprom_out(sc, s | QLI_NVRAM_DATA_OUT | QLI_NVRAM_CLOCK); qli_eeprom_out(sc, s | QLI_NVRAM_DATA_OUT); /* clock low */ /* send read command */ mask = 1 << (QLI_NVRAM_NUM_CMD_BITS - 1); for (i = 0; i < QLI_NVRAM_NUM_CMD_BITS; i++) { data = ((QLI_NVRAM_CMD_READ << i) & mask) ? QLI_NVRAM_DATA_OUT : 0; qli_eeprom_out(sc, s | data); qli_eeprom_out(sc, s | data | QLI_NVRAM_CLOCK); qli_eeprom_out(sc, s | data); } /* send read address */ mask = 1 << (QLI_NVRAM_NUM_ADDR_BITS(sc) - 1); for (i = 0; i < QLI_NVRAM_NUM_ADDR_BITS(sc); i++) { data = ((offset << i) & mask) ? QLI_NVRAM_DATA_OUT : 0; qli_eeprom_out(sc, s | data); qli_eeprom_out(sc, s | data | QLI_NVRAM_CLOCK); qli_eeprom_out(sc, s | data); } /* read data */ for (i = 0; i < QLI_NVRAM_NUM_DATA_BITS; i++) { qli_eeprom_out(sc, s | QLI_NVRAM_CLOCK); qli_eeprom_out(sc, s); data = (qli_read(sc, QLI_NVRAM(sc)) & QLI_NVRAM_DATA_IN) ? 1 : 0; val = (val << 1) | data; } /* deselect chip */ s = QLI_NVRAM_MASK; qli_write(sc, QLI_NVRAM(sc), s); #ifdef QLI_DEBUG qli_debug = qli_debug_save; #endif /* QLI_DEBUG */ DNPRINTF(QLI_D_RW, "%s: qli_read_nvram 0x%x 0x%04x\n", DEVNAME(sc), offset, letoh16(val)); return (letoh16(val)); } int qli_validate_nvram(struct qli_softc *sc) { int i, rv = 1; u_int16_t nvram_checksum = 0; DNPRINTF(QLI_D_MISC, "%s: qli_validate_nvram\n", DEVNAME(sc)); for (i = 0; i < QLI_NVRAM_SIZE(sc); i++) nvram_checksum += qli_read_nvram(sc, i); DNPRINTF(QLI_D_MISC, "%s: nvram checksum 0x%04x\n", DEVNAME(sc), nvram_checksum); if (nvram_checksum == 0) rv = 0; return (rv); } int qli_start_firmware(struct qli_softc *sc) { int rv = 1, reset_required = 1, config_required = 0; int boot_required = 0, i; u_int32_t mbox[QLI_MBOX_SIZE], r; DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware\n", DEVNAME(sc)); if (qli_lock_driver(sc)) { printf("%s: could not acquire global driver semaphore, " "aborting firmware bring-up\n", DEVNAME(sc)); goto done; } if (qli_read(sc, QLI_PORT_CTRL(sc)) & QLI_PORT_CTRL_INITIALIZED) { /* Hardware has been initialized */ DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: hardware has " "been initialized\n", DEVNAME(sc)); if (qli_read(sc, &sc->sc_reg->qlr_mbox[0]) == 0) { /* firmware is not running */ DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: fw " "not running\n", DEVNAME(sc)); reset_required = 0; config_required = 1; } else { qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_SCSI_RESET_INTR)); /* issue command to fw to find out if we are up */ bzero(mbox, sizeof(mbox)); if (qli_get_fw_state(sc, mbox)) { /* command failed, reset chip */ DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: " "firmware in unknown state, reseting " "chip\n", DEVNAME(sc)); } else { if (mbox[1] & QLI_MBOX_STATE_CONFIG_WAIT) { config_required = 1; reset_required = 0; } } } } if (reset_required) { if (qli_soft_reset(sc)) { printf("%s: soft reset failed, aborting firmware " "bring-up\n", DEVNAME(sc)); goto done; } config_required = 1; if (qli_lock_driver(sc)) { printf("%s: could not acquire global driver semaphore " "after reseting chip, aborting firmware bring-up\n", DEVNAME(sc)); goto done; } } if (config_required) { DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: configuring " "firmware\n", DEVNAME(sc)); if (qli_lock_sem(sc, QLI_SEM_FLASH(sc), QLI_SEM_FLASH_MASK(sc))) { printf("%s: could not lock flash during firmware " "bring-up\n", DEVNAME(sc)); goto unlock_driver; } if (qli_lock_sem(sc, QLI_SEM_NVRAM(sc), QLI_SEM_NVRAM_MASK(sc))) { printf("%s: could not lock nvram during firmware " "bring-up\n", DEVNAME(sc)); qli_unlock_sem(sc, QLI_SEM_FLASH_MASK(sc)); goto unlock_driver; } if (qli_validate_nvram(sc)) { printf("%s: invalid NVRAM checksum. Flash your " "controller", DEVNAME(sc)); if (sc->sc_ql4010) r = QLI_EXT_HW_CFG_DEFAULT_QL4010; else r = QLI_EXT_HW_CFG_DEFAULT_QL4022; } else r = (u_int32_t)qli_read_nvram(sc, QLI_NVRAM_EXT_HW_CFG(sc)); /* upper 16 bits are write mask; enable everything */ qli_write(sc, QLI_EXT_HW_CFG(sc), (0xffff << 16 ) | r); qli_unlock_sem(sc, QLI_SEM_NVRAM_MASK(sc)); qli_unlock_sem(sc, QLI_SEM_FLASH_MASK(sc)); boot_required = 1; } if (boot_required) { /* boot firmware */ DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: booting " "firmware\n", DEVNAME(sc)); /* stuff random value in mbox[7] to randomize source ports */ /* XXX use random ne instead of 1234 */ qli_write(sc, &sc->sc_reg->qlr_mbox[7], 1234); /* XXX linux driver sets ACB v2 into mbox[6] */ qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_BOOT_ENABLE)); /* wait for firmware to come up */ for (i = 0; i < 60 * 4 /* up to 60 seconds */; i ++) { if (qli_read(sc, &sc->sc_reg->qlr_ctrl_status) & QLI_SET_MASK(QLI_REG_CTRLSTAT_SCSI_PROC_INTR)) break; if (qli_read(sc, &sc->sc_reg->qlr_mbox[0]) == QLI_MBOX_STATUS_COMMAND_COMPLETE) break; DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: waiting " "for firmware, retry = %d\n", DEVNAME(sc), i); delay(250000); /* 250ms */ } if (qli_read(sc, &sc->sc_reg->qlr_mbox[0]) == QLI_MBOX_STATUS_COMMAND_COMPLETE) { /* firmware is done booting */ qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_SCSI_PROC_INTR)); DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: firmware " "booting complete\n", DEVNAME(sc)); rv = 0; } else { DNPRINTF(QLI_D_MISC, "%s: qli_start_firmware: firmware " "booting failed\n", DEVNAME(sc)); rv = 1; } } unlock_driver: qli_unlock_sem(sc, QLI_SEM_DRIVER_MASK(sc)); done: return (rv); } int qli_mgmt(struct qli_softc *sc, int len, u_int32_t *mbox) { int rv = 1, s, i; u_int32_t x; DNPRINTF(QLI_D_MBOX, "%s: qli_mgmt: cold: %d\n", DEVNAME(sc), cold); if (!mbox) goto done; s = splbio(); rw_enter_write(&sc->sc_mbox_lock); if (qli_read(sc, &sc->sc_reg->qlr_ctrl_status) & QLI_REG_CTRLSTAT_SCSI_PROC_INTR) { /* this should not happen */ printf("%s: qli_mgmt called while interrupt is pending\n", DEVNAME(sc)); qli_intr(sc); } qli_dump_mbox(sc, mbox); /* mbox[0] needs to be written last so write backwards */ for (i = QLI_MBOX_SIZE - 1; i >= 0; i--) qli_write(sc, &sc->sc_reg->qlr_mbox[i], i < len ? mbox[i] : 0); /* notify chip it has to deal with mailbox */ qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_EP_INTR)); /* wait for completion */ if (cold) { sc->sc_mbox_flags = QLI_MBOX_F_POLL; for (i = 0; i < 6000000 /* up to a minute */; i++) { delay(10); if ((qli_read(sc, &sc->sc_reg->qlr_ctrl_status) & (QLI_REG_CTRLSTAT_SCSI_RESET_INTR | QLI_REG_CTRLSTAT_SCSI_COMPL_INTR | QLI_REG_CTRLSTAT_SCSI_PROC_INTR))) { qli_intr(sc); break; } } } else { sc->sc_mbox_flags = QLI_MBOX_F_PENDING; while ((sc->sc_mbox_flags & QLI_MBOX_F_WAKEUP) == 0) tsleep(sc->sc_mbox, PRIBIO, "qli_mgmt", 0); } x = sc->sc_mbox[0]; switch (x) { case QLI_MBOX_STATUS_COMMAND_COMPLETE: for (i = 0; i < QLI_MBOX_SIZE; i++) mbox[i] = sc->sc_mbox[i]; sc->sc_mbox_flags = QLI_MBOX_F_INVALID; rv = 0; qli_dump_mbox(sc, mbox); break; default: printf("%s: qli_mgmt: mailbox failed opcode 0x%08x failed " "with error code 0x%08x\n", DEVNAME(sc), mbox[0], x); } rw_exit_write(&sc->sc_mbox_lock); splx(s); done: return (rv); } int qli_attach(struct qli_softc *sc) { /* struct scsibus_attach_args saa; */ int rv = 1; u_int32_t f, mbox[QLI_MBOX_SIZE]; unsigned int align; DNPRINTF(QLI_D_MISC, "%s: qli_attach\n", DEVNAME(sc)); rw_init(&sc->sc_lock, "qli_lock"); rw_init(&sc->sc_mbox_lock, "qli_mbox_lock"); if (sc->sc_ql4010) sc->sc_resource = QLI_SEM_4010_SCSI; else { f = qli_read(sc, &sc->sc_reg->qlr_ctrl_status) & QLI_REG_CTRLSTAT_FUNC_MASK; sc->sc_resource = f >> 8; } DNPRINTF(QLI_D_MISC, "%s: qli_attach resource: %d\n", DEVNAME(sc), sc->sc_resource); if (qli_start_firmware(sc)) { printf("%s: could not start firmware\n", DEVNAME(sc)); goto done; } bzero(mbox, sizeof(mbox)); mbox[0] = QLI_MBOX_OPC_ABOUT_FIRMWARE; if (qli_mgmt(sc, 4, mbox)) { printf("%s: about firmware command failed\n", DEVNAME(sc)); goto done; } printf("%s: version %d.%d.%d.%d\n", DEVNAME(sc), mbox[1], mbox[2], mbox[3], mbox[4]); /* get state */ bzero(mbox, sizeof(mbox)); if (qli_get_fw_state(sc, mbox)) { printf("%s: get firmware state command failed\n", DEVNAME(sc)); goto done; } /* initialize firmware */ sc->sc_fw_cb = qli_allocmem(sc, QLI_FW_CTRL_BLK_SIZE); if (sc->sc_fw_cb == NULL) { printf("%s: unable to allocate firmware control block memory\n", DEVNAME(sc)); goto done; } bzero(mbox, sizeof(mbox)); mbox[0] = QLI_MBOX_OPC_GET_INITIAL_FW_CB; mbox[2] = (u_int32_t)QLIMEM_DVA(sc->sc_fw_cb); mbox[3] = (u_int32_t)((u_int64_t)QLIMEM_DVA(sc->sc_fw_cb) >> 32); if (qli_mgmt(sc, 4, mbox)) { printf("%s: get initial firmware control block failed\n", DEVNAME(sc)); goto nofwcb; } /* setup queues & shadow registers */ sc->sc_queues_len = (QLI_REQUESTQ_DEPTH * QLI_QUEUE_SIZE) + (QLI_RESPONSEQ_DEPTH * QLI_QUEUE_SIZE) + sizeof(struct qli_shadow_regs) + QLIMEM_ALIGN + PAGE_SIZE - 1; sc->sc_queues_len &= ~(PAGE_SIZE - 1); sc->sc_queues = qli_allocmem(sc, sc->sc_queues_len); if (sc->sc_queues == NULL) { printf("%s: unable to allocate firmware control block memory\n", DEVNAME(sc)); goto nofwcb; } if (QLIMEM_DVA(sc->sc_queues) & (QLIMEM_ALIGN - 1)) align = QLIMEM_ALIGN - (QLIMEM_DVA(sc->sc_queues) & (QLIMEM_ALIGN - 1)); else align = 0; sc->sc_request_dva = QLIMEM_DVA(sc->sc_queues) + align; sc->sc_request_ring = QLIMEM_KVA(sc->sc_queues) + align; #if 0 /* enable interrupts */ qli_enable_interrupts(sc); #endif #if NBIO > 0 if (bio_register(&sc->sc_dev, qli_ioctl) != 0) panic("%s: controller registration failed", DEVNAME(sc)); else sc->sc_ioctl = qli_ioctl; #ifndef SMALL_KERNEL if (qli_create_sensors(sc) != 0) printf("%s: unable to create sensors\n", DEVNAME(sc)); #endif /* SMALL_KERNEL */ #endif /* NBIO > 0 */ done: return (rv); /* noqueues: */ qli_freemem(sc, sc->sc_queues); nofwcb: qli_freemem(sc, sc->sc_fw_cb); return (rv); } void qli_scsi_cmd(struct scsi_xfer *xs) { int s; #ifdef QLI_DEBUG struct scsi_link *link = xs->sc_link; struct qli_softc *sc = link->adapter_softc; DNPRINTF(QLI_D_CMD, "%s: qli_scsi_cmd opcode: %#x\n", DEVNAME(sc), xs->cmd->opcode); #endif goto stuffup; return; stuffup: xs->error = XS_DRIVER_STUFFUP; scsi_done(xs); } int qli_intr(void *arg) { struct qli_softc *sc = arg; int claimed = 0, i; u_int32_t intr, mbox_status; intr = qli_read(sc, &sc->sc_reg->qlr_ctrl_status); if ((intr & (QLI_REG_CTRLSTAT_SCSI_RESET_INTR | QLI_REG_CTRLSTAT_SCSI_COMPL_INTR | QLI_REG_CTRLSTAT_SCSI_PROC_INTR | QLI_REG_CTRLSTAT_FATAL_ERROR)) == 0) goto done; DNPRINTF(QLI_D_INTR, "%s: qli_intr %#x cs: 0x%08x\n", DEVNAME(sc), sc, intr); if (intr & QLI_REG_CTRLSTAT_SCSI_RESET_INTR) { /* chip requests soft reset */ /* XXX */ panic("%s: qli_intr chip reset not implemented", DEVNAME(sc)); } if (intr & QLI_REG_CTRLSTAT_FATAL_ERROR) { /* reset firmware */ /* XXX */ panic("%s: qli_intr chip hang recovery not implemented", DEVNAME(sc)); } if (intr & QLI_REG_CTRLSTAT_SCSI_COMPL_INTR) { /* io completion */ /* XXX */ panic("%s: qli_intr io completion not implemented", DEVNAME(sc)); } if (intr & QLI_REG_CTRLSTAT_SCSI_PROC_INTR) { /* mailbox completion */ mbox_status = qli_read(sc, &sc->sc_reg->qlr_mbox[0]); switch (mbox_status >> QLI_MBOX_TYPE_SHIFT) { case QLI_MBOX_COMPLETION_STATUS: for (i = 0; i < QLI_MBOX_SIZE; i++) sc->sc_mbox[i] = qli_read(sc, &sc->sc_reg->qlr_mbox[i]); qli_write(sc, &sc->sc_reg->qlr_ctrl_status, QLI_SET_MASK(QLI_REG_CTRLSTAT_SCSI_PROC_INTR)); if (sc->sc_mbox_flags & QLI_MBOX_F_PENDING) { sc->sc_mbox_flags |= QLI_MBOX_F_WAKEUP; wakeup(sc->sc_mbox); } claimed = 1; break; case QLI_MBOX_ASYNC_EVENT_STATUS: printf("%s: unhandled async event 0x%08x\n", DEVNAME(sc), qli_read(sc, &sc->sc_reg->qlr_mbox[0])); break; default: printf("%s: invalid mailbox return 0x%08x\n", DEVNAME(sc), qli_read(sc, &sc->sc_reg->qlr_mbox[0])); break; } } done: return (claimed); } int qli_scsi_ioctl(struct scsi_link *link, u_long cmd, caddr_t addr, int flag, struct proc *p) { struct qli_softc *sc = (struct qli_softc *)link->adapter_softc; DNPRINTF(QLI_D_IOCTL, "%s: qli_scsi_ioctl\n", DEVNAME(sc)); if (sc->sc_ioctl) return (sc->sc_ioctl(link->adapter_softc, cmd, addr)); else return (ENOTTY); } #if NBIO > 0 int qli_ioctl(struct device *dev, u_long cmd, caddr_t addr) { struct qli_softc *sc = (struct qli_softc *)dev; int error = EINVAL; DNPRINTF(QLI_D_IOCTL, "%s: qli_ioctl ", DEVNAME(sc)); rw_enter_write(&sc->sc_lock); switch (cmd) { case BIOCINQ: DNPRINTF(QLI_D_IOCTL, "inq\n"); break; case BIOCVOL: DNPRINTF(QLI_D_IOCTL, "vol\n"); break; case BIOCDISK: DNPRINTF(QLI_D_IOCTL, "disk\n"); break; case BIOCALARM: DNPRINTF(QLI_D_IOCTL, "alarm\n"); break; case BIOCBLINK: DNPRINTF(QLI_D_IOCTL, "blink\n"); break; case BIOCSETSTATE: DNPRINTF(QLI_D_IOCTL, "setstate\n"); break; default: DNPRINTF(QLI_D_IOCTL, " invalid ioctl\n"); error = EINVAL; } rw_exit_write(&sc->sc_lock); return (error); } #endif /* NBIO > 0 */ #ifndef SMALL_KERNEL int qli_create_sensors(struct qli_softc *sc) { return (1); } #endif /* SMALL_KERNEL */ #ifdef QLI_DEBUG void qli_dump_mbox(struct qli_softc *sc, u_int32_t *mbox) { int i; if ((qli_debug & QLI_D_MBOX) == 0) return; printf("%s: qli_dump_mbox: ", DEVNAME(sc)); for (i = 0; i < QLI_MBOX_SIZE; i++) printf("mbox[%d] = 0x%08x ", i, mbox[i]); printf("\n"); } #endif /* QLI_DEBUG */