/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2023 Tintri by DDN, Inc. All rights reserved. * Copyright 2021 RackTop Systems, Inc. */ /* * This file contains all routines necessary to interface with SCSA trans. */ #include /* * []------------------------------------------------------------------[] * | Forward declarations for SCSA trans routines. | * []------------------------------------------------------------------[] */ static int pqi_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd); static void pqi_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd); static int pqi_start(struct scsi_address *ap, struct scsi_pkt *pkt); static int pqi_scsi_reset(struct scsi_address *ap, int level); static int pqi_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt); static int pqi_scsi_getcap(struct scsi_address *ap, char *cap, int tgtonly); static int pqi_scsi_setcap(struct scsi_address *ap, char *cap, int value, int tgtonly); static struct scsi_pkt *pqi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(), caddr_t arg); static void pqi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt); static void pqi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt); static void pqi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt); static int pqi_reset_notify(struct scsi_address *ap, int flag, void (*callback)(caddr_t), caddr_t arg); static int pqi_quiesce(dev_info_t *dip); static int pqi_unquiesce(dev_info_t *dip); static int pqi_bus_config(dev_info_t *pdip, uint_t flag, ddi_bus_config_op_t op, void *arg, dev_info_t **childp); /* ---- Support method declaration ---- */ static int config_one(dev_info_t *pdip, pqi_state_t *s, pqi_device_t *, dev_info_t **childp); static void abort_all(struct scsi_address *ap, pqi_state_t *s); static int cmd_ext_alloc(pqi_cmd_t *cmd, int kf); static void cmd_ext_free(pqi_cmd_t *cmd); static boolean_t is_physical_dev(pqi_device_t *d); static void cmd_timeout_scan(void *); boolean_t smartpqi_register_hba(pqi_state_t *s) { scsi_hba_tran_t *tran; int flags; char iport_str[16]; int instance = ddi_get_instance(s->s_dip); tran = scsi_hba_tran_alloc(s->s_dip, SCSI_HBA_CANSLEEP); if (tran == NULL) return (B_FALSE); s->s_tran = tran; tran->tran_hba_private = s; tran->tran_tgt_private = NULL; tran->tran_tgt_init = pqi_scsi_tgt_init; tran->tran_tgt_free = pqi_scsi_tgt_free; tran->tran_tgt_probe = scsi_hba_probe; tran->tran_start = pqi_start; tran->tran_reset = pqi_scsi_reset; tran->tran_abort = pqi_scsi_abort; tran->tran_getcap = pqi_scsi_getcap; tran->tran_setcap = pqi_scsi_setcap; tran->tran_bus_config = pqi_bus_config; tran->tran_init_pkt = pqi_init_pkt; tran->tran_destroy_pkt = pqi_destroy_pkt; tran->tran_dmafree = pqi_dmafree; tran->tran_sync_pkt = pqi_sync_pkt; tran->tran_reset_notify = pqi_reset_notify; tran->tran_quiesce = pqi_quiesce; tran->tran_unquiesce = pqi_unquiesce; tran->tran_bus_reset = NULL; tran->tran_add_eventcall = NULL; tran->tran_get_eventcookie = NULL; tran->tran_post_event = NULL; tran->tran_remove_eventcall = NULL; tran->tran_bus_config = pqi_bus_config; tran->tran_interconnect_type = INTERCONNECT_SAS; /* * scsi_vhci needs to have "initiator-port" set, but doesn't * seem to care what it's set to. iSCSI uses the InitiatorName * whereas mpt_sas uses the WWN port id, but this HBA doesn't * have such a value. So, for now the instance number will be used. */ (void) snprintf(iport_str, sizeof (iport_str), "0x%x", instance); if (ddi_prop_update_string(DDI_DEV_T_NONE, s->s_dip, SCSI_ADDR_PROP_INITIATOR_PORT, iport_str) != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "%s: Failed to create prop (%s) on %d\n", __func__, SCSI_ADDR_PROP_INITIATOR_PORT, instance); } flags = SCSI_HBA_ADDR_COMPLEX | SCSI_HBA_TRAN_SCB; if (scsi_hba_attach_setup(s->s_dip, &s->s_msg_dma_attr, tran, flags) != DDI_SUCCESS) { dev_err(s->s_dip, CE_NOTE, "scsi_hba_attach_setup failed"); scsi_hba_tran_free(s->s_tran); s->s_tran = NULL; return (B_FALSE); } if (!s->s_disable_mpxio) { if (mdi_phci_register(MDI_HCI_CLASS_SCSI, s->s_dip, 0) != MDI_SUCCESS) { cmn_err(CE_WARN, "%s: Failed to register with mpxio", __func__); s->s_disable_mpxio = B_TRUE; } } s->s_cmd_timeout = timeout(cmd_timeout_scan, s, CMD_TIMEOUT_SCAN_SECS * drv_usectohz(MICROSEC)); return (B_TRUE); } void smartpqi_unregister_hba(pqi_state_t *s) { if (!s->s_disable_mpxio) (void) mdi_phci_unregister(s->s_dip, 0); if (s->s_cmd_timeout != NULL) { (void) untimeout(s->s_cmd_timeout); s->s_cmd_timeout = NULL; } if (s->s_tran == NULL) return; scsi_hba_tran_free(s->s_tran); s->s_tran = NULL; } static int pqi_scsi_tgt_init(dev_info_t *hba_dip __unused, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd) { pqi_device_t *d; pqi_state_t *s = hba_tran->tran_hba_private; mdi_pathinfo_t *pip; int type; char *ua; if ((ua = scsi_device_unit_address(sd)) == NULL) { return (DDI_FAILURE); } if ((d = pqi_find_target_ua(s, ua)) == NULL) { return (DDI_FAILURE); } scsi_device_hba_private_set(sd, d); type = mdi_get_component_type(tgt_dip); if (type == MDI_COMPONENT_CLIENT) { char wwid_str[64]; if ((pip = (mdi_pathinfo_t *)sd->sd_private) == NULL) return (DDI_NOT_WELL_FORMED); (void) snprintf(wwid_str, sizeof (wwid_str), "%" PRIx64, d->pd_wwid); (void) mdi_prop_update_string(pip, SCSI_ADDR_PROP_TARGET_PORT, wwid_str); } return (DDI_SUCCESS); } static void pqi_scsi_tgt_free(dev_info_t *hba_dip __unused, dev_info_t *tgt_dip __unused, scsi_hba_tran_t *hba_tran __unused, struct scsi_device *sd __unused) { } /* * Notes: * - transport the command to the addressed SCSI target/lun device * - normal operation is to schedule the command to be transported, * and return TRAN_ACCEPT if this is successful. * - if NO_INTR, tran_start must poll device for command completion */ static int pqi_start(struct scsi_address *ap, struct scsi_pkt *pkt) { boolean_t poll = ((pkt->pkt_flags & FLAG_NOINTR) != 0); int rc; pqi_cmd_t *cmd = PKT2CMD(pkt); pqi_state_t *s = ap->a_hba_tran->tran_hba_private; ASSERT3P(cmd->pc_pkt, ==, pkt); ASSERT3P(cmd->pc_softc, ==, s); if (pqi_is_offline(s) || !cmd->pc_device->pd_online) return (TRAN_FATAL_ERROR); /* * Reinitialize some fields because the packet may have been * resubmitted. */ pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state = 0; pkt->pkt_statistics = 0; /* ---- Zero status byte ---- */ *(pkt->pkt_scbp) = 0; if ((cmd->pc_flags & PQI_FLAG_DMA_VALID) != 0) { ASSERT(cmd->pc_dma_count); pkt->pkt_resid = cmd->pc_dma_count; /* ---- Sync consistent packets first (only write data) ---- */ if (((cmd->pc_flags & PQI_FLAG_IO_IOPB) != 0) || ((cmd->pc_flags & PQI_FLAG_IO_READ) == 0)) { (void) ddi_dma_sync(cmd->pc_dmahdl, 0, 0, DDI_DMA_SYNC_FORDEV); } } mutex_enter(&s->s_mutex); if (HBA_IS_QUIESCED(s) && !poll) { mutex_exit(&s->s_mutex); return (TRAN_BUSY); } mutex_exit(&s->s_mutex); rc = pqi_transport_command(s, cmd); if (poll) { boolean_t qnotify; if (rc == TRAN_ACCEPT) { uint32_t old_state; int timeo; timeo = pkt->pkt_time ? pkt->pkt_time : SCSI_POLL_TIMEOUT; timeo *= MILLISEC / 2; old_state = pqi_disable_intr(s); do { drv_usecwait(MILLISEC / 2); pqi_process_io_intr(s, &s->s_queue_groups[0]); if (--timeo == 0) { pkt->pkt_state |= STAT_TIMEOUT; pkt->pkt_reason = CMD_TIMEOUT; break; } } while (pkt->pkt_state == 0); pqi_enable_intr(s, old_state); } scsi_hba_pkt_comp(pkt); mutex_enter(&s->s_mutex); qnotify = HBA_QUIESCED_PENDING(s); mutex_exit(&s->s_mutex); if (qnotify) pqi_quiesced_notify(s); } return (rc); } static int pqi_scsi_reset(struct scsi_address *ap, int level) { pqi_device_t *d; pqi_state_t *s; int rval = FALSE; s = ap->a_hba_tran->tran_hba_private; switch (level) { case RESET_TARGET: case RESET_LUN: if ((d = scsi_device_hba_private_get(ap->a.a_sd)) == NULL) break; pqi_lun_reset(s, d); rval = TRUE; break; case RESET_BUS: case RESET_ALL: mutex_enter(&s->s_mutex); for (d = list_head(&s->s_devnodes); d != NULL; d = list_next(&s->s_devnodes, d)) { pqi_lun_reset(s, d); } mutex_exit(&s->s_mutex); rval = TRUE; break; } return (rval); } /* * abort handling: * * Notes: * - if pkt is not NULL, abort just that command * - if pkt is NULL, abort all outstanding commands for target */ static int pqi_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt) { boolean_t qnotify = B_FALSE; pqi_state_t *s = ADDR2PQI(ap); if (pkt != NULL) { /* ---- Abort single command ---- */ pqi_cmd_t *cmd = PKT2CMD(pkt); mutex_enter(&cmd->pc_device->pd_mutex); (void) pqi_fail_cmd(cmd, CMD_ABORTED, STAT_ABORTED); mutex_exit(&cmd->pc_device->pd_mutex); } else { abort_all(ap, s); } qnotify = HBA_QUIESCED_PENDING(s); if (qnotify) pqi_quiesced_notify(s); return (1); } /* * capability handling: * (*tran_getcap). Get the capability named, and return its value. */ static int pqi_scsi_getcap(struct scsi_address *ap, char *cap, int tgtonly __unused) { pqi_state_t *s = ap->a_hba_tran->tran_hba_private; if (cap == NULL) return (-1); switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_LUN_RESET: return ((s->s_flags & PQI_HBA_LUN_RESET_CAP) != 0); case SCSI_CAP_ARQ: return ((s->s_flags & PQI_HBA_AUTO_REQUEST_SENSE) != 0); case SCSI_CAP_UNTAGGED_QING: return (1); default: return (-1); } } /* * (*tran_setcap). Set the capability named to the value given. */ static int pqi_scsi_setcap(struct scsi_address *ap, char *cap, int value, int tgtonly __unused) { pqi_state_t *s = ADDR2PQI(ap); int rval = FALSE; if (cap == NULL) return (-1); switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: if (value) s->s_flags |= PQI_HBA_AUTO_REQUEST_SENSE; else s->s_flags &= ~PQI_HBA_AUTO_REQUEST_SENSE; rval = 1; break; case SCSI_CAP_LUN_RESET: if (value) s->s_flags |= PQI_HBA_LUN_RESET_CAP; else s->s_flags &= ~PQI_HBA_LUN_RESET_CAP; break; default: break; } return (rval); } int pqi_cache_constructor(void *buf, void *un, int flags) { pqi_cmd_t *c = (pqi_cmd_t *)buf; pqi_state_t *s = un; int (*callback)(caddr_t); bzero(c, sizeof (*c)); c->pc_softc = s; callback = (flags == KM_SLEEP) ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT; /* ---- Allocate a DMA handle for data transfers ---- */ if (ddi_dma_alloc_handle(s->s_dip, &s->s_msg_dma_attr, callback, NULL, &c->pc_dmahdl) != DDI_SUCCESS) { dev_err(s->s_dip, CE_WARN, "Failed to alloc dma handle"); return (-1); } return (0); } void pqi_cache_destructor(void *buf, void *un __unused) { pqi_cmd_t *cmd = buf; if (cmd->pc_dmahdl != NULL) { (void) ddi_dma_unbind_handle(cmd->pc_dmahdl); ddi_dma_free_handle(&cmd->pc_dmahdl); cmd->pc_dmahdl = NULL; } } /* * tran_init_pkt(9E) - allocate scsi_pkt(9S) for command * * One of three possibilities: * - allocate scsi_pkt * - allocate scsi_pkt and DMA resources * - allocate DMA resources to an already-allocated pkt */ static struct scsi_pkt * pqi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(), caddr_t arg) { pqi_cmd_t *cmd; pqi_state_t *s; int kf = (callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP; boolean_t is_new = B_FALSE; int rc; int i; pqi_device_t *devp; s = ap->a_hba_tran->tran_hba_private; if (pkt == NULL) { ddi_dma_handle_t saved_dmahdl; pqi_cmd_action_t saved_action; if ((devp = scsi_device_hba_private_get(ap->a.a_sd)) == NULL) return (NULL); if ((cmd = kmem_cache_alloc(s->s_cmd_cache, kf)) == NULL) return (NULL); is_new = B_TRUE; saved_dmahdl = cmd->pc_dmahdl; saved_action = cmd->pc_last_action; (void) memset(cmd, 0, sizeof (*cmd)); mutex_init(&cmd->pc_mutex, NULL, MUTEX_DRIVER, NULL); cmd->pc_dmahdl = saved_dmahdl; cmd->pc_last_action = saved_action; cmd->pc_device = devp; cmd->pc_pkt = &cmd->pc_cached_pkt; cmd->pc_softc = s; cmd->pc_tgtlen = tgtlen; cmd->pc_statuslen = statuslen; cmd->pc_cmdlen = cmdlen; cmd->pc_dma_count = 0; pkt = cmd->pc_pkt; pkt->pkt_ha_private = cmd; pkt->pkt_address = *ap; pkt->pkt_scbp = (uint8_t *)&cmd->pc_cmd_scb; pkt->pkt_cdbp = cmd->pc_cdb; pkt->pkt_private = (opaque_t)cmd->pc_tgt_priv; if (pkt->pkt_time == 0) pkt->pkt_time = SCSI_POLL_TIMEOUT; if (cmdlen > sizeof (cmd->pc_cdb) || statuslen > sizeof (cmd->pc_cmd_scb) || tgtlen > sizeof (cmd->pc_tgt_priv)) { if (cmd_ext_alloc(cmd, kf) != DDI_SUCCESS) { dev_err(s->s_dip, CE_WARN, "extent allocation failed"); goto out; } } } else { cmd = PKT2CMD(pkt); cmd->pc_flags &= PQI_FLAGS_PERSISTENT; } /* ---- Handle partial DMA transfer ---- */ if (cmd->pc_nwin > 0) { if (++cmd->pc_winidx >= cmd->pc_nwin) return (NULL); if (ddi_dma_getwin(cmd->pc_dmahdl, cmd->pc_winidx, &cmd->pc_dma_offset, &cmd->pc_dma_len, &cmd->pc_dmac, &cmd->pc_dmaccount) == DDI_FAILURE) return (NULL); goto handle_dma_cookies; } /* ---- Setup data buffer ---- */ if (bp != NULL && bp->b_bcount > 0 && (cmd->pc_flags & PQI_FLAG_DMA_VALID) == 0) { int dma_flags; ASSERT(cmd->pc_dmahdl != NULL); if ((bp->b_flags & B_READ) != 0) { cmd->pc_flags |= PQI_FLAG_IO_READ; dma_flags = DDI_DMA_READ; } else { cmd->pc_flags &= ~PQI_FLAG_IO_READ; dma_flags = DDI_DMA_WRITE; } if ((flags & PKT_CONSISTENT) != 0) { cmd->pc_flags |= PQI_FLAG_IO_IOPB; dma_flags |= DDI_DMA_CONSISTENT; } if ((flags & PKT_DMA_PARTIAL) != 0) { dma_flags |= DDI_DMA_PARTIAL; } rc = ddi_dma_buf_bind_handle(cmd->pc_dmahdl, bp, dma_flags, callback, arg, &cmd->pc_dmac, &cmd->pc_dmaccount); if (rc == DDI_DMA_PARTIAL_MAP) { (void) ddi_dma_numwin(cmd->pc_dmahdl, &cmd->pc_nwin); cmd->pc_winidx = 0; (void) ddi_dma_getwin(cmd->pc_dmahdl, cmd->pc_winidx, &cmd->pc_dma_offset, &cmd->pc_dma_len, &cmd->pc_dmac, &cmd->pc_dmaccount); } else if (rc != 0 && rc != DDI_DMA_MAPPED) { switch (rc) { case DDI_DMA_NORESOURCES: bioerror(bp, 0); break; case DDI_DMA_BADATTR: case DDI_DMA_NOMAPPING: bioerror(bp, EFAULT); break; case DDI_DMA_TOOBIG: default: bioerror(bp, EINVAL); break; } goto out; } handle_dma_cookies: ASSERT(cmd->pc_dmaccount > 0); if (cmd->pc_dmaccount > (sizeof (cmd->pc_cached_cookies) / sizeof (ddi_dma_cookie_t))) { dev_err(s->s_dip, CE_WARN, "invalid cookie count: %d", cmd->pc_dmaccount); goto out; } if (cmd->pc_dmaccount > (s->s_sg_chain_buf_length / sizeof (pqi_sg_entry_t))) { dev_err(s->s_dip, CE_WARN, "Cookie(0x%x) verses SG(0x%" PRIx64 ") mismatch", cmd->pc_dmaccount, s->s_sg_chain_buf_length / sizeof (pqi_sg_entry_t)); goto out; } cmd->pc_flags |= PQI_FLAG_DMA_VALID; cmd->pc_dma_count = cmd->pc_dmac.dmac_size; cmd->pc_cached_cookies[0] = cmd->pc_dmac; for (i = 1; i < cmd->pc_dmaccount; i++) { ddi_dma_nextcookie(cmd->pc_dmahdl, &cmd->pc_dmac); cmd->pc_cached_cookies[i] = cmd->pc_dmac; cmd->pc_dma_count += cmd->pc_dmac.dmac_size; } pkt->pkt_resid = bp->b_bcount - cmd->pc_dma_count; } return (pkt); out: if (is_new == B_TRUE) pqi_destroy_pkt(ap, pkt); return (NULL); } /* * tran_destroy_pkt(9E) - scsi_pkt(9s) deallocation * * Notes: * - also frees DMA resources if allocated * - implicit DMA synchonization */ static void pqi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { pqi_cmd_t *c = PKT2CMD(pkt); pqi_state_t *s = ADDR2PQI(ap); if ((c->pc_flags & PQI_FLAG_DMA_VALID) != 0) { c->pc_flags &= ~PQI_FLAG_DMA_VALID; (void) ddi_dma_unbind_handle(c->pc_dmahdl); } cmd_ext_free(c); kmem_cache_free(s->s_cmd_cache, c); } /* * tran_dmafree(9E) - deallocate DMA resources allocated for command */ static void pqi_dmafree(struct scsi_address *ap __unused, struct scsi_pkt *pkt) { pqi_cmd_t *cmd = PKT2CMD(pkt); if (cmd->pc_flags & PQI_FLAG_DMA_VALID) { cmd->pc_flags &= ~PQI_FLAG_DMA_VALID; (void) ddi_dma_unbind_handle(cmd->pc_dmahdl); } } /* * tran_sync_pkt(9E) - explicit DMA synchronization */ static void pqi_sync_pkt(struct scsi_address *ap __unused, struct scsi_pkt *pkt) { pqi_cmd_t *cmd = PKT2CMD(pkt); if (cmd->pc_dmahdl != NULL) { (void) ddi_dma_sync(cmd->pc_dmahdl, 0, 0, (cmd->pc_flags & PQI_FLAG_IO_READ) ? DDI_DMA_SYNC_FORCPU : DDI_DMA_SYNC_FORDEV); } } static int pqi_reset_notify(struct scsi_address *ap, int flag, void (*callback)(caddr_t), caddr_t arg) { pqi_state_t *s = ADDR2PQI(ap); return (scsi_hba_reset_notify_setup(ap, flag, callback, arg, &s->s_mutex, &s->s_reset_notify_listf)); } /* * Device / Hotplug control */ static int pqi_quiesce(dev_info_t *dip) { pqi_state_t *s; scsi_hba_tran_t *tran; if ((tran = ddi_get_driver_private(dip)) == NULL || (s = TRAN2PQI(tran)) == NULL) return (-1); mutex_enter(&s->s_mutex); if (!HBA_IS_QUIESCED(s)) s->s_flags |= PQI_HBA_QUIESCED; if (s->s_cmd_queue_len != 0) { /* ---- Outstanding commands present, wait ---- */ s->s_flags |= PQI_HBA_QUIESCED_PENDING; cv_wait(&s->s_quiescedvar, &s->s_mutex); ASSERT0(s->s_cmd_queue_len); } mutex_exit(&s->s_mutex); return (0); } static int pqi_unquiesce(dev_info_t *dip) { pqi_state_t *s; scsi_hba_tran_t *tran; if ((tran = ddi_get_driver_private(dip)) == NULL || (s = TRAN2PQI(tran)) == NULL) return (-1); mutex_enter(&s->s_mutex); if (!HBA_IS_QUIESCED(s)) { mutex_exit(&s->s_mutex); return (0); } ASSERT0(s->s_cmd_queue_len); s->s_flags &= ~PQI_HBA_QUIESCED; mutex_exit(&s->s_mutex); return (0); } static int pqi_bus_config(dev_info_t *pdip, uint_t flag, ddi_bus_config_op_t op, void *arg, dev_info_t **childp) { scsi_hba_tran_t *tran; pqi_state_t *s; int ret = NDI_FAILURE; pqi_device_t *d; char *ua; tran = ddi_get_driver_private(pdip); s = tran->tran_hba_private; if (pqi_is_offline(s)) return (NDI_FAILURE); ndi_devi_enter(scsi_vhci_dip); ndi_devi_enter(pdip); switch (op) { case BUS_CONFIG_ONE: if ((ua = strrchr((char *)arg, '@')) != NULL) { ua++; d = pqi_find_target_ua(s, ua); if (d != NULL) ret = config_one(pdip, s, d, childp); } else { dev_err(s->s_dip, CE_WARN, "Couldn't decode %s", (char *)arg); } flag |= NDI_MDI_FALLBACK; break; case BUS_CONFIG_DRIVER: case BUS_CONFIG_ALL: ret = pqi_config_all(pdip, s); break; default: ret = NDI_FAILURE; } if (ret == NDI_SUCCESS) ret = ndi_busop_bus_config(pdip, flag, op, arg, childp, 0); ndi_devi_exit(pdip); ndi_devi_exit(scsi_vhci_dip); return (ret); } pqi_device_t * pqi_find_target_ua(pqi_state_t *s, char *ua) { pqi_device_t *d; mutex_enter(&s->s_mutex); for (d = list_head(&s->s_devnodes); d != NULL; d = list_next(&s->s_devnodes, d)) { if (d->pd_online && strcmp(ua, d->pd_unit_address) == 0) break; } mutex_exit(&s->s_mutex); return (d); } int pqi_config_all(dev_info_t *pdip, pqi_state_t *s) { pqi_device_t *d; /* * Make sure we bring the available devices into play first. These * might be brand new devices just hotplugged into the system or * they could be devices previously offlined because either they * were pulled from an enclosure or a cable to the enclosure was * pulled. */ /* ---- XXX Grab s_mutex ---- */ for (d = list_head(&s->s_devnodes); d != NULL; d = list_next(&s->s_devnodes, d)) { if (d->pd_online) (void) config_one(pdip, s, d, NULL); } /* * Now deal with devices that we had previously known about, but are * no longer available. */ for (d = list_head(&s->s_devnodes); d != NULL; d = list_next(&s->s_devnodes, d)) { if (!d->pd_online) (void) config_one(pdip, s, d, NULL); } return (NDI_SUCCESS); } void pqi_quiesced_notify(pqi_state_t *s) { mutex_enter(&s->s_mutex); if (s->s_cmd_queue_len == 0 && (s->s_flags & PQI_HBA_QUIESCED_PENDING) != 0) { s->s_flags &= ~PQI_HBA_QUIESCED_PENDING; cv_broadcast(&s->s_quiescedvar); } mutex_exit(&s->s_mutex); } /* * []------------------------------------------------------------------[] * | Support routines used only by the trans_xxx routines | * []------------------------------------------------------------------[] */ #ifdef DEBUG int pqi_force_timeout; #endif /* DEBUG */ static void cmd_timeout_drive(pqi_device_t *d) { uint32_t timed_out_cnt = 0; pqi_cmd_t *c, *next_c; hrtime_t now = gethrtime(); mutex_enter(&d->pd_mutex); rescan: c = list_head(&d->pd_cmd_list); while (c != NULL) { next_c = list_next(&d->pd_cmd_list, c); #ifdef DEBUG if (c->pc_expiration < now || pqi_force_timeout != 0) { pqi_force_timeout = 0; #else if (c->pc_expiration < now) { #endif /* DEBUG */ struct scsi_pkt *pkt = CMD2PKT(c); if (pkt != NULL) { pkt->pkt_reason = CMD_TIMEOUT; pkt->pkt_statistics = STAT_TIMEOUT; } ASSERT(c->pc_io_rqst != NULL); /* * If the i/o has not been serviced yet, * mark the i/o as timed out and clear it out */ if (pqi_timeout_io(c->pc_io_rqst)) { (void) pqi_cmd_action_nolock(c, PQI_CMD_TIMEOUT); timed_out_cnt++; /* * We dropped pd_mutex so the cmd * list could have changed, restart the * scan of the cmds. This will terminate * since timed out cmds are removed from * the list. */ goto rescan; } } c = next_c; } d->pd_timedout += timed_out_cnt; mutex_exit(&d->pd_mutex); } static void cmd_timeout_scan(void *v) { pqi_state_t *s = v; pqi_device_t *d; mutex_enter(&s->s_mutex); for (d = list_head(&s->s_devnodes); d != NULL; d = list_next(&s->s_devnodes, d)) { cmd_timeout_drive(d); } cmd_timeout_drive(&s->s_special_device); mutex_exit(&s->s_mutex); s->s_cmd_timeout = timeout(cmd_timeout_scan, s, CMD_TIMEOUT_SCAN_SECS * drv_usectohz(MICROSEC)); } static void abort_all(struct scsi_address *ap, pqi_state_t *s __unused) { pqi_device_t *devp; if ((devp = scsi_device_hba_private_get(ap->a.a_sd)) == NULL) return; pqi_fail_drive_cmds(devp, CMD_ABORTED); } static boolean_t create_phys_lun(pqi_state_t *s, pqi_device_t *d, struct scsi_inquiry *inq, dev_info_t **childp) { char **compatible = NULL; char *nodename = NULL; int ncompatible = 0; dev_info_t *dip; /* ---- At this point we have a new device not in our list ---- */ scsi_hba_nodename_compatible_get(inq, NULL, inq->inq_dtype, NULL, &nodename, &compatible, &ncompatible); if (nodename == NULL) return (B_FALSE); if (ndi_devi_alloc(s->s_dip, nodename, DEVI_SID_NODEID, &dip) != NDI_SUCCESS) { dev_err(s->s_dip, CE_WARN, "failed to alloc device instance"); goto free_nodename; } d->pd_dip = dip; d->pd_pip = NULL; if (ndi_prop_update_int64(DDI_DEV_T_NONE, dip, LUN64_PROP, d->pd_lun) != DDI_PROP_SUCCESS) { goto free_devi; } if (ndi_prop_update_string_array(DDI_DEV_T_NONE, dip, COMPAT_PROP, compatible, ncompatible) != DDI_PROP_SUCCESS) { goto free_devi; } if (d->pd_wwid != 0) { char wwn_str[20]; (void) snprintf(wwn_str, 20, "w%016" PRIx64, d->pd_wwid); if (ndi_prop_update_string(DDI_DEV_T_NONE, dip, SCSI_ADDR_PROP_TARGET_PORT, wwn_str) != DDI_PROP_SUCCESS) { goto free_devi; } } else { if (ndi_prop_update_int(DDI_DEV_T_NONE, dip, TARGET_PROP, d->pd_target) != DDI_PROP_SUCCESS) { goto free_devi; } } if (d->pd_guid != NULL) { if (ddi_prop_update_string(DDI_DEV_T_NONE, dip, NDI_GUID, d->pd_guid) != DDI_PROP_SUCCESS) { goto free_devi; } } if (ndi_prop_update_int(DDI_DEV_T_NONE, dip, "pm-capable", 1) != DDI_PROP_SUCCESS) { goto free_devi; } if (ndi_devi_online(dip, NDI_ONLINE_ATTACH) != NDI_SUCCESS) goto free_devi; if (childp != NULL) *childp = dip; scsi_hba_nodename_compatible_free(nodename, compatible); return (B_TRUE); free_devi: ndi_prop_remove_all(dip); (void) ndi_devi_free(dip); d->pd_dip = NULL; free_nodename: scsi_hba_nodename_compatible_free(nodename, compatible); return (B_FALSE); } static boolean_t create_virt_lun(pqi_state_t *s, pqi_device_t *d, struct scsi_inquiry *inq, dev_info_t **childp) { char *nodename; char **compatible; int ncompatible; int rval; mdi_pathinfo_t *pip = NULL; char *guid_ptr; char wwid_str[17]; dev_info_t *lun_dip; char *old_guid; if (d->pd_pip_offlined != NULL) { lun_dip = mdi_pi_get_client(d->pd_pip_offlined); ASSERT(lun_dip != NULL); if (ddi_prop_lookup_string(DDI_DEV_T_ANY, lun_dip, (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM), MDI_CLIENT_GUID_PROP, &old_guid) == DDI_SUCCESS) { if (strncmp(d->pd_guid, old_guid, strlen(d->pd_guid)) == 0) { /* ---- Same path came back online ---- */ (void) ddi_prop_free(old_guid); if (mdi_pi_online(d->pd_pip_offlined, 0) == DDI_SUCCESS) { d->pd_pip = d->pd_pip_offlined; d->pd_pip_offlined = NULL; return (B_TRUE); } else { return (B_FALSE); } } else { /* ---- Different device in slot ---- */ (void) ddi_prop_free(old_guid); if (mdi_pi_offline(d->pd_pip_offlined, 0) != DDI_SUCCESS) { return (B_FALSE); } if (mdi_pi_free(d->pd_pip_offlined, 0) != MDI_SUCCESS) { return (B_FALSE); } d->pd_pip_offlined = NULL; } } else { dev_err(s->s_dip, CE_WARN, "Can't get client-guid " "property for lun %lx", d->pd_wwid); return (B_FALSE); } } scsi_hba_nodename_compatible_get(inq, NULL, inq->inq_dtype, NULL, &nodename, &compatible, &ncompatible); if (nodename == NULL) return (B_FALSE); if (d->pd_guid != NULL) { guid_ptr = d->pd_guid; } else { (void) snprintf(wwid_str, sizeof (wwid_str), "%" PRIx64, d->pd_wwid); guid_ptr = wwid_str; } rval = mdi_pi_alloc_compatible(s->s_dip, nodename, guid_ptr, d->pd_unit_address, compatible, ncompatible, 0, &pip); if (rval == MDI_SUCCESS) { mdi_pi_set_phci_private(pip, (caddr_t)d); if (mdi_prop_update_string(pip, MDI_GUID, guid_ptr) != DDI_SUCCESS) { dev_err(s->s_dip, CE_WARN, "unable to create property (MDI_GUID) for %s", guid_ptr); goto cleanup; } /* * For MPxIO, we actually don't really need to care * about the LUN or target property, because nothing * really uses them. */ if (mdi_prop_update_int64(pip, LUN64_PROP, d->pd_lun) != DDI_SUCCESS) { dev_err(s->s_dip, CE_WARN, "unable to create property (%s) for %s", LUN64_PROP, guid_ptr); goto cleanup; } if (mdi_prop_update_string_array(pip, COMPAT_PROP, compatible, ncompatible) != DDI_SUCCESS) { dev_err(s->s_dip, CE_WARN, "unable to create property (%s) for %s", COMPAT_PROP, guid_ptr); goto cleanup; } if (mdi_pi_online(pip, 0) == MDI_NOT_SUPPORTED) goto cleanup; d->pd_dip = NULL; d->pd_pip = pip; } scsi_hba_nodename_compatible_free(nodename, compatible); if (childp != NULL) *childp = mdi_pi_get_client(pip); return (B_TRUE); cleanup: scsi_hba_nodename_compatible_free(nodename, compatible); d->pd_pip = NULL; d->pd_dip = NULL; (void) mdi_prop_remove(pip, NULL); (void) mdi_pi_free(pip, 0); return (B_FALSE); } static int config_one(dev_info_t *pdip, pqi_state_t *s, pqi_device_t *d, dev_info_t **childp) { struct scsi_inquiry inq; boolean_t rval = B_FALSE; /* ---- Inquiry target ---- */ if (!d->pd_online || pqi_scsi_inquiry(s, d, 0, &inq, sizeof (inq)) == B_FALSE) { pqi_fail_drive_cmds(d, CMD_DEV_GONE); if (d->pd_dip != NULL) { (void) ndi_devi_offline(d->pd_dip, NDI_DEVFS_CLEAN | NDI_DEVI_REMOVE); d->pd_dip = NULL; } else if (d->pd_pip != NULL) { (void) mdi_pi_offline(d->pd_pip, 0); d->pd_pip_offlined = d->pd_pip; d->pd_pip = NULL; } return (NDI_FAILURE); } else if (d->pd_dip != NULL) { if (childp != NULL) *childp = d->pd_dip; return (NDI_SUCCESS); } else if (d->pd_pip != NULL) { if (childp != NULL) *childp = mdi_pi_get_client(d->pd_pip); return (NDI_SUCCESS); } d->pd_parent = pdip; if ((!s->s_disable_mpxio) && is_physical_dev(d)) rval = create_virt_lun(s, d, &inq, childp); if (rval == B_FALSE) rval = create_phys_lun(s, d, &inq, childp); return ((rval == B_TRUE) ? NDI_SUCCESS : NDI_FAILURE); } static void cmd_ext_free(pqi_cmd_t *cmd) { struct scsi_pkt *pkt = CMD2PKT(cmd); if ((cmd->pc_flags & PQI_FLAG_CDB_EXT) != 0) { kmem_free(pkt->pkt_cdbp, cmd->pc_cmdlen); cmd->pc_flags &= ~PQI_FLAG_CDB_EXT; } if ((cmd->pc_flags & PQI_FLAG_SCB_EXT) != 0) { kmem_free(pkt->pkt_scbp, cmd->pc_statuslen); cmd->pc_flags &= ~PQI_FLAG_SCB_EXT; } if ((cmd->pc_flags & PQI_FLAG_PRIV_EXT) != 0) { kmem_free(pkt->pkt_private, cmd->pc_tgtlen); cmd->pc_flags &= ~PQI_FLAG_PRIV_EXT; } } static int cmd_ext_alloc(pqi_cmd_t *cmd, int kf) { struct scsi_pkt *pkt = CMD2PKT(cmd); void *buf; if (cmd->pc_cmdlen > sizeof (cmd->pc_cdb)) { if ((buf = kmem_zalloc(cmd->pc_cmdlen, kf)) == NULL) return (DDI_FAILURE); pkt->pkt_cdbp = buf; cmd->pc_flags |= PQI_FLAG_CDB_EXT; } if (cmd->pc_statuslen > sizeof (cmd->pc_cmd_scb)) { if ((buf = kmem_zalloc(cmd->pc_statuslen, kf)) == NULL) goto out; pkt->pkt_scbp = buf; cmd->pc_flags |= PQI_FLAG_SCB_EXT; } if (cmd->pc_tgtlen > sizeof (cmd->pc_tgt_priv)) { if ((buf = kmem_zalloc(cmd->pc_tgtlen, kf)) == NULL) goto out; pkt->pkt_private = buf; cmd->pc_flags |= PQI_FLAG_PRIV_EXT; } return (DDI_SUCCESS); out: cmd_ext_free(cmd); return (DDI_FAILURE); } static boolean_t is_physical_dev(pqi_device_t *d) { return (d->pd_phys_dev ? B_TRUE : B_FALSE); }