/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2011 Bayard G. Bell. All rights reserved. */ /* * Copyright (c) 1999,2000 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 2002 Eric Moore * Copyright (c) 2002 LSI Logic Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The party using or redistributing the source code and binary forms * agrees to the disclaimer below and the terms and conditions set forth * herein. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "amrreg.h" #include "amrvar.h" /* dynamic debug symbol */ int amr_debug_var = 0; #define AMR_DELAY(cond, count, done_flag) { \ int local_counter = 0; \ done_flag = 1; \ while (!(cond)) { \ delay(drv_usectohz(100)); \ if ((local_counter) > count) { \ done_flag = 0; \ break; \ } \ (local_counter)++; \ } \ } #define AMR_BUSYWAIT(cond, count, done_flag) { \ int local_counter = 0; \ done_flag = 1; \ while (!(cond)) { \ drv_usecwait(100); \ if ((local_counter) > count) { \ done_flag = 0; \ break; \ } \ (local_counter)++; \ } \ } /* * driver interfaces */ static uint_t amr_intr(caddr_t arg); static void amr_done(struct amr_softs *softs); static int amr_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int amr_attach(dev_info_t *, ddi_attach_cmd_t); static int amr_detach(dev_info_t *, ddi_detach_cmd_t); static int amr_setup_mbox(struct amr_softs *softs); static int amr_setup_sg(struct amr_softs *softs); /* * Command wrappers */ static int amr_query_controller(struct amr_softs *softs); static void *amr_enquiry(struct amr_softs *softs, size_t bufsize, uint8_t cmd, uint8_t cmdsub, uint8_t cmdqual); static int amr_flush(struct amr_softs *softs); /* * Command processing. */ static void amr_rw_command(struct amr_softs *softs, struct scsi_pkt *pkt, int lun); static void amr_mode_sense(union scsi_cdb *cdbp, struct buf *bp, unsigned int capacity); static void amr_set_arq_data(struct scsi_pkt *pkt, uchar_t key); static int amr_enquiry_mapcmd(struct amr_command *ac, uint32_t data_size); static void amr_enquiry_unmapcmd(struct amr_command *ac); static int amr_mapcmd(struct amr_command *ac, int (*callback)(), caddr_t arg); static void amr_unmapcmd(struct amr_command *ac); /* * Status monitoring */ static void amr_periodic(void *data); /* * Interface-specific shims */ static int amr_poll_command(struct amr_command *ac); static void amr_start_waiting_queue(void *softp); static void amr_call_pkt_comp(struct amr_command *head); /* * SCSI interface */ static int amr_setup_tran(dev_info_t *dip, struct amr_softs *softp); /* * Function prototypes * * SCSA functions exported by means of the transport table */ static int amr_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd); static int amr_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt); static int amr_tran_reset(struct scsi_address *ap, int level); static int amr_tran_getcap(struct scsi_address *ap, char *cap, int whom); static int amr_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom); static struct scsi_pkt *amr_tran_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 amr_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt); static void amr_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt); static void amr_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt); static ddi_dma_attr_t buffer_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x00ffffffull, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ AMR_NSEG, /* maximum number of segments */ AMR_BLKSIZE, /* granularity */ 0, /* flags (reserved) */ }; static ddi_dma_attr_t addr_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0, /* flags (reserved) */ }; static struct dev_ops amr_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ amr_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ amr_attach, /* attach */ amr_detach, /* detach */ nodev, /* reset */ NULL, /* driver operations */ (struct bus_ops *)0, /* bus operations */ 0, /* power */ ddi_quiesce_not_supported, /* devo_quiesce */ }; extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, /* Type of module. driver here */ "AMR Driver", /* Name of the module. */ &amr_ops, /* Driver ops vector */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; /* DMA access attributes */ static ddi_device_acc_attr_t accattr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static struct amr_softs *amr_softstatep; int _init(void) { int error; error = ddi_soft_state_init((void *)&amr_softstatep, sizeof (struct amr_softs), 0); if (error != 0) goto error_out; if ((error = scsi_hba_init(&modlinkage)) != 0) { ddi_soft_state_fini((void*)&amr_softstatep); goto error_out; } error = mod_install(&modlinkage); if (error != 0) { scsi_hba_fini(&modlinkage); ddi_soft_state_fini((void*)&amr_softstatep); goto error_out; } return (error); error_out: cmn_err(CE_NOTE, "_init failed"); return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) != 0) { return (error); } scsi_hba_fini(&modlinkage); ddi_soft_state_fini((void*)&amr_softstatep); return (error); } static int amr_attach(dev_info_t *dev, ddi_attach_cmd_t cmd) { struct amr_softs *softs; int error; uint32_t command, i; int instance; caddr_t cfgaddr; instance = ddi_get_instance(dev); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: return (DDI_FAILURE); default: return (DDI_FAILURE); } /* * Initialize softs. */ if (ddi_soft_state_zalloc(amr_softstatep, instance) != DDI_SUCCESS) return (DDI_FAILURE); softs = ddi_get_soft_state(amr_softstatep, instance); softs->state |= AMR_STATE_SOFT_STATE_SETUP; softs->dev_info_p = dev; AMRDB_PRINT((CE_NOTE, "softs: %p; busy_slot addr: %p", (void *)softs, (void *)&(softs->amr_busyslots))); if (pci_config_setup(dev, &(softs->pciconfig_handle)) != DDI_SUCCESS) { goto error_out; } softs->state |= AMR_STATE_PCI_CONFIG_SETUP; error = ddi_regs_map_setup(dev, 1, &cfgaddr, 0, 0, &accattr, &(softs->regsmap_handle)); if (error != DDI_SUCCESS) { goto error_out; } softs->state |= AMR_STATE_PCI_MEM_MAPPED; /* * Determine board type. */ command = pci_config_get16(softs->pciconfig_handle, PCI_CONF_COMM); /* * Make sure we are going to be able to talk to this board. */ if ((command & PCI_COMM_MAE) == 0) { AMRDB_PRINT((CE_NOTE, "memory window not available")); goto error_out; } /* force the busmaster enable bit on */ if (!(command & PCI_COMM_ME)) { command |= PCI_COMM_ME; pci_config_put16(softs->pciconfig_handle, PCI_CONF_COMM, command); command = pci_config_get16(softs->pciconfig_handle, PCI_CONF_COMM); if (!(command & PCI_COMM_ME)) goto error_out; } /* * Allocate and connect our interrupt. */ if (ddi_intr_hilevel(dev, 0) != 0) { AMRDB_PRINT((CE_NOTE, "High level interrupt is not supported!")); goto error_out; } if (ddi_get_iblock_cookie(dev, 0, &softs->iblock_cookiep) != DDI_SUCCESS) { goto error_out; } mutex_init(&softs->cmd_mutex, NULL, MUTEX_DRIVER, softs->iblock_cookiep); /* should be used in interrupt */ mutex_init(&softs->queue_mutex, NULL, MUTEX_DRIVER, softs->iblock_cookiep); /* should be used in interrupt */ mutex_init(&softs->periodic_mutex, NULL, MUTEX_DRIVER, softs->iblock_cookiep); /* should be used in interrupt */ /* sychronize waits for the busy slots via this cv */ cv_init(&softs->cmd_cv, NULL, CV_DRIVER, NULL); softs->state |= AMR_STATE_KMUTEX_INITED; /* * Do bus-independent initialisation, bring controller online. */ if (amr_setup_mbox(softs) != DDI_SUCCESS) goto error_out; softs->state |= AMR_STATE_MAILBOX_SETUP; if (amr_setup_sg(softs) != DDI_SUCCESS) goto error_out; softs->state |= AMR_STATE_SG_TABLES_SETUP; if (amr_query_controller(softs) != DDI_SUCCESS) goto error_out; /* * A taskq is created for dispatching the waiting queue processing * thread. The threads number equals to the logic drive number and * the thread number should be 1 if there is no logic driver is * configured for this instance. */ if ((softs->amr_taskq = ddi_taskq_create(dev, "amr_taskq", MAX(softs->amr_nlogdrives, 1), TASKQ_DEFAULTPRI, 0)) == NULL) { goto error_out; } softs->state |= AMR_STATE_TASKQ_SETUP; if (ddi_add_intr(dev, 0, &softs->iblock_cookiep, NULL, amr_intr, (caddr_t)softs) != DDI_SUCCESS) { goto error_out; } softs->state |= AMR_STATE_INTR_SETUP; /* set up the tran interface */ if (amr_setup_tran(softs->dev_info_p, softs) != DDI_SUCCESS) { AMRDB_PRINT((CE_NOTE, "setup tran failed")); goto error_out; } softs->state |= AMR_STATE_TRAN_SETUP; /* schedule a thread for periodic check */ mutex_enter(&softs->periodic_mutex); softs->timeout_t = timeout(amr_periodic, (void *)softs, drv_usectohz(500000*AMR_PERIODIC_TIMEOUT)); softs->state |= AMR_STATE_TIMEOUT_ENABLED; mutex_exit(&softs->periodic_mutex); /* print firmware information in verbose mode */ cmn_err(CE_CONT, "?MegaRaid %s %s attached.", softs->amr_product_info.pi_product_name, softs->amr_product_info.pi_firmware_ver); /* clear any interrupts */ AMR_QCLEAR_INTR(softs); return (DDI_SUCCESS); error_out: if (softs->state & AMR_STATE_INTR_SETUP) { ddi_remove_intr(dev, 0, softs->iblock_cookiep); } if (softs->state & AMR_STATE_TASKQ_SETUP) { ddi_taskq_destroy(softs->amr_taskq); } if (softs->state & AMR_STATE_SG_TABLES_SETUP) { for (i = 0; i < softs->sg_max_count; i++) { (void) ddi_dma_unbind_handle( softs->sg_items[i].sg_handle); (void) ddi_dma_mem_free( &((softs->sg_items[i]).sg_acc_handle)); (void) ddi_dma_free_handle( &(softs->sg_items[i].sg_handle)); } } if (softs->state & AMR_STATE_MAILBOX_SETUP) { (void) ddi_dma_unbind_handle(softs->mbox_dma_handle); (void) ddi_dma_mem_free(&softs->mbox_acc_handle); (void) ddi_dma_free_handle(&softs->mbox_dma_handle); } if (softs->state & AMR_STATE_KMUTEX_INITED) { mutex_destroy(&softs->queue_mutex); mutex_destroy(&softs->cmd_mutex); mutex_destroy(&softs->periodic_mutex); cv_destroy(&softs->cmd_cv); } if (softs->state & AMR_STATE_PCI_MEM_MAPPED) ddi_regs_map_free(&softs->regsmap_handle); if (softs->state & AMR_STATE_PCI_CONFIG_SETUP) pci_config_teardown(&softs->pciconfig_handle); if (softs->state & AMR_STATE_SOFT_STATE_SETUP) ddi_soft_state_free(amr_softstatep, instance); return (DDI_FAILURE); } /* * Bring the controller down to a dormant state and detach all child devices. * This function is called during detach, system shutdown. * * Note that we can assume that the bufq on the controller is empty, as we won't * allow shutdown if any device is open. */ /*ARGSUSED*/ static int amr_detach(dev_info_t *dev, ddi_detach_cmd_t cmd) { struct amr_softs *softs; int instance; uint32_t i, done_flag; instance = ddi_get_instance(dev); softs = ddi_get_soft_state(amr_softstatep, instance); /* flush the controllor */ if (amr_flush(softs) != 0) { AMRDB_PRINT((CE_NOTE, "device shutdown failed")); return (EIO); } /* release the amr timer */ mutex_enter(&softs->periodic_mutex); softs->state &= ~AMR_STATE_TIMEOUT_ENABLED; if (softs->timeout_t) { (void) untimeout(softs->timeout_t); softs->timeout_t = 0; } mutex_exit(&softs->periodic_mutex); for (i = 0; i < softs->sg_max_count; i++) { (void) ddi_dma_unbind_handle( softs->sg_items[i].sg_handle); (void) ddi_dma_mem_free( &((softs->sg_items[i]).sg_acc_handle)); (void) ddi_dma_free_handle( &(softs->sg_items[i].sg_handle)); } (void) ddi_dma_unbind_handle(softs->mbox_dma_handle); (void) ddi_dma_mem_free(&softs->mbox_acc_handle); (void) ddi_dma_free_handle(&softs->mbox_dma_handle); /* disconnect the interrupt handler */ ddi_remove_intr(softs->dev_info_p, 0, softs->iblock_cookiep); /* wait for the completion of current in-progress interruptes */ AMR_DELAY((softs->amr_interrupts_counter == 0), 1000, done_flag); if (!done_flag) { cmn_err(CE_WARN, "Suspicious interrupts in-progress."); } ddi_taskq_destroy(softs->amr_taskq); (void) scsi_hba_detach(dev); scsi_hba_tran_free(softs->hba_tran); ddi_regs_map_free(&softs->regsmap_handle); pci_config_teardown(&softs->pciconfig_handle); mutex_destroy(&softs->queue_mutex); mutex_destroy(&softs->cmd_mutex); mutex_destroy(&softs->periodic_mutex); cv_destroy(&softs->cmd_cv); /* print firmware information in verbose mode */ cmn_err(CE_NOTE, "?MegaRaid %s %s detached.", softs->amr_product_info.pi_product_name, softs->amr_product_info.pi_firmware_ver); ddi_soft_state_free(amr_softstatep, instance); return (DDI_SUCCESS); } /*ARGSUSED*/ static int amr_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { struct amr_softs *softs; int instance; instance = ddi_get_instance(dip); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: softs = ddi_get_soft_state(amr_softstatep, instance); if (softs != NULL) { *result = softs->dev_info_p; return (DDI_SUCCESS); } else { *result = NULL; return (DDI_FAILURE); } case DDI_INFO_DEVT2INSTANCE: *(int *)result = instance; break; default: break; } return (DDI_SUCCESS); } /* * Take an interrupt, or be poked by other code to look for interrupt-worthy * status. */ static uint_t amr_intr(caddr_t arg) { struct amr_softs *softs = (struct amr_softs *)arg; softs->amr_interrupts_counter++; if (AMR_QGET_ODB(softs) != AMR_QODB_READY) { softs->amr_interrupts_counter--; return (DDI_INTR_UNCLAIMED); } /* collect finished commands, queue anything waiting */ amr_done(softs); softs->amr_interrupts_counter--; return (DDI_INTR_CLAIMED); } /* * Setup the amr mailbox */ static int amr_setup_mbox(struct amr_softs *softs) { uint32_t move; size_t mbox_len; if (ddi_dma_alloc_handle( softs->dev_info_p, &addr_dma_attr, DDI_DMA_SLEEP, NULL, &softs->mbox_dma_handle) != DDI_SUCCESS) { AMRDB_PRINT((CE_NOTE, "Cannot alloc dma handle for mailbox")); goto error_out; } if (ddi_dma_mem_alloc( softs->mbox_dma_handle, sizeof (struct amr_mailbox) + 16, &accattr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)(&softs->mbox), &mbox_len, &softs->mbox_acc_handle) != DDI_SUCCESS) { AMRDB_PRINT((CE_WARN, "Cannot alloc dma memory for mailbox")); goto error_out; } if (ddi_dma_addr_bind_handle( softs->mbox_dma_handle, NULL, (caddr_t)softs->mbox, mbox_len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &softs->mbox_dma_cookie, &softs->mbox_dma_cookien) != DDI_DMA_MAPPED) { AMRDB_PRINT((CE_NOTE, "Cannot bind dma memory for mailbox")); goto error_out; } if (softs->mbox_dma_cookien != 1) goto error_out; /* The phy address of mailbox must be aligned on a 16-byte boundary */ move = 16 - (((uint32_t)softs->mbox_dma_cookie.dmac_address)&0xf); softs->mbox_phyaddr = (softs->mbox_dma_cookie.dmac_address + move); softs->mailbox = (struct amr_mailbox *)(((uintptr_t)softs->mbox) + move); AMRDB_PRINT((CE_NOTE, "phraddy=%x, mailbox=%p, softs->mbox=%p, move=%x", softs->mbox_phyaddr, (void *)softs->mailbox, softs->mbox, move)); return (DDI_SUCCESS); error_out: if (softs->mbox_dma_cookien) (void) ddi_dma_unbind_handle(softs->mbox_dma_handle); if (softs->mbox_acc_handle) { (void) ddi_dma_mem_free(&(softs->mbox_acc_handle)); softs->mbox_acc_handle = NULL; } if (softs->mbox_dma_handle) { (void) ddi_dma_free_handle(&softs->mbox_dma_handle); softs->mbox_dma_handle = NULL; } return (DDI_FAILURE); } /* * Perform a periodic check of the controller status */ static void amr_periodic(void *data) { uint32_t i; struct amr_softs *softs = (struct amr_softs *)data; struct scsi_pkt *pkt; register struct amr_command *ac; for (i = 0; i < softs->sg_max_count; i++) { if (softs->busycmd[i] == NULL) continue; mutex_enter(&softs->cmd_mutex); if (softs->busycmd[i] == NULL) { mutex_exit(&softs->cmd_mutex); continue; } pkt = softs->busycmd[i]->pkt; if ((pkt->pkt_time != 0) && (ddi_get_time() - softs->busycmd[i]->ac_timestamp > pkt->pkt_time)) { cmn_err(CE_WARN, "!timed out packet detected,\ sc = %p, pkt = %p, index = %d, ac = %p", (void *)softs, (void *)pkt, i, (void *)softs->busycmd[i]); ac = softs->busycmd[i]; ac->ac_next = NULL; /* pull command from the busy index */ softs->busycmd[i] = NULL; if (softs->amr_busyslots > 0) softs->amr_busyslots--; if (softs->amr_busyslots == 0) cv_broadcast(&softs->cmd_cv); mutex_exit(&softs->cmd_mutex); pkt = ac->pkt; *pkt->pkt_scbp = 0; pkt->pkt_statistics |= STAT_TIMEOUT; pkt->pkt_reason = CMD_TIMEOUT; if (!(pkt->pkt_flags & FLAG_NOINTR)) { /* call pkt callback */ scsi_hba_pkt_comp(pkt); } } else { mutex_exit(&softs->cmd_mutex); } } /* restart the amr timer */ mutex_enter(&softs->periodic_mutex); if (softs->state & AMR_STATE_TIMEOUT_ENABLED) softs->timeout_t = timeout(amr_periodic, (void *)softs, drv_usectohz(500000*AMR_PERIODIC_TIMEOUT)); mutex_exit(&softs->periodic_mutex); } /* * Interrogate the controller for the operational parameters we require. */ static int amr_query_controller(struct amr_softs *softs) { struct amr_enquiry3 *aex; struct amr_prodinfo *ap; struct amr_enquiry *ae; uint32_t ldrv; int instance; /* * If we haven't found the real limit yet, let us have a couple of * commands in order to be able to probe. */ if (softs->maxio == 0) softs->maxio = 2; instance = ddi_get_instance(softs->dev_info_p); /* * Try to issue an ENQUIRY3 command */ if ((aex = amr_enquiry(softs, AMR_ENQ_BUFFER_SIZE, AMR_CMD_CONFIG, AMR_CONFIG_ENQ3, AMR_CONFIG_ENQ3_SOLICITED_FULL)) != NULL) { AMRDB_PRINT((CE_NOTE, "First enquiry")); for (ldrv = 0; ldrv < aex->ae_numldrives; ldrv++) { softs->logic_drive[ldrv].al_size = aex->ae_drivesize[ldrv]; softs->logic_drive[ldrv].al_state = aex->ae_drivestate[ldrv]; softs->logic_drive[ldrv].al_properties = aex->ae_driveprop[ldrv]; AMRDB_PRINT((CE_NOTE, " drive %d: size: %d state %x properties %x\n", ldrv, softs->logic_drive[ldrv].al_size, softs->logic_drive[ldrv].al_state, softs->logic_drive[ldrv].al_properties)); if (softs->logic_drive[ldrv].al_state == AMR_LDRV_OFFLINE) cmn_err(CE_NOTE, "!instance %d log-drive %d is offline", instance, ldrv); else softs->amr_nlogdrives++; } kmem_free(aex, AMR_ENQ_BUFFER_SIZE); if ((ap = amr_enquiry(softs, AMR_ENQ_BUFFER_SIZE, AMR_CMD_CONFIG, AMR_CONFIG_PRODUCT_INFO, 0)) == NULL) { AMRDB_PRINT((CE_NOTE, "Cannot obtain product data from controller")); return (EIO); } softs->maxdrives = AMR_40LD_MAXDRIVES; softs->maxchan = ap->ap_nschan; softs->maxio = ap->ap_maxio; bcopy(ap->ap_firmware, softs->amr_product_info.pi_firmware_ver, AMR_FIRMWARE_VER_SIZE); softs->amr_product_info. pi_firmware_ver[AMR_FIRMWARE_VER_SIZE] = 0; bcopy(ap->ap_product, softs->amr_product_info.pi_product_name, AMR_PRODUCT_INFO_SIZE); softs->amr_product_info. pi_product_name[AMR_PRODUCT_INFO_SIZE] = 0; kmem_free(ap, AMR_ENQ_BUFFER_SIZE); AMRDB_PRINT((CE_NOTE, "maxio=%d", softs->maxio)); } else { AMRDB_PRINT((CE_NOTE, "First enquiry failed, \ so try another way")); /* failed, try the 8LD ENQUIRY commands */ if ((ae = (struct amr_enquiry *)amr_enquiry(softs, AMR_ENQ_BUFFER_SIZE, AMR_CMD_EXT_ENQUIRY2, 0, 0)) == NULL) { if ((ae = (struct amr_enquiry *)amr_enquiry(softs, AMR_ENQ_BUFFER_SIZE, AMR_CMD_ENQUIRY, 0, 0)) == NULL) { AMRDB_PRINT((CE_NOTE, "Cannot obtain configuration data")); return (EIO); } ae->ae_signature = 0; } /* * Fetch current state of logical drives. */ for (ldrv = 0; ldrv < ae->ae_ldrv.al_numdrives; ldrv++) { softs->logic_drive[ldrv].al_size = ae->ae_ldrv.al_size[ldrv]; softs->logic_drive[ldrv].al_state = ae->ae_ldrv.al_state[ldrv]; softs->logic_drive[ldrv].al_properties = ae->ae_ldrv.al_properties[ldrv]; AMRDB_PRINT((CE_NOTE, " ********* drive %d: %d state %x properties %x", ldrv, softs->logic_drive[ldrv].al_size, softs->logic_drive[ldrv].al_state, softs->logic_drive[ldrv].al_properties)); if (softs->logic_drive[ldrv].al_state == AMR_LDRV_OFFLINE) cmn_err(CE_NOTE, "!instance %d log-drive %d is offline", instance, ldrv); else softs->amr_nlogdrives++; } softs->maxdrives = AMR_8LD_MAXDRIVES; softs->maxchan = ae->ae_adapter.aa_channels; softs->maxio = ae->ae_adapter.aa_maxio; kmem_free(ae, AMR_ENQ_BUFFER_SIZE); } /* * Mark remaining drives as unused. */ for (; ldrv < AMR_MAXLD; ldrv++) softs->logic_drive[ldrv].al_state = AMR_LDRV_OFFLINE; /* * Cap the maximum number of outstanding I/Os. AMI's driver * doesn't trust the controller's reported value, and lockups have * been seen when we do. */ softs->maxio = MIN(softs->maxio, AMR_LIMITCMD); return (DDI_SUCCESS); } /* * Run a generic enquiry-style command. */ static void * amr_enquiry(struct amr_softs *softs, size_t bufsize, uint8_t cmd, uint8_t cmdsub, uint8_t cmdqual) { struct amr_command ac; void *result; result = NULL; bzero(&ac, sizeof (struct amr_command)); ac.ac_softs = softs; /* set command flags */ ac.ac_flags |= AMR_CMD_DATAOUT; /* build the command proper */ ac.mailbox.mb_command = cmd; ac.mailbox.mb_cmdsub = cmdsub; ac.mailbox.mb_cmdqual = cmdqual; if (amr_enquiry_mapcmd(&ac, bufsize) != DDI_SUCCESS) return (NULL); if (amr_poll_command(&ac) || ac.ac_status != 0) { AMRDB_PRINT((CE_NOTE, "can not poll command, goto out")); amr_enquiry_unmapcmd(&ac); return (NULL); } /* allocate the response structure */ result = kmem_zalloc(bufsize, KM_SLEEP); bcopy(ac.ac_data, result, bufsize); amr_enquiry_unmapcmd(&ac); return (result); } /* * Flush the controller's internal cache, return status. */ static int amr_flush(struct amr_softs *softs) { struct amr_command ac; int error = 0; bzero(&ac, sizeof (struct amr_command)); ac.ac_softs = softs; ac.ac_flags |= AMR_CMD_DATAOUT; /* build the command proper */ ac.mailbox.mb_command = AMR_CMD_FLUSH; /* have to poll, as the system may be going down or otherwise damaged */ if (error = amr_poll_command(&ac)) { AMRDB_PRINT((CE_NOTE, "can not poll this cmd")); return (error); } return (error); } /* * Take a command, submit it to the controller and wait for it to return. * Returns nonzero on error. Can be safely called with interrupts enabled. */ static int amr_poll_command(struct amr_command *ac) { struct amr_softs *softs = ac->ac_softs; volatile uint32_t done_flag; AMRDB_PRINT((CE_NOTE, "Amr_Poll bcopy(%p, %p, %d)", (void *)&ac->mailbox, (void *)softs->mailbox, (uint32_t)AMR_MBOX_CMDSIZE)); mutex_enter(&softs->cmd_mutex); while (softs->amr_busyslots != 0) cv_wait(&softs->cmd_cv, &softs->cmd_mutex); /* * For read/write commands, the scatter/gather table should be * filled, and the last entry in scatter/gather table will be used. */ if ((ac->mailbox.mb_command == AMR_CMD_LREAD) || (ac->mailbox.mb_command == AMR_CMD_LWRITE)) { bcopy(ac->sgtable, softs->sg_items[softs->sg_max_count - 1].sg_table, sizeof (struct amr_sgentry) * AMR_NSEG); (void) ddi_dma_sync( softs->sg_items[softs->sg_max_count - 1].sg_handle, 0, 0, DDI_DMA_SYNC_FORDEV); ac->mailbox.mb_physaddr = softs->sg_items[softs->sg_max_count - 1].sg_phyaddr; } bcopy(&ac->mailbox, (void *)softs->mailbox, AMR_MBOX_CMDSIZE); /* sync the dma memory */ (void) ddi_dma_sync(softs->mbox_dma_handle, 0, 0, DDI_DMA_SYNC_FORDEV); /* clear the poll/ack fields in the mailbox */ softs->mailbox->mb_ident = AMR_POLL_COMMAND_ID; softs->mailbox->mb_nstatus = AMR_POLL_DEFAULT_NSTATUS; softs->mailbox->mb_status = AMR_POLL_DEFAULT_STATUS; softs->mailbox->mb_poll = 0; softs->mailbox->mb_ack = 0; softs->mailbox->mb_busy = 1; AMR_QPUT_IDB(softs, softs->mbox_phyaddr | AMR_QIDB_SUBMIT); /* sync the dma memory */ (void) ddi_dma_sync(softs->mbox_dma_handle, 0, 0, DDI_DMA_SYNC_FORCPU); AMR_DELAY((softs->mailbox->mb_nstatus != AMR_POLL_DEFAULT_NSTATUS), 1000, done_flag); if (!done_flag) { mutex_exit(&softs->cmd_mutex); return (1); } ac->ac_status = softs->mailbox->mb_status; AMR_DELAY((softs->mailbox->mb_poll == AMR_POLL_ACK), 1000, done_flag); if (!done_flag) { mutex_exit(&softs->cmd_mutex); return (1); } softs->mailbox->mb_poll = 0; softs->mailbox->mb_ack = AMR_POLL_ACK; /* acknowledge that we have the commands */ AMR_QPUT_IDB(softs, softs->mbox_phyaddr | AMR_QIDB_ACK); AMR_DELAY(!(AMR_QGET_IDB(softs) & AMR_QIDB_ACK), 1000, done_flag); if (!done_flag) { mutex_exit(&softs->cmd_mutex); return (1); } mutex_exit(&softs->cmd_mutex); return (ac->ac_status != AMR_STATUS_SUCCESS); } /* * setup the scatter/gather table */ static int amr_setup_sg(struct amr_softs *softs) { uint32_t i; size_t len; ddi_dma_cookie_t cookie; uint_t cookien; softs->sg_max_count = 0; for (i = 0; i < AMR_MAXCMD; i++) { /* reset the cookien */ cookien = 0; (softs->sg_items[i]).sg_handle = NULL; if (ddi_dma_alloc_handle( softs->dev_info_p, &addr_dma_attr, DDI_DMA_SLEEP, NULL, &((softs->sg_items[i]).sg_handle)) != DDI_SUCCESS) { AMRDB_PRINT((CE_WARN, "Cannot alloc dma handle for s/g table")); goto error_out; } if (ddi_dma_mem_alloc((softs->sg_items[i]).sg_handle, sizeof (struct amr_sgentry) * AMR_NSEG, &accattr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)(&(softs->sg_items[i]).sg_table), &len, &(softs->sg_items[i]).sg_acc_handle) != DDI_SUCCESS) { AMRDB_PRINT((CE_WARN, "Cannot allocate DMA memory")); goto error_out; } if (ddi_dma_addr_bind_handle( (softs->sg_items[i]).sg_handle, NULL, (caddr_t)((softs->sg_items[i]).sg_table), len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &cookien) != DDI_DMA_MAPPED) { AMRDB_PRINT((CE_WARN, "Cannot bind communication area for s/g table")); goto error_out; } if (cookien != 1) goto error_out; softs->sg_items[i].sg_phyaddr = cookie.dmac_address; softs->sg_max_count++; } return (DDI_SUCCESS); error_out: /* * Couldn't allocate/initialize all of the sg table entries. * Clean up the partially-initialized entry before returning. */ if (cookien) { (void) ddi_dma_unbind_handle((softs->sg_items[i]).sg_handle); } if ((softs->sg_items[i]).sg_acc_handle) { (void) ddi_dma_mem_free(&((softs->sg_items[i]).sg_acc_handle)); (softs->sg_items[i]).sg_acc_handle = NULL; } if ((softs->sg_items[i]).sg_handle) { (void) ddi_dma_free_handle(&((softs->sg_items[i]).sg_handle)); (softs->sg_items[i]).sg_handle = NULL; } /* * At least two sg table entries are needed. One is for regular data * I/O commands, the other is for poll I/O commands. */ return (softs->sg_max_count > 1 ? DDI_SUCCESS : DDI_FAILURE); } /* * Map/unmap (ac)'s data in the controller's addressable space as required. * * These functions may be safely called multiple times on a given command. */ static void amr_setup_dmamap(struct amr_command *ac, ddi_dma_cookie_t *buffer_dma_cookiep, int nsegments) { struct amr_sgentry *sg; uint32_t i, size; sg = ac->sgtable; size = 0; ac->mailbox.mb_nsgelem = (uint8_t)nsegments; for (i = 0; i < nsegments; i++, sg++) { sg->sg_addr = buffer_dma_cookiep->dmac_address; sg->sg_count = buffer_dma_cookiep->dmac_size; size += sg->sg_count; /* * There is no next cookie if the end of the current * window is reached. Otherwise, the next cookie * would be found. */ if ((ac->current_cookie + i + 1) != ac->num_of_cookie) ddi_dma_nextcookie(ac->buffer_dma_handle, buffer_dma_cookiep); } ac->transfer_size = size; ac->data_transfered += size; } /* * map the amr command for enquiry, allocate the DMA resource */ static int amr_enquiry_mapcmd(struct amr_command *ac, uint32_t data_size) { struct amr_softs *softs = ac->ac_softs; size_t len; uint_t dma_flags; AMRDB_PRINT((CE_NOTE, "Amr_enquiry_mapcmd called, ac=%p, flags=%x", (void *)ac, ac->ac_flags)); if (ac->ac_flags & AMR_CMD_DATAOUT) { dma_flags = DDI_DMA_READ; } else { dma_flags = DDI_DMA_WRITE; } dma_flags |= DDI_DMA_CONSISTENT; /* process the DMA by address bind mode */ if (ddi_dma_alloc_handle(softs->dev_info_p, &addr_dma_attr, DDI_DMA_SLEEP, NULL, &ac->buffer_dma_handle) != DDI_SUCCESS) { AMRDB_PRINT((CE_WARN, "Cannot allocate addr DMA tag")); goto error_out; } if (ddi_dma_mem_alloc(ac->buffer_dma_handle, data_size, &accattr, dma_flags, DDI_DMA_SLEEP, NULL, (caddr_t *)&ac->ac_data, &len, &ac->buffer_acc_handle) != DDI_SUCCESS) { AMRDB_PRINT((CE_WARN, "Cannot allocate DMA memory")); goto error_out; } if ((ddi_dma_addr_bind_handle( ac->buffer_dma_handle, NULL, ac->ac_data, len, dma_flags, DDI_DMA_SLEEP, NULL, &ac->buffer_dma_cookie, &ac->num_of_cookie)) != DDI_DMA_MAPPED) { AMRDB_PRINT((CE_WARN, "Cannot bind addr for dma")); goto error_out; } ac->ac_dataphys = (&ac->buffer_dma_cookie)->dmac_address; ((struct amr_mailbox *)&(ac->mailbox))->mb_param = 0; ac->mailbox.mb_nsgelem = 0; ac->mailbox.mb_physaddr = ac->ac_dataphys; ac->ac_flags |= AMR_CMD_MAPPED; return (DDI_SUCCESS); error_out: if (ac->num_of_cookie) (void) ddi_dma_unbind_handle(ac->buffer_dma_handle); if (ac->buffer_acc_handle) { ddi_dma_mem_free(&ac->buffer_acc_handle); ac->buffer_acc_handle = NULL; } if (ac->buffer_dma_handle) { (void) ddi_dma_free_handle(&ac->buffer_dma_handle); ac->buffer_dma_handle = NULL; } return (DDI_FAILURE); } /* * unmap the amr command for enquiry, free the DMA resource */ static void amr_enquiry_unmapcmd(struct amr_command *ac) { AMRDB_PRINT((CE_NOTE, "Amr_enquiry_unmapcmd called, ac=%p", (void *)ac)); /* if the command involved data at all and was mapped */ if ((ac->ac_flags & AMR_CMD_MAPPED) && ac->ac_data) { if (ac->buffer_dma_handle) (void) ddi_dma_unbind_handle( ac->buffer_dma_handle); if (ac->buffer_acc_handle) { ddi_dma_mem_free(&ac->buffer_acc_handle); ac->buffer_acc_handle = NULL; } if (ac->buffer_dma_handle) { (void) ddi_dma_free_handle( &ac->buffer_dma_handle); ac->buffer_dma_handle = NULL; } } ac->ac_flags &= ~AMR_CMD_MAPPED; } /* * map the amr command, allocate the DMA resource */ static int amr_mapcmd(struct amr_command *ac, int (*callback)(), caddr_t arg) { uint_t dma_flags; off_t off; size_t len; int error; int (*cb)(caddr_t); AMRDB_PRINT((CE_NOTE, "Amr_mapcmd called, ac=%p, flags=%x", (void *)ac, ac->ac_flags)); if (ac->ac_flags & AMR_CMD_DATAOUT) { dma_flags = DDI_DMA_READ; } else { dma_flags = DDI_DMA_WRITE; } if (ac->ac_flags & AMR_CMD_PKT_CONSISTENT) { dma_flags |= DDI_DMA_CONSISTENT; } if (ac->ac_flags & AMR_CMD_PKT_DMA_PARTIAL) { dma_flags |= DDI_DMA_PARTIAL; } if ((!(ac->ac_flags & AMR_CMD_MAPPED)) && (ac->ac_buf == NULL)) { ac->ac_flags |= AMR_CMD_MAPPED; return (DDI_SUCCESS); } cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; /* if the command involves data at all, and hasn't been mapped */ if (!(ac->ac_flags & AMR_CMD_MAPPED)) { /* process the DMA by buffer bind mode */ error = ddi_dma_buf_bind_handle(ac->buffer_dma_handle, ac->ac_buf, dma_flags, cb, arg, &ac->buffer_dma_cookie, &ac->num_of_cookie); switch (error) { case DDI_DMA_PARTIAL_MAP: if (ddi_dma_numwin(ac->buffer_dma_handle, &ac->num_of_win) == DDI_FAILURE) { AMRDB_PRINT((CE_WARN, "Cannot get dma num win")); (void) ddi_dma_unbind_handle( ac->buffer_dma_handle); (void) ddi_dma_free_handle( &ac->buffer_dma_handle); ac->buffer_dma_handle = NULL; return (DDI_FAILURE); } ac->current_win = 0; break; case DDI_DMA_MAPPED: ac->num_of_win = 1; ac->current_win = 0; break; default: AMRDB_PRINT((CE_WARN, "Cannot bind buf for dma")); (void) ddi_dma_free_handle( &ac->buffer_dma_handle); ac->buffer_dma_handle = NULL; return (DDI_FAILURE); } ac->current_cookie = 0; ac->ac_flags |= AMR_CMD_MAPPED; } else if (ac->current_cookie == AMR_LAST_COOKIE_TAG) { /* get the next window */ ac->current_win++; (void) ddi_dma_getwin(ac->buffer_dma_handle, ac->current_win, &off, &len, &ac->buffer_dma_cookie, &ac->num_of_cookie); ac->current_cookie = 0; } if ((ac->num_of_cookie - ac->current_cookie) > AMR_NSEG) { amr_setup_dmamap(ac, &ac->buffer_dma_cookie, AMR_NSEG); ac->current_cookie += AMR_NSEG; } else { amr_setup_dmamap(ac, &ac->buffer_dma_cookie, ac->num_of_cookie - ac->current_cookie); ac->current_cookie = AMR_LAST_COOKIE_TAG; } return (DDI_SUCCESS); } /* * unmap the amr command, free the DMA resource */ static void amr_unmapcmd(struct amr_command *ac) { AMRDB_PRINT((CE_NOTE, "Amr_unmapcmd called, ac=%p", (void *)ac)); /* if the command involved data at all and was mapped */ if ((ac->ac_flags & AMR_CMD_MAPPED) && ac->ac_buf && ac->buffer_dma_handle) (void) ddi_dma_unbind_handle(ac->buffer_dma_handle); ac->ac_flags &= ~AMR_CMD_MAPPED; } static int amr_setup_tran(dev_info_t *dip, struct amr_softs *softp) { softp->hba_tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); /* * hba_private always points to the amr_softs struct */ softp->hba_tran->tran_hba_private = softp; softp->hba_tran->tran_tgt_init = amr_tran_tgt_init; softp->hba_tran->tran_tgt_probe = scsi_hba_probe; softp->hba_tran->tran_start = amr_tran_start; softp->hba_tran->tran_reset = amr_tran_reset; softp->hba_tran->tran_getcap = amr_tran_getcap; softp->hba_tran->tran_setcap = amr_tran_setcap; softp->hba_tran->tran_init_pkt = amr_tran_init_pkt; softp->hba_tran->tran_destroy_pkt = amr_tran_destroy_pkt; softp->hba_tran->tran_dmafree = amr_tran_dmafree; softp->hba_tran->tran_sync_pkt = amr_tran_sync_pkt; softp->hba_tran->tran_abort = NULL; softp->hba_tran->tran_tgt_free = NULL; softp->hba_tran->tran_quiesce = NULL; softp->hba_tran->tran_unquiesce = NULL; softp->hba_tran->tran_sd = NULL; if (scsi_hba_attach_setup(dip, &buffer_dma_attr, softp->hba_tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) { scsi_hba_tran_free(softp->hba_tran); softp->hba_tran = NULL; return (DDI_FAILURE); } else { return (DDI_SUCCESS); } } /*ARGSUSED*/ static int amr_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { struct amr_softs *softs; ushort_t target = sd->sd_address.a_target; uchar_t lun = sd->sd_address.a_lun; softs = (struct amr_softs *) (sd->sd_address.a_hba_tran->tran_hba_private); if ((lun == 0) && (target < AMR_MAXLD)) if (softs->logic_drive[target].al_state != AMR_LDRV_OFFLINE) return (DDI_SUCCESS); return (DDI_FAILURE); } static int amr_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct amr_softs *softs; struct buf *bp = NULL; union scsi_cdb *cdbp = (union scsi_cdb *)pkt->pkt_cdbp; int ret; uint32_t capacity; struct amr_command *ac; AMRDB_PRINT((CE_NOTE, "amr_tran_start, cmd=%X,target=%d,lun=%d", cdbp->scc_cmd, ap->a_target, ap->a_lun)); softs = (struct amr_softs *)(ap->a_hba_tran->tran_hba_private); if ((ap->a_lun != 0) || (ap->a_target >= AMR_MAXLD) || (softs->logic_drive[ap->a_target].al_state == AMR_LDRV_OFFLINE)) { cmn_err(CE_WARN, "target or lun is not correct!"); ret = TRAN_BADPKT; return (ret); } ac = (struct amr_command *)pkt->pkt_ha_private; bp = ac->ac_buf; AMRDB_PRINT((CE_NOTE, "scsi cmd accepted, cmd=%X", cdbp->scc_cmd)); switch (cdbp->scc_cmd) { case SCMD_READ: /* read */ case SCMD_READ_G1: /* read g1 */ case SCMD_READ_BUFFER: /* read buffer */ case SCMD_WRITE: /* write */ case SCMD_WRITE_G1: /* write g1 */ case SCMD_WRITE_BUFFER: /* write buffer */ amr_rw_command(softs, pkt, ap->a_target); if (pkt->pkt_flags & FLAG_NOINTR) { (void) amr_poll_command(ac); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); *pkt->pkt_scbp = 0; pkt->pkt_statistics |= STAT_SYNC; pkt->pkt_reason = CMD_CMPLT; } else { mutex_enter(&softs->queue_mutex); if (softs->waiting_q_head == NULL) { ac->ac_prev = NULL; ac->ac_next = NULL; softs->waiting_q_head = ac; softs->waiting_q_tail = ac; } else { ac->ac_next = NULL; ac->ac_prev = softs->waiting_q_tail; softs->waiting_q_tail->ac_next = ac; softs->waiting_q_tail = ac; } mutex_exit(&softs->queue_mutex); amr_start_waiting_queue((void *)softs); } ret = TRAN_ACCEPT; break; case SCMD_INQUIRY: /* inquiry */ if (bp && bp->b_un.b_addr && bp->b_bcount) { struct scsi_inquiry inqp; uint8_t *sinq_p = (uint8_t *)&inqp; bzero(&inqp, sizeof (struct scsi_inquiry)); if (((char *)cdbp)[1] || ((char *)cdbp)[2]) { /* * The EVDP and pagecode is * not supported */ sinq_p[1] = 0xFF; sinq_p[2] = 0x0; } else { inqp.inq_len = AMR_INQ_ADDITIONAL_LEN; inqp.inq_ansi = AMR_INQ_ANSI_VER; inqp.inq_rdf = AMR_INQ_RESP_DATA_FORMAT; /* Enable Tag Queue */ inqp.inq_cmdque = 1; bcopy("MegaRaid", inqp.inq_vid, sizeof (inqp.inq_vid)); bcopy(softs->amr_product_info.pi_product_name, inqp.inq_pid, AMR_PRODUCT_INFO_SIZE); bcopy(softs->amr_product_info.pi_firmware_ver, inqp.inq_revision, AMR_FIRMWARE_VER_SIZE); } amr_unmapcmd(ac); if (bp->b_flags & (B_PHYS | B_PAGEIO)) bp_mapin(bp); bcopy(&inqp, bp->b_un.b_addr, sizeof (struct scsi_inquiry)); pkt->pkt_state |= STATE_XFERRED_DATA; } pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD); *pkt->pkt_scbp = 0; ret = TRAN_ACCEPT; if (!(pkt->pkt_flags & FLAG_NOINTR)) scsi_hba_pkt_comp(pkt); break; case SCMD_READ_CAPACITY: /* read capacity */ if (bp && bp->b_un.b_addr && bp->b_bcount) { struct scsi_capacity cp; capacity = softs->logic_drive[ap->a_target].al_size - 1; cp.capacity = BE_32(capacity); cp.lbasize = BE_32(512); amr_unmapcmd(ac); if (bp->b_flags & (B_PHYS | B_PAGEIO)) bp_mapin(bp); bcopy(&cp, bp->b_un.b_addr, 8); } pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); *pkt->pkt_scbp = 0; ret = TRAN_ACCEPT; if (!(pkt->pkt_flags & FLAG_NOINTR)) scsi_hba_pkt_comp(pkt); break; case SCMD_MODE_SENSE: /* mode sense */ case SCMD_MODE_SENSE_G1: /* mode sense g1 */ amr_unmapcmd(ac); capacity = softs->logic_drive[ap->a_target].al_size - 1; amr_mode_sense(cdbp, bp, capacity); pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); *pkt->pkt_scbp = 0; ret = TRAN_ACCEPT; if (!(pkt->pkt_flags & FLAG_NOINTR)) scsi_hba_pkt_comp(pkt); break; case SCMD_TEST_UNIT_READY: /* test unit ready */ case SCMD_REQUEST_SENSE: /* request sense */ case SCMD_FORMAT: /* format */ case SCMD_START_STOP: /* start stop */ case SCMD_SYNCHRONIZE_CACHE: /* synchronize cache */ if (bp && bp->b_un.b_addr && bp->b_bcount) { amr_unmapcmd(ac); if (bp->b_flags & (B_PHYS | B_PAGEIO)) bp_mapin(bp); bzero(bp->b_un.b_addr, bp->b_bcount); pkt->pkt_state |= STATE_XFERRED_DATA; } pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD); ret = TRAN_ACCEPT; *pkt->pkt_scbp = 0; if (!(pkt->pkt_flags & FLAG_NOINTR)) scsi_hba_pkt_comp(pkt); break; default: /* any other commands */ amr_unmapcmd(ac); pkt->pkt_reason = CMD_INCOMPLETE; pkt->pkt_state = (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_GOT_STATUS | STATE_ARQ_DONE); ret = TRAN_ACCEPT; *pkt->pkt_scbp = 0; amr_set_arq_data(pkt, KEY_ILLEGAL_REQUEST); if (!(pkt->pkt_flags & FLAG_NOINTR)) scsi_hba_pkt_comp(pkt); break; } return (ret); } /* * tran_reset() will reset the bus/target/adapter to support the fault recovery * functionality according to the "level" in interface. However, we got the * confirmation from LSI that these HBA cards does not support any commands to * reset bus/target/adapter/channel. * * If the tran_reset() return a FAILURE to the sd, the system will not * continue to dump the core. But core dump is an crucial method to analyze * problems in panic. Now we adopt a work around solution, that is to return * a fake SUCCESS to sd during panic, which will force the system continue * to dump core though the core may have problems in some situtation because * some on-the-fly commands will continue DMAing data to the memory. * In addition, the work around core dump method may not be performed * successfully if the panic is caused by the HBA itself. So the work around * solution is not a good example for the implementation of tran_reset(), * the most reasonable approach should send a reset command to the adapter. */ /*ARGSUSED*/ static int amr_tran_reset(struct scsi_address *ap, int level) { struct amr_softs *softs; volatile uint32_t done_flag; if (ddi_in_panic()) { softs = (struct amr_softs *)(ap->a_hba_tran->tran_hba_private); /* Acknowledge the card if there are any significant commands */ while (softs->amr_busyslots > 0) { AMR_DELAY((softs->mailbox->mb_busy == 0), AMR_RETRYCOUNT, done_flag); if (!done_flag) { /* * command not completed, indicate the * problem and continue get ac */ cmn_err(CE_WARN, "AMR command is not completed"); return (0); } AMR_QPUT_IDB(softs, softs->mbox_phyaddr | AMR_QIDB_ACK); /* wait for the acknowledge from hardware */ AMR_BUSYWAIT(!(AMR_QGET_IDB(softs) & AMR_QIDB_ACK), AMR_RETRYCOUNT, done_flag); if (!done_flag) { /* * command is not completed, return from the * current interrupt and wait for the next one */ cmn_err(CE_WARN, "No answer from the hardware"); mutex_exit(&softs->cmd_mutex); return (0); } softs->amr_busyslots -= softs->mailbox->mb_nstatus; } /* flush the controllor */ (void) amr_flush(softs); /* * If the system is in panic, the tran_reset() will return a * fake SUCCESS to sd, then the system would continue dump the * core by poll commands. This is a work around for dumping * core in panic. * * Note: Some on-the-fly command will continue DMAing data to * the memory when the core is dumping, which may cause * some flaws in the dumped core file, so a cmn_err() * will be printed out to warn users. However, for most * cases, the core file will be fine. */ cmn_err(CE_WARN, "This system contains a SCSI HBA card/driver " "that doesn't support software reset. This " "means that memory being used by the HBA for " "DMA based reads could have been updated after " "we panic'd."); return (1); } else { /* return failure to sd */ return (0); } } /*ARGSUSED*/ static int amr_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct amr_softs *softs; /* * We don't allow inquiring about capabilities for other targets */ if (cap == NULL || whom == 0) return (-1); softs = ((struct amr_softs *)(ap->a_hba_tran)->tran_hba_private); switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); case SCSI_CAP_GEOMETRY: return ((AMR_DEFAULT_HEADS << 16) | AMR_DEFAULT_CYLINDERS); case SCSI_CAP_SECTOR_SIZE: return (AMR_DEFAULT_SECTORS); case SCSI_CAP_TOTAL_SECTORS: /* number of sectors */ return (softs->logic_drive[ap->a_target].al_size); case SCSI_CAP_UNTAGGED_QING: case SCSI_CAP_TAGGED_QING: return (1); default: return (-1); } } /*ARGSUSED*/ static int amr_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { /* * We don't allow setting capabilities for other targets */ if (cap == NULL || whom == 0) { AMRDB_PRINT((CE_NOTE, "Set Cap not supported, string = %s, whom=%d", cap, whom)); return (-1); } switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); case SCSI_CAP_UNTAGGED_QING: case SCSI_CAP_TAGGED_QING: return ((value == 1) ? 1 : 0); default: return (0); } } static struct scsi_pkt * amr_tran_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) { struct amr_softs *softs; struct amr_command *ac; uint32_t slen; softs = (struct amr_softs *)(ap->a_hba_tran->tran_hba_private); if ((ap->a_lun != 0)||(ap->a_target >= AMR_MAXLD)|| (softs->logic_drive[ap->a_target].al_state == AMR_LDRV_OFFLINE)) { return (NULL); } if (pkt == NULL) { /* force auto request sense */ slen = MAX(statuslen, sizeof (struct scsi_arq_status)); pkt = scsi_hba_pkt_alloc(softs->dev_info_p, ap, cmdlen, slen, tgtlen, sizeof (struct amr_command), callback, arg); if (pkt == NULL) { AMRDB_PRINT((CE_WARN, "scsi_hba_pkt_alloc failed")); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)())NULL; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; ac = (struct amr_command *)pkt->pkt_ha_private; ac->ac_buf = bp; ac->cmdlen = cmdlen; ac->ac_softs = softs; ac->pkt = pkt; ac->ac_flags &= ~AMR_CMD_GOT_SLOT; ac->ac_flags &= ~AMR_CMD_BUSY; if ((bp == NULL) || (bp->b_bcount == 0)) { return (pkt); } if (ddi_dma_alloc_handle(softs->dev_info_p, &buffer_dma_attr, DDI_DMA_SLEEP, NULL, &ac->buffer_dma_handle) != DDI_SUCCESS) { AMRDB_PRINT((CE_WARN, "Cannot allocate buffer DMA tag")); scsi_hba_pkt_free(ap, pkt); return (NULL); } } else { if ((bp == NULL) || (bp->b_bcount == 0)) { return (pkt); } ac = (struct amr_command *)pkt->pkt_ha_private; } ASSERT(ac != NULL); if (bp->b_flags & B_READ) { ac->ac_flags |= AMR_CMD_DATAOUT; } else { ac->ac_flags |= AMR_CMD_DATAIN; } if (flags & PKT_CONSISTENT) { ac->ac_flags |= AMR_CMD_PKT_CONSISTENT; } if (flags & PKT_DMA_PARTIAL) { ac->ac_flags |= AMR_CMD_PKT_DMA_PARTIAL; } if (amr_mapcmd(ac, callback, arg) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); return (NULL); } pkt->pkt_resid = bp->b_bcount - ac->data_transfered; AMRDB_PRINT((CE_NOTE, "init pkt, pkt_resid=%d, b_bcount=%d, data_transfered=%d", (uint32_t)pkt->pkt_resid, (uint32_t)bp->b_bcount, ac->data_transfered)); ASSERT(pkt->pkt_resid >= 0); return (pkt); } static void amr_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct amr_command *ac = (struct amr_command *)pkt->pkt_ha_private; amr_unmapcmd(ac); if (ac->buffer_dma_handle) { (void) ddi_dma_free_handle(&ac->buffer_dma_handle); ac->buffer_dma_handle = NULL; } scsi_hba_pkt_free(ap, pkt); AMRDB_PRINT((CE_NOTE, "Destroy pkt called")); } /*ARGSUSED*/ static void amr_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct amr_command *ac = (struct amr_command *)pkt->pkt_ha_private; if (ac->buffer_dma_handle) { (void) ddi_dma_sync(ac->buffer_dma_handle, 0, 0, (ac->ac_flags & AMR_CMD_DATAIN) ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } } /*ARGSUSED*/ static void amr_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct amr_command *ac = (struct amr_command *)pkt->pkt_ha_private; if (ac->ac_flags & AMR_CMD_MAPPED) { (void) ddi_dma_unbind_handle(ac->buffer_dma_handle); (void) ddi_dma_free_handle(&ac->buffer_dma_handle); ac->buffer_dma_handle = NULL; ac->ac_flags &= ~AMR_CMD_MAPPED; } } /*ARGSUSED*/ static void amr_rw_command(struct amr_softs *softs, struct scsi_pkt *pkt, int target) { struct amr_command *ac = (struct amr_command *)pkt->pkt_ha_private; union scsi_cdb *cdbp = (union scsi_cdb *)pkt->pkt_cdbp; uint8_t cmd; if (ac->ac_flags & AMR_CMD_DATAOUT) { cmd = AMR_CMD_LREAD; } else { cmd = AMR_CMD_LWRITE; } ac->mailbox.mb_command = cmd; ac->mailbox.mb_blkcount = (ac->transfer_size + AMR_BLKSIZE - 1)/AMR_BLKSIZE; ac->mailbox.mb_lba = (ac->cmdlen == 10) ? GETG1ADDR(cdbp) : GETG0ADDR(cdbp); ac->mailbox.mb_drive = (uint8_t)target; } static void amr_mode_sense(union scsi_cdb *cdbp, struct buf *bp, unsigned int capacity) { uchar_t pagecode; struct mode_format *page3p; struct mode_geometry *page4p; struct mode_header *headerp; uint32_t ncyl; if (!(bp && bp->b_un.b_addr && bp->b_bcount)) return; if (bp->b_flags & (B_PHYS | B_PAGEIO)) bp_mapin(bp); pagecode = cdbp->cdb_un.sg.scsi[0]; switch (pagecode) { case SD_MODE_SENSE_PAGE3_CODE: headerp = (struct mode_header *)(bp->b_un.b_addr); headerp->bdesc_length = MODE_BLK_DESC_LENGTH; page3p = (struct mode_format *)((caddr_t)headerp + MODE_HEADER_LENGTH + MODE_BLK_DESC_LENGTH); page3p->mode_page.code = BE_8(SD_MODE_SENSE_PAGE3_CODE); page3p->mode_page.length = BE_8(sizeof (struct mode_format)); page3p->data_bytes_sect = BE_16(AMR_DEFAULT_SECTORS); page3p->sect_track = BE_16(AMR_DEFAULT_CYLINDERS); return; case SD_MODE_SENSE_PAGE4_CODE: headerp = (struct mode_header *)(bp->b_un.b_addr); headerp->bdesc_length = MODE_BLK_DESC_LENGTH; page4p = (struct mode_geometry *)((caddr_t)headerp + MODE_HEADER_LENGTH + MODE_BLK_DESC_LENGTH); page4p->mode_page.code = BE_8(SD_MODE_SENSE_PAGE4_CODE); page4p->mode_page.length = BE_8(sizeof (struct mode_geometry)); page4p->heads = BE_8(AMR_DEFAULT_HEADS); page4p->rpm = BE_16(AMR_DEFAULT_ROTATIONS); ncyl = capacity / (AMR_DEFAULT_HEADS*AMR_DEFAULT_CYLINDERS); page4p->cyl_lb = BE_8(ncyl & 0xff); page4p->cyl_mb = BE_8((ncyl >> 8) & 0xff); page4p->cyl_ub = BE_8((ncyl >> 16) & 0xff); return; default: bzero(bp->b_un.b_addr, bp->b_bcount); return; } } static void amr_set_arq_data(struct scsi_pkt *pkt, uchar_t key) { struct scsi_arq_status *arqstat; arqstat = (struct scsi_arq_status *)(pkt->pkt_scbp); arqstat->sts_status.sts_chk = 1; /* CHECK CONDITION */ arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; arqstat->sts_sensedata.es_valid = 1; arqstat->sts_sensedata.es_class = CLASS_EXTENDED_SENSE; arqstat->sts_sensedata.es_key = key; } static void amr_start_waiting_queue(void *softp) { uint32_t slot; struct amr_command *ac; volatile uint32_t done_flag; struct amr_softs *softs = (struct amr_softs *)softp; /* only one command allowed at the same time */ mutex_enter(&softs->queue_mutex); mutex_enter(&softs->cmd_mutex); while ((ac = softs->waiting_q_head) != NULL) { /* * Find an available slot, the last slot is * occupied by poll I/O command. */ for (slot = 0; slot < (softs->sg_max_count - 1); slot++) { if (softs->busycmd[slot] == NULL) { if (AMR_QGET_IDB(softs) & AMR_QIDB_SUBMIT) { /* * only one command allowed at the * same time */ mutex_exit(&softs->cmd_mutex); mutex_exit(&softs->queue_mutex); return; } ac->ac_timestamp = ddi_get_time(); if (!(ac->ac_flags & AMR_CMD_GOT_SLOT)) { softs->busycmd[slot] = ac; ac->ac_slot = slot; softs->amr_busyslots++; bcopy(ac->sgtable, softs->sg_items[slot].sg_table, sizeof (struct amr_sgentry) * AMR_NSEG); (void) ddi_dma_sync( softs->sg_items[slot].sg_handle, 0, 0, DDI_DMA_SYNC_FORDEV); ac->mailbox.mb_physaddr = softs->sg_items[slot].sg_phyaddr; } /* take the cmd from the queue */ softs->waiting_q_head = ac->ac_next; ac->mailbox.mb_ident = ac->ac_slot + 1; ac->mailbox.mb_busy = 1; ac->ac_next = NULL; ac->ac_prev = NULL; ac->ac_flags |= AMR_CMD_GOT_SLOT; /* clear the poll/ack fields in the mailbox */ softs->mailbox->mb_poll = 0; softs->mailbox->mb_ack = 0; AMR_DELAY((softs->mailbox->mb_busy == 0), AMR_RETRYCOUNT, done_flag); if (!done_flag) { /* * command not completed, indicate the * problem and continue get ac */ cmn_err(CE_WARN, "AMR command is not completed"); break; } bcopy(&ac->mailbox, (void *)softs->mailbox, AMR_MBOX_CMDSIZE); ac->ac_flags |= AMR_CMD_BUSY; (void) ddi_dma_sync(softs->mbox_dma_handle, 0, 0, DDI_DMA_SYNC_FORDEV); AMR_QPUT_IDB(softs, softs->mbox_phyaddr | AMR_QIDB_SUBMIT); /* * current ac is submitted * so quit 'for-loop' to get next ac */ break; } } /* no slot, finish our task */ if (slot == softs->maxio) break; } /* only one command allowed at the same time */ mutex_exit(&softs->cmd_mutex); mutex_exit(&softs->queue_mutex); } static void amr_done(struct amr_softs *softs) { uint32_t i, idx; volatile uint32_t done_flag; struct amr_mailbox *mbox, mbsave; struct amr_command *ac, *head, *tail; head = tail = NULL; AMR_QPUT_ODB(softs, AMR_QODB_READY); /* acknowledge interrupt */ (void) AMR_QGET_ODB(softs); mutex_enter(&softs->cmd_mutex); if (softs->mailbox->mb_nstatus != 0) { (void) ddi_dma_sync(softs->mbox_dma_handle, 0, 0, DDI_DMA_SYNC_FORCPU); /* save mailbox, which contains a list of completed commands */ bcopy((void *)(uintptr_t)(volatile void *)softs->mailbox, &mbsave, sizeof (mbsave)); mbox = &mbsave; AMR_QPUT_IDB(softs, softs->mbox_phyaddr | AMR_QIDB_ACK); /* wait for the acknowledge from hardware */ AMR_BUSYWAIT(!(AMR_QGET_IDB(softs) & AMR_QIDB_ACK), AMR_RETRYCOUNT, done_flag); if (!done_flag) { /* * command is not completed, return from the current * interrupt and wait for the next one */ cmn_err(CE_WARN, "No answer from the hardware"); mutex_exit(&softs->cmd_mutex); return; } for (i = 0; i < mbox->mb_nstatus; i++) { idx = mbox->mb_completed[i] - 1; ac = softs->busycmd[idx]; if (ac != NULL) { /* pull the command from the busy index */ softs->busycmd[idx] = NULL; if (softs->amr_busyslots > 0) softs->amr_busyslots--; if (softs->amr_busyslots == 0) cv_broadcast(&softs->cmd_cv); ac->ac_flags &= ~AMR_CMD_BUSY; ac->ac_flags &= ~AMR_CMD_GOT_SLOT; ac->ac_status = mbox->mb_status; /* enqueue here */ if (head) { tail->ac_next = ac; tail = ac; tail->ac_next = NULL; } else { tail = head = ac; ac->ac_next = NULL; } } else { AMRDB_PRINT((CE_WARN, "ac in mailbox is NULL!")); } } } else { AMRDB_PRINT((CE_WARN, "mailbox is not ready for copy out!")); } mutex_exit(&softs->cmd_mutex); if (head != NULL) { amr_call_pkt_comp(head); } /* dispatch a thread to process the pending I/O if there is any */ if ((ddi_taskq_dispatch(softs->amr_taskq, amr_start_waiting_queue, (void *)softs, DDI_NOSLEEP)) != DDI_SUCCESS) { cmn_err(CE_WARN, "No memory available to dispatch taskq"); } } static void amr_call_pkt_comp(register struct amr_command *head) { register struct scsi_pkt *pkt; register struct amr_command *ac, *localhead; localhead = head; while (localhead) { ac = localhead; localhead = ac->ac_next; ac->ac_next = NULL; pkt = ac->pkt; *pkt->pkt_scbp = 0; if (ac->ac_status == AMR_STATUS_SUCCESS) { pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = CMD_CMPLT; } else { pkt->pkt_state |= STATE_GOT_BUS | STATE_ARQ_DONE; pkt->pkt_reason = CMD_INCOMPLETE; amr_set_arq_data(pkt, KEY_HARDWARE_ERROR); } if (!(pkt->pkt_flags & FLAG_NOINTR)) { scsi_hba_pkt_comp(pkt); } } }