/* $NetBSD: mpii.c,v 1.31 2024/02/04 20:50:30 andvar Exp $ */ /* $OpenBSD: mpii.c,v 1.115 2018/08/14 05:22:21 jmatthew Exp $ */ /* * Copyright (c) 2010, 2012 Mike Belopuhov * Copyright (c) 2009 James Giannoules * Copyright (c) 2005 - 2010 David Gwynne * Copyright (c) 2005 - 2010 Marco Peereboom * * 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 __KERNEL_RCSID(0, "$NetBSD: mpii.c,v 1.31 2024/02/04 20:50:30 andvar Exp $"); #include "bio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if NBIO > 0 #include #include #include #endif #include // #define MPII_DEBUG #ifdef MPII_DEBUG #define DPRINTF(x...) do { if (mpii_debug) printf(x); } while(0) #define DNPRINTF(n,x...) do { if (mpii_debug & (n)) printf(x); } while(0) #define MPII_D_CMD (0x0001) #define MPII_D_INTR (0x0002) #define MPII_D_MISC (0x0004) #define MPII_D_DMA (0x0008) #define MPII_D_IOCTL (0x0010) #define MPII_D_RW (0x0020) #define MPII_D_MEM (0x0040) #define MPII_D_CCB (0x0080) #define MPII_D_PPR (0x0100) #define MPII_D_RAID (0x0200) #define MPII_D_EVT (0x0400) #define MPII_D_CFG (0x0800) #define MPII_D_MAP (0x1000) u_int32_t mpii_debug = 0 // | MPII_D_CMD // | MPII_D_INTR // | MPII_D_MISC // | MPII_D_DMA // | MPII_D_IOCTL // | MPII_D_RW // | MPII_D_MEM // | MPII_D_CCB // | MPII_D_PPR // | MPII_D_RAID // | MPII_D_EVT // | MPII_D_CFG // | MPII_D_MAP ; #else #define DPRINTF(x...) #define DNPRINTF(n,x...) #endif #define MPII_REQUEST_SIZE (512) #define MPII_REQUEST_CREDIT (128) struct mpii_dmamem { bus_dmamap_t mdm_map; bus_dma_segment_t mdm_seg; size_t mdm_size; void *mdm_kva; }; #define MPII_DMA_MAP(_mdm) ((_mdm)->mdm_map) #define MPII_DMA_DVA(_mdm) ((uint64_t)(_mdm)->mdm_map->dm_segs[0].ds_addr) #define MPII_DMA_KVA(_mdm) ((_mdm)->mdm_kva) struct mpii_softc; struct mpii_rcb { SIMPLEQ_ENTRY(mpii_rcb) rcb_link; void *rcb_reply; u_int32_t rcb_reply_dva; }; SIMPLEQ_HEAD(mpii_rcb_list, mpii_rcb); struct mpii_device { int flags; #define MPII_DF_ATTACH (0x0001) #define MPII_DF_DETACH (0x0002) #define MPII_DF_HIDDEN (0x0004) #define MPII_DF_UNUSED (0x0008) #define MPII_DF_VOLUME (0x0010) #define MPII_DF_VOLUME_DISK (0x0020) #define MPII_DF_HOT_SPARE (0x0040) short slot; short percent; u_int16_t dev_handle; u_int16_t enclosure; u_int16_t expander; u_int8_t phy_num; u_int8_t physical_port; }; struct mpii_ccb { struct mpii_softc *ccb_sc; void * ccb_cookie; kmutex_t ccb_mtx; kcondvar_t ccb_cv; bus_dmamap_t ccb_dmamap; bus_addr_t ccb_offset; void *ccb_cmd; bus_addr_t ccb_cmd_dva; u_int16_t ccb_dev_handle; u_int16_t ccb_smid; volatile enum { MPII_CCB_FREE, MPII_CCB_READY, MPII_CCB_QUEUED, MPII_CCB_TIMEOUT } ccb_state; void (*ccb_done)(struct mpii_ccb *); struct mpii_rcb *ccb_rcb; SIMPLEQ_ENTRY(mpii_ccb) ccb_link; }; SIMPLEQ_HEAD(mpii_ccb_list, mpii_ccb); struct mpii_softc { device_t sc_dev; pci_chipset_tag_t sc_pc; pcitag_t sc_tag; void *sc_ih; pci_intr_handle_t *sc_pihp; struct scsipi_adapter sc_adapt; struct scsipi_channel sc_chan; device_t sc_child; /* our scsibus */ int sc_flags; #define MPII_F_RAID (1<<1) #define MPII_F_SAS3 (1<<2) struct mpii_device **sc_devs; kmutex_t sc_devs_mtx; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; bus_size_t sc_ios; bus_dma_tag_t sc_dmat; kmutex_t sc_req_mtx; kmutex_t sc_rep_mtx; ushort sc_reply_size; ushort sc_request_size; ushort sc_max_cmds; ushort sc_num_reply_frames; u_int sc_reply_free_qdepth; u_int sc_reply_post_qdepth; ushort sc_chain_sge; ushort sc_max_sgl; u_int8_t sc_ioc_event_replay; u_int8_t sc_porttype; u_int8_t sc_max_volumes; u_int16_t sc_max_devices; u_int16_t sc_vd_count; u_int16_t sc_vd_id_low; u_int16_t sc_pd_id_start; int sc_ioc_number; u_int8_t sc_vf_id; struct mpii_ccb *sc_ccbs; struct mpii_ccb_list sc_ccb_free; kmutex_t sc_ccb_free_mtx; kcondvar_t sc_ccb_free_cv; struct mpii_ccb_list sc_ccb_tmos; kmutex_t sc_ssb_tmomtx; struct workqueue *sc_ssb_tmowk; struct work sc_ssb_tmowork; struct mpii_dmamem *sc_requests; struct mpii_dmamem *sc_replies; struct mpii_rcb *sc_rcbs; struct mpii_dmamem *sc_reply_postq; struct mpii_reply_descr *sc_reply_postq_kva; u_int sc_reply_post_host_index; struct mpii_dmamem *sc_reply_freeq; u_int sc_reply_free_host_index; kmutex_t sc_reply_free_mtx; struct mpii_rcb_list sc_evt_sas_queue; kmutex_t sc_evt_sas_mtx; struct workqueue *sc_evt_sas_wq; struct work sc_evt_sas_work; struct mpii_rcb_list sc_evt_ack_queue; kmutex_t sc_evt_ack_mtx; struct workqueue *sc_evt_ack_wq; struct work sc_evt_ack_work; #if NBIO > 0 struct sysmon_envsys *sc_sme; envsys_data_t *sc_sensors; #endif }; static int mpii_match(device_t, cfdata_t, void *); static void mpii_attach(device_t, device_t, void *); static int mpii_detach(device_t, int); static void mpii_childdetached(device_t, device_t); static int mpii_rescan(device_t, const char *, const int *); static int mpii_intr(void *); CFATTACH_DECL3_NEW(mpii, sizeof(struct mpii_softc), mpii_match, mpii_attach, mpii_detach, NULL, mpii_rescan, mpii_childdetached, DVF_DETACH_SHUTDOWN); static void mpii_scsipi_request(struct scsipi_channel *, scsipi_adapter_req_t, void *); static void mpii_scsi_cmd_done(struct mpii_ccb *); static struct mpii_dmamem * mpii_dmamem_alloc(struct mpii_softc *, size_t); static void mpii_dmamem_free(struct mpii_softc *, struct mpii_dmamem *); static int mpii_alloc_ccbs(struct mpii_softc *); static struct mpii_ccb *mpii_get_ccb(struct mpii_softc *); static void mpii_put_ccb(struct mpii_softc *, struct mpii_ccb *); static int mpii_alloc_replies(struct mpii_softc *); static int mpii_alloc_queues(struct mpii_softc *); static void mpii_push_reply(struct mpii_softc *, struct mpii_rcb *); static void mpii_push_replies(struct mpii_softc *); static void mpii_scsi_cmd_tmo(void *); static void mpii_scsi_cmd_tmo_handler(struct work *, void *); static void mpii_scsi_cmd_tmo_done(struct mpii_ccb *); static int mpii_insert_dev(struct mpii_softc *, struct mpii_device *); static int mpii_remove_dev(struct mpii_softc *, struct mpii_device *); static struct mpii_device * mpii_find_dev(struct mpii_softc *, u_int16_t); static void mpii_start(struct mpii_softc *, struct mpii_ccb *); static int mpii_poll(struct mpii_softc *, struct mpii_ccb *); static void mpii_poll_done(struct mpii_ccb *); static struct mpii_rcb * mpii_reply(struct mpii_softc *, struct mpii_reply_descr *); static void mpii_wait(struct mpii_softc *, struct mpii_ccb *); static void mpii_wait_done(struct mpii_ccb *); static void mpii_init_queues(struct mpii_softc *); static int mpii_load_xs(struct mpii_ccb *); static int mpii_load_xs_sas3(struct mpii_ccb *); static u_int32_t mpii_read(struct mpii_softc *, bus_size_t); static void mpii_write(struct mpii_softc *, bus_size_t, u_int32_t); static int mpii_wait_eq(struct mpii_softc *, bus_size_t, u_int32_t, u_int32_t); static int mpii_wait_ne(struct mpii_softc *, bus_size_t, u_int32_t, u_int32_t); static int mpii_init(struct mpii_softc *); static int mpii_reset_soft(struct mpii_softc *); static int mpii_reset_hard(struct mpii_softc *); static int mpii_handshake_send(struct mpii_softc *, void *, size_t); static int mpii_handshake_recv_dword(struct mpii_softc *, u_int32_t *); static int mpii_handshake_recv(struct mpii_softc *, void *, size_t); static void mpii_empty_done(struct mpii_ccb *); static int mpii_iocinit(struct mpii_softc *); static int mpii_iocfacts(struct mpii_softc *); static int mpii_portfacts(struct mpii_softc *); static int mpii_portenable(struct mpii_softc *); static int mpii_cfg_coalescing(struct mpii_softc *); static int mpii_board_info(struct mpii_softc *); static int mpii_target_map(struct mpii_softc *); static int mpii_eventnotify(struct mpii_softc *); static void mpii_eventnotify_done(struct mpii_ccb *); static void mpii_eventack(struct work *, void *); static void mpii_eventack_done(struct mpii_ccb *); static void mpii_event_process(struct mpii_softc *, struct mpii_rcb *); static void mpii_event_done(struct mpii_softc *, struct mpii_rcb *); static void mpii_event_sas(struct mpii_softc *, struct mpii_rcb *); static void mpii_event_sas_work(struct work *, void *); static void mpii_event_raid(struct mpii_softc *, struct mpii_msg_event_reply *); static void mpii_event_discovery(struct mpii_softc *, struct mpii_msg_event_reply *); static void mpii_sas_remove_device(struct mpii_softc *, u_int16_t); static int mpii_req_cfg_header(struct mpii_softc *, u_int8_t, u_int8_t, u_int32_t, int, void *); static int mpii_req_cfg_page(struct mpii_softc *, u_int32_t, int, void *, int, void *, size_t); #if 0 int mpii_ioctl_cache(struct scsi_link *, u_long, struct dk_cache *); #endif #if NBIO > 0 static int mpii_ioctl(device_t, u_long, void *); static int mpii_ioctl_inq(struct mpii_softc *, struct bioc_inq *); static int mpii_ioctl_vol(struct mpii_softc *, struct bioc_vol *); static int mpii_ioctl_disk(struct mpii_softc *, struct bioc_disk *); static int mpii_bio_hs(struct mpii_softc *, struct bioc_disk *, int, int, int *); static int mpii_bio_disk(struct mpii_softc *, struct bioc_disk *, u_int8_t); static struct mpii_device * mpii_find_vol(struct mpii_softc *, int); #ifndef SMALL_KERNEL static int mpii_bio_volstate(struct mpii_softc *, struct bioc_vol *); static int mpii_create_sensors(struct mpii_softc *); static void mpii_refresh_sensors(struct sysmon_envsys *, envsys_data_t *); static int mpii_destroy_sensors(struct mpii_softc *); #endif /* SMALL_KERNEL */ #endif /* NBIO > 0 */ #define DEVNAME(s) (device_xname((s)->sc_dev)) #define dwordsof(s) (sizeof(s) / sizeof(u_int32_t)) #define mpii_read_db(s) mpii_read((s), MPII_DOORBELL) #define mpii_write_db(s, v) mpii_write((s), MPII_DOORBELL, (v)) #define mpii_read_intr(s) mpii_read((s), MPII_INTR_STATUS) #define mpii_write_intr(s, v) mpii_write((s), MPII_INTR_STATUS, (v)) #define mpii_reply_waiting(s) ((mpii_read_intr((s)) & MPII_INTR_STATUS_REPLY)\ == MPII_INTR_STATUS_REPLY) #define mpii_write_reply_free(s, v) \ bus_space_write_4((s)->sc_iot, (s)->sc_ioh, \ MPII_REPLY_FREE_HOST_INDEX, (v)) #define mpii_write_reply_post(s, v) \ bus_space_write_4((s)->sc_iot, (s)->sc_ioh, \ MPII_REPLY_POST_HOST_INDEX, (v)) #define mpii_wait_db_int(s) mpii_wait_ne((s), MPII_INTR_STATUS, \ MPII_INTR_STATUS_IOC2SYSDB, 0) #define mpii_wait_db_ack(s) mpii_wait_eq((s), MPII_INTR_STATUS, \ MPII_INTR_STATUS_SYS2IOCDB, 0) static inline void mpii_dvatosge(struct mpii_sge *sge, u_int64_t dva) { sge->sg_addr_lo = htole32(dva); sge->sg_addr_hi = htole32(dva >> 32); } #define MPII_PG_EXTENDED (1<<0) #define MPII_PG_POLL (1<<1) #define MPII_PG_FMT "\020" "\002POLL" "\001EXTENDED" static const struct mpii_pci_product { pci_vendor_id_t mpii_vendor; pci_product_id_t mpii_product; } mpii_devices[] = { { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2004 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2008 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2108_3 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2108_4 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2108_5 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2116_1 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2116_2 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2208_1 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2208_2 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2208_3 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2208_4 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2208_5 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2208_6 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2308_1 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2308_2 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS2308_3 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3004 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3008 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3108_1 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3108_2 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3108_3 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3108_4 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3408 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3416 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3508 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3508_1 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3516 }, { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_SAS3516_1 }, { 0, 0} }; static int mpii_match(device_t parent, cfdata_t match, void *aux) { struct pci_attach_args *pa = aux; const struct mpii_pci_product *mpii; for (mpii = mpii_devices; mpii->mpii_vendor != 0; mpii++) { if (PCI_VENDOR(pa->pa_id) == mpii->mpii_vendor && PCI_PRODUCT(pa->pa_id) == mpii->mpii_product) return (1); } return (0); } static void mpii_attach(device_t parent, device_t self, void *aux) { struct mpii_softc *sc = device_private(self); struct pci_attach_args *pa = aux; pcireg_t memtype; int r; struct mpii_ccb *ccb; struct scsipi_adapter *adapt = &sc->sc_adapt; struct scsipi_channel *chan = &sc->sc_chan; char intrbuf[PCI_INTRSTR_LEN]; const char *intrstr; pci_aprint_devinfo(pa, NULL); sc->sc_pc = pa->pa_pc; sc->sc_tag = pa->pa_tag; sc->sc_dmat = pa->pa_dmat; sc->sc_dev = self; mutex_init(&sc->sc_req_mtx, MUTEX_DEFAULT, IPL_BIO); mutex_init(&sc->sc_rep_mtx, MUTEX_DEFAULT, IPL_BIO); /* find the appropriate memory base */ for (r = PCI_MAPREG_START; r < PCI_MAPREG_END; r += sizeof(memtype)) { memtype = pci_mapreg_type(sc->sc_pc, sc->sc_tag, r); if (PCI_MAPREG_TYPE(memtype) == PCI_MAPREG_TYPE_MEM) break; } if (r >= PCI_MAPREG_END) { aprint_error_dev(self, "unable to locate system interface registers\n"); return; } if (pci_mapreg_map(pa, r, memtype, 0, &sc->sc_iot, &sc->sc_ioh, NULL, &sc->sc_ios) != 0) { aprint_error_dev(self, "unable to map system interface registers\n"); return; } /* disable the expansion rom */ pci_conf_write(sc->sc_pc, sc->sc_tag, PCI_MAPREG_ROM, pci_conf_read(sc->sc_pc, sc->sc_tag, PCI_MAPREG_ROM) & ~PCI_MAPREG_ROM_ENABLE); /* disable interrupts */ mpii_write(sc, MPII_INTR_MASK, MPII_INTR_MASK_RESET | MPII_INTR_MASK_REPLY | MPII_INTR_MASK_DOORBELL); /* hook up the interrupt */ if (pci_intr_alloc(pa, &sc->sc_pihp, NULL, 0)) { aprint_error_dev(self, "unable to map interrupt\n"); goto unmap; } intrstr = pci_intr_string(pa->pa_pc, sc->sc_pihp[0], intrbuf, sizeof(intrbuf)); pci_intr_setattr(pa->pa_pc, &sc->sc_pihp[0], PCI_INTR_MPSAFE, true); sc->sc_ih = pci_intr_establish_xname(pa->pa_pc, sc->sc_pihp[0], IPL_BIO, mpii_intr, sc, device_xname(self)); if (sc->sc_ih == NULL) { aprint_error_dev(self, "couldn't establish interrupt"); if (intrstr != NULL) aprint_error(" at %s", intrstr); aprint_error("\n"); return; } aprint_normal_dev(self, "interrupting at %s\n", intrstr); aprint_naive("\n"); if (mpii_iocfacts(sc) != 0) { aprint_error_dev(self, "unable to get iocfacts\n"); goto unmap; } if (mpii_init(sc) != 0) { aprint_error_dev(self, "unable to initialize ioc\n"); goto unmap; } if (mpii_alloc_ccbs(sc) != 0) { /* error already printed */ goto unmap; } if (mpii_alloc_replies(sc) != 0) { aprint_error_dev(self, "unable to allocated reply space\n"); goto free_ccbs; } if (mpii_alloc_queues(sc) != 0) { aprint_error_dev(self, "unable to allocate reply queues\n"); goto free_replies; } if (mpii_iocinit(sc) != 0) { aprint_error_dev(self, "unable to send iocinit\n"); goto free_queues; } if (mpii_wait_eq(sc, MPII_DOORBELL, MPII_DOORBELL_STATE, MPII_DOORBELL_STATE_OPER) != 0) { aprint_error_dev(self, "state: 0x%08x\n", mpii_read_db(sc) & MPII_DOORBELL_STATE); aprint_error_dev(self, "operational state timeout\n"); goto free_queues; } mpii_push_replies(sc); mpii_init_queues(sc); if (mpii_board_info(sc) != 0) { aprint_error_dev(self, "unable to get manufacturing page 0\n"); goto free_queues; } if (mpii_portfacts(sc) != 0) { aprint_error_dev(self, "unable to get portfacts\n"); goto free_queues; } if (mpii_target_map(sc) != 0) { aprint_error_dev(self, "unable to setup target mappings\n"); goto free_queues; } if (mpii_cfg_coalescing(sc) != 0) { aprint_error_dev(self, "unable to configure coalescing\n"); goto free_queues; } /* XXX bail on unsupported porttype? */ if ((sc->sc_porttype == MPII_PORTFACTS_PORTTYPE_SAS_PHYSICAL) || (sc->sc_porttype == MPII_PORTFACTS_PORTTYPE_SAS_VIRTUAL) || (sc->sc_porttype == MPII_PORTFACTS_PORTTYPE_TRI_MODE)) { if (mpii_eventnotify(sc) != 0) { aprint_error_dev(self, "unable to enable events\n"); goto free_queues; } } mutex_init(&sc->sc_devs_mtx, MUTEX_DEFAULT, IPL_BIO); sc->sc_devs = malloc(sc->sc_max_devices * sizeof(struct mpii_device *), M_DEVBUF, M_WAITOK | M_ZERO); if (mpii_portenable(sc) != 0) { aprint_error_dev(self, "unable to enable port\n"); goto free_devs; } /* we should be good to go now, attach scsibus */ memset(adapt, 0, sizeof(*adapt)); adapt->adapt_dev = sc->sc_dev; adapt->adapt_nchannels = 1; adapt->adapt_openings = sc->sc_max_cmds - 4; adapt->adapt_max_periph = adapt->adapt_openings; adapt->adapt_request = mpii_scsipi_request; adapt->adapt_minphys = minphys; adapt->adapt_flags = SCSIPI_ADAPT_MPSAFE; memset(chan, 0, sizeof(*chan)); chan->chan_adapter = adapt; chan->chan_bustype = &scsi_sas_bustype; chan->chan_channel = 0; chan->chan_flags = 0; chan->chan_nluns = 8; chan->chan_ntargets = sc->sc_max_devices; chan->chan_id = -1; mpii_rescan(self, NULL, NULL); /* enable interrupts */ mpii_write(sc, MPII_INTR_MASK, MPII_INTR_MASK_DOORBELL | MPII_INTR_MASK_RESET); #if NBIO > 0 if (ISSET(sc->sc_flags, MPII_F_RAID)) { if (bio_register(sc->sc_dev, mpii_ioctl) != 0) panic("%s: controller registration failed", DEVNAME(sc)); if (mpii_create_sensors(sc) != 0) aprint_error_dev(self, "unable to create sensors\n"); } #endif return; free_devs: free(sc->sc_devs, M_DEVBUF); sc->sc_devs = NULL; free_queues: bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_reply_freeq), 0, sc->sc_reply_free_qdepth * 4, BUS_DMASYNC_POSTREAD); mpii_dmamem_free(sc, sc->sc_reply_freeq); bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_reply_postq), 0, sc->sc_reply_post_qdepth * 8, BUS_DMASYNC_POSTREAD); mpii_dmamem_free(sc, sc->sc_reply_postq); free_replies: bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_replies), 0, PAGE_SIZE, BUS_DMASYNC_POSTREAD); mpii_dmamem_free(sc, sc->sc_replies); free_ccbs: while ((ccb = mpii_get_ccb(sc)) != NULL) bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap); mpii_dmamem_free(sc, sc->sc_requests); free(sc->sc_ccbs, M_DEVBUF); unmap: bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); sc->sc_ios = 0; } static int mpii_detach(device_t self, int flags) { struct mpii_softc *sc = device_private(self); int error; struct mpii_ccb *ccb; if ((error = config_detach_children(sc->sc_dev, flags)) != 0) return error; #if NBIO > 0 mpii_destroy_sensors(sc); bio_unregister(sc->sc_dev); #endif /* NBIO > 0 */ if (sc->sc_ih != NULL) { pci_intr_disestablish(sc->sc_pc, sc->sc_ih); sc->sc_ih = NULL; } if (sc->sc_ios != 0) { bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); free(sc->sc_devs, M_DEVBUF); sc->sc_devs = NULL; bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_reply_freeq), 0, sc->sc_reply_free_qdepth * 4, BUS_DMASYNC_POSTREAD); mpii_dmamem_free(sc, sc->sc_reply_freeq); bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_reply_postq), 0, sc->sc_reply_post_qdepth * 8, BUS_DMASYNC_POSTREAD); mpii_dmamem_free(sc, sc->sc_reply_postq); bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_replies), 0, PAGE_SIZE, BUS_DMASYNC_POSTREAD); mpii_dmamem_free(sc, sc->sc_replies); while ((ccb = mpii_get_ccb(sc)) != NULL) bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap); mpii_dmamem_free(sc, sc->sc_requests); free(sc->sc_ccbs, M_DEVBUF); sc->sc_ios = 0; } return (0); } static int mpii_rescan(device_t self, const char *ifattr, const int *locators) { struct mpii_softc *sc = device_private(self); if (sc->sc_child != NULL) return 0; sc->sc_child = config_found(self, &sc->sc_chan, scsiprint, CFARGS_NONE); return 0; } static void mpii_childdetached(device_t self, device_t child) { struct mpii_softc *sc = device_private(self); KASSERT(self == sc->sc_dev); KASSERT(child == sc->sc_child); if (child == sc->sc_child) sc->sc_child = NULL; } static int mpii_intr(void *arg) { struct mpii_rcb_list evts = SIMPLEQ_HEAD_INITIALIZER(evts); struct mpii_ccb_list ccbs = SIMPLEQ_HEAD_INITIALIZER(ccbs); struct mpii_softc *sc = arg; struct mpii_reply_descr *postq = sc->sc_reply_postq_kva, *rdp; struct mpii_ccb *ccb; struct mpii_rcb *rcb; int smid; u_int idx; int rv = 0; mutex_enter(&sc->sc_rep_mtx); bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_reply_postq), 0, sc->sc_reply_post_qdepth * sizeof(*rdp), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); idx = sc->sc_reply_post_host_index; for (;;) { rdp = &postq[idx]; if ((rdp->reply_flags & MPII_REPLY_DESCR_TYPE_MASK) == MPII_REPLY_DESCR_UNUSED) break; if (rdp->data == 0xffffffff) { /* * ioc is still writing to the reply post queue * race condition - bail! */ break; } smid = le16toh(rdp->smid); rcb = mpii_reply(sc, rdp); if (smid) { ccb = &sc->sc_ccbs[smid - 1]; ccb->ccb_state = MPII_CCB_READY; ccb->ccb_rcb = rcb; SIMPLEQ_INSERT_TAIL(&ccbs, ccb, ccb_link); } else SIMPLEQ_INSERT_TAIL(&evts, rcb, rcb_link); if (++idx >= sc->sc_reply_post_qdepth) idx = 0; rv = 1; } bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_reply_postq), 0, sc->sc_reply_post_qdepth * sizeof(*rdp), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); if (rv) mpii_write_reply_post(sc, sc->sc_reply_post_host_index = idx); mutex_exit(&sc->sc_rep_mtx); if (rv == 0) return (0); while ((ccb = SIMPLEQ_FIRST(&ccbs)) != NULL) { SIMPLEQ_REMOVE_HEAD(&ccbs, ccb_link); ccb->ccb_done(ccb); } while ((rcb = SIMPLEQ_FIRST(&evts)) != NULL) { SIMPLEQ_REMOVE_HEAD(&evts, rcb_link); mpii_event_process(sc, rcb); } return (1); } static int mpii_load_xs_sas3(struct mpii_ccb *ccb) { struct mpii_softc *sc = ccb->ccb_sc; struct scsipi_xfer *xs = ccb->ccb_cookie; struct mpii_msg_scsi_io *io = ccb->ccb_cmd; struct mpii_ieee_sge *csge, *nsge, *sge; bus_dmamap_t dmap = ccb->ccb_dmamap; int i, error; /* Request frame structure is described in the mpii_iocfacts */ nsge = (struct mpii_ieee_sge *)(io + 1); csge = nsge + sc->sc_chain_sge; /* zero length transfer still requires an SGE */ if (xs->datalen == 0) { nsge->sg_flags = MPII_IEEE_SGE_END_OF_LIST; return (0); } error = bus_dmamap_load(sc->sc_dmat, dmap, xs->data, xs->datalen, NULL, (xs->xs_control & XS_CTL_NOSLEEP) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK); if (error) { printf("%s: error %d loading dmamap\n", DEVNAME(sc), error); return (1); } sge = nsge; for (i = 0; i < dmap->dm_nsegs; i++, nsge++) { if (nsge == csge) { nsge++; /* offset to the chain sge from the beginning */ io->chain_offset = ((uintptr_t)csge - (uintptr_t)io) / 4; csge->sg_flags = MPII_IEEE_SGE_CHAIN_ELEMENT | MPII_IEEE_SGE_ADDR_SYSTEM; /* address of the next sge */ csge->sg_addr = htole64(ccb->ccb_cmd_dva + ((uintptr_t)nsge - (uintptr_t)io)); csge->sg_len = htole32((dmap->dm_nsegs - i) * sizeof(*sge)); } sge = nsge; sge->sg_flags = MPII_IEEE_SGE_ADDR_SYSTEM; sge->sg_len = htole32(dmap->dm_segs[i].ds_len); sge->sg_addr = htole64(dmap->dm_segs[i].ds_addr); } /* terminate list */ sge->sg_flags |= MPII_IEEE_SGE_END_OF_LIST; bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize, (xs->xs_control & XS_CTL_DATA_IN) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); return (0); } static int mpii_load_xs(struct mpii_ccb *ccb) { struct mpii_softc *sc = ccb->ccb_sc; struct scsipi_xfer *xs = ccb->ccb_cookie; struct mpii_msg_scsi_io *io = ccb->ccb_cmd; struct mpii_sge *csge, *nsge, *sge; bus_dmamap_t dmap = ccb->ccb_dmamap; u_int32_t flags; u_int16_t len; int i, error; /* Request frame structure is described in the mpii_iocfacts */ nsge = (struct mpii_sge *)(io + 1); csge = nsge + sc->sc_chain_sge; /* zero length transfer still requires an SGE */ if (xs->datalen == 0) { nsge->sg_hdr = htole32(MPII_SGE_FL_TYPE_SIMPLE | MPII_SGE_FL_LAST | MPII_SGE_FL_EOB | MPII_SGE_FL_EOL); return (0); } error = bus_dmamap_load(sc->sc_dmat, dmap, xs->data, xs->datalen, NULL, (xs->xs_control & XS_CTL_NOSLEEP) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK); if (error) { printf("%s: error %d loading dmamap\n", DEVNAME(sc), error); return (1); } /* safe default starting flags */ flags = MPII_SGE_FL_TYPE_SIMPLE | MPII_SGE_FL_SIZE_64; if (xs->xs_control & XS_CTL_DATA_OUT) flags |= MPII_SGE_FL_DIR_OUT; sge = nsge; for (i = 0; i < dmap->dm_nsegs; i++, nsge++) { if (nsge == csge) { nsge++; /* offset to the chain sge from the beginning */ io->chain_offset = ((uintptr_t)csge - (uintptr_t)io) / 4; /* length of the sgl segment we're pointing to */ len = (dmap->dm_nsegs - i) * sizeof(*sge); csge->sg_hdr = htole32(MPII_SGE_FL_TYPE_CHAIN | MPII_SGE_FL_SIZE_64 | len); /* address of the next sge */ mpii_dvatosge(csge, ccb->ccb_cmd_dva + ((uintptr_t)nsge - (uintptr_t)io)); } sge = nsge; sge->sg_hdr = htole32(flags | dmap->dm_segs[i].ds_len); mpii_dvatosge(sge, dmap->dm_segs[i].ds_addr); } /* terminate list */ sge->sg_hdr |= htole32(MPII_SGE_FL_LAST | MPII_SGE_FL_EOB | MPII_SGE_FL_EOL); bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize, (xs->xs_control & XS_CTL_DATA_IN) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); return (0); } static u_int32_t mpii_read(struct mpii_softc *sc, bus_size_t r) { u_int32_t rv; bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4, BUS_SPACE_BARRIER_READ); rv = bus_space_read_4(sc->sc_iot, sc->sc_ioh, r); DNPRINTF(MPII_D_RW, "%s: mpii_read %#lx %#x\n", DEVNAME(sc), r, rv); return (rv); } static void mpii_write(struct mpii_softc *sc, bus_size_t r, u_int32_t v) { DNPRINTF(MPII_D_RW, "%s: mpii_write %#lx %#x\n", DEVNAME(sc), r, v); bus_space_write_4(sc->sc_iot, sc->sc_ioh, r, v); bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4, BUS_SPACE_BARRIER_WRITE); } static int mpii_wait_eq(struct mpii_softc *sc, bus_size_t r, u_int32_t mask, u_int32_t target) { int i; DNPRINTF(MPII_D_RW, "%s: mpii_wait_eq %#lx %#x %#x\n", DEVNAME(sc), r, mask, target); for (i = 0; i < 15000; i++) { if ((mpii_read(sc, r) & mask) == target) return (0); delay(1000); } return (1); } static int mpii_wait_ne(struct mpii_softc *sc, bus_size_t r, u_int32_t mask, u_int32_t target) { int i; DNPRINTF(MPII_D_RW, "%s: mpii_wait_ne %#lx %#x %#x\n", DEVNAME(sc), r, mask, target); for (i = 0; i < 15000; i++) { if ((mpii_read(sc, r) & mask) != target) return (0); delay(1000); } return (1); } static int mpii_init(struct mpii_softc *sc) { u_int32_t db; int i; /* spin until the ioc leaves the reset state */ if (mpii_wait_ne(sc, MPII_DOORBELL, MPII_DOORBELL_STATE, MPII_DOORBELL_STATE_RESET) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_init timeout waiting to leave " "reset state\n", DEVNAME(sc)); return (1); } /* check current ownership */ db = mpii_read_db(sc); if ((db & MPII_DOORBELL_WHOINIT) == MPII_DOORBELL_WHOINIT_PCIPEER) { DNPRINTF(MPII_D_MISC, "%s: mpii_init initialised by pci peer\n", DEVNAME(sc)); return (0); } for (i = 0; i < 5; i++) { switch (db & MPII_DOORBELL_STATE) { case MPII_DOORBELL_STATE_READY: DNPRINTF(MPII_D_MISC, "%s: mpii_init ioc is ready\n", DEVNAME(sc)); return (0); case MPII_DOORBELL_STATE_OPER: DNPRINTF(MPII_D_MISC, "%s: mpii_init ioc is oper\n", DEVNAME(sc)); if (sc->sc_ioc_event_replay) mpii_reset_soft(sc); else mpii_reset_hard(sc); break; case MPII_DOORBELL_STATE_FAULT: DNPRINTF(MPII_D_MISC, "%s: mpii_init ioc is being " "reset hard\n" , DEVNAME(sc)); mpii_reset_hard(sc); break; case MPII_DOORBELL_STATE_RESET: DNPRINTF(MPII_D_MISC, "%s: mpii_init waiting to come " "out of reset\n", DEVNAME(sc)); if (mpii_wait_ne(sc, MPII_DOORBELL, MPII_DOORBELL_STATE, MPII_DOORBELL_STATE_RESET) != 0) return (1); break; } db = mpii_read_db(sc); } return (1); } static int mpii_reset_soft(struct mpii_softc *sc) { DNPRINTF(MPII_D_MISC, "%s: mpii_reset_soft\n", DEVNAME(sc)); if (mpii_read_db(sc) & MPII_DOORBELL_INUSE) { return (1); } mpii_write_db(sc, MPII_DOORBELL_FUNCTION(MPII_FUNCTION_IOC_MESSAGE_UNIT_RESET)); /* XXX LSI waits 15 sec */ if (mpii_wait_db_ack(sc) != 0) return (1); /* XXX LSI waits 15 sec */ if (mpii_wait_eq(sc, MPII_DOORBELL, MPII_DOORBELL_STATE, MPII_DOORBELL_STATE_READY) != 0) return (1); /* XXX wait for Sys2IOCDB bit to clear in HIS?? */ return (0); } static int mpii_reset_hard(struct mpii_softc *sc) { u_int16_t i; DNPRINTF(MPII_D_MISC, "%s: mpii_reset_hard\n", DEVNAME(sc)); mpii_write_intr(sc, 0); /* enable diagnostic register */ mpii_write(sc, MPII_WRITESEQ, MPII_WRITESEQ_FLUSH); mpii_write(sc, MPII_WRITESEQ, MPII_WRITESEQ_1); mpii_write(sc, MPII_WRITESEQ, MPII_WRITESEQ_2); mpii_write(sc, MPII_WRITESEQ, MPII_WRITESEQ_3); mpii_write(sc, MPII_WRITESEQ, MPII_WRITESEQ_4); mpii_write(sc, MPII_WRITESEQ, MPII_WRITESEQ_5); mpii_write(sc, MPII_WRITESEQ, MPII_WRITESEQ_6); delay(100); if ((mpii_read(sc, MPII_HOSTDIAG) & MPII_HOSTDIAG_DWRE) == 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_reset_hard failure to enable " "diagnostic read/write\n", DEVNAME(sc)); return(1); } /* reset ioc */ mpii_write(sc, MPII_HOSTDIAG, MPII_HOSTDIAG_RESET_ADAPTER); /* 240 milliseconds */ delay(240000); /* XXX this whole function should be more robust */ /* XXX read the host diagnostic reg until reset adapter bit clears ? */ for (i = 0; i < 30000; i++) { if ((mpii_read(sc, MPII_HOSTDIAG) & MPII_HOSTDIAG_RESET_ADAPTER) == 0) break; delay(10000); } /* disable diagnostic register */ mpii_write(sc, MPII_WRITESEQ, 0xff); /* XXX what else? */ DNPRINTF(MPII_D_MISC, "%s: done with mpii_reset_hard\n", DEVNAME(sc)); return(0); } static int mpii_handshake_send(struct mpii_softc *sc, void *buf, size_t dwords) { u_int32_t *query = buf; int i; /* make sure the doorbell is not in use. */ if (mpii_read_db(sc) & MPII_DOORBELL_INUSE) return (1); /* clear pending doorbell interrupts */ if (mpii_read_intr(sc) & MPII_INTR_STATUS_IOC2SYSDB) mpii_write_intr(sc, 0); /* * first write the doorbell with the handshake function and the * dword count. */ mpii_write_db(sc, MPII_DOORBELL_FUNCTION(MPII_FUNCTION_HANDSHAKE) | MPII_DOORBELL_DWORDS(dwords)); /* * the doorbell used bit will be set because a doorbell function has * started. wait for the interrupt and then ack it. */ if (mpii_wait_db_int(sc) != 0) return (1); mpii_write_intr(sc, 0); /* poll for the acknowledgement. */ if (mpii_wait_db_ack(sc) != 0) return (1); /* write the query through the doorbell. */ for (i = 0; i < dwords; i++) { mpii_write_db(sc, htole32(query[i])); if (mpii_wait_db_ack(sc) != 0) return (1); } return (0); } static int mpii_handshake_recv_dword(struct mpii_softc *sc, u_int32_t *dword) { u_int16_t *words = (u_int16_t *)dword; int i; for (i = 0; i < 2; i++) { if (mpii_wait_db_int(sc) != 0) return (1); words[i] = le16toh(mpii_read_db(sc) & MPII_DOORBELL_DATA_MASK); mpii_write_intr(sc, 0); } return (0); } static int mpii_handshake_recv(struct mpii_softc *sc, void *buf, size_t dwords) { struct mpii_msg_reply *reply = buf; u_int32_t *dbuf = buf, dummy; int i; /* get the first dword so we can read the length out of the header. */ if (mpii_handshake_recv_dword(sc, &dbuf[0]) != 0) return (1); DNPRINTF(MPII_D_CMD, "%s: mpii_handshake_recv dwords: %lu reply: %d\n", DEVNAME(sc), dwords, reply->msg_length); /* * the total length, in dwords, is in the message length field of the * reply header. */ for (i = 1; i < MIN(dwords, reply->msg_length); i++) { if (mpii_handshake_recv_dword(sc, &dbuf[i]) != 0) return (1); } /* if there's extra stuff to come off the ioc, discard it */ while (i++ < reply->msg_length) { if (mpii_handshake_recv_dword(sc, &dummy) != 0) return (1); DNPRINTF(MPII_D_CMD, "%s: mpii_handshake_recv dummy read: " "0x%08x\n", DEVNAME(sc), dummy); } /* wait for the doorbell used bit to be reset and clear the intr */ if (mpii_wait_db_int(sc) != 0) return (1); if (mpii_wait_eq(sc, MPII_DOORBELL, MPII_DOORBELL_INUSE, 0) != 0) return (1); mpii_write_intr(sc, 0); return (0); } static void mpii_empty_done(struct mpii_ccb *ccb) { /* nothing to do */ } static int mpii_iocfacts(struct mpii_softc *sc) { struct mpii_msg_iocfacts_request ifq; struct mpii_msg_iocfacts_reply ifp; int irs; int sge_size; u_int qdepth; DNPRINTF(MPII_D_MISC, "%s: mpii_iocfacts\n", DEVNAME(sc)); memset(&ifq, 0, sizeof(ifq)); memset(&ifp, 0, sizeof(ifp)); ifq.function = MPII_FUNCTION_IOC_FACTS; if (mpii_handshake_send(sc, &ifq, dwordsof(ifq)) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_iocfacts send failed\n", DEVNAME(sc)); return (1); } if (mpii_handshake_recv(sc, &ifp, dwordsof(ifp)) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_iocfacts recv failed\n", DEVNAME(sc)); return (1); } sc->sc_ioc_number = ifp.ioc_number; sc->sc_vf_id = ifp.vf_id; sc->sc_max_volumes = ifp.max_volumes; sc->sc_max_devices = ifp.max_volumes + le16toh(ifp.max_targets); if (ISSET(le32toh(ifp.ioc_capabilities), MPII_IOCFACTS_CAPABILITY_INTEGRATED_RAID)) SET(sc->sc_flags, MPII_F_RAID); if (ISSET(le32toh(ifp.ioc_capabilities), MPII_IOCFACTS_CAPABILITY_EVENT_REPLAY)) sc->sc_ioc_event_replay = 1; sc->sc_max_cmds = MIN(le16toh(ifp.request_credit), MPII_REQUEST_CREDIT); /* SAS3 and 3.5 controllers have different sgl layouts */ if (ifp.msg_version_maj == 2 && ((ifp.msg_version_min == 5) || (ifp.msg_version_min == 6))) SET(sc->sc_flags, MPII_F_SAS3); /* * The host driver must ensure that there is at least one * unused entry in the Reply Free Queue. One way to ensure * that this requirement is met is to never allocate a number * of reply frames that is a multiple of 16. */ sc->sc_num_reply_frames = sc->sc_max_cmds + 32; if (!(sc->sc_num_reply_frames % 16)) sc->sc_num_reply_frames--; /* must be multiple of 16 */ sc->sc_reply_post_qdepth = sc->sc_max_cmds + sc->sc_num_reply_frames; sc->sc_reply_post_qdepth += 16 - (sc->sc_reply_post_qdepth % 16); qdepth = le16toh(ifp.max_reply_descriptor_post_queue_depth); if (sc->sc_reply_post_qdepth > qdepth) { sc->sc_reply_post_qdepth = qdepth; if (sc->sc_reply_post_qdepth < 16) { printf("%s: RDPQ is too shallow\n", DEVNAME(sc)); return (1); } sc->sc_max_cmds = sc->sc_reply_post_qdepth / 2 - 4; sc->sc_num_reply_frames = sc->sc_max_cmds + 4; } sc->sc_reply_free_qdepth = sc->sc_num_reply_frames + 16 - (sc->sc_num_reply_frames % 16); /* * Our request frame for an I/O operation looks like this: * * +-------------------+ -. * | mpii_msg_scsi_io | | * +-------------------| | * | mpii_sge | | * + - - - - - - - - - + | * | ... | > ioc_request_frame_size * + - - - - - - - - - + | * | mpii_sge (tail) | | * + - - - - - - - - - + | * | mpii_sge (csge) | | --. * + - - - - - - - - - + -' | chain sge points to the next sge * | mpii_sge |<-----' * + - - - - - - - - - + * | ... | * + - - - - - - - - - + * | mpii_sge (tail) | * +-------------------+ * | | * ~~~~~~~~~~~~~~~~~~~~~ * | | * +-------------------+ <- sc_request_size - sizeof(scsi_sense_data) * | scsi_sense_data | * +-------------------+ */ /* both sizes are in 32-bit words */ sc->sc_reply_size = ifp.reply_frame_size * 4; irs = le16toh(ifp.ioc_request_frame_size) * 4; sc->sc_request_size = MPII_REQUEST_SIZE; /* make sure we have enough space for scsi sense data */ if (irs > sc->sc_request_size) { sc->sc_request_size = irs + sizeof(struct scsi_sense_data); sc->sc_request_size += 16 - (sc->sc_request_size % 16); } if (ISSET(sc->sc_flags, MPII_F_SAS3)) { sge_size = sizeof(struct mpii_ieee_sge); } else { sge_size = sizeof(struct mpii_sge); } /* offset to the chain sge */ sc->sc_chain_sge = (irs - sizeof(struct mpii_msg_scsi_io)) / sge_size - 1; /* * A number of simple scatter-gather elements we can fit into the * request buffer after the I/O command minus the chain element. */ sc->sc_max_sgl = (sc->sc_request_size - sizeof(struct mpii_msg_scsi_io) - sizeof(struct scsi_sense_data)) / sge_size - 1; return (0); } static int mpii_iocinit(struct mpii_softc *sc) { struct mpii_msg_iocinit_request iiq; struct mpii_msg_iocinit_reply iip; DNPRINTF(MPII_D_MISC, "%s: mpii_iocinit\n", DEVNAME(sc)); memset(&iiq, 0, sizeof(iiq)); memset(&iip, 0, sizeof(iip)); iiq.function = MPII_FUNCTION_IOC_INIT; iiq.whoinit = MPII_WHOINIT_HOST_DRIVER; /* XXX JPG do something about vf_id */ iiq.vf_id = 0; iiq.msg_version_maj = 0x02; iiq.msg_version_min = 0x00; /* XXX JPG ensure compliance with some level and hard-code? */ iiq.hdr_version_unit = 0x00; iiq.hdr_version_dev = 0x00; iiq.system_request_frame_size = htole16(sc->sc_request_size / 4); iiq.reply_descriptor_post_queue_depth = htole16(sc->sc_reply_post_qdepth); iiq.reply_free_queue_depth = htole16(sc->sc_reply_free_qdepth); iiq.sense_buffer_address_high = htole32(MPII_DMA_DVA(sc->sc_requests) >> 32); iiq.system_reply_address_high = htole32(MPII_DMA_DVA(sc->sc_replies) >> 32); iiq.system_request_frame_base_address_lo = htole32(MPII_DMA_DVA(sc->sc_requests)); iiq.system_request_frame_base_address_hi = htole32(MPII_DMA_DVA(sc->sc_requests) >> 32); iiq.reply_descriptor_post_queue_address_lo = htole32(MPII_DMA_DVA(sc->sc_reply_postq)); iiq.reply_descriptor_post_queue_address_hi = htole32(MPII_DMA_DVA(sc->sc_reply_postq) >> 32); iiq.reply_free_queue_address_lo = htole32(MPII_DMA_DVA(sc->sc_reply_freeq)); iiq.reply_free_queue_address_hi = htole32(MPII_DMA_DVA(sc->sc_reply_freeq) >> 32); if (mpii_handshake_send(sc, &iiq, dwordsof(iiq)) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_iocinit send failed\n", DEVNAME(sc)); return (1); } if (mpii_handshake_recv(sc, &iip, dwordsof(iip)) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_iocinit recv failed\n", DEVNAME(sc)); return (1); } DNPRINTF(MPII_D_MISC, "%s: function: 0x%02x msg_length: %d " "whoinit: 0x%02x\n", DEVNAME(sc), iip.function, iip.msg_length, iip.whoinit); DNPRINTF(MPII_D_MISC, "%s: msg_flags: 0x%02x\n", DEVNAME(sc), iip.msg_flags); DNPRINTF(MPII_D_MISC, "%s: vf_id: 0x%02x vp_id: 0x%02x\n", DEVNAME(sc), iip.vf_id, iip.vp_id); DNPRINTF(MPII_D_MISC, "%s: ioc_status: 0x%04x\n", DEVNAME(sc), le16toh(iip.ioc_status)); DNPRINTF(MPII_D_MISC, "%s: ioc_loginfo: 0x%08x\n", DEVNAME(sc), le32toh(iip.ioc_loginfo)); if (le16toh(iip.ioc_status) != MPII_IOCSTATUS_SUCCESS || le32toh(iip.ioc_loginfo)) return (1); return (0); } static void mpii_push_reply(struct mpii_softc *sc, struct mpii_rcb *rcb) { u_int32_t *rfp; u_int idx; if (rcb == NULL) return; mutex_enter(&sc->sc_reply_free_mtx); idx = sc->sc_reply_free_host_index; rfp = MPII_DMA_KVA(sc->sc_reply_freeq); rfp[idx] = htole32(rcb->rcb_reply_dva); if (++idx >= sc->sc_reply_free_qdepth) idx = 0; mpii_write_reply_free(sc, sc->sc_reply_free_host_index = idx); mutex_exit(&sc->sc_reply_free_mtx); } static int mpii_portfacts(struct mpii_softc *sc) { struct mpii_msg_portfacts_request *pfq; struct mpii_msg_portfacts_reply *pfp; struct mpii_ccb *ccb; int rv = 1; DNPRINTF(MPII_D_MISC, "%s: mpii_portfacts\n", DEVNAME(sc)); ccb = mpii_get_ccb(sc); if (ccb == NULL) { DNPRINTF(MPII_D_MISC, "%s: mpii_portfacts mpii_get_ccb fail\n", DEVNAME(sc)); return (rv); } ccb->ccb_done = mpii_empty_done; pfq = ccb->ccb_cmd; memset(pfq, 0, sizeof(*pfq)); pfq->function = MPII_FUNCTION_PORT_FACTS; pfq->chain_offset = 0; pfq->msg_flags = 0; pfq->port_number = 0; pfq->vp_id = 0; pfq->vf_id = 0; if (mpii_poll(sc, ccb) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_portfacts poll\n", DEVNAME(sc)); goto err; } if (ccb->ccb_rcb == NULL) { DNPRINTF(MPII_D_MISC, "%s: empty portfacts reply\n", DEVNAME(sc)); goto err; } pfp = ccb->ccb_rcb->rcb_reply; sc->sc_porttype = pfp->port_type; mpii_push_reply(sc, ccb->ccb_rcb); rv = 0; err: mpii_put_ccb(sc, ccb); return (rv); } static void mpii_eventack(struct work *wk, void * cookie) { struct mpii_softc *sc = cookie; struct mpii_ccb *ccb; struct mpii_rcb *rcb, *next; struct mpii_msg_event_reply *enp; struct mpii_msg_eventack_request *eaq; mutex_enter(&sc->sc_evt_ack_mtx); next = SIMPLEQ_FIRST(&sc->sc_evt_ack_queue); SIMPLEQ_INIT(&sc->sc_evt_ack_queue); mutex_exit(&sc->sc_evt_ack_mtx); while (next != NULL) { rcb = next; next = SIMPLEQ_NEXT(rcb, rcb_link); enp = (struct mpii_msg_event_reply *)rcb->rcb_reply; ccb = mpii_get_ccb(sc); ccb->ccb_done = mpii_eventack_done; eaq = ccb->ccb_cmd; eaq->function = MPII_FUNCTION_EVENT_ACK; eaq->event = enp->event; eaq->event_context = enp->event_context; mpii_push_reply(sc, rcb); mpii_start(sc, ccb); } } static void mpii_eventack_done(struct mpii_ccb *ccb) { struct mpii_softc *sc = ccb->ccb_sc; DNPRINTF(MPII_D_EVT, "%s: event ack done\n", DEVNAME(sc)); mpii_push_reply(sc, ccb->ccb_rcb); mpii_put_ccb(sc, ccb); } static int mpii_portenable(struct mpii_softc *sc) { struct mpii_msg_portenable_request *peq; struct mpii_ccb *ccb; DNPRINTF(MPII_D_MISC, "%s: mpii_portenable\n", DEVNAME(sc)); ccb = mpii_get_ccb(sc); if (ccb == NULL) { DNPRINTF(MPII_D_MISC, "%s: mpii_portenable ccb_get\n", DEVNAME(sc)); return (1); } ccb->ccb_done = mpii_empty_done; peq = ccb->ccb_cmd; peq->function = MPII_FUNCTION_PORT_ENABLE; peq->vf_id = sc->sc_vf_id; if (mpii_poll(sc, ccb) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_portenable poll\n", DEVNAME(sc)); return (1); } if (ccb->ccb_rcb == NULL) { DNPRINTF(MPII_D_MISC, "%s: empty portenable reply\n", DEVNAME(sc)); return (1); } mpii_push_reply(sc, ccb->ccb_rcb); mpii_put_ccb(sc, ccb); return (0); } static int mpii_cfg_coalescing(struct mpii_softc *sc) { struct mpii_cfg_hdr hdr; struct mpii_cfg_ioc_pg1 ipg; hdr.page_version = 0; hdr.page_length = sizeof(ipg) / 4; hdr.page_number = 1; hdr.page_type = MPII_CONFIG_REQ_PAGE_TYPE_IOC; memset(&ipg, 0, sizeof(ipg)); if (mpii_req_cfg_page(sc, 0, MPII_PG_POLL, &hdr, 1, &ipg, sizeof(ipg)) != 0) { DNPRINTF(MPII_D_MISC, "%s: unable to fetch IOC page 1\n" "page 1\n", DEVNAME(sc)); return (1); } if (!ISSET(le32toh(ipg.flags), MPII_CFG_IOC_1_REPLY_COALESCING)) return (0); /* Disable coalescing */ CLR(ipg.flags, htole32(MPII_CFG_IOC_1_REPLY_COALESCING)); if (mpii_req_cfg_page(sc, 0, MPII_PG_POLL, &hdr, 0, &ipg, sizeof(ipg)) != 0) { DNPRINTF(MPII_D_MISC, "%s: unable to clear coalescing\n", DEVNAME(sc)); return (1); } return (0); } #define MPII_EVENT_MASKALL(enq) do { \ enq->event_masks[0] = 0xffffffff; \ enq->event_masks[1] = 0xffffffff; \ enq->event_masks[2] = 0xffffffff; \ enq->event_masks[3] = 0xffffffff; \ } while (0) #define MPII_EVENT_UNMASK(enq, evt) do { \ enq->event_masks[evt / 32] &= \ htole32(~(1 << (evt % 32))); \ } while (0) static int mpii_eventnotify(struct mpii_softc *sc) { struct mpii_msg_event_request *enq; struct mpii_ccb *ccb; char wkname[15]; ccb = mpii_get_ccb(sc); if (ccb == NULL) { DNPRINTF(MPII_D_MISC, "%s: mpii_eventnotify ccb_get\n", DEVNAME(sc)); return (1); } SIMPLEQ_INIT(&sc->sc_evt_sas_queue); mutex_init(&sc->sc_evt_sas_mtx, MUTEX_DEFAULT, IPL_BIO); snprintf(wkname, sizeof(wkname), "%ssas", DEVNAME(sc)); if (workqueue_create(&sc->sc_evt_sas_wq, wkname, mpii_event_sas_work, sc, PRI_NONE, IPL_BIO, WQ_MPSAFE) != 0) { mpii_put_ccb(sc, ccb); aprint_error_dev(sc->sc_dev, "can't create %s workqueue\n", wkname); return 1; } SIMPLEQ_INIT(&sc->sc_evt_ack_queue); mutex_init(&sc->sc_evt_ack_mtx, MUTEX_DEFAULT, IPL_BIO); snprintf(wkname, sizeof(wkname), "%sevt", DEVNAME(sc)); if (workqueue_create(&sc->sc_evt_ack_wq, wkname, mpii_eventack, sc, PRI_NONE, IPL_BIO, WQ_MPSAFE) != 0) { mpii_put_ccb(sc, ccb); aprint_error_dev(sc->sc_dev, "can't create %s workqueue\n", wkname); return 1; } ccb->ccb_done = mpii_eventnotify_done; enq = ccb->ccb_cmd; enq->function = MPII_FUNCTION_EVENT_NOTIFICATION; /* * Enable reporting of the following events: * * MPII_EVENT_SAS_DISCOVERY * MPII_EVENT_SAS_TOPOLOGY_CHANGE_LIST * MPII_EVENT_SAS_DEVICE_STATUS_CHANGE * MPII_EVENT_SAS_ENCL_DEVICE_STATUS_CHANGE * MPII_EVENT_IR_CONFIGURATION_CHANGE_LIST * MPII_EVENT_IR_VOLUME * MPII_EVENT_IR_PHYSICAL_DISK * MPII_EVENT_IR_OPERATION_STATUS */ MPII_EVENT_MASKALL(enq); MPII_EVENT_UNMASK(enq, MPII_EVENT_SAS_DISCOVERY); MPII_EVENT_UNMASK(enq, MPII_EVENT_SAS_TOPOLOGY_CHANGE_LIST); MPII_EVENT_UNMASK(enq, MPII_EVENT_SAS_DEVICE_STATUS_CHANGE); MPII_EVENT_UNMASK(enq, MPII_EVENT_SAS_ENCL_DEVICE_STATUS_CHANGE); MPII_EVENT_UNMASK(enq, MPII_EVENT_IR_CONFIGURATION_CHANGE_LIST); MPII_EVENT_UNMASK(enq, MPII_EVENT_IR_VOLUME); MPII_EVENT_UNMASK(enq, MPII_EVENT_IR_PHYSICAL_DISK); MPII_EVENT_UNMASK(enq, MPII_EVENT_IR_OPERATION_STATUS); mpii_start(sc, ccb); return (0); } static void mpii_eventnotify_done(struct mpii_ccb *ccb) { struct mpii_softc *sc = ccb->ccb_sc; struct mpii_rcb *rcb = ccb->ccb_rcb; DNPRINTF(MPII_D_EVT, "%s: mpii_eventnotify_done\n", DEVNAME(sc)); mpii_put_ccb(sc, ccb); mpii_event_process(sc, rcb); } static void mpii_event_raid(struct mpii_softc *sc, struct mpii_msg_event_reply *enp) { struct mpii_evt_ir_cfg_change_list *ccl; struct mpii_evt_ir_cfg_element *ce; struct mpii_device *dev; u_int16_t type; int i; ccl = (struct mpii_evt_ir_cfg_change_list *)(enp + 1); if (ccl->num_elements == 0) return; if (ISSET(le32toh(ccl->flags), MPII_EVT_IR_CFG_CHANGE_LIST_FOREIGN)) { /* bail on foreign configurations */ return; } ce = (struct mpii_evt_ir_cfg_element *)(ccl + 1); for (i = 0; i < ccl->num_elements; i++, ce++) { type = (le16toh(ce->element_flags) & MPII_EVT_IR_CFG_ELEMENT_TYPE_MASK); switch (type) { case MPII_EVT_IR_CFG_ELEMENT_TYPE_VOLUME: switch (ce->reason_code) { case MPII_EVT_IR_CFG_ELEMENT_RC_ADDED: case MPII_EVT_IR_CFG_ELEMENT_RC_VOLUME_CREATED: dev = malloc(sizeof(*dev), M_DEVBUF, M_WAITOK | M_ZERO); mutex_enter(&sc->sc_devs_mtx); if (mpii_find_dev(sc, le16toh(ce->vol_dev_handle))) { mutex_exit(&sc->sc_devs_mtx); free(dev, M_DEVBUF); printf("%s: device %#x is already " "configured\n", DEVNAME(sc), le16toh(ce->vol_dev_handle)); break; } SET(dev->flags, MPII_DF_VOLUME); dev->slot = sc->sc_vd_id_low; dev->dev_handle = le16toh(ce->vol_dev_handle); if (mpii_insert_dev(sc, dev)) { mutex_exit(&sc->sc_devs_mtx); free(dev, M_DEVBUF); break; } sc->sc_vd_count++; mutex_exit(&sc->sc_devs_mtx); break; case MPII_EVT_IR_CFG_ELEMENT_RC_REMOVED: case MPII_EVT_IR_CFG_ELEMENT_RC_VOLUME_DELETED: mutex_enter(&sc->sc_devs_mtx); if (!(dev = mpii_find_dev(sc, le16toh(ce->vol_dev_handle)))) { mutex_exit(&sc->sc_devs_mtx); break; } mpii_remove_dev(sc, dev); sc->sc_vd_count--; mutex_exit(&sc->sc_devs_mtx); break; } break; case MPII_EVT_IR_CFG_ELEMENT_TYPE_VOLUME_DISK: if (ce->reason_code == MPII_EVT_IR_CFG_ELEMENT_RC_PD_CREATED || ce->reason_code == MPII_EVT_IR_CFG_ELEMENT_RC_HIDE) { /* there should be an underlying sas drive */ mutex_enter(&sc->sc_devs_mtx); if (!(dev = mpii_find_dev(sc, le16toh(ce->phys_disk_dev_handle)))) { mutex_exit(&sc->sc_devs_mtx); break; } /* promoted from a hot spare? */ CLR(dev->flags, MPII_DF_HOT_SPARE); SET(dev->flags, MPII_DF_VOLUME_DISK | MPII_DF_HIDDEN); mutex_exit(&sc->sc_devs_mtx); } break; case MPII_EVT_IR_CFG_ELEMENT_TYPE_HOT_SPARE: if (ce->reason_code == MPII_EVT_IR_CFG_ELEMENT_RC_HIDE) { /* there should be an underlying sas drive */ mutex_enter(&sc->sc_devs_mtx); if (!(dev = mpii_find_dev(sc, le16toh(ce->phys_disk_dev_handle)))) { mutex_exit(&sc->sc_devs_mtx); break; } SET(dev->flags, MPII_DF_HOT_SPARE | MPII_DF_HIDDEN); mutex_exit(&sc->sc_devs_mtx); } break; } } } static void mpii_event_sas(struct mpii_softc *sc, struct mpii_rcb *rcb) { struct mpii_msg_event_reply *enp; struct mpii_evt_sas_tcl *tcl; struct mpii_evt_phy_entry *pe; struct mpii_device *dev; int i; u_int16_t handle; int need_queue = 0; enp = (struct mpii_msg_event_reply *)rcb->rcb_reply; DNPRINTF(MPII_D_EVT, "%s: mpii_event_sas 0x%x\n", DEVNAME(sc), le16toh(enp->event)); KASSERT(le16toh(enp->event) == MPII_EVENT_SAS_TOPOLOGY_CHANGE_LIST); tcl = (struct mpii_evt_sas_tcl *)(enp + 1); pe = (struct mpii_evt_phy_entry *)(tcl + 1); for (i = 0; i < tcl->num_entries; i++, pe++) { DNPRINTF(MPII_D_EVT, "%s: sas change %d stat %d h %d slot %d phy %d enc %d expand %d\n", DEVNAME(sc), i, pe->phy_status, le16toh(pe->dev_handle), sc->sc_pd_id_start + tcl->start_phy_num + i, tcl->start_phy_num + i, le16toh(tcl->enclosure_handle), le16toh(tcl->expander_handle)); switch (pe->phy_status & MPII_EVENT_SAS_TOPO_PS_RC_MASK) { case MPII_EVENT_SAS_TOPO_PS_RC_ADDED: handle = le16toh(pe->dev_handle); DNPRINTF(MPII_D_EVT, "%s: sas add handle %d\n", DEVNAME(sc), handle); dev = malloc(sizeof(*dev), M_DEVBUF, M_WAITOK | M_ZERO); mutex_enter(&sc->sc_devs_mtx); if (mpii_find_dev(sc, handle)) { mutex_exit(&sc->sc_devs_mtx); free(dev, M_DEVBUF); printf("%s: device %#x is already " "configured\n", DEVNAME(sc), handle); break; } dev->slot = sc->sc_pd_id_start + tcl->start_phy_num + i; dev->dev_handle = handle; dev->phy_num = tcl->start_phy_num + i; if (tcl->enclosure_handle) dev->physical_port = tcl->physical_port; dev->enclosure = le16toh(tcl->enclosure_handle); dev->expander = le16toh(tcl->expander_handle); if (mpii_insert_dev(sc, dev)) { mutex_exit(&sc->sc_devs_mtx); free(dev, M_DEVBUF); break; } printf("%s: physical device inserted in slot %d\n", DEVNAME(sc), dev->slot); mutex_exit(&sc->sc_devs_mtx); break; case MPII_EVENT_SAS_TOPO_PS_RC_MISSING: /* defer to workqueue thread */ need_queue++; break; } } if (need_queue) { bool start_wk; mutex_enter(&sc->sc_evt_sas_mtx); start_wk = (SIMPLEQ_FIRST(&sc->sc_evt_sas_queue) == 0); SIMPLEQ_INSERT_TAIL(&sc->sc_evt_sas_queue, rcb, rcb_link); if (start_wk) { workqueue_enqueue(sc->sc_evt_sas_wq, &sc->sc_evt_sas_work, NULL); } mutex_exit(&sc->sc_evt_sas_mtx); } else mpii_event_done(sc, rcb); } static void mpii_event_sas_work(struct work *wq, void *xsc) { struct mpii_softc *sc = xsc; struct mpii_rcb *rcb, *next; struct mpii_msg_event_reply *enp; struct mpii_evt_sas_tcl *tcl; struct mpii_evt_phy_entry *pe; struct mpii_device *dev; int i; mutex_enter(&sc->sc_evt_sas_mtx); next = SIMPLEQ_FIRST(&sc->sc_evt_sas_queue); SIMPLEQ_INIT(&sc->sc_evt_sas_queue); mutex_exit(&sc->sc_evt_sas_mtx); while (next != NULL) { rcb = next; next = SIMPLEQ_NEXT(rcb, rcb_link); enp = (struct mpii_msg_event_reply *)rcb->rcb_reply; DNPRINTF(MPII_D_EVT, "%s: mpii_event_sas_work 0x%x\n", DEVNAME(sc), le16toh(enp->event)); KASSERT(le16toh(enp->event) == MPII_EVENT_SAS_TOPOLOGY_CHANGE_LIST); tcl = (struct mpii_evt_sas_tcl *)(enp + 1); pe = (struct mpii_evt_phy_entry *)(tcl + 1); for (i = 0; i < tcl->num_entries; i++, pe++) { DNPRINTF(MPII_D_EVT, "%s: sas change %d stat %d h %d slot %d phy %d enc %d expand %d\n", DEVNAME(sc), i, pe->phy_status, le16toh(pe->dev_handle), sc->sc_pd_id_start + tcl->start_phy_num + i, tcl->start_phy_num + i, le16toh(tcl->enclosure_handle), le16toh(tcl->expander_handle)); switch (pe->phy_status & MPII_EVENT_SAS_TOPO_PS_RC_MASK) { case MPII_EVENT_SAS_TOPO_PS_RC_ADDED: /* already handled */ break; case MPII_EVENT_SAS_TOPO_PS_RC_MISSING: mutex_enter(&sc->sc_devs_mtx); dev = mpii_find_dev(sc, le16toh(pe->dev_handle)); if (dev == NULL) { mutex_exit(&sc->sc_devs_mtx); break; } printf( "%s: physical device removed from slot %d\n", DEVNAME(sc), dev->slot); mpii_remove_dev(sc, dev); mutex_exit(&sc->sc_devs_mtx); mpii_sas_remove_device(sc, dev->dev_handle); if (!ISSET(dev->flags, MPII_DF_HIDDEN)) { scsipi_target_detach(&sc->sc_chan, dev->slot, 0, DETACH_FORCE); } free(dev, M_DEVBUF); break; } } mpii_event_done(sc, rcb); } } static void mpii_event_discovery(struct mpii_softc *sc, struct mpii_msg_event_reply *enp) { struct mpii_evt_sas_discovery *esd = (struct mpii_evt_sas_discovery *)(enp + 1); if (esd->reason_code == MPII_EVENT_SAS_DISC_REASON_CODE_COMPLETED) { if (esd->discovery_status != 0) { printf("%s: sas discovery completed with status %#x\n", DEVNAME(sc), esd->discovery_status); } } } static void mpii_event_process(struct mpii_softc *sc, struct mpii_rcb *rcb) { struct mpii_msg_event_reply *enp; enp = (struct mpii_msg_event_reply *)rcb->rcb_reply; DNPRINTF(MPII_D_EVT, "%s: mpii_event_process: %#x\n", DEVNAME(sc), le16toh(enp->event)); switch (le16toh(enp->event)) { case MPII_EVENT_EVENT_CHANGE: /* should be properly ignored */ break; case MPII_EVENT_SAS_DISCOVERY: mpii_event_discovery(sc, enp); break; case MPII_EVENT_SAS_TOPOLOGY_CHANGE_LIST: mpii_event_sas(sc, rcb); return; case MPII_EVENT_SAS_DEVICE_STATUS_CHANGE: break; case MPII_EVENT_SAS_ENCL_DEVICE_STATUS_CHANGE: break; case MPII_EVENT_IR_VOLUME: { struct mpii_evt_ir_volume *evd = (struct mpii_evt_ir_volume *)(enp + 1); struct mpii_device *dev; #if NBIO > 0 const char *vol_states[] = { BIOC_SVINVALID_S, BIOC_SVOFFLINE_S, BIOC_SVBUILDING_S, BIOC_SVONLINE_S, BIOC_SVDEGRADED_S, BIOC_SVONLINE_S, }; #endif if (cold) break; mutex_enter(&sc->sc_devs_mtx); dev = mpii_find_dev(sc, le16toh(evd->vol_dev_handle)); if (dev == NULL) { mutex_exit(&sc->sc_devs_mtx); break; } #if NBIO > 0 if (evd->reason_code == MPII_EVENT_IR_VOL_RC_STATE_CHANGED) printf("%s: volume %d state changed from %s to %s\n", DEVNAME(sc), dev->slot - sc->sc_vd_id_low, vol_states[evd->prev_value], vol_states[evd->new_value]); #endif if (evd->reason_code == MPII_EVENT_IR_VOL_RC_STATUS_CHANGED && ISSET(evd->new_value, MPII_CFG_RAID_VOL_0_STATUS_RESYNC) && !ISSET(evd->prev_value, MPII_CFG_RAID_VOL_0_STATUS_RESYNC)) printf("%s: started resync on a volume %d\n", DEVNAME(sc), dev->slot - sc->sc_vd_id_low); } mutex_exit(&sc->sc_devs_mtx); break; case MPII_EVENT_IR_PHYSICAL_DISK: break; case MPII_EVENT_IR_CONFIGURATION_CHANGE_LIST: mpii_event_raid(sc, enp); break; case MPII_EVENT_IR_OPERATION_STATUS: { struct mpii_evt_ir_status *evs = (struct mpii_evt_ir_status *)(enp + 1); struct mpii_device *dev; mutex_enter(&sc->sc_devs_mtx); dev = mpii_find_dev(sc, le16toh(evs->vol_dev_handle)); if (dev != NULL && evs->operation == MPII_EVENT_IR_RAIDOP_RESYNC) dev->percent = evs->percent; mutex_exit(&sc->sc_devs_mtx); break; } default: DNPRINTF(MPII_D_EVT, "%s: unhandled event 0x%02x\n", DEVNAME(sc), le16toh(enp->event)); } mpii_event_done(sc, rcb); } static void mpii_event_done(struct mpii_softc *sc, struct mpii_rcb *rcb) { struct mpii_msg_event_reply *enp = rcb->rcb_reply; bool need_start; if (enp->ack_required) { mutex_enter(&sc->sc_evt_ack_mtx); need_start = (SIMPLEQ_FIRST(&sc->sc_evt_ack_queue) == 0); SIMPLEQ_INSERT_TAIL(&sc->sc_evt_ack_queue, rcb, rcb_link); if (need_start) workqueue_enqueue(sc->sc_evt_ack_wq, &sc->sc_evt_ack_work, NULL); mutex_exit(&sc->sc_evt_ack_mtx); } else mpii_push_reply(sc, rcb); } static void mpii_sas_remove_device(struct mpii_softc *sc, u_int16_t handle) { struct mpii_msg_scsi_task_request *stq; struct mpii_msg_sas_oper_request *soq; struct mpii_ccb *ccb; ccb = mpii_get_ccb(sc); if (ccb == NULL) return; stq = ccb->ccb_cmd; stq->function = MPII_FUNCTION_SCSI_TASK_MGMT; stq->task_type = MPII_SCSI_TASK_TARGET_RESET; stq->dev_handle = htole16(handle); ccb->ccb_done = mpii_empty_done; mpii_wait(sc, ccb); if (ccb->ccb_rcb != NULL) mpii_push_reply(sc, ccb->ccb_rcb); /* reuse a ccb */ ccb->ccb_state = MPII_CCB_READY; ccb->ccb_rcb = NULL; soq = ccb->ccb_cmd; memset(soq, 0, sizeof(*soq)); soq->function = MPII_FUNCTION_SAS_IO_UNIT_CONTROL; soq->operation = MPII_SAS_OP_REMOVE_DEVICE; soq->dev_handle = htole16(handle); ccb->ccb_done = mpii_empty_done; mpii_wait(sc, ccb); if (ccb->ccb_rcb != NULL) mpii_push_reply(sc, ccb->ccb_rcb); mpii_put_ccb(sc, ccb); } static int mpii_board_info(struct mpii_softc *sc) { struct mpii_msg_iocfacts_request ifq; struct mpii_msg_iocfacts_reply ifp; struct mpii_cfg_manufacturing_pg0 mpg; struct mpii_cfg_hdr hdr; memset(&ifq, 0, sizeof(ifq)); memset(&ifp, 0, sizeof(ifp)); ifq.function = MPII_FUNCTION_IOC_FACTS; if (mpii_handshake_send(sc, &ifq, dwordsof(ifq)) != 0) { DNPRINTF(MPII_D_MISC, "%s: failed to request ioc facts\n", DEVNAME(sc)); return (1); } if (mpii_handshake_recv(sc, &ifp, dwordsof(ifp)) != 0) { DNPRINTF(MPII_D_MISC, "%s: failed to receive ioc facts\n", DEVNAME(sc)); return (1); } hdr.page_version = 0; hdr.page_length = sizeof(mpg) / 4; hdr.page_number = 0; hdr.page_type = MPII_CONFIG_REQ_PAGE_TYPE_MANUFACTURING; memset(&mpg, 0, sizeof(mpg)); if (mpii_req_cfg_page(sc, 0, MPII_PG_POLL, &hdr, 1, &mpg, sizeof(mpg)) != 0) { printf("%s: unable to fetch manufacturing page 0\n", DEVNAME(sc)); return (EINVAL); } printf("%s: %s, firmware %u.%u.%u.%u%s, MPI %u.%u\n", DEVNAME(sc), mpg.board_name, ifp.fw_version_maj, ifp.fw_version_min, ifp.fw_version_unit, ifp.fw_version_dev, ISSET(sc->sc_flags, MPII_F_RAID) ? " IR" : "", ifp.msg_version_maj, ifp.msg_version_min); return (0); } static int mpii_target_map(struct mpii_softc *sc) { struct mpii_cfg_hdr hdr; struct mpii_cfg_ioc_pg8 ipg; int flags, pad = 0; hdr.page_version = 0; hdr.page_length = sizeof(ipg) / 4; hdr.page_number = 8; hdr.page_type = MPII_CONFIG_REQ_PAGE_TYPE_IOC; memset(&ipg, 0, sizeof(ipg)); if (mpii_req_cfg_page(sc, 0, MPII_PG_POLL, &hdr, 1, &ipg, sizeof(ipg)) != 0) { printf("%s: unable to fetch ioc page 8\n", DEVNAME(sc)); return (EINVAL); } if (le16toh(ipg.flags) & MPII_IOC_PG8_FLAGS_RESERVED_TARGETID_0) pad = 1; flags = le16toh(ipg.ir_volume_mapping_flags) & MPII_IOC_PG8_IRFLAGS_VOLUME_MAPPING_MODE_MASK; if (ISSET(sc->sc_flags, MPII_F_RAID)) { if (flags == MPII_IOC_PG8_IRFLAGS_LOW_VOLUME_MAPPING) { sc->sc_vd_id_low += pad; pad = sc->sc_max_volumes; /* for sc_pd_id_start */ } else sc->sc_vd_id_low = sc->sc_max_devices - sc->sc_max_volumes; } sc->sc_pd_id_start += pad; return (0); } static int mpii_req_cfg_header(struct mpii_softc *sc, u_int8_t type, u_int8_t number, u_int32_t address, int flags, void *p) { struct mpii_msg_config_request *cq; struct mpii_msg_config_reply *cp; struct mpii_ccb *ccb; struct mpii_cfg_hdr *hdr = p; struct mpii_ecfg_hdr *ehdr = p; int etype = 0; int rv = 0; DNPRINTF(MPII_D_MISC, "%s: mpii_req_cfg_header type: %#x number: %x " "address: 0x%08x flags: 0x%x\n", DEVNAME(sc), type, number, address, flags); ccb = mpii_get_ccb(sc); if (ccb == NULL) { DNPRINTF(MPII_D_MISC, "%s: mpii_cfg_header ccb_get\n", DEVNAME(sc)); return (1); } if (ISSET(flags, MPII_PG_EXTENDED)) { etype = type; type = MPII_CONFIG_REQ_PAGE_TYPE_EXTENDED; } cq = ccb->ccb_cmd; cq->function = MPII_FUNCTION_CONFIG; cq->action = MPII_CONFIG_REQ_ACTION_PAGE_HEADER; cq->config_header.page_number = number; cq->config_header.page_type = type; cq->ext_page_type = etype; cq->page_address = htole32(address); cq->page_buffer.sg_hdr = htole32(MPII_SGE_FL_TYPE_SIMPLE | MPII_SGE_FL_LAST | MPII_SGE_FL_EOB | MPII_SGE_FL_EOL); ccb->ccb_done = mpii_empty_done; if (ISSET(flags, MPII_PG_POLL)) { if (mpii_poll(sc, ccb) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_cfg_header poll\n", DEVNAME(sc)); return (1); } } else mpii_wait(sc, ccb); if (ccb->ccb_rcb == NULL) { mpii_put_ccb(sc, ccb); return (1); } cp = ccb->ccb_rcb->rcb_reply; DNPRINTF(MPII_D_MISC, "%s: action: 0x%02x sgl_flags: 0x%02x " "msg_length: %d function: 0x%02x\n", DEVNAME(sc), cp->action, cp->sgl_flags, cp->msg_length, cp->function); DNPRINTF(MPII_D_MISC, "%s: ext_page_length: %d ext_page_type: 0x%02x " "msg_flags: 0x%02x\n", DEVNAME(sc), le16toh(cp->ext_page_length), cp->ext_page_type, cp->msg_flags); DNPRINTF(MPII_D_MISC, "%s: vp_id: 0x%02x vf_id: 0x%02x\n", DEVNAME(sc), cp->vp_id, cp->vf_id); DNPRINTF(MPII_D_MISC, "%s: ioc_status: 0x%04x\n", DEVNAME(sc), le16toh(cp->ioc_status)); DNPRINTF(MPII_D_MISC, "%s: ioc_loginfo: 0x%08x\n", DEVNAME(sc), le32toh(cp->ioc_loginfo)); DNPRINTF(MPII_D_MISC, "%s: page_version: 0x%02x page_length: %d " "page_number: 0x%02x page_type: 0x%02x\n", DEVNAME(sc), cp->config_header.page_version, cp->config_header.page_length, cp->config_header.page_number, cp->config_header.page_type); if (le16toh(cp->ioc_status) != MPII_IOCSTATUS_SUCCESS) rv = 1; else if (ISSET(flags, MPII_PG_EXTENDED)) { memset(ehdr, 0, sizeof(*ehdr)); ehdr->page_version = cp->config_header.page_version; ehdr->page_number = cp->config_header.page_number; ehdr->page_type = cp->config_header.page_type; ehdr->ext_page_length = cp->ext_page_length; ehdr->ext_page_type = cp->ext_page_type; } else *hdr = cp->config_header; mpii_push_reply(sc, ccb->ccb_rcb); mpii_put_ccb(sc, ccb); return (rv); } static int mpii_req_cfg_page(struct mpii_softc *sc, u_int32_t address, int flags, void *p, int read, void *page, size_t len) { struct mpii_msg_config_request *cq; struct mpii_msg_config_reply *cp; struct mpii_ccb *ccb; struct mpii_cfg_hdr *hdr = p; struct mpii_ecfg_hdr *ehdr = p; uintptr_t kva; int page_length; int rv = 0; DNPRINTF(MPII_D_MISC, "%s: mpii_cfg_page address: %d read: %d " "type: %x\n", DEVNAME(sc), address, read, hdr->page_type); page_length = ISSET(flags, MPII_PG_EXTENDED) ? le16toh(ehdr->ext_page_length) : hdr->page_length; if (len > sc->sc_request_size - sizeof(*cq) || len < page_length * 4) return (1); ccb = mpii_get_ccb(sc); if (ccb == NULL) { DNPRINTF(MPII_D_MISC, "%s: mpii_cfg_page ccb_get\n", DEVNAME(sc)); return (1); } cq = ccb->ccb_cmd; cq->function = MPII_FUNCTION_CONFIG; cq->action = (read ? MPII_CONFIG_REQ_ACTION_PAGE_READ_CURRENT : MPII_CONFIG_REQ_ACTION_PAGE_WRITE_CURRENT); if (ISSET(flags, MPII_PG_EXTENDED)) { cq->config_header.page_version = ehdr->page_version; cq->config_header.page_number = ehdr->page_number; cq->config_header.page_type = ehdr->page_type; cq->ext_page_len = ehdr->ext_page_length; cq->ext_page_type = ehdr->ext_page_type; } else cq->config_header = *hdr; cq->config_header.page_type &= MPII_CONFIG_REQ_PAGE_TYPE_MASK; cq->page_address = htole32(address); cq->page_buffer.sg_hdr = htole32(MPII_SGE_FL_TYPE_SIMPLE | MPII_SGE_FL_LAST | MPII_SGE_FL_EOB | MPII_SGE_FL_EOL | MPII_SGE_FL_SIZE_64 | (page_length * 4) | (read ? MPII_SGE_FL_DIR_IN : MPII_SGE_FL_DIR_OUT)); /* bounce the page via the request space to avoid more bus_dma games */ mpii_dvatosge(&cq->page_buffer, ccb->ccb_cmd_dva + sizeof(struct mpii_msg_config_request)); kva = (uintptr_t)ccb->ccb_cmd; kva += sizeof(struct mpii_msg_config_request); if (!read) memcpy((void *)kva, page, len); ccb->ccb_done = mpii_empty_done; if (ISSET(flags, MPII_PG_POLL)) { if (mpii_poll(sc, ccb) != 0) { DNPRINTF(MPII_D_MISC, "%s: mpii_cfg_header poll\n", DEVNAME(sc)); return (1); } } else mpii_wait(sc, ccb); if (ccb->ccb_rcb == NULL) { mpii_put_ccb(sc, ccb); return (1); } cp = ccb->ccb_rcb->rcb_reply; DNPRINTF(MPII_D_MISC, "%s: action: 0x%02x msg_length: %d " "function: 0x%02x\n", DEVNAME(sc), cp->action, cp->msg_length, cp->function); DNPRINTF(MPII_D_MISC, "%s: ext_page_length: %d ext_page_type: 0x%02x " "msg_flags: 0x%02x\n", DEVNAME(sc), le16toh(cp->ext_page_length), cp->ext_page_type, cp->msg_flags); DNPRINTF(MPII_D_MISC, "%s: vp_id: 0x%02x vf_id: 0x%02x\n", DEVNAME(sc), cp->vp_id, cp->vf_id); DNPRINTF(MPII_D_MISC, "%s: ioc_status: 0x%04x\n", DEVNAME(sc), le16toh(cp->ioc_status)); DNPRINTF(MPII_D_MISC, "%s: ioc_loginfo: 0x%08x\n", DEVNAME(sc), le32toh(cp->ioc_loginfo)); DNPRINTF(MPII_D_MISC, "%s: page_version: 0x%02x page_length: %d " "page_number: 0x%02x page_type: 0x%02x\n", DEVNAME(sc), cp->config_header.page_version, cp->config_header.page_length, cp->config_header.page_number, cp->config_header.page_type); if (le16toh(cp->ioc_status) != MPII_IOCSTATUS_SUCCESS) rv = 1; else if (read) memcpy(page, (void *)kva, len); mpii_push_reply(sc, ccb->ccb_rcb); mpii_put_ccb(sc, ccb); return (rv); } static struct mpii_rcb * mpii_reply(struct mpii_softc *sc, struct mpii_reply_descr *rdp) { struct mpii_rcb *rcb = NULL; u_int32_t rfid; KASSERT(mutex_owned(&sc->sc_rep_mtx)); DNPRINTF(MPII_D_INTR, "%s: mpii_reply\n", DEVNAME(sc)); if ((rdp->reply_flags & MPII_REPLY_DESCR_TYPE_MASK) == MPII_REPLY_DESCR_ADDRESS_REPLY) { rfid = (le32toh(rdp->frame_addr) - (u_int32_t)MPII_DMA_DVA(sc->sc_replies)) / sc->sc_reply_size; bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_replies), sc->sc_reply_size * rfid, sc->sc_reply_size, BUS_DMASYNC_POSTREAD); rcb = &sc->sc_rcbs[rfid]; } memset(rdp, 0xff, sizeof(*rdp)); bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_reply_postq), 8 * sc->sc_reply_post_host_index, 8, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); return (rcb); } static struct mpii_dmamem * mpii_dmamem_alloc(struct mpii_softc *sc, size_t size) { struct mpii_dmamem *mdm; int nsegs; mdm = malloc(sizeof(*mdm), M_DEVBUF, M_WAITOK | M_ZERO); mdm->mdm_size = size; if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &mdm->mdm_map) != 0) goto mdmfree; if (bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &mdm->mdm_seg, 1, &nsegs, BUS_DMA_NOWAIT) != 0) goto destroy; if (bus_dmamem_map(sc->sc_dmat, &mdm->mdm_seg, nsegs, size, &mdm->mdm_kva, BUS_DMA_NOWAIT) != 0) goto free; if (bus_dmamap_load(sc->sc_dmat, mdm->mdm_map, mdm->mdm_kva, size, NULL, BUS_DMA_NOWAIT) != 0) goto unmap; memset(mdm->mdm_kva, 0, size); return (mdm); unmap: bus_dmamem_unmap(sc->sc_dmat, mdm->mdm_kva, size); free: bus_dmamem_free(sc->sc_dmat, &mdm->mdm_seg, 1); destroy: bus_dmamap_destroy(sc->sc_dmat, mdm->mdm_map); mdmfree: free(mdm, M_DEVBUF); return (NULL); } static void mpii_dmamem_free(struct mpii_softc *sc, struct mpii_dmamem *mdm) { DNPRINTF(MPII_D_MEM, "%s: mpii_dmamem_free %p\n", DEVNAME(sc), mdm); bus_dmamap_unload(sc->sc_dmat, mdm->mdm_map); bus_dmamem_unmap(sc->sc_dmat, mdm->mdm_kva, mdm->mdm_size); bus_dmamem_free(sc->sc_dmat, &mdm->mdm_seg, 1); bus_dmamap_destroy(sc->sc_dmat, mdm->mdm_map); free(mdm, M_DEVBUF); } static int mpii_insert_dev(struct mpii_softc *sc, struct mpii_device *dev) { int slot; /* initial hint */ KASSERT(mutex_owned(&sc->sc_devs_mtx)); DNPRINTF(MPII_D_EVT, "%s: mpii_insert_dev wants slot %d\n", DEVNAME(sc), dev->slot); if (dev == NULL || dev->slot < 0) return (1); slot = dev->slot; while (slot < sc->sc_max_devices && sc->sc_devs[slot] != NULL) slot++; if (slot >= sc->sc_max_devices) return (1); DNPRINTF(MPII_D_EVT, "%s: mpii_insert_dev alloc slot %d\n", DEVNAME(sc), slot); dev->slot = slot; sc->sc_devs[slot] = dev; return (0); } static int mpii_remove_dev(struct mpii_softc *sc, struct mpii_device *dev) { int i; KASSERT(mutex_owned(&sc->sc_devs_mtx)); if (dev == NULL) return (1); for (i = 0; i < sc->sc_max_devices; i++) { if (sc->sc_devs[i] == NULL) continue; if (sc->sc_devs[i]->dev_handle == dev->dev_handle) { sc->sc_devs[i] = NULL; return (0); } } return (1); } static struct mpii_device * mpii_find_dev(struct mpii_softc *sc, u_int16_t handle) { int i; KASSERT(mutex_owned(&sc->sc_devs_mtx)); for (i = 0; i < sc->sc_max_devices; i++) { if (sc->sc_devs[i] == NULL) continue; if (sc->sc_devs[i]->dev_handle == handle) return (sc->sc_devs[i]); } return (NULL); } static int mpii_alloc_ccbs(struct mpii_softc *sc) { struct mpii_ccb *ccb; u_int8_t *cmd; int i; char wqname[16]; SIMPLEQ_INIT(&sc->sc_ccb_free); SIMPLEQ_INIT(&sc->sc_ccb_tmos); mutex_init(&sc->sc_ccb_free_mtx, MUTEX_DEFAULT, IPL_BIO); cv_init(&sc->sc_ccb_free_cv, "mpii_ccbs"); mutex_init(&sc->sc_ssb_tmomtx, MUTEX_DEFAULT, IPL_BIO); snprintf(wqname, sizeof(wqname) - 1, "%sabrt", DEVNAME(sc)); workqueue_create(&sc->sc_ssb_tmowk, wqname, mpii_scsi_cmd_tmo_handler, sc, PRI_BIO, IPL_BIO, WQ_MPSAFE); if (sc->sc_ssb_tmowk == NULL) return 1; sc->sc_ccbs = malloc((sc->sc_max_cmds-1) * sizeof(*ccb), M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_requests = mpii_dmamem_alloc(sc, sc->sc_request_size * sc->sc_max_cmds); if (sc->sc_requests == NULL) { printf("%s: unable to allocate ccb dmamem\n", DEVNAME(sc)); goto free_ccbs; } cmd = MPII_DMA_KVA(sc->sc_requests); /* * we have sc->sc_max_cmds system request message * frames, but smid zero cannot be used. so we then * have (sc->sc_max_cmds - 1) number of ccbs */ for (i = 1; i < sc->sc_max_cmds; i++) { ccb = &sc->sc_ccbs[i - 1]; if (bus_dmamap_create(sc->sc_dmat, MAXPHYS, sc->sc_max_sgl, 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; mutex_init(&ccb->ccb_mtx, MUTEX_DEFAULT, IPL_BIO); cv_init(&ccb->ccb_cv, "mpiiexec"); ccb->ccb_smid = htole16(i); ccb->ccb_offset = sc->sc_request_size * i; ccb->ccb_cmd = &cmd[ccb->ccb_offset]; ccb->ccb_cmd_dva = (u_int32_t)MPII_DMA_DVA(sc->sc_requests) + ccb->ccb_offset; DNPRINTF(MPII_D_CCB, "%s: mpii_alloc_ccbs(%d) ccb: %p map: %p " "sc: %p smid: %#x offs: %#lx cmd: %p dva: %#lx\n", DEVNAME(sc), i, ccb, ccb->ccb_dmamap, ccb->ccb_sc, ccb->ccb_smid, ccb->ccb_offset, ccb->ccb_cmd, ccb->ccb_cmd_dva); mpii_put_ccb(sc, ccb); } return (0); free_maps: while ((ccb = mpii_get_ccb(sc)) != NULL) bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap); mpii_dmamem_free(sc, sc->sc_requests); free_ccbs: free(sc->sc_ccbs, M_DEVBUF); return (1); } static void mpii_put_ccb(struct mpii_softc *sc, struct mpii_ccb *ccb) { DNPRINTF(MPII_D_CCB, "%s: mpii_put_ccb %p\n", DEVNAME(sc), ccb); ccb->ccb_state = MPII_CCB_FREE; ccb->ccb_cookie = NULL; ccb->ccb_done = NULL; ccb->ccb_rcb = NULL; memset(ccb->ccb_cmd, 0, sc->sc_request_size); mutex_enter(&sc->sc_ccb_free_mtx); SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_free, ccb, ccb_link); mutex_exit(&sc->sc_ccb_free_mtx); } static struct mpii_ccb * mpii_get_ccb(struct mpii_softc *sc) { struct mpii_ccb *ccb; mutex_enter(&sc->sc_ccb_free_mtx); ccb = SIMPLEQ_FIRST(&sc->sc_ccb_free); if (ccb != NULL) { SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_free, ccb_link); ccb->ccb_state = MPII_CCB_READY; KASSERT(ccb->ccb_sc == sc); } mutex_exit(&sc->sc_ccb_free_mtx); DNPRINTF(MPII_D_CCB, "%s: mpii_get_ccb %p\n", DEVNAME(sc), ccb); return (ccb); } static int mpii_alloc_replies(struct mpii_softc *sc) { DNPRINTF(MPII_D_MISC, "%s: mpii_alloc_replies\n", DEVNAME(sc)); sc->sc_rcbs = malloc(sc->sc_num_reply_frames * sizeof(struct mpii_rcb), M_DEVBUF, M_WAITOK); sc->sc_replies = mpii_dmamem_alloc(sc, sc->sc_reply_size * sc->sc_num_reply_frames); if (sc->sc_replies == NULL) { free(sc->sc_rcbs, M_DEVBUF); return (1); } return (0); } static void mpii_push_replies(struct mpii_softc *sc) { struct mpii_rcb *rcb; uintptr_t kva = (uintptr_t)MPII_DMA_KVA(sc->sc_replies); int i; bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_replies), 0, sc->sc_reply_size * sc->sc_num_reply_frames, BUS_DMASYNC_PREREAD); for (i = 0; i < sc->sc_num_reply_frames; i++) { rcb = &sc->sc_rcbs[i]; rcb->rcb_reply = (void *)(kva + sc->sc_reply_size * i); rcb->rcb_reply_dva = (u_int32_t)MPII_DMA_DVA(sc->sc_replies) + sc->sc_reply_size * i; mpii_push_reply(sc, rcb); } } static void mpii_start(struct mpii_softc *sc, struct mpii_ccb *ccb) { struct mpii_request_header *rhp; struct mpii_request_descr descr; #if defined(__LP64__) && 0 u_long *rdp = (u_long *)&descr; #else u_int32_t *rdp = (u_int32_t *)&descr; #endif DNPRINTF(MPII_D_RW, "%s: mpii_start %#lx\n", DEVNAME(sc), ccb->ccb_cmd_dva); bus_dmamap_sync(sc->sc_dmat, MPII_DMA_MAP(sc->sc_requests), ccb->ccb_offset, sc->sc_request_size, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); ccb->ccb_state = MPII_CCB_QUEUED; rhp = ccb->ccb_cmd; memset(&descr, 0, sizeof(descr)); switch (rhp->function) { case MPII_FUNCTION_SCSI_IO_REQUEST: descr.request_flags = MPII_REQ_DESCR_SCSI_IO; descr.dev_handle = htole16(ccb->ccb_dev_handle); break; case MPII_FUNCTION_SCSI_TASK_MGMT: descr.request_flags = MPII_REQ_DESCR_HIGH_PRIORITY; break; default: descr.request_flags = MPII_REQ_DESCR_DEFAULT; } descr.vf_id = sc->sc_vf_id; descr.smid = ccb->ccb_smid; #if defined(__LP64__) && 0 DNPRINTF(MPII_D_RW, "%s: MPII_REQ_DESCR_POST_LOW (0x%08x) write " "0x%08lx\n", DEVNAME(sc), MPII_REQ_DESCR_POST_LOW, *rdp); bus_space_write_raw_8(sc->sc_iot, sc->sc_ioh, MPII_REQ_DESCR_POST_LOW, *rdp); #else DNPRINTF(MPII_D_RW, "%s: MPII_REQ_DESCR_POST_LOW (0x%08x) write " "0x%04x\n", DEVNAME(sc), MPII_REQ_DESCR_POST_LOW, *rdp); DNPRINTF(MPII_D_RW, "%s: MPII_REQ_DESCR_POST_HIGH (0x%08x) write " "0x%04x\n", DEVNAME(sc), MPII_REQ_DESCR_POST_HIGH, *(rdp+1)); mutex_enter(&sc->sc_req_mtx); bus_space_write_4(sc->sc_iot, sc->sc_ioh, MPII_REQ_DESCR_POST_LOW, rdp[0]); bus_space_barrier(sc->sc_iot, sc->sc_ioh, MPII_REQ_DESCR_POST_LOW, 8, BUS_SPACE_BARRIER_WRITE); bus_space_write_4(sc->sc_iot, sc->sc_ioh, MPII_REQ_DESCR_POST_HIGH, rdp[1]); bus_space_barrier(sc->sc_iot, sc->sc_ioh, MPII_REQ_DESCR_POST_LOW, 8, BUS_SPACE_BARRIER_WRITE); mutex_exit(&sc->sc_req_mtx); #endif } static int mpii_poll(struct mpii_softc *sc, struct mpii_ccb *ccb) { void (*done)(struct mpii_ccb *); void *cookie; int rv = 1; DNPRINTF(MPII_D_INTR, "%s: mpii_poll\n", DEVNAME(sc)); done = ccb->ccb_done; cookie = ccb->ccb_cookie; ccb->ccb_done = mpii_poll_done; ccb->ccb_cookie = &rv; mpii_start(sc, ccb); while (rv == 1) { /* avoid excessive polling */ if (mpii_reply_waiting(sc)) mpii_intr(sc); else delay(10); } ccb->ccb_cookie = cookie; done(ccb); return (0); } static void mpii_poll_done(struct mpii_ccb *ccb) { int *rv = ccb->ccb_cookie; *rv = 0; } static int mpii_alloc_queues(struct mpii_softc *sc) { u_int32_t *rfp; int i; DNPRINTF(MPII_D_MISC, "%s: mpii_alloc_queues\n", DEVNAME(sc)); mutex_init(&sc->sc_reply_free_mtx, MUTEX_DEFAULT, IPL_BIO); sc->sc_reply_freeq = mpii_dmamem_alloc(sc, sc->sc_reply_free_qdepth * sizeof(*rfp)); if (sc->sc_reply_freeq == NULL) return (1); rfp = MPII_DMA_KVA(sc->sc_reply_freeq); for (i = 0; i < sc->sc_num_reply_frames; i++) { rfp[i] = (u_int32_t)MPII_DMA_DVA(sc->sc_replies) + sc->sc_reply_size * i; } sc->sc_reply_postq = mpii_dmamem_alloc(sc, sc->sc_reply_post_qdepth * sizeof(struct mpii_reply_descr)); if (sc->sc_reply_postq == NULL) goto free_reply_freeq; sc->sc_reply_postq_kva = MPII_DMA_KVA(sc->sc_reply_postq); memset(sc->sc_reply_postq_kva, 0xff, sc->sc_reply_post_qdepth * sizeof(struct mpii_reply_descr)); return (0); free_reply_freeq: mpii_dmamem_free(sc, sc->sc_reply_freeq); return (1); } static void mpii_init_queues(struct mpii_softc *sc) { DNPRINTF(MPII_D_MISC, "%s: mpii_init_queues\n", DEVNAME(sc)); sc->sc_reply_free_host_index = sc->sc_reply_free_qdepth - 1; sc->sc_reply_post_host_index = 0; mpii_write_reply_free(sc, sc->sc_reply_free_host_index); mpii_write_reply_post(sc, sc->sc_reply_post_host_index); } static void mpii_wait(struct mpii_softc *sc, struct mpii_ccb *ccb) { void (*done)(struct mpii_ccb *); void *cookie; done = ccb->ccb_done; cookie = ccb->ccb_cookie; ccb->ccb_done = mpii_wait_done; ccb->ccb_cookie = ccb; /* XXX this will wait forever for the ccb to complete */ mpii_start(sc, ccb); mutex_enter(&ccb->ccb_mtx); while (ccb->ccb_cookie != NULL) cv_wait(&ccb->ccb_cv, &ccb->ccb_mtx); mutex_exit(&ccb->ccb_mtx); ccb->ccb_cookie = cookie; done(ccb); } static void mpii_wait_done(struct mpii_ccb *ccb) { mutex_enter(&ccb->ccb_mtx); ccb->ccb_cookie = NULL; cv_signal(&ccb->ccb_cv); mutex_exit(&ccb->ccb_mtx); } static void mpii_scsipi_request(struct scsipi_channel *chan, scsipi_adapter_req_t req, void *arg) { struct scsipi_periph *periph; struct scsipi_xfer *xs; struct scsipi_adapter *adapt = chan->chan_adapter; struct mpii_softc *sc = device_private(adapt->adapt_dev); struct mpii_ccb *ccb; struct mpii_msg_scsi_io *io; struct mpii_device *dev; int target, timeout, ret; u_int16_t dev_handle; DNPRINTF(MPII_D_CMD, "%s: mpii_scsipi_request\n", DEVNAME(sc)); switch (req) { case ADAPTER_REQ_GROW_RESOURCES: /* Not supported. */ return; case ADAPTER_REQ_SET_XFER_MODE: { struct scsipi_xfer_mode *xm = arg; xm->xm_mode = PERIPH_CAP_TQING; xm->xm_period = 0; xm->xm_offset = 0; scsipi_async_event(&sc->sc_chan, ASYNC_EVENT_XFER_MODE, xm); return; } case ADAPTER_REQ_RUN_XFER: break; } xs = arg; periph = xs->xs_periph; target = periph->periph_target; if (xs->cmdlen > MPII_CDB_LEN) { DNPRINTF(MPII_D_CMD, "%s: CDB too big %d\n", DEVNAME(sc), xs->cmdlen); memset(&xs->sense, 0, sizeof(xs->sense)); xs->sense.scsi_sense.response_code = SSD_RCODE_VALID | SSD_RCODE_CURRENT; xs->sense.scsi_sense.flags = SKEY_ILLEGAL_REQUEST; xs->sense.scsi_sense.asc = 0x20; xs->error = XS_SENSE; scsipi_done(xs); return; } mutex_enter(&sc->sc_devs_mtx); if ((dev = sc->sc_devs[target]) == NULL) { mutex_exit(&sc->sc_devs_mtx); /* device no longer exists */ xs->error = XS_SELTIMEOUT; scsipi_done(xs); return; } dev_handle = dev->dev_handle; mutex_exit(&sc->sc_devs_mtx); ccb = mpii_get_ccb(sc); if (ccb == NULL) { xs->error = XS_RESOURCE_SHORTAGE; scsipi_done(xs); return; } DNPRINTF(MPII_D_CMD, "%s: ccb_smid: %d xs->cmd->opcode: 0x%02x xs->xs_control: 0x%x\n", DEVNAME(sc), ccb->ccb_smid, xs->cmd->opcode, xs->xs_control); ccb->ccb_cookie = xs; ccb->ccb_done = mpii_scsi_cmd_done; ccb->ccb_dev_handle = dev_handle; io = ccb->ccb_cmd; memset(io, 0, sizeof(*io)); io->function = MPII_FUNCTION_SCSI_IO_REQUEST; io->sense_buffer_length = sizeof(xs->sense); io->sgl_offset0 = sizeof(struct mpii_msg_scsi_io) / 4; io->io_flags = htole16(xs->cmdlen); io->dev_handle = htole16(ccb->ccb_dev_handle); io->lun[0] = htobe16(periph->periph_lun); switch (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) { case XS_CTL_DATA_IN: io->direction = MPII_SCSIIO_DIR_READ; break; case XS_CTL_DATA_OUT: io->direction = MPII_SCSIIO_DIR_WRITE; break; default: io->direction = MPII_SCSIIO_DIR_NONE; break; } io->tagging = MPII_SCSIIO_ATTR_SIMPLE_Q; memcpy(io->cdb, xs->cmd, xs->cmdlen); io->data_length = htole32(xs->datalen); /* sense data is at the end of a request */ io->sense_buffer_low_address = htole32(ccb->ccb_cmd_dva + sc->sc_request_size - sizeof(struct scsi_sense_data)); if (ISSET(sc->sc_flags, MPII_F_SAS3)) ret = mpii_load_xs_sas3(ccb); else ret = mpii_load_xs(ccb); if (ret != 0) { xs->error = XS_DRIVER_STUFFUP; goto done; } if (xs->xs_control & XS_CTL_POLL) { if (mpii_poll(sc, ccb) != 0) { xs->error = XS_DRIVER_STUFFUP; goto done; } return; } timeout = mstohz(xs->timeout); if (timeout == 0) timeout = 1; callout_reset(&xs->xs_callout, timeout, mpii_scsi_cmd_tmo, ccb); mpii_start(sc, ccb); return; done: mpii_put_ccb(sc, ccb); scsipi_done(xs); } static void mpii_scsi_cmd_tmo(void *xccb) { struct mpii_ccb *ccb = xccb; struct mpii_softc *sc = ccb->ccb_sc; bool start_work; printf("%s: mpii_scsi_cmd_tmo\n", DEVNAME(sc)); if (ccb->ccb_state == MPII_CCB_QUEUED) { mutex_enter(&sc->sc_ssb_tmomtx); start_work = (SIMPLEQ_FIRST(&sc->sc_ccb_tmos) == 0); ccb->ccb_state = MPII_CCB_TIMEOUT; SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_tmos, ccb, ccb_link); if (start_work) { workqueue_enqueue(sc->sc_ssb_tmowk, &sc->sc_ssb_tmowork, NULL); } mutex_exit(&sc->sc_ssb_tmomtx); } } static void mpii_scsi_cmd_tmo_handler(struct work *wk, void *cookie) { struct mpii_softc *sc = cookie; struct mpii_ccb *next; struct mpii_ccb *ccb; struct mpii_ccb *tccb; struct mpii_msg_scsi_task_request *stq; mutex_enter(&sc->sc_ssb_tmomtx); next = SIMPLEQ_FIRST(&sc->sc_ccb_tmos); SIMPLEQ_INIT(&sc->sc_ccb_tmos); mutex_exit(&sc->sc_ssb_tmomtx); while (next != NULL) { ccb = next; next = SIMPLEQ_NEXT(ccb, ccb_link); if (ccb->ccb_state != MPII_CCB_TIMEOUT) continue; tccb = mpii_get_ccb(sc); stq = tccb->ccb_cmd; stq->function = MPII_FUNCTION_SCSI_TASK_MGMT; stq->task_type = MPII_SCSI_TASK_TARGET_RESET; stq->dev_handle = htole16(ccb->ccb_dev_handle); tccb->ccb_done = mpii_scsi_cmd_tmo_done; mpii_wait(sc, tccb); } } static void mpii_scsi_cmd_tmo_done(struct mpii_ccb *tccb) { mpii_put_ccb(tccb->ccb_sc, tccb); } static u_int8_t map_scsi_status(u_int8_t mpii_scsi_status) { u_int8_t scsi_status; switch (mpii_scsi_status) { case MPII_SCSIIO_STATUS_GOOD: scsi_status = SCSI_OK; break; case MPII_SCSIIO_STATUS_CHECK_COND: scsi_status = SCSI_CHECK; break; case MPII_SCSIIO_STATUS_BUSY: scsi_status = SCSI_BUSY; break; case MPII_SCSIIO_STATUS_INTERMEDIATE: scsi_status = SCSI_INTERM; break; case MPII_SCSIIO_STATUS_INTERMEDIATE_CONDMET: scsi_status = SCSI_INTERM; break; case MPII_SCSIIO_STATUS_RESERVATION_CONFLICT: scsi_status = SCSI_RESV_CONFLICT; break; case MPII_SCSIIO_STATUS_CMD_TERM: case MPII_SCSIIO_STATUS_TASK_ABORTED: scsi_status = SCSI_TERMINATED; break; case MPII_SCSIIO_STATUS_TASK_SET_FULL: scsi_status = SCSI_QUEUE_FULL; break; case MPII_SCSIIO_STATUS_ACA_ACTIVE: scsi_status = SCSI_ACA_ACTIVE; break; default: /* XXX: for the lack of anything better and other than OK */ scsi_status = 0xFF; break; } return scsi_status; } static void mpii_scsi_cmd_done(struct mpii_ccb *ccb) { struct mpii_msg_scsi_io_error *sie; struct mpii_softc *sc = ccb->ccb_sc; struct scsipi_xfer *xs = ccb->ccb_cookie; struct scsi_sense_data *sense; bus_dmamap_t dmap = ccb->ccb_dmamap; bool timeout = 1; callout_stop(&xs->xs_callout); if (ccb->ccb_state == MPII_CCB_TIMEOUT) timeout = 1; ccb->ccb_state = MPII_CCB_READY; if (xs->datalen != 0) { bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize, (xs->xs_control & XS_CTL_DATA_IN) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, dmap); } KASSERT(xs->error == XS_NOERROR); KASSERT(xs->status == SCSI_OK); if (ccb->ccb_rcb == NULL) { /* no scsi error, we're ok so drop out early */ xs->resid = 0; goto done; } sie = ccb->ccb_rcb->rcb_reply; DNPRINTF(MPII_D_CMD, "%s: mpii_scsi_cmd_done xs cmd: 0x%02x len: %d " "flags 0x%x\n", DEVNAME(sc), xs->cmd->opcode, xs->datalen, xs->xs_control); DNPRINTF(MPII_D_CMD, "%s: dev_handle: %d msg_length: %d " "function: 0x%02x\n", DEVNAME(sc), le16toh(sie->dev_handle), sie->msg_length, sie->function); DNPRINTF(MPII_D_CMD, "%s: vp_id: 0x%02x vf_id: 0x%02x\n", DEVNAME(sc), sie->vp_id, sie->vf_id); DNPRINTF(MPII_D_CMD, "%s: scsi_status: 0x%02x scsi_state: 0x%02x " "ioc_status: 0x%04x\n", DEVNAME(sc), sie->scsi_status, sie->scsi_state, le16toh(sie->ioc_status)); DNPRINTF(MPII_D_CMD, "%s: ioc_loginfo: 0x%08x\n", DEVNAME(sc), le32toh(sie->ioc_loginfo)); DNPRINTF(MPII_D_CMD, "%s: transfer_count: %d\n", DEVNAME(sc), le32toh(sie->transfer_count)); DNPRINTF(MPII_D_CMD, "%s: sense_count: %d\n", DEVNAME(sc), le32toh(sie->sense_count)); DNPRINTF(MPII_D_CMD, "%s: response_info: 0x%08x\n", DEVNAME(sc), le32toh(sie->response_info)); DNPRINTF(MPII_D_CMD, "%s: task_tag: 0x%04x\n", DEVNAME(sc), le16toh(sie->task_tag)); DNPRINTF(MPII_D_CMD, "%s: bidirectional_transfer_count: 0x%08x\n", DEVNAME(sc), le32toh(sie->bidirectional_transfer_count)); xs->status = map_scsi_status(sie->scsi_status); switch (le16toh(sie->ioc_status) & MPII_IOCSTATUS_MASK) { case MPII_IOCSTATUS_SCSI_DATA_UNDERRUN: switch(sie->scsi_status) { case MPII_SCSIIO_STATUS_CHECK_COND: xs->error = XS_SENSE; /* FALLTHROUGH */ case MPII_SCSIIO_STATUS_GOOD: xs->resid = xs->datalen - le32toh(sie->transfer_count); break; default: xs->error = XS_DRIVER_STUFFUP; break; } break; case MPII_IOCSTATUS_SUCCESS: case MPII_IOCSTATUS_SCSI_RECOVERED_ERROR: switch (sie->scsi_status) { case MPII_SCSIIO_STATUS_GOOD: xs->resid = 0; break; case MPII_SCSIIO_STATUS_CHECK_COND: xs->error = XS_SENSE; break; case MPII_SCSIIO_STATUS_BUSY: case MPII_SCSIIO_STATUS_TASK_SET_FULL: xs->error = XS_BUSY; break; default: xs->error = XS_DRIVER_STUFFUP; } break; case MPII_IOCSTATUS_BUSY: case MPII_IOCSTATUS_INSUFFICIENT_RESOURCES: xs->error = XS_BUSY; break; case MPII_IOCSTATUS_SCSI_IOC_TERMINATED: case MPII_IOCSTATUS_SCSI_TASK_TERMINATED: xs->error = timeout ? XS_TIMEOUT : XS_RESET; break; case MPII_IOCSTATUS_SCSI_INVALID_DEVHANDLE: case MPII_IOCSTATUS_SCSI_DEVICE_NOT_THERE: xs->error = XS_SELTIMEOUT; break; default: xs->error = XS_DRIVER_STUFFUP; break; } sense = (struct scsi_sense_data *)((uintptr_t)ccb->ccb_cmd + sc->sc_request_size - sizeof(*sense)); if (sie->scsi_state & MPII_SCSIIO_STATE_AUTOSENSE_VALID) memcpy(&xs->sense, sense, sizeof(xs->sense)); mpii_push_reply(sc, ccb->ccb_rcb); done: mpii_put_ccb(sc, ccb); DNPRINTF(MPII_D_CMD, "%s: xs err: %d status: %#x len: %d resid: %d\n", DEVNAME(sc), xs->error, xs->status, xs->datalen, xs->resid); scsipi_done(xs); } #if 0 int mpii_scsi_ioctl(struct scsi_link *link, u_long cmd, void *addr, int flag) { struct mpii_softc *sc = (struct mpii_softc *)link->adapter_softc; struct mpii_device *dev = sc->sc_devs[link->target]; DNPRINTF(MPII_D_IOCTL, "%s: mpii_scsi_ioctl\n", DEVNAME(sc)); switch (cmd) { case DIOCGCACHE: case DIOCSCACHE: if (dev != NULL && ISSET(dev->flags, MPII_DF_VOLUME)) { return (mpii_ioctl_cache(link, cmd, (struct dk_cache *)addr)); } break; default: if (sc->sc_ioctl) return (sc->sc_ioctl(link->adapter_softc, cmd, addr)); break; } return (ENOTTY); } int mpii_ioctl_cache(struct scsi_link *link, u_long cmd, struct dk_cache *dc) { struct mpii_softc *sc = (struct mpii_softc *)link->adapter_softc; struct mpii_device *dev = sc->sc_devs[link->target]; struct mpii_cfg_raid_vol_pg0 *vpg; struct mpii_msg_raid_action_request *req; struct mpii_msg_raid_action_reply *rep; struct mpii_cfg_hdr hdr; struct mpii_ccb *ccb; u_int32_t addr = MPII_CFG_RAID_VOL_ADDR_HANDLE | dev->dev_handle; size_t pagelen; int rv = 0; int enabled; if (mpii_req_cfg_header(sc, MPII_CONFIG_REQ_PAGE_TYPE_RAID_VOL, 0, addr, MPII_PG_POLL, &hdr) != 0) return (EINVAL); pagelen = hdr.page_length * 4; vpg = malloc(pagelen, M_TEMP, M_WAITOK | M_ZERO); if (vpg == NULL) return (ENOMEM); if (mpii_req_cfg_page(sc, addr, MPII_PG_POLL, &hdr, 1, vpg, pagelen) != 0) { rv = EINVAL; goto done; } enabled = ((le16toh(vpg->volume_settings) & MPII_CFG_RAID_VOL_0_SETTINGS_CACHE_MASK) == MPII_CFG_RAID_VOL_0_SETTINGS_CACHE_ENABLED) ? 1 : 0; if (cmd == DIOCGCACHE) { dc->wrcache = enabled; dc->rdcache = 0; goto done; } /* else DIOCSCACHE */ if (dc->rdcache) { rv = EOPNOTSUPP; goto done; } if (((dc->wrcache) ? 1 : 0) == enabled) goto done; ccb = scsi_io_get(&sc->sc_iopool, SCSI_POLL); if (ccb == NULL) { rv = ENOMEM; goto done; } ccb->ccb_done = mpii_empty_done; req = ccb->ccb_cmd; memset(req, 0, sizeof(*req)); req->function = MPII_FUNCTION_RAID_ACTION; req->action = MPII_RAID_ACTION_CHANGE_VOL_WRITE_CACHE; req->vol_dev_handle = htole16(dev->dev_handle); req->action_data = htole32(dc->wrcache ? MPII_RAID_VOL_WRITE_CACHE_ENABLE : MPII_RAID_VOL_WRITE_CACHE_DISABLE); if (mpii_poll(sc, ccb) != 0) { rv = EIO; goto done; } if (ccb->ccb_rcb != NULL) { rep = ccb->ccb_rcb->rcb_reply; if ((rep->ioc_status != MPII_IOCSTATUS_SUCCESS) || ((rep->action_data[0] & MPII_RAID_VOL_WRITE_CACHE_MASK) != (dc->wrcache ? MPII_RAID_VOL_WRITE_CACHE_ENABLE : MPII_RAID_VOL_WRITE_CACHE_DISABLE))) rv = EINVAL; mpii_push_reply(sc, ccb->ccb_rcb); } scsi_io_put(&sc->sc_iopool, ccb); done: free(vpg, M_TEMP); return (rv); } #endif /* 0 */ #if NBIO > 0 static int mpii_ioctl(device_t dev, u_long cmd, void *addr) { struct mpii_softc *sc = device_private(dev); int error = 0; DNPRINTF(MPII_D_IOCTL, "%s: mpii_ioctl ", DEVNAME(sc)); switch (cmd) { case BIOCINQ: DNPRINTF(MPII_D_IOCTL, "inq\n"); error = mpii_ioctl_inq(sc, (struct bioc_inq *)addr); break; case BIOCVOL: DNPRINTF(MPII_D_IOCTL, "vol\n"); error = mpii_ioctl_vol(sc, (struct bioc_vol *)addr); break; case BIOCDISK: DNPRINTF(MPII_D_IOCTL, "disk\n"); error = mpii_ioctl_disk(sc, (struct bioc_disk *)addr); break; default: DNPRINTF(MPII_D_IOCTL, " invalid ioctl\n"); error = ENOTTY; } return (error); } static int mpii_ioctl_inq(struct mpii_softc *sc, struct bioc_inq *bi) { int i; DNPRINTF(MPII_D_IOCTL, "%s: mpii_ioctl_inq\n", DEVNAME(sc)); strlcpy(bi->bi_dev, DEVNAME(sc), sizeof(bi->bi_dev)); mutex_enter(&sc->sc_devs_mtx); for (i = 0; i < sc->sc_max_devices; i++) if (sc->sc_devs[i] && ISSET(sc->sc_devs[i]->flags, MPII_DF_VOLUME)) bi->bi_novol++; mutex_exit(&sc->sc_devs_mtx); return (0); } static int mpii_ioctl_vol(struct mpii_softc *sc, struct bioc_vol *bv) { struct mpii_cfg_raid_vol_pg0 *vpg; struct mpii_cfg_hdr hdr; struct mpii_device *dev; size_t pagelen; u_int16_t volh; int rv, hcnt = 0; int percent; DNPRINTF(MPII_D_IOCTL, "%s: mpii_ioctl_vol %d\n", DEVNAME(sc), bv->bv_volid); mutex_enter(&sc->sc_devs_mtx); if ((dev = mpii_find_vol(sc, bv->bv_volid)) == NULL) { mutex_exit(&sc->sc_devs_mtx); return (ENODEV); } volh = dev->dev_handle; percent = dev->percent; mutex_exit(&sc->sc_devs_mtx); if (mpii_req_cfg_header(sc, MPII_CONFIG_REQ_PAGE_TYPE_RAID_VOL, 0, MPII_CFG_RAID_VOL_ADDR_HANDLE | volh, 0, &hdr) != 0) { printf("%s: unable to fetch header for raid volume page 0\n", DEVNAME(sc)); return (EINVAL); } pagelen = hdr.page_length * 4; vpg = malloc(pagelen, M_TEMP, M_WAITOK | M_ZERO); if (vpg == NULL) { printf("%s: unable to allocate space for raid " "volume page 0\n", DEVNAME(sc)); return (ENOMEM); } if (mpii_req_cfg_page(sc, MPII_CFG_RAID_VOL_ADDR_HANDLE | volh, 0, &hdr, 1, vpg, pagelen) != 0) { printf("%s: unable to fetch raid volume page 0\n", DEVNAME(sc)); free(vpg, M_TEMP); return (EINVAL); } switch (vpg->volume_state) { case MPII_CFG_RAID_VOL_0_STATE_ONLINE: case MPII_CFG_RAID_VOL_0_STATE_OPTIMAL: bv->bv_status = BIOC_SVONLINE; break; case MPII_CFG_RAID_VOL_0_STATE_DEGRADED: if (ISSET(le32toh(vpg->volume_status), MPII_CFG_RAID_VOL_0_STATUS_RESYNC)) { bv->bv_status = BIOC_SVREBUILD; bv->bv_percent = percent; } else bv->bv_status = BIOC_SVDEGRADED; break; case MPII_CFG_RAID_VOL_0_STATE_FAILED: bv->bv_status = BIOC_SVOFFLINE; break; case MPII_CFG_RAID_VOL_0_STATE_INITIALIZING: bv->bv_status = BIOC_SVBUILDING; break; case MPII_CFG_RAID_VOL_0_STATE_MISSING: default: bv->bv_status = BIOC_SVINVALID; break; } switch (vpg->volume_type) { case MPII_CFG_RAID_VOL_0_TYPE_RAID0: bv->bv_level = 0; break; case MPII_CFG_RAID_VOL_0_TYPE_RAID1: bv->bv_level = 1; break; case MPII_CFG_RAID_VOL_0_TYPE_RAID1E: case MPII_CFG_RAID_VOL_0_TYPE_RAID10: bv->bv_level = 10; break; default: bv->bv_level = -1; } if ((rv = mpii_bio_hs(sc, NULL, 0, vpg->hot_spare_pool, &hcnt)) != 0) { free(vpg, M_TEMP); return (rv); } bv->bv_nodisk = vpg->num_phys_disks + hcnt; bv->bv_size = le64toh(vpg->max_lba) * le16toh(vpg->block_size); free(vpg, M_TEMP); return (0); } static int mpii_ioctl_disk(struct mpii_softc *sc, struct bioc_disk *bd) { struct mpii_cfg_raid_vol_pg0 *vpg; struct mpii_cfg_raid_vol_pg0_physdisk *pd; struct mpii_cfg_hdr hdr; struct mpii_device *dev; size_t pagelen; u_int16_t volh; u_int8_t dn; DNPRINTF(MPII_D_IOCTL, "%s: mpii_ioctl_disk %d/%d\n", DEVNAME(sc), bd->bd_volid, bd->bd_diskid); mutex_enter(&sc->sc_devs_mtx); if ((dev = mpii_find_vol(sc, bd->bd_volid)) == NULL) { mutex_exit(&sc->sc_devs_mtx); return (ENODEV); } volh = dev->dev_handle; mutex_exit(&sc->sc_devs_mtx); if (mpii_req_cfg_header(sc, MPII_CONFIG_REQ_PAGE_TYPE_RAID_VOL, 0, MPII_CFG_RAID_VOL_ADDR_HANDLE | volh, 0, &hdr) != 0) { printf("%s: unable to fetch header for raid volume page 0\n", DEVNAME(sc)); return (EINVAL); } pagelen = hdr.page_length * 4; vpg = malloc(pagelen, M_TEMP, M_WAITOK | M_ZERO); if (vpg == NULL) { printf("%s: unable to allocate space for raid " "volume page 0\n", DEVNAME(sc)); return (ENOMEM); } if (mpii_req_cfg_page(sc, MPII_CFG_RAID_VOL_ADDR_HANDLE | volh, 0, &hdr, 1, vpg, pagelen) != 0) { printf("%s: unable to fetch raid volume page 0\n", DEVNAME(sc)); free(vpg, M_TEMP); return (EINVAL); } if (bd->bd_diskid >= vpg->num_phys_disks) { int nvdsk = vpg->num_phys_disks; int hsmap = vpg->hot_spare_pool; free(vpg, M_TEMP); return (mpii_bio_hs(sc, bd, nvdsk, hsmap, NULL)); } pd = (struct mpii_cfg_raid_vol_pg0_physdisk *)(vpg + 1) + bd->bd_diskid; dn = pd->phys_disk_num; free(vpg, M_TEMP); return (mpii_bio_disk(sc, bd, dn)); } static int mpii_bio_hs(struct mpii_softc *sc, struct bioc_disk *bd, int nvdsk, int hsmap, int *hscnt) { struct mpii_cfg_raid_config_pg0 *cpg; struct mpii_raid_config_element *el; struct mpii_ecfg_hdr ehdr; size_t pagelen; int i, nhs = 0; if (bd) { DNPRINTF(MPII_D_IOCTL, "%s: mpii_bio_hs %d\n", DEVNAME(sc), bd->bd_diskid - nvdsk); } else { DNPRINTF(MPII_D_IOCTL, "%s: mpii_bio_hs\n", DEVNAME(sc)); } if (mpii_req_cfg_header(sc, MPII_CONFIG_REQ_PAGE_TYPE_RAID_CONFIG, 0, MPII_CFG_RAID_CONFIG_ACTIVE_CONFIG, MPII_PG_EXTENDED, &ehdr) != 0) { printf("%s: unable to fetch header for raid config page 0\n", DEVNAME(sc)); return (EINVAL); } pagelen = le16toh(ehdr.ext_page_length) * 4; cpg = malloc(pagelen, M_TEMP, M_WAITOK | M_ZERO); if (cpg == NULL) { printf("%s: unable to allocate space for raid config page 0\n", DEVNAME(sc)); return (ENOMEM); } if (mpii_req_cfg_page(sc, MPII_CFG_RAID_CONFIG_ACTIVE_CONFIG, MPII_PG_EXTENDED, &ehdr, 1, cpg, pagelen) != 0) { printf("%s: unable to fetch raid config page 0\n", DEVNAME(sc)); free(cpg, M_TEMP); return (EINVAL); } el = (struct mpii_raid_config_element *)(cpg + 1); for (i = 0; i < cpg->num_elements; i++, el++) { if (ISSET(le16toh(el->element_flags), MPII_RAID_CONFIG_ELEMENT_FLAG_HSP_PHYS_DISK) && el->hot_spare_pool == hsmap) { /* * diskid comparison is based on the idea that all * disks are counted by the bio(4) in sequence, thus * subtracting the number of disks in the volume * from the diskid yields us a "relative" hotspare * number, which is good enough for us. */ if (bd != NULL && bd->bd_diskid == nhs + nvdsk) { u_int8_t dn = el->phys_disk_num; free(cpg, M_TEMP); return (mpii_bio_disk(sc, bd, dn)); } nhs++; } } if (hscnt) *hscnt = nhs; free(cpg, M_TEMP); return (0); } static int mpii_bio_disk(struct mpii_softc *sc, struct bioc_disk *bd, u_int8_t dn) { struct mpii_cfg_raid_physdisk_pg0 *ppg; struct mpii_cfg_hdr hdr; struct mpii_device *dev; int len; DNPRINTF(MPII_D_IOCTL, "%s: mpii_bio_disk %d\n", DEVNAME(sc), bd->bd_diskid); ppg = malloc(sizeof(*ppg), M_TEMP, M_WAITOK | M_ZERO); if (ppg == NULL) { printf("%s: unable to allocate space for raid physical disk " "page 0\n", DEVNAME(sc)); return (ENOMEM); } hdr.page_version = 0; hdr.page_length = sizeof(*ppg) / 4; hdr.page_number = 0; hdr.page_type = MPII_CONFIG_REQ_PAGE_TYPE_RAID_PD; if (mpii_req_cfg_page(sc, MPII_CFG_RAID_PHYS_DISK_ADDR_NUMBER | dn, 0, &hdr, 1, ppg, sizeof(*ppg)) != 0) { printf("%s: unable to fetch raid drive page 0\n", DEVNAME(sc)); free(ppg, M_TEMP); return (EINVAL); } bd->bd_target = ppg->phys_disk_num; mutex_enter(&sc->sc_devs_mtx); if ((dev = mpii_find_dev(sc, le16toh(ppg->dev_handle))) == NULL) { mutex_exit(&sc->sc_devs_mtx); bd->bd_status = BIOC_SDINVALID; free(ppg, M_TEMP); return (0); } mutex_exit(&sc->sc_devs_mtx); switch (ppg->phys_disk_state) { case MPII_CFG_RAID_PHYDISK_0_STATE_ONLINE: case MPII_CFG_RAID_PHYDISK_0_STATE_OPTIMAL: bd->bd_status = BIOC_SDONLINE; break; case MPII_CFG_RAID_PHYDISK_0_STATE_OFFLINE: if (ppg->offline_reason == MPII_CFG_RAID_PHYDISK_0_OFFLINE_FAILED || ppg->offline_reason == MPII_CFG_RAID_PHYDISK_0_OFFLINE_FAILEDREQ) bd->bd_status = BIOC_SDFAILED; else bd->bd_status = BIOC_SDOFFLINE; break; case MPII_CFG_RAID_PHYDISK_0_STATE_DEGRADED: bd->bd_status = BIOC_SDFAILED; break; case MPII_CFG_RAID_PHYDISK_0_STATE_REBUILDING: bd->bd_status = BIOC_SDREBUILD; break; case MPII_CFG_RAID_PHYDISK_0_STATE_HOTSPARE: bd->bd_status = BIOC_SDHOTSPARE; break; case MPII_CFG_RAID_PHYDISK_0_STATE_NOTCONFIGURED: bd->bd_status = BIOC_SDUNUSED; break; case MPII_CFG_RAID_PHYDISK_0_STATE_NOTCOMPATIBLE: default: bd->bd_status = BIOC_SDINVALID; break; } bd->bd_size = le64toh(ppg->dev_max_lba) * le16toh(ppg->block_size); strnvisx(bd->bd_vendor, sizeof(bd->bd_vendor), ppg->vendor_id, sizeof(ppg->vendor_id), VIS_TRIM|VIS_SAFE|VIS_OCTAL); len = strlen(bd->bd_vendor); bd->bd_vendor[len] = ' '; strnvisx(&bd->bd_vendor[len + 1], sizeof(ppg->vendor_id) - len - 1, ppg->product_id, sizeof(ppg->product_id), VIS_TRIM|VIS_SAFE|VIS_OCTAL); strnvisx(bd->bd_serial, sizeof(bd->bd_serial), ppg->serial, sizeof(ppg->serial), VIS_TRIM|VIS_SAFE|VIS_OCTAL); free(ppg, M_TEMP); return (0); } static struct mpii_device * mpii_find_vol(struct mpii_softc *sc, int volid) { struct mpii_device *dev = NULL; KASSERT(mutex_owned(&sc->sc_devs_mtx)); if (sc->sc_vd_id_low + volid >= sc->sc_max_devices) return (NULL); dev = sc->sc_devs[sc->sc_vd_id_low + volid]; if (dev && ISSET(dev->flags, MPII_DF_VOLUME)) return (dev); return (NULL); } /* * Non-sleeping lightweight version of the mpii_ioctl_vol */ static int mpii_bio_volstate(struct mpii_softc *sc, struct bioc_vol *bv) { struct mpii_cfg_raid_vol_pg0 *vpg; struct mpii_cfg_hdr hdr; struct mpii_device *dev = NULL; size_t pagelen; u_int16_t volh; mutex_enter(&sc->sc_devs_mtx); if ((dev = mpii_find_vol(sc, bv->bv_volid)) == NULL) { mutex_exit(&sc->sc_devs_mtx); return (ENODEV); } volh = dev->dev_handle; mutex_exit(&sc->sc_devs_mtx); if (mpii_req_cfg_header(sc, MPII_CONFIG_REQ_PAGE_TYPE_RAID_VOL, 0, MPII_CFG_RAID_VOL_ADDR_HANDLE | volh, MPII_PG_POLL, &hdr) != 0) { DNPRINTF(MPII_D_MISC, "%s: unable to fetch header for raid " "volume page 0\n", DEVNAME(sc)); return (EINVAL); } pagelen = hdr.page_length * 4; vpg = malloc(pagelen, M_TEMP, M_WAITOK | M_ZERO); if (mpii_req_cfg_page(sc, MPII_CFG_RAID_VOL_ADDR_HANDLE | volh, MPII_PG_POLL, &hdr, 1, vpg, pagelen) != 0) { DNPRINTF(MPII_D_MISC, "%s: unable to fetch raid volume " "page 0\n", DEVNAME(sc)); free(vpg, M_TEMP); return (EINVAL); } switch (vpg->volume_state) { case MPII_CFG_RAID_VOL_0_STATE_ONLINE: case MPII_CFG_RAID_VOL_0_STATE_OPTIMAL: bv->bv_status = BIOC_SVONLINE; break; case MPII_CFG_RAID_VOL_0_STATE_DEGRADED: if (ISSET(le32toh(vpg->volume_status), MPII_CFG_RAID_VOL_0_STATUS_RESYNC)) bv->bv_status = BIOC_SVREBUILD; else bv->bv_status = BIOC_SVDEGRADED; break; case MPII_CFG_RAID_VOL_0_STATE_FAILED: bv->bv_status = BIOC_SVOFFLINE; break; case MPII_CFG_RAID_VOL_0_STATE_INITIALIZING: bv->bv_status = BIOC_SVBUILDING; break; case MPII_CFG_RAID_VOL_0_STATE_MISSING: default: bv->bv_status = BIOC_SVINVALID; break; } free(vpg, M_TEMP); return (0); } static int mpii_create_sensors(struct mpii_softc *sc) { int i, rv; DNPRINTF(MPII_D_MISC, "%s: mpii_create_sensors(%d)\n", DEVNAME(sc), sc->sc_max_volumes); sc->sc_sme = sysmon_envsys_create(); sc->sc_sensors = malloc(sizeof(envsys_data_t) * sc->sc_max_volumes, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0; i < sc->sc_max_volumes; i++) { sc->sc_sensors[i].units = ENVSYS_DRIVE; sc->sc_sensors[i].state = ENVSYS_SINVALID; sc->sc_sensors[i].value_cur = ENVSYS_DRIVE_EMPTY; sc->sc_sensors[i].flags |= ENVSYS_FMONSTCHANGED; /* logical drives */ snprintf(sc->sc_sensors[i].desc, sizeof(sc->sc_sensors[i].desc), "%s:%d", DEVNAME(sc), i); if ((rv = sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensors[i])) != 0) { aprint_error_dev(sc->sc_dev, "unable to attach sensor (rv = %d)\n", rv); goto out; } } sc->sc_sme->sme_name = DEVNAME(sc); sc->sc_sme->sme_cookie = sc; sc->sc_sme->sme_refresh = mpii_refresh_sensors; rv = sysmon_envsys_register(sc->sc_sme); if (rv != 0) { aprint_error_dev(sc->sc_dev, "unable to register with sysmon (rv = %d)\n", rv); goto out; } return 0; out: free(sc->sc_sensors, M_DEVBUF); sysmon_envsys_destroy(sc->sc_sme); sc->sc_sme = NULL; return 1; } static int mpii_destroy_sensors(struct mpii_softc *sc) { if (sc->sc_sme == NULL) return 0; sysmon_envsys_unregister(sc->sc_sme); sc->sc_sme = NULL; free(sc->sc_sensors, M_DEVBUF); return 0; } static void mpii_refresh_sensors(struct sysmon_envsys *sme, envsys_data_t *edata) { struct mpii_softc *sc = sme->sme_cookie; struct bioc_vol bv; memset(&bv, 0, sizeof(bv)); bv.bv_volid = edata->sensor; if (mpii_bio_volstate(sc, &bv)) bv.bv_status = BIOC_SVINVALID; bio_vol_to_envsys(edata, &bv); } #endif /* NBIO > 0 */