| /* |
| * megaraid_sas.c: source for mega_sas driver |
| * |
| * MegaRAID device driver for SAS controllers |
| * Copyright (c) 2005-2008, LSI Logic Corporation. |
| * All rights reserved. |
| * |
| * Version: |
| * Author: |
| * Rajesh Prabhakaran<Rajesh.Prabhakaran@lsil.com> |
| * Seokmann Ju |
| * |
| * 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. Neither the name of the author nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 |
| * COPYRIGHT OWNER 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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * Copyright (c) 2011 Bayard G. Bell. All rights reserved. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/file.h> |
| #include <sys/errno.h> |
| #include <sys/open.h> |
| #include <sys/cred.h> |
| #include <sys/modctl.h> |
| #include <sys/conf.h> |
| #include <sys/devops.h> |
| #include <sys/cmn_err.h> |
| #include <sys/kmem.h> |
| #include <sys/stat.h> |
| #include <sys/mkdev.h> |
| #include <sys/pci.h> |
| #include <sys/scsi/scsi.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include <sys/atomic.h> |
| #include <sys/signal.h> |
| |
| #include "megaraid_sas.h" |
| |
| /* |
| * FMA header files |
| */ |
| #include <sys/ddifm.h> |
| #include <sys/fm/protocol.h> |
| #include <sys/fm/util.h> |
| #include <sys/fm/io/ddi.h> |
| |
| /* |
| * Local static data |
| */ |
| static void *megasas_state = NULL; |
| static int debug_level_g = CL_ANN; |
| |
| #pragma weak scsi_hba_open |
| #pragma weak scsi_hba_close |
| #pragma weak scsi_hba_ioctl |
| |
| static ddi_dma_attr_t megasas_generic_dma_attr = { |
| DMA_ATTR_V0, /* dma_attr_version */ |
| 0, /* low DMA address range */ |
| 0xFFFFFFFFU, /* high DMA address range */ |
| 0xFFFFFFFFU, /* DMA counter register */ |
| 8, /* DMA address alignment */ |
| 0x07, /* DMA burstsizes */ |
| 1, /* min DMA size */ |
| 0xFFFFFFFFU, /* max DMA size */ |
| 0xFFFFFFFFU, /* segment boundary */ |
| MEGASAS_MAX_SGE_CNT, /* dma_attr_sglen */ |
| 512, /* granularity of device */ |
| 0 /* bus specific DMA flags */ |
| }; |
| |
| int32_t megasas_max_cap_maxxfer = 0x1000000; |
| |
| /* |
| * cb_ops contains base level routines |
| */ |
| static struct cb_ops megasas_cb_ops = { |
| megasas_open, /* open */ |
| megasas_close, /* close */ |
| nodev, /* strategy */ |
| nodev, /* print */ |
| nodev, /* dump */ |
| nodev, /* read */ |
| nodev, /* write */ |
| megasas_ioctl, /* ioctl */ |
| nodev, /* devmap */ |
| nodev, /* mmap */ |
| nodev, /* segmap */ |
| nochpoll, /* poll */ |
| nodev, /* cb_prop_op */ |
| 0, /* streamtab */ |
| D_NEW | D_HOTPLUG, /* cb_flag */ |
| CB_REV, /* cb_rev */ |
| nodev, /* cb_aread */ |
| nodev /* cb_awrite */ |
| }; |
| |
| /* |
| * dev_ops contains configuration routines |
| */ |
| static struct dev_ops megasas_ops = { |
| DEVO_REV, /* rev, */ |
| 0, /* refcnt */ |
| megasas_getinfo, /* getinfo */ |
| nulldev, /* identify */ |
| nulldev, /* probe */ |
| megasas_attach, /* attach */ |
| megasas_detach, /* detach */ |
| megasas_reset, /* reset */ |
| &megasas_cb_ops, /* char/block ops */ |
| NULL, /* bus ops */ |
| NULL, /* power */ |
| ddi_quiesce_not_supported, /* devo_quiesce */ |
| }; |
| |
| static struct modldrv modldrv = { |
| &mod_driverops, /* module type - driver */ |
| MEGASAS_VERSION, |
| &megasas_ops, /* driver ops */ |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, /* ml_rev - must be MODREV_1 */ |
| &modldrv, /* ml_linkage */ |
| NULL /* end of driver linkage */ |
| }; |
| |
| static struct ddi_device_acc_attr endian_attr = { |
| DDI_DEVICE_ATTR_V1, |
| DDI_STRUCTURE_LE_ACC, |
| DDI_STRICTORDER_ACC, |
| DDI_DEFAULT_ACC |
| }; |
| |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * common entry points - for loadable kernel modules * |
| * * |
| * ************************************************************************** * |
| */ |
| |
| /* |
| * _init - initialize a loadable module |
| * @void |
| * |
| * The driver should perform any one-time resource allocation or data |
| * initialization during driver loading in _init(). For example, the driver |
| * should initialize any mutexes global to the driver in this routine. |
| * The driver should not, however, use _init() to allocate or initialize |
| * anything that has to do with a particular instance of the device. |
| * Per-instance initialization must be done in attach(). |
| */ |
| int |
| _init(void) |
| { |
| int ret; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| ret = ddi_soft_state_init(&megasas_state, |
| sizeof (struct megasas_instance), 0); |
| |
| if (ret != 0) { |
| con_log(CL_ANN, (CE_WARN, "megaraid: could not init state")); |
| return (ret); |
| } |
| |
| if ((ret = scsi_hba_init(&modlinkage)) != 0) { |
| con_log(CL_ANN, (CE_WARN, "megaraid: could not init scsi hba")); |
| ddi_soft_state_fini(&megasas_state); |
| return (ret); |
| } |
| |
| ret = mod_install(&modlinkage); |
| |
| if (ret != 0) { |
| con_log(CL_ANN, (CE_WARN, "megaraid: mod_install failed")); |
| scsi_hba_fini(&modlinkage); |
| ddi_soft_state_fini(&megasas_state); |
| } |
| |
| return (ret); |
| } |
| |
| /* |
| * _info - returns information about a loadable module. |
| * @void |
| * |
| * _info() is called to return module information. This is a typical entry |
| * point that does predefined role. It simply calls mod_info(). |
| */ |
| int |
| _info(struct modinfo *modinfop) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| |
| /* |
| * _fini - prepare a loadable module for unloading |
| * @void |
| * |
| * In _fini(), the driver should release any resources that were allocated in |
| * _init(). The driver must remove itself from the system module list. |
| */ |
| int |
| _fini(void) |
| { |
| int ret; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if ((ret = mod_remove(&modlinkage)) != 0) |
| return (ret); |
| |
| scsi_hba_fini(&modlinkage); |
| |
| ddi_soft_state_fini(&megasas_state); |
| |
| return (ret); |
| } |
| |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * common entry points - for autoconfiguration * |
| * * |
| * ************************************************************************** * |
| */ |
| /* |
| * attach - adds a device to the system as part of initialization |
| * @dip: |
| * @cmd: |
| * |
| * The kernel calls a driver's attach() entry point to attach an instance of |
| * a device (for MegaRAID, it is instance of a controller) or to resume |
| * operation for an instance of a device that has been suspended or has been |
| * shut down by the power management framework |
| * The attach() entry point typically includes the following types of |
| * processing: |
| * - allocate a soft-state structure for the device instance (for MegaRAID, |
| * controller instance) |
| * - initialize per-instance mutexes |
| * - initialize condition variables |
| * - register the device's interrupts (for MegaRAID, controller's interrupts) |
| * - map the registers and memory of the device instance (for MegaRAID, |
| * controller instance) |
| * - create minor device nodes for the device instance (for MegaRAID, |
| * controller instance) |
| * - report that the device instance (for MegaRAID, controller instance) has |
| * attached |
| */ |
| static int |
| megasas_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) |
| { |
| int instance_no; |
| int nregs; |
| uint8_t added_isr_f = 0; |
| uint8_t added_soft_isr_f = 0; |
| uint8_t create_devctl_node_f = 0; |
| uint8_t create_scsi_node_f = 0; |
| uint8_t create_ioc_node_f = 0; |
| uint8_t tran_alloc_f = 0; |
| uint8_t irq; |
| uint16_t vendor_id; |
| uint16_t device_id; |
| uint16_t subsysvid; |
| uint16_t subsysid; |
| uint16_t command; |
| |
| scsi_hba_tran_t *tran; |
| ddi_dma_attr_t tran_dma_attr; |
| struct megasas_instance *instance; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* CONSTCOND */ |
| ASSERT(NO_COMPETING_THREADS); |
| |
| instance_no = ddi_get_instance(dip); |
| |
| /* |
| * Since we know that some instantiations of this device can be |
| * plugged into slave-only SBus slots, check to see whether this is |
| * one such. |
| */ |
| if (ddi_slaveonly(dip) == DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| "mega%d: Device in slave-only slot, unused", instance_no)); |
| return (DDI_FAILURE); |
| } |
| |
| switch (cmd) { |
| case DDI_ATTACH: |
| con_log(CL_DLEVEL1, (CE_NOTE, "megasas: DDI_ATTACH")); |
| /* allocate the soft state for the instance */ |
| if (ddi_soft_state_zalloc(megasas_state, instance_no) |
| != DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| "mega%d: Failed to allocate soft state", |
| instance_no)); |
| |
| return (DDI_FAILURE); |
| } |
| |
| instance = (struct megasas_instance *)ddi_get_soft_state |
| (megasas_state, instance_no); |
| |
| if (instance == NULL) { |
| con_log(CL_ANN, (CE_WARN, |
| "mega%d: Bad soft state", instance_no)); |
| |
| ddi_soft_state_free(megasas_state, instance_no); |
| |
| return (DDI_FAILURE); |
| } |
| |
| bzero((caddr_t)instance, |
| sizeof (struct megasas_instance)); |
| |
| instance->func_ptr = kmem_zalloc( |
| sizeof (struct megasas_func_ptr), KM_SLEEP); |
| ASSERT(instance->func_ptr); |
| |
| /* Setup the PCI configuration space handles */ |
| if (pci_config_setup(dip, &instance->pci_handle) != |
| DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| "mega%d: pci config setup failed ", |
| instance_no)); |
| |
| kmem_free(instance->func_ptr, |
| sizeof (struct megasas_func_ptr)); |
| ddi_soft_state_free(megasas_state, instance_no); |
| |
| return (DDI_FAILURE); |
| } |
| |
| if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid: failed to get registers.")); |
| |
| pci_config_teardown(&instance->pci_handle); |
| kmem_free(instance->func_ptr, |
| sizeof (struct megasas_func_ptr)); |
| ddi_soft_state_free(megasas_state, instance_no); |
| |
| return (DDI_FAILURE); |
| } |
| |
| vendor_id = pci_config_get16(instance->pci_handle, |
| PCI_CONF_VENID); |
| device_id = pci_config_get16(instance->pci_handle, |
| PCI_CONF_DEVID); |
| |
| subsysvid = pci_config_get16(instance->pci_handle, |
| PCI_CONF_SUBVENID); |
| subsysid = pci_config_get16(instance->pci_handle, |
| PCI_CONF_SUBSYSID); |
| |
| pci_config_put16(instance->pci_handle, PCI_CONF_COMM, |
| (pci_config_get16(instance->pci_handle, |
| PCI_CONF_COMM) | PCI_COMM_ME)); |
| irq = pci_config_get8(instance->pci_handle, |
| PCI_CONF_ILINE); |
| |
| con_log(CL_DLEVEL1, (CE_CONT, "megasas%d: " |
| "0x%x:0x%x 0x%x:0x%x, irq:%d drv-ver:%s\n", |
| instance_no, vendor_id, device_id, subsysvid, |
| subsysid, irq, MEGASAS_VERSION)); |
| |
| /* enable bus-mastering */ |
| command = pci_config_get16(instance->pci_handle, |
| PCI_CONF_COMM); |
| |
| if (!(command & PCI_COMM_ME)) { |
| command |= PCI_COMM_ME; |
| |
| pci_config_put16(instance->pci_handle, |
| PCI_CONF_COMM, command); |
| |
| con_log(CL_ANN, (CE_CONT, "megaraid%d: " |
| "enable bus-mastering\n", instance_no)); |
| } else { |
| con_log(CL_DLEVEL1, (CE_CONT, "megaraid%d: " |
| "bus-mastering already set\n", instance_no)); |
| } |
| |
| /* initialize function pointers */ |
| if ((device_id == PCI_DEVICE_ID_LSI_1078) || |
| (device_id == PCI_DEVICE_ID_LSI_1078DE)) { |
| con_log(CL_DLEVEL1, (CE_CONT, "megasas%d: " |
| "1078R/DE detected\n", instance_no)); |
| instance->func_ptr->read_fw_status_reg = |
| read_fw_status_reg_ppc; |
| instance->func_ptr->issue_cmd = issue_cmd_ppc; |
| instance->func_ptr->issue_cmd_in_sync_mode = |
| issue_cmd_in_sync_mode_ppc; |
| instance->func_ptr->issue_cmd_in_poll_mode = |
| issue_cmd_in_poll_mode_ppc; |
| instance->func_ptr->enable_intr = |
| enable_intr_ppc; |
| instance->func_ptr->disable_intr = |
| disable_intr_ppc; |
| instance->func_ptr->intr_ack = intr_ack_ppc; |
| } else { |
| con_log(CL_DLEVEL1, (CE_CONT, "megasas%d: " |
| "1064/8R detected\n", instance_no)); |
| instance->func_ptr->read_fw_status_reg = |
| read_fw_status_reg_xscale; |
| instance->func_ptr->issue_cmd = |
| issue_cmd_xscale; |
| instance->func_ptr->issue_cmd_in_sync_mode = |
| issue_cmd_in_sync_mode_xscale; |
| instance->func_ptr->issue_cmd_in_poll_mode = |
| issue_cmd_in_poll_mode_xscale; |
| instance->func_ptr->enable_intr = |
| enable_intr_xscale; |
| instance->func_ptr->disable_intr = |
| disable_intr_xscale; |
| instance->func_ptr->intr_ack = |
| intr_ack_xscale; |
| } |
| |
| instance->baseaddress = pci_config_get32( |
| instance->pci_handle, PCI_CONF_BASE0); |
| instance->baseaddress &= 0x0fffc; |
| |
| instance->dip = dip; |
| instance->vendor_id = vendor_id; |
| instance->device_id = device_id; |
| instance->subsysvid = subsysvid; |
| instance->subsysid = subsysid; |
| |
| /* Initialize FMA */ |
| instance->fm_capabilities = ddi_prop_get_int( |
| DDI_DEV_T_ANY, instance->dip, DDI_PROP_DONTPASS, |
| "fm-capable", DDI_FM_EREPORT_CAPABLE | |
| DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE |
| | DDI_FM_ERRCB_CAPABLE); |
| |
| megasas_fm_init(instance); |
| |
| /* setup the mfi based low level driver */ |
| if (init_mfi(instance) != DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, "megaraid: " |
| "could not initialize the low level driver")); |
| |
| goto fail_attach; |
| } |
| |
| /* |
| * Allocate the interrupt blocking cookie. |
| * It represents the information the framework |
| * needs to block interrupts. This cookie will |
| * be used by the locks shared accross our ISR. |
| * These locks must be initialized before we |
| * register our ISR. |
| * ddi_add_intr(9F) |
| */ |
| if (ddi_get_iblock_cookie(dip, 0, |
| &instance->iblock_cookie) != DDI_SUCCESS) { |
| |
| goto fail_attach; |
| } |
| |
| if (ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_HIGH, |
| &instance->soft_iblock_cookie) != DDI_SUCCESS) { |
| |
| goto fail_attach; |
| } |
| |
| /* |
| * Initialize the driver mutexes common to |
| * normal/high level isr |
| */ |
| if (ddi_intr_hilevel(dip, 0)) { |
| instance->isr_level = HIGH_LEVEL_INTR; |
| mutex_init(&instance->cmd_pool_mtx, |
| "cmd_pool_mtx", MUTEX_DRIVER, |
| instance->soft_iblock_cookie); |
| mutex_init(&instance->cmd_pend_mtx, |
| "cmd_pend_mtx", MUTEX_DRIVER, |
| instance->soft_iblock_cookie); |
| } else { |
| /* |
| * Initialize the driver mutexes |
| * specific to soft-isr |
| */ |
| instance->isr_level = NORMAL_LEVEL_INTR; |
| mutex_init(&instance->cmd_pool_mtx, |
| "cmd_pool_mtx", MUTEX_DRIVER, |
| instance->iblock_cookie); |
| mutex_init(&instance->cmd_pend_mtx, |
| "cmd_pend_mtx", MUTEX_DRIVER, |
| instance->iblock_cookie); |
| } |
| |
| mutex_init(&instance->completed_pool_mtx, |
| "completed_pool_mtx", MUTEX_DRIVER, |
| instance->iblock_cookie); |
| mutex_init(&instance->int_cmd_mtx, "int_cmd_mtx", |
| MUTEX_DRIVER, instance->iblock_cookie); |
| mutex_init(&instance->aen_cmd_mtx, "aen_cmd_mtx", |
| MUTEX_DRIVER, instance->iblock_cookie); |
| mutex_init(&instance->abort_cmd_mtx, "abort_cmd_mtx", |
| MUTEX_DRIVER, instance->iblock_cookie); |
| |
| cv_init(&instance->int_cmd_cv, NULL, CV_DRIVER, NULL); |
| cv_init(&instance->abort_cmd_cv, NULL, CV_DRIVER, NULL); |
| |
| INIT_LIST_HEAD(&instance->completed_pool_list); |
| |
| /* Register our isr. */ |
| if (ddi_add_intr(dip, 0, NULL, NULL, megasas_isr, |
| (caddr_t)instance) != DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| " ISR did not register")); |
| |
| goto fail_attach; |
| } |
| |
| added_isr_f = 1; |
| |
| /* Register our soft-isr for highlevel interrupts. */ |
| if (instance->isr_level == HIGH_LEVEL_INTR) { |
| if (ddi_add_softintr(dip, DDI_SOFTINT_HIGH, |
| &instance->soft_intr_id, NULL, NULL, |
| megasas_softintr, (caddr_t)instance) != |
| DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| " Software ISR did not register")); |
| |
| goto fail_attach; |
| } |
| |
| added_soft_isr_f = 1; |
| } |
| |
| /* Allocate a transport structure */ |
| tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); |
| |
| if (tran == NULL) { |
| con_log(CL_ANN, (CE_WARN, |
| "scsi_hba_tran_alloc failed")); |
| goto fail_attach; |
| } |
| |
| tran_alloc_f = 1; |
| |
| instance->tran = tran; |
| |
| tran->tran_hba_private = instance; |
| tran->tran_tgt_private = NULL; |
| tran->tran_tgt_init = megasas_tran_tgt_init; |
| tran->tran_tgt_probe = scsi_hba_probe; |
| tran->tran_tgt_free = (void (*)())NULL; |
| tran->tran_init_pkt = megasas_tran_init_pkt; |
| tran->tran_start = megasas_tran_start; |
| tran->tran_abort = megasas_tran_abort; |
| tran->tran_reset = megasas_tran_reset; |
| tran->tran_bus_reset = megasas_tran_bus_reset; |
| tran->tran_getcap = megasas_tran_getcap; |
| tran->tran_setcap = megasas_tran_setcap; |
| tran->tran_destroy_pkt = megasas_tran_destroy_pkt; |
| tran->tran_dmafree = megasas_tran_dmafree; |
| tran->tran_sync_pkt = megasas_tran_sync_pkt; |
| tran->tran_reset_notify = NULL; |
| tran->tran_quiesce = megasas_tran_quiesce; |
| tran->tran_unquiesce = megasas_tran_unquiesce; |
| |
| tran_dma_attr = megasas_generic_dma_attr; |
| tran_dma_attr.dma_attr_sgllen = instance->max_num_sge; |
| |
| /* Attach this instance of the hba */ |
| if (scsi_hba_attach_setup(dip, &tran_dma_attr, tran, 0) |
| != DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| "scsi_hba_attach failed\n")); |
| |
| goto fail_attach; |
| } |
| |
| /* create devctl node for cfgadm command */ |
| if (ddi_create_minor_node(dip, "devctl", |
| S_IFCHR, INST2DEVCTL(instance_no), |
| DDI_NT_SCSI_NEXUS, 0) == DDI_FAILURE) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid: failed to create devctl node.")); |
| |
| goto fail_attach; |
| } |
| |
| create_devctl_node_f = 1; |
| |
| /* create scsi node for cfgadm command */ |
| if (ddi_create_minor_node(dip, "scsi", S_IFCHR, |
| INST2SCSI(instance_no), |
| DDI_NT_SCSI_ATTACHMENT_POINT, 0) == |
| DDI_FAILURE) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid: failed to create scsi node.")); |
| |
| goto fail_attach; |
| } |
| |
| create_scsi_node_f = 1; |
| |
| (void) sprintf(instance->iocnode, "%d:lsirdctl", |
| instance_no); |
| |
| /* |
| * Create a node for applications |
| * for issuing ioctl to the driver. |
| */ |
| if (ddi_create_minor_node(dip, instance->iocnode, |
| S_IFCHR, INST2LSIRDCTL(instance_no), |
| DDI_PSEUDO, 0) == DDI_FAILURE) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid: failed to create ioctl node.")); |
| |
| goto fail_attach; |
| } |
| |
| create_ioc_node_f = 1; |
| |
| /* enable interrupt */ |
| instance->func_ptr->enable_intr(instance); |
| |
| /* initiate AEN */ |
| if (start_mfi_aen(instance)) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid: failed to initiate AEN.")); |
| goto fail_initiate_aen; |
| } |
| |
| con_log(CL_DLEVEL1, (CE_NOTE, |
| "AEN started for instance %d.", instance_no)); |
| |
| /* Finally! We are on the air. */ |
| ddi_report_dev(dip); |
| |
| if (megasas_check_acc_handle(instance->regmap_handle) != |
| DDI_SUCCESS) { |
| goto fail_attach; |
| } |
| if (megasas_check_acc_handle(instance->pci_handle) != |
| DDI_SUCCESS) { |
| goto fail_attach; |
| } |
| break; |
| case DDI_PM_RESUME: |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: DDI_PM_RESUME")); |
| break; |
| case DDI_RESUME: |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: DDI_RESUME")); |
| break; |
| default: |
| con_log(CL_ANN, (CE_WARN, |
| "megasas: invalid attach cmd=%x", cmd)); |
| return (DDI_FAILURE); |
| } |
| |
| return (DDI_SUCCESS); |
| |
| fail_initiate_aen: |
| fail_attach: |
| if (create_devctl_node_f) { |
| ddi_remove_minor_node(dip, "devctl"); |
| } |
| |
| if (create_scsi_node_f) { |
| ddi_remove_minor_node(dip, "scsi"); |
| } |
| |
| if (create_ioc_node_f) { |
| ddi_remove_minor_node(dip, instance->iocnode); |
| } |
| |
| if (tran_alloc_f) { |
| scsi_hba_tran_free(tran); |
| } |
| |
| |
| if (added_soft_isr_f) { |
| ddi_remove_softintr(instance->soft_intr_id); |
| } |
| |
| if (added_isr_f) { |
| ddi_remove_intr(dip, 0, instance->iblock_cookie); |
| } |
| |
| megasas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); |
| ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); |
| |
| megasas_fm_fini(instance); |
| |
| pci_config_teardown(&instance->pci_handle); |
| |
| ddi_soft_state_free(megasas_state, instance_no); |
| |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: return failure from mega_attach\n")); |
| |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * getinfo - gets device information |
| * @dip: |
| * @cmd: |
| * @arg: |
| * @resultp: |
| * |
| * The system calls getinfo() to obtain configuration information that only |
| * the driver knows. The mapping of minor numbers to device instance is |
| * entirely under the control of the driver. The system sometimes needs to ask |
| * the driver which device a particular dev_t represents. |
| * Given the device number return the devinfo pointer from the scsi_device |
| * structure. |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) |
| { |
| int rval; |
| int megasas_minor = getminor((dev_t)arg); |
| |
| struct megasas_instance *instance; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| switch (cmd) { |
| case DDI_INFO_DEVT2DEVINFO: |
| instance = (struct megasas_instance *) |
| ddi_get_soft_state(megasas_state, |
| MINOR2INST(megasas_minor)); |
| |
| if (instance == NULL) { |
| *resultp = NULL; |
| rval = DDI_FAILURE; |
| } else { |
| *resultp = instance->dip; |
| rval = DDI_SUCCESS; |
| } |
| break; |
| case DDI_INFO_DEVT2INSTANCE: |
| *resultp = (void *)instance; |
| rval = DDI_SUCCESS; |
| break; |
| default: |
| *resultp = NULL; |
| rval = DDI_FAILURE; |
| } |
| |
| return (rval); |
| } |
| |
| /* |
| * detach - detaches a device from the system |
| * @dip: pointer to the device's dev_info structure |
| * @cmd: type of detach |
| * |
| * A driver's detach() entry point is called to detach an instance of a device |
| * that is bound to the driver. The entry point is called with the instance of |
| * the device node to be detached and with DDI_DETACH, which is specified as |
| * the cmd argument to the entry point. |
| * This routine is called during driver unload. We free all the allocated |
| * resources and call the corresponding LLD so that it can also release all |
| * its resources. |
| */ |
| static int |
| megasas_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) |
| { |
| int instance_no; |
| |
| struct megasas_instance *instance; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* CONSTCOND */ |
| ASSERT(NO_COMPETING_THREADS); |
| |
| instance_no = ddi_get_instance(dip); |
| |
| instance = (struct megasas_instance *)ddi_get_soft_state(megasas_state, |
| instance_no); |
| |
| if (!instance) { |
| con_log(CL_ANN, (CE_WARN, |
| "megasas:%d could not get instance in detach", |
| instance_no)); |
| |
| return (DDI_FAILURE); |
| } |
| |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas%d: detaching device 0x%4x:0x%4x:0x%4x:0x%4x\n", |
| instance_no, instance->vendor_id, instance->device_id, |
| instance->subsysvid, instance->subsysid)); |
| |
| switch (cmd) { |
| case DDI_DETACH: |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas_detach: DDI_DETACH\n")); |
| |
| if (scsi_hba_detach(dip) != DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_WARN, |
| "megasas:%d failed to detach", |
| instance_no)); |
| |
| return (DDI_FAILURE); |
| } |
| |
| scsi_hba_tran_free(instance->tran); |
| |
| if (abort_aen_cmd(instance, instance->aen_cmd)) { |
| con_log(CL_ANN, (CE_WARN, "megasas_detach: " |
| "failed to abort prevous AEN command\n")); |
| |
| return (DDI_FAILURE); |
| } |
| |
| instance->func_ptr->disable_intr(instance); |
| |
| if (instance->isr_level == HIGH_LEVEL_INTR) { |
| ddi_remove_softintr(instance->soft_intr_id); |
| } |
| |
| ddi_remove_intr(dip, 0, instance->iblock_cookie); |
| |
| free_space_for_mfi(instance); |
| |
| megasas_fm_fini(instance); |
| |
| pci_config_teardown(&instance->pci_handle); |
| |
| kmem_free(instance->func_ptr, |
| sizeof (struct megasas_func_ptr)); |
| |
| ddi_soft_state_free(megasas_state, instance_no); |
| break; |
| case DDI_PM_SUSPEND: |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas_detach: DDI_PM_SUSPEND\n")); |
| |
| break; |
| case DDI_SUSPEND: |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas_detach: DDI_SUSPEND\n")); |
| |
| break; |
| default: |
| con_log(CL_ANN, (CE_WARN, |
| "invalid detach command:0x%x", cmd)); |
| return (DDI_FAILURE); |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * common entry points - for character driver types * |
| * * |
| * ************************************************************************** * |
| */ |
| /* |
| * open - gets access to a device |
| * @dev: |
| * @openflags: |
| * @otyp: |
| * @credp: |
| * |
| * Access to a device by one or more application programs is controlled |
| * through the open() and close() entry points. The primary function of |
| * open() is to verify that the open request is allowed. |
| */ |
| static int |
| megasas_open(dev_t *dev, int openflags, int otyp, cred_t *credp) |
| { |
| int rval = 0; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* Check root permissions */ |
| if (drv_priv(credp) != 0) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid: Non-root ioctl access tried!")); |
| return (EPERM); |
| } |
| |
| /* Verify we are being opened as a character device */ |
| if (otyp != OTYP_CHR) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid: ioctl node must be a char node\n")); |
| return (EINVAL); |
| } |
| |
| if (ddi_get_soft_state(megasas_state, MINOR2INST(getminor(*dev))) |
| == NULL) { |
| return (ENXIO); |
| } |
| |
| if (scsi_hba_open) { |
| rval = scsi_hba_open(dev, openflags, otyp, credp); |
| } |
| |
| return (rval); |
| } |
| |
| /* |
| * close - gives up access to a device |
| * @dev: |
| * @openflags: |
| * @otyp: |
| * @credp: |
| * |
| * close() should perform any cleanup necessary to finish using the minor |
| * device, and prepare the device (and driver) to be opened again. |
| */ |
| static int |
| megasas_close(dev_t dev, int openflags, int otyp, cred_t *credp) |
| { |
| int rval = 0; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* no need for locks! */ |
| |
| if (scsi_hba_close) { |
| rval = scsi_hba_close(dev, openflags, otyp, credp); |
| } |
| |
| return (rval); |
| } |
| |
| /* |
| * ioctl - performs a range of I/O commands for character drivers |
| * @dev: |
| * @cmd: |
| * @arg: |
| * @mode: |
| * @credp: |
| * @rvalp: |
| * |
| * ioctl() routine must make sure that user data is copied into or out of the |
| * kernel address space explicitly using copyin(), copyout(), ddi_copyin(), |
| * and ddi_copyout(), as appropriate. |
| * This is a wrapper routine to serialize access to the actual ioctl routine. |
| * ioctl() should return 0 on success, or the appropriate error number. The |
| * driver may also set the value returned to the calling process through rvalp. |
| */ |
| static int |
| megasas_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, |
| int *rvalp) |
| { |
| int rval = 0; |
| |
| struct megasas_instance *instance; |
| struct megasas_ioctl ioctl; |
| struct megasas_aen aen; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| instance = ddi_get_soft_state(megasas_state, MINOR2INST(getminor(dev))); |
| |
| if (instance == NULL) { |
| /* invalid minor number */ |
| con_log(CL_ANN, (CE_WARN, "megaraid: adapter not found.")); |
| return (ENXIO); |
| } |
| |
| switch ((uint_t)cmd) { |
| case MEGASAS_IOCTL_FIRMWARE: |
| if (ddi_copyin((void *) arg, &ioctl, |
| sizeof (struct megasas_ioctl), mode)) { |
| con_log(CL_ANN, (CE_WARN, "megasas_ioctl: " |
| "ERROR IOCTL copyin")); |
| return (EFAULT); |
| } |
| |
| if (ioctl.control_code == MR_DRIVER_IOCTL_COMMON) { |
| rval = handle_drv_ioctl(instance, &ioctl, mode); |
| } else { |
| rval = handle_mfi_ioctl(instance, &ioctl, mode); |
| } |
| |
| if (ddi_copyout((void *) &ioctl, (void *)arg, |
| (sizeof (struct megasas_ioctl) - 1), mode)) { |
| con_log(CL_ANN, (CE_WARN, |
| "megasas_ioctl: copy_to_user failed\n")); |
| rval = 1; |
| } |
| |
| break; |
| case MEGASAS_IOCTL_AEN: |
| if (ddi_copyin((void *) arg, &aen, |
| sizeof (struct megasas_aen), mode)) { |
| con_log(CL_ANN, (CE_WARN, |
| "megasas_ioctl: ERROR AEN copyin")); |
| return (EFAULT); |
| } |
| |
| rval = handle_mfi_aen(instance, &aen); |
| |
| if (ddi_copyout((void *) &aen, (void *)arg, |
| sizeof (struct megasas_aen), mode)) { |
| con_log(CL_ANN, (CE_WARN, |
| "megasas_ioctl: copy_to_user failed\n")); |
| rval = 1; |
| } |
| |
| break; |
| default: |
| rval = scsi_hba_ioctl(dev, cmd, arg, |
| mode, credp, rvalp); |
| |
| con_log(CL_DLEVEL1, (CE_NOTE, "megasas_ioctl: " |
| "scsi_hba_ioctl called, ret = %x.", rval)); |
| } |
| |
| return (rval); |
| } |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * common entry points - for block driver types * |
| * * |
| * ************************************************************************** * |
| */ |
| /* |
| * reset - TBD |
| * @dip: |
| * @cmd: |
| * |
| * TBD |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) |
| { |
| int instance_no; |
| |
| struct megasas_instance *instance; |
| |
| instance_no = ddi_get_instance(dip); |
| instance = (struct megasas_instance *)ddi_get_soft_state |
| (megasas_state, instance_no); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (!instance) { |
| con_log(CL_ANN, (CE_WARN, |
| "megaraid:%d could not get adapter in reset", |
| instance_no)); |
| return (DDI_FAILURE); |
| } |
| |
| con_log(CL_ANN, (CE_NOTE, "flushing cache for instance %d ..", |
| instance_no)); |
| |
| flush_cache(instance); |
| |
| return (DDI_SUCCESS); |
| } |
| |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * entry points (SCSI HBA) * |
| * * |
| * ************************************************************************** * |
| */ |
| /* |
| * tran_tgt_init - initialize a target device instance |
| * @hba_dip: |
| * @tgt_dip: |
| * @tran: |
| * @sd: |
| * |
| * The tran_tgt_init() entry point enables the HBA to allocate and initialize |
| * any per-target resources. tran_tgt_init() also enables the HBA to qualify |
| * the device's address as valid and supportable for that particular HBA. |
| * By returning DDI_FAILURE, the instance of the target driver for that device |
| * is not probed or attached. |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, |
| scsi_hba_tran_t *tran, struct scsi_device *sd) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * tran_init_pkt - allocate & initialize a scsi_pkt structure |
| * @ap: |
| * @pkt: |
| * @bp: |
| * @cmdlen: |
| * @statuslen: |
| * @tgtlen: |
| * @flags: |
| * @callback: |
| * |
| * The tran_init_pkt() entry point allocates and initializes a scsi_pkt |
| * structure and DMA resources for a target driver request. The |
| * tran_init_pkt() entry point is called when the target driver calls the |
| * SCSA function scsi_init_pkt(). Each call of the tran_init_pkt() entry point |
| * is a request to perform one or more of three possible services: |
| * - allocation and initialization of a scsi_pkt structure |
| * - allocation of DMA resources for data transfer |
| * - reallocation of DMA resources for the next portion of the data transfer |
| */ |
| static struct scsi_pkt * |
| megasas_tran_init_pkt(struct scsi_address *ap, register struct scsi_pkt *pkt, |
| struct buf *bp, int cmdlen, int statuslen, int tgtlen, |
| int flags, int (*callback)(), caddr_t arg) |
| { |
| struct scsa_cmd *acmd; |
| struct megasas_instance *instance; |
| struct scsi_pkt *new_pkt; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| instance = ADDR2MEGA(ap); |
| |
| /* step #1 : pkt allocation */ |
| if (pkt == NULL) { |
| pkt = scsi_hba_pkt_alloc(instance->dip, ap, cmdlen, statuslen, |
| tgtlen, sizeof (struct scsa_cmd), callback, arg); |
| if (pkt == NULL) { |
| return (NULL); |
| } |
| |
| acmd = PKT2CMD(pkt); |
| |
| /* |
| * Initialize the new pkt - we redundantly initialize |
| * all the fields for illustrative purposes. |
| */ |
| acmd->cmd_pkt = pkt; |
| acmd->cmd_flags = 0; |
| acmd->cmd_scblen = statuslen; |
| acmd->cmd_cdblen = cmdlen; |
| acmd->cmd_dmahandle = NULL; |
| acmd->cmd_ncookies = 0; |
| acmd->cmd_cookie = 0; |
| acmd->cmd_cookiecnt = 0; |
| acmd->cmd_nwin = 0; |
| |
| pkt->pkt_address = *ap; |
| pkt->pkt_comp = (void (*)())NULL; |
| pkt->pkt_flags = 0; |
| pkt->pkt_time = 0; |
| pkt->pkt_resid = 0; |
| pkt->pkt_state = 0; |
| pkt->pkt_statistics = 0; |
| pkt->pkt_reason = 0; |
| new_pkt = pkt; |
| } else { |
| acmd = PKT2CMD(pkt); |
| new_pkt = NULL; |
| } |
| |
| /* step #2 : dma allocation/move */ |
| if (bp && bp->b_bcount != 0) { |
| if (acmd->cmd_dmahandle == NULL) { |
| if (megasas_dma_alloc(instance, pkt, bp, flags, |
| callback) == -1) { |
| if (new_pkt) { |
| scsi_hba_pkt_free(ap, new_pkt); |
| } |
| |
| return ((struct scsi_pkt *)NULL); |
| } |
| } else { |
| if (megasas_dma_move(instance, pkt, bp) == -1) { |
| return ((struct scsi_pkt *)NULL); |
| } |
| } |
| } |
| |
| return (pkt); |
| } |
| |
| /* |
| * tran_start - transport a SCSI command to the addressed target |
| * @ap: |
| * @pkt: |
| * |
| * The tran_start() entry point for a SCSI HBA driver is called to transport a |
| * SCSI command to the addressed target. The SCSI command is described |
| * entirely within the scsi_pkt structure, which the target driver allocated |
| * through the HBA driver's tran_init_pkt() entry point. If the command |
| * involves a data transfer, DMA resources must also have been allocated for |
| * the scsi_pkt structure. |
| * |
| * Return Values : |
| * TRAN_BUSY - request queue is full, no more free scbs |
| * TRAN_ACCEPT - pkt has been submitted to the instance |
| */ |
| static int |
| megasas_tran_start(struct scsi_address *ap, register struct scsi_pkt *pkt) |
| { |
| uchar_t cmd_done = 0; |
| |
| struct megasas_instance *instance = ADDR2MEGA(ap); |
| struct megasas_cmd *cmd; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d:SCSI CDB[0]=0x%x", |
| __func__, __LINE__, pkt->pkt_cdbp[0])); |
| |
| pkt->pkt_reason = CMD_CMPLT; |
| *pkt->pkt_scbp = STATUS_GOOD; /* clear arq scsi_status */ |
| |
| cmd = build_cmd(instance, ap, pkt, &cmd_done); |
| |
| /* |
| * Check if the command is already completed by the mega_build_cmd() |
| * routine. In which case the busy_flag would be clear and scb will be |
| * NULL and appropriate reason provided in pkt_reason field |
| */ |
| if (cmd_done) { |
| if ((pkt->pkt_flags & FLAG_NOINTR) == 0) { |
| scsi_hba_pkt_comp(pkt); |
| } |
| pkt->pkt_reason = CMD_CMPLT; |
| pkt->pkt_scbp[0] = STATUS_GOOD; |
| pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET |
| | STATE_SENT_CMD; |
| return (TRAN_ACCEPT); |
| } |
| |
| if (cmd == NULL) { |
| return (TRAN_BUSY); |
| } |
| |
| if ((pkt->pkt_flags & FLAG_NOINTR) == 0) { |
| if (instance->fw_outstanding > instance->max_fw_cmds) { |
| con_log(CL_ANN, (CE_CONT, "megasas:Firmware busy")); |
| return_mfi_pkt(instance, cmd); |
| return (TRAN_BUSY); |
| } |
| |
| /* Syncronize the Cmd frame for the controller */ |
| (void) ddi_dma_sync(cmd->frame_dma_obj.dma_handle, 0, 0, |
| DDI_DMA_SYNC_FORDEV); |
| |
| instance->func_ptr->issue_cmd(cmd, instance); |
| |
| } else { |
| struct megasas_header *hdr = &cmd->frame->hdr; |
| |
| cmd->sync_cmd = MEGASAS_TRUE; |
| |
| instance->func_ptr-> issue_cmd_in_poll_mode(instance, cmd); |
| |
| pkt->pkt_reason = CMD_CMPLT; |
| pkt->pkt_statistics = 0; |
| pkt->pkt_state |= STATE_XFERRED_DATA | STATE_GOT_STATUS; |
| |
| switch (hdr->cmd_status) { |
| case MFI_STAT_OK: |
| pkt->pkt_scbp[0] = STATUS_GOOD; |
| break; |
| |
| case MFI_STAT_SCSI_DONE_WITH_ERROR: |
| |
| pkt->pkt_reason = CMD_CMPLT; |
| pkt->pkt_statistics = 0; |
| |
| ((struct scsi_status *)pkt->pkt_scbp)->sts_chk = 1; |
| break; |
| |
| case MFI_STAT_DEVICE_NOT_FOUND: |
| pkt->pkt_reason = CMD_DEV_GONE; |
| pkt->pkt_statistics = STAT_DISCON; |
| break; |
| |
| default: |
| ((struct scsi_status *)pkt->pkt_scbp)->sts_busy = 1; |
| } |
| |
| return_mfi_pkt(instance, cmd); |
| (void) megasas_common_check(instance, cmd); |
| |
| scsi_hba_pkt_comp(pkt); |
| |
| } |
| |
| return (TRAN_ACCEPT); |
| } |
| |
| /* |
| * tran_abort - Abort any commands that are currently in transport |
| * @ap: |
| * @pkt: |
| * |
| * The tran_abort() entry point for a SCSI HBA driver is called to abort any |
| * commands that are currently in transport for a particular target. This entry |
| * point is called when a target driver calls scsi_abort(). The tran_abort() |
| * entry point should attempt to abort the command denoted by the pkt |
| * parameter. If the pkt parameter is NULL, tran_abort() should attempt to |
| * abort all outstanding commands in the transport layer for the particular |
| * target or logical unit. |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_abort(struct scsi_address *ap, struct scsi_pkt *pkt) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* aborting command not supported by H/W */ |
| |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * tran_reset - reset either the SCSI bus or target |
| * @ap: |
| * @level: |
| * |
| * The tran_reset() entry point for a SCSI HBA driver is called to reset either |
| * the SCSI bus or a particular SCSI target device. This entry point is called |
| * when a target driver calls scsi_reset(). The tran_reset() entry point must |
| * reset the SCSI bus if level is RESET_ALL. If level is RESET_TARGET, just the |
| * particular target or logical unit must be reset. |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_reset(struct scsi_address *ap, int level) |
| { |
| struct megasas_instance *instance = ADDR2MEGA(ap); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (wait_for_outstanding(instance)) { |
| return (DDI_FAILURE); |
| } else { |
| return (DDI_SUCCESS); |
| } |
| } |
| |
| /* |
| * tran_bus_reset - reset the SCSI bus |
| * @dip: |
| * @level: |
| * |
| * The tran_bus_reset() vector in the scsi_hba_tran structure should be |
| * initialized during the HBA driver's attach(). The vector should point to |
| * an HBA entry point that is to be called when a user initiates a bus reset. |
| * Implementation is hardware specific. If the HBA driver cannot reset the |
| * SCSI bus without affecting the targets, the driver should fail RESET_BUS |
| * or not initialize this vector. |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_bus_reset(dev_info_t *dip, int level) |
| { |
| int instance_no = ddi_get_instance(dip); |
| |
| struct megasas_instance *instance = ddi_get_soft_state(megasas_state, |
| instance_no); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (wait_for_outstanding(instance)) { |
| return (DDI_FAILURE); |
| } else { |
| return (DDI_SUCCESS); |
| } |
| } |
| |
| /* |
| * tran_getcap - get one of a set of SCSA-defined capabilities |
| * @ap: |
| * @cap: |
| * @whom: |
| * |
| * The target driver can request the current setting of the capability for a |
| * particular target by setting the whom parameter to nonzero. A whom value of |
| * zero indicates a request for the current setting of the general capability |
| * for the SCSI bus or for adapter hardware. The tran_getcap() should return -1 |
| * for undefined capabilities or the current value of the requested capability. |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_getcap(struct scsi_address *ap, char *cap, int whom) |
| { |
| int rval = 0; |
| |
| struct megasas_instance *instance = ADDR2MEGA(ap); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* we do allow inquiring about capabilities for other targets */ |
| if (cap == NULL) { |
| return (-1); |
| } |
| |
| switch (scsi_hba_lookup_capstr(cap)) { |
| case SCSI_CAP_DMA_MAX: |
| /* Limit to 16MB max transfer */ |
| rval = megasas_max_cap_maxxfer; |
| break; |
| case SCSI_CAP_MSG_OUT: |
| rval = 1; |
| break; |
| case SCSI_CAP_DISCONNECT: |
| rval = 0; |
| break; |
| case SCSI_CAP_SYNCHRONOUS: |
| rval = 0; |
| break; |
| case SCSI_CAP_WIDE_XFER: |
| rval = 1; |
| break; |
| case SCSI_CAP_TAGGED_QING: |
| rval = 1; |
| break; |
| case SCSI_CAP_UNTAGGED_QING: |
| rval = 1; |
| break; |
| case SCSI_CAP_PARITY: |
| rval = 1; |
| break; |
| case SCSI_CAP_INITIATOR_ID: |
| rval = instance->init_id; |
| break; |
| case SCSI_CAP_ARQ: |
| rval = 1; |
| break; |
| case SCSI_CAP_LINKED_CMDS: |
| rval = 0; |
| break; |
| case SCSI_CAP_RESET_NOTIFICATION: |
| rval = 1; |
| break; |
| case SCSI_CAP_GEOMETRY: |
| rval = -1; |
| |
| break; |
| default: |
| con_log(CL_DLEVEL2, (CE_NOTE, "Default cap coming 0x%x", |
| scsi_hba_lookup_capstr(cap))); |
| rval = -1; |
| break; |
| } |
| |
| return (rval); |
| } |
| |
| /* |
| * tran_setcap - set one of a set of SCSA-defined capabilities |
| * @ap: |
| * @cap: |
| * @value: |
| * @whom: |
| * |
| * The target driver might request that the new value be set for a particular |
| * target by setting the whom parameter to nonzero. A whom value of zero |
| * means that request is to set the new value for the SCSI bus or for adapter |
| * hardware in general. |
| * The tran_setcap() should return the following values as appropriate: |
| * - -1 for undefined capabilities |
| * - 0 if the HBA driver cannot set the capability to the requested value |
| * - 1 if the HBA driver is able to set the capability to the requested value |
| */ |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) |
| { |
| int rval = 1; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* We don't allow setting capabilities for other targets */ |
| if (cap == NULL || whom == 0) { |
| return (-1); |
| } |
| |
| switch (scsi_hba_lookup_capstr(cap)) { |
| case SCSI_CAP_DMA_MAX: |
| case SCSI_CAP_MSG_OUT: |
| case SCSI_CAP_PARITY: |
| case SCSI_CAP_LINKED_CMDS: |
| case SCSI_CAP_RESET_NOTIFICATION: |
| case SCSI_CAP_DISCONNECT: |
| case SCSI_CAP_SYNCHRONOUS: |
| case SCSI_CAP_UNTAGGED_QING: |
| case SCSI_CAP_WIDE_XFER: |
| case SCSI_CAP_INITIATOR_ID: |
| case SCSI_CAP_ARQ: |
| /* |
| * None of these are settable via |
| * the capability interface. |
| */ |
| break; |
| case SCSI_CAP_TAGGED_QING: |
| rval = 1; |
| break; |
| case SCSI_CAP_SECTOR_SIZE: |
| rval = 1; |
| break; |
| |
| case SCSI_CAP_TOTAL_SECTORS: |
| rval = 1; |
| break; |
| default: |
| rval = -1; |
| break; |
| } |
| |
| return (rval); |
| } |
| |
| /* |
| * tran_destroy_pkt - deallocate scsi_pkt structure |
| * @ap: |
| * @pkt: |
| * |
| * The tran_destroy_pkt() entry point is the HBA driver function that |
| * deallocates scsi_pkt structures. The tran_destroy_pkt() entry point is |
| * called when the target driver calls scsi_destroy_pkt(). The |
| * tran_destroy_pkt() entry point must free any DMA resources that have been |
| * allocated for the packet. An implicit DMA synchronization occurs if the |
| * DMA resources are freed and any cached data remains after the completion |
| * of the transfer. |
| */ |
| static void |
| megasas_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) |
| { |
| struct scsa_cmd *acmd = PKT2CMD(pkt); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (acmd->cmd_flags & CFLAG_DMAVALID) { |
| acmd->cmd_flags &= ~CFLAG_DMAVALID; |
| |
| (void) ddi_dma_unbind_handle(acmd->cmd_dmahandle); |
| |
| ddi_dma_free_handle(&acmd->cmd_dmahandle); |
| |
| acmd->cmd_dmahandle = NULL; |
| } |
| |
| /* free the pkt */ |
| scsi_hba_pkt_free(ap, pkt); |
| } |
| |
| /* |
| * tran_dmafree - deallocates DMA resources |
| * @ap: |
| * @pkt: |
| * |
| * The tran_dmafree() entry point deallocates DMAQ resources that have been |
| * allocated for a scsi_pkt structure. The tran_dmafree() entry point is |
| * called when the target driver calls scsi_dmafree(). The tran_dmafree() must |
| * free only DMA resources allocated for a scsi_pkt structure, not the |
| * scsi_pkt itself. When DMA resources are freed, a DMA synchronization is |
| * implicitly performed. |
| */ |
| /*ARGSUSED*/ |
| static void |
| megasas_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) |
| { |
| register struct scsa_cmd *acmd = PKT2CMD(pkt); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (acmd->cmd_flags & CFLAG_DMAVALID) { |
| acmd->cmd_flags &= ~CFLAG_DMAVALID; |
| |
| (void) ddi_dma_unbind_handle(acmd->cmd_dmahandle); |
| |
| ddi_dma_free_handle(&acmd->cmd_dmahandle); |
| |
| acmd->cmd_dmahandle = NULL; |
| } |
| } |
| |
| /* |
| * tran_sync_pkt - synchronize the DMA object allocated |
| * @ap: |
| * @pkt: |
| * |
| * The tran_sync_pkt() entry point synchronizes the DMA object allocated for |
| * the scsi_pkt structure before or after a DMA transfer. The tran_sync_pkt() |
| * entry point is called when the target driver calls scsi_sync_pkt(). If the |
| * data transfer direction is a DMA read from device to memory, tran_sync_pkt() |
| * must synchronize the CPU's view of the data. If the data transfer direction |
| * is a DMA write from memory to device, tran_sync_pkt() must synchronize the |
| * device's view of the data. |
| */ |
| /*ARGSUSED*/ |
| static void |
| megasas_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* |
| * following 'ddi_dma_sync()' API call |
| * already called for each I/O in the ISR |
| */ |
| #if 0 |
| int i; |
| |
| register struct scsa_cmd *acmd = PKT2CMD(pkt); |
| |
| if (acmd->cmd_flags & CFLAG_DMAVALID) { |
| (void) ddi_dma_sync(acmd->cmd_dmahandle, acmd->cmd_dma_offset, |
| acmd->cmd_dma_len, (acmd->cmd_flags & CFLAG_DMASEND) ? |
| DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); |
| } |
| #endif |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_quiesce(dev_info_t *dip) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| return (1); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| megasas_tran_unquiesce(dev_info_t *dip) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| return (1); |
| } |
| |
| /* |
| * megasas_isr(caddr_t) |
| * |
| * The Interrupt Service Routine |
| * |
| * Collect status for all completed commands and do callback |
| * |
| */ |
| static uint_t |
| megasas_isr(struct megasas_instance *instance) |
| { |
| int need_softintr; |
| uint32_t producer; |
| uint32_t consumer; |
| uint32_t context; |
| |
| struct megasas_cmd *cmd; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| ASSERT(instance); |
| if (!instance->func_ptr->intr_ack(instance)) { |
| return (DDI_INTR_UNCLAIMED); |
| } |
| |
| (void) ddi_dma_sync(instance->mfi_internal_dma_obj.dma_handle, |
| 0, 0, DDI_DMA_SYNC_FORCPU); |
| |
| if (megasas_check_dma_handle(instance->mfi_internal_dma_obj.dma_handle) |
| != DDI_SUCCESS) { |
| megasas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); |
| ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); |
| return (DDI_INTR_UNCLAIMED); |
| } |
| |
| producer = *instance->producer; |
| consumer = *instance->consumer; |
| |
| con_log(CL_ANN1, (CE_CONT, " producer %x consumer %x ", |
| producer, consumer)); |
| |
| mutex_enter(&instance->completed_pool_mtx); |
| |
| while (consumer != producer) { |
| context = instance->reply_queue[consumer]; |
| cmd = instance->cmd_list[context]; |
| mlist_add_tail(&cmd->list, &instance->completed_pool_list); |
| |
| consumer++; |
| if (consumer == (instance->max_fw_cmds + 1)) { |
| consumer = 0; |
| } |
| } |
| |
| mutex_exit(&instance->completed_pool_mtx); |
| |
| *instance->consumer = consumer; |
| (void) ddi_dma_sync(instance->mfi_internal_dma_obj.dma_handle, |
| 0, 0, DDI_DMA_SYNC_FORDEV); |
| |
| if (instance->softint_running) { |
| need_softintr = 0; |
| } else { |
| need_softintr = 1; |
| } |
| |
| if (instance->isr_level == HIGH_LEVEL_INTR) { |
| if (need_softintr) { |
| ddi_trigger_softintr(instance->soft_intr_id); |
| } |
| } else { |
| /* |
| * Not a high-level interrupt, therefore call the soft level |
| * interrupt explicitly |
| */ |
| (void) megasas_softintr(instance); |
| } |
| |
| return (DDI_INTR_CLAIMED); |
| } |
| |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * libraries * |
| * * |
| * ************************************************************************** * |
| */ |
| /* |
| * get_mfi_pkt : Get a command from the free pool |
| */ |
| static struct megasas_cmd * |
| get_mfi_pkt(struct megasas_instance *instance) |
| { |
| mlist_t *head = &instance->cmd_pool_list; |
| struct megasas_cmd *cmd = NULL; |
| |
| mutex_enter(&instance->cmd_pool_mtx); |
| ASSERT(mutex_owned(&instance->cmd_pool_mtx)); |
| |
| if (!mlist_empty(head)) { |
| cmd = mlist_entry(head->next, struct megasas_cmd, list); |
| mlist_del_init(head->next); |
| } |
| if (cmd != NULL) |
| cmd->pkt = NULL; |
| mutex_exit(&instance->cmd_pool_mtx); |
| |
| return (cmd); |
| } |
| |
| /* |
| * return_mfi_pkt : Return a cmd to free command pool |
| */ |
| static void |
| return_mfi_pkt(struct megasas_instance *instance, struct megasas_cmd *cmd) |
| { |
| mutex_enter(&instance->cmd_pool_mtx); |
| ASSERT(mutex_owned(&instance->cmd_pool_mtx)); |
| |
| mlist_add(&cmd->list, &instance->cmd_pool_list); |
| |
| mutex_exit(&instance->cmd_pool_mtx); |
| } |
| |
| /* |
| * destroy_mfi_frame_pool |
| */ |
| static void |
| destroy_mfi_frame_pool(struct megasas_instance *instance) |
| { |
| int i; |
| uint32_t max_cmd = instance->max_fw_cmds; |
| |
| struct megasas_cmd *cmd; |
| |
| /* return all frames to pool */ |
| for (i = 0; i < max_cmd; i++) { |
| |
| cmd = instance->cmd_list[i]; |
| |
| if (cmd->frame_dma_obj_status == DMA_OBJ_ALLOCATED) |
| (void) mega_free_dma_obj(instance, cmd->frame_dma_obj); |
| |
| cmd->frame_dma_obj_status = DMA_OBJ_FREED; |
| } |
| |
| } |
| |
| /* |
| * create_mfi_frame_pool |
| */ |
| static int |
| create_mfi_frame_pool(struct megasas_instance *instance) |
| { |
| int i = 0; |
| int cookie_cnt; |
| uint16_t max_cmd; |
| uint16_t sge_sz; |
| uint32_t sgl_sz; |
| uint32_t tot_frame_size; |
| |
| struct megasas_cmd *cmd; |
| |
| max_cmd = instance->max_fw_cmds; |
| |
| sge_sz = sizeof (struct megasas_sge64); |
| |
| /* calculated the number of 64byte frames required for SGL */ |
| sgl_sz = sge_sz * instance->max_num_sge; |
| tot_frame_size = sgl_sz + MEGAMFI_FRAME_SIZE + SENSE_LENGTH; |
| |
| con_log(CL_DLEVEL3, (CE_NOTE, "create_mfi_frame_pool: " |
| "sgl_sz %x tot_frame_size %x", sgl_sz, tot_frame_size)); |
| |
| while (i < max_cmd) { |
| cmd = instance->cmd_list[i]; |
| |
| cmd->frame_dma_obj.size = tot_frame_size; |
| cmd->frame_dma_obj.dma_attr = megasas_generic_dma_attr; |
| cmd->frame_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; |
| cmd->frame_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; |
| cmd->frame_dma_obj.dma_attr.dma_attr_sgllen = 1; |
| cmd->frame_dma_obj.dma_attr.dma_attr_align = 64; |
| |
| |
| cookie_cnt = mega_alloc_dma_obj(instance, &cmd->frame_dma_obj); |
| |
| if (cookie_cnt == -1 || cookie_cnt > 1) { |
| con_log(CL_ANN, (CE_WARN, |
| "create_mfi_frame_pool: could not alloc.")); |
| return (DDI_FAILURE); |
| } |
| |
| bzero(cmd->frame_dma_obj.buffer, tot_frame_size); |
| |
| cmd->frame_dma_obj_status = DMA_OBJ_ALLOCATED; |
| cmd->frame = (union megasas_frame *)cmd->frame_dma_obj.buffer; |
| cmd->frame_phys_addr = |
| cmd->frame_dma_obj.dma_cookie[0].dmac_address; |
| |
| cmd->sense = (uint8_t *)(((unsigned long) |
| cmd->frame_dma_obj.buffer) + |
| tot_frame_size - SENSE_LENGTH); |
| cmd->sense_phys_addr = |
| cmd->frame_dma_obj.dma_cookie[0].dmac_address + |
| tot_frame_size - SENSE_LENGTH; |
| |
| if (!cmd->frame || !cmd->sense) { |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: pci_pool_alloc failed \n")); |
| |
| return (-ENOMEM); |
| } |
| |
| cmd->frame->io.context = cmd->index; |
| i++; |
| |
| con_log(CL_DLEVEL3, (CE_NOTE, "[%x]-%x", |
| cmd->frame->io.context, cmd->frame_phys_addr)); |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * free_additional_dma_buffer |
| */ |
| static void |
| free_additional_dma_buffer(struct megasas_instance *instance) |
| { |
| if (instance->mfi_internal_dma_obj.status == DMA_OBJ_ALLOCATED) { |
| (void) mega_free_dma_obj(instance, |
| instance->mfi_internal_dma_obj); |
| instance->mfi_internal_dma_obj.status = DMA_OBJ_FREED; |
| } |
| |
| if (instance->mfi_evt_detail_obj.status == DMA_OBJ_ALLOCATED) { |
| (void) mega_free_dma_obj(instance, |
| instance->mfi_evt_detail_obj); |
| instance->mfi_evt_detail_obj.status = DMA_OBJ_FREED; |
| } |
| } |
| |
| /* |
| * alloc_additional_dma_buffer |
| */ |
| static int |
| alloc_additional_dma_buffer(struct megasas_instance *instance) |
| { |
| uint32_t reply_q_sz; |
| uint32_t internal_buf_size = PAGESIZE*2; |
| |
| /* max cmds plus 1 + producer & consumer */ |
| reply_q_sz = sizeof (uint32_t) * (instance->max_fw_cmds + 1 + 2); |
| |
| instance->mfi_internal_dma_obj.size = internal_buf_size; |
| instance->mfi_internal_dma_obj.dma_attr = megasas_generic_dma_attr; |
| instance->mfi_internal_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; |
| instance->mfi_internal_dma_obj.dma_attr.dma_attr_count_max = |
| 0xFFFFFFFFU; |
| instance->mfi_internal_dma_obj.dma_attr.dma_attr_sgllen = 1; |
| |
| if (mega_alloc_dma_obj(instance, &instance->mfi_internal_dma_obj) |
| != 1) { |
| con_log(CL_ANN, (CE_WARN, "megaraid: could not alloc reply Q")); |
| return (DDI_FAILURE); |
| } |
| |
| bzero(instance->mfi_internal_dma_obj.buffer, internal_buf_size); |
| |
| instance->mfi_internal_dma_obj.status |= DMA_OBJ_ALLOCATED; |
| |
| instance->producer = (uint32_t *)((unsigned long) |
| instance->mfi_internal_dma_obj.buffer); |
| instance->consumer = (uint32_t *)((unsigned long) |
| instance->mfi_internal_dma_obj.buffer + 4); |
| instance->reply_queue = (uint32_t *)((unsigned long) |
| instance->mfi_internal_dma_obj.buffer + 8); |
| instance->internal_buf = (caddr_t)(((unsigned long) |
| instance->mfi_internal_dma_obj.buffer) + reply_q_sz + 8); |
| instance->internal_buf_dmac_add = |
| instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address + |
| reply_q_sz; |
| instance->internal_buf_size = internal_buf_size - |
| (reply_q_sz + 8); |
| |
| /* allocate evt_detail */ |
| instance->mfi_evt_detail_obj.size = sizeof (struct megasas_evt_detail); |
| instance->mfi_evt_detail_obj.dma_attr = megasas_generic_dma_attr; |
| instance->mfi_evt_detail_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; |
| instance->mfi_evt_detail_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; |
| instance->mfi_evt_detail_obj.dma_attr.dma_attr_sgllen = 1; |
| instance->mfi_evt_detail_obj.dma_attr.dma_attr_align = 1; |
| |
| if (mega_alloc_dma_obj(instance, &instance->mfi_evt_detail_obj) != 1) { |
| con_log(CL_ANN, (CE_WARN, "alloc_additional_dma_buffer: " |
| "could not data transfer buffer alloc.")); |
| return (DDI_FAILURE); |
| } |
| |
| bzero(instance->mfi_evt_detail_obj.buffer, |
| sizeof (struct megasas_evt_detail)); |
| |
| instance->mfi_evt_detail_obj.status |= DMA_OBJ_ALLOCATED; |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * free_space_for_mfi |
| */ |
| static void |
| free_space_for_mfi(struct megasas_instance *instance) |
| { |
| int i; |
| uint32_t max_cmd = instance->max_fw_cmds; |
| |
| /* already freed */ |
| if (instance->cmd_list == NULL) { |
| return; |
| } |
| |
| free_additional_dma_buffer(instance); |
| |
| /* first free the MFI frame pool */ |
| destroy_mfi_frame_pool(instance); |
| |
| /* free all the commands in the cmd_list */ |
| for (i = 0; i < instance->max_fw_cmds; i++) { |
| kmem_free(instance->cmd_list[i], |
| sizeof (struct megasas_cmd)); |
| |
| instance->cmd_list[i] = NULL; |
| } |
| |
| /* free the cmd_list buffer itself */ |
| kmem_free(instance->cmd_list, |
| sizeof (struct megasas_cmd *) * max_cmd); |
| |
| instance->cmd_list = NULL; |
| |
| INIT_LIST_HEAD(&instance->cmd_pool_list); |
| } |
| |
| /* |
| * alloc_space_for_mfi |
| */ |
| static int |
| alloc_space_for_mfi(struct megasas_instance *instance) |
| { |
| int i; |
| uint32_t max_cmd; |
| size_t sz; |
| |
| struct megasas_cmd *cmd; |
| |
| max_cmd = instance->max_fw_cmds; |
| sz = sizeof (struct megasas_cmd *) * max_cmd; |
| |
| /* |
| * instance->cmd_list is an array of struct megasas_cmd pointers. |
| * Allocate the dynamic array first and then allocate individual |
| * commands. |
| */ |
| instance->cmd_list = kmem_zalloc(sz, KM_SLEEP); |
| ASSERT(instance->cmd_list); |
| |
| for (i = 0; i < max_cmd; i++) { |
| instance->cmd_list[i] = kmem_zalloc(sizeof (struct megasas_cmd), |
| KM_SLEEP); |
| ASSERT(instance->cmd_list[i]); |
| } |
| |
| INIT_LIST_HEAD(&instance->cmd_pool_list); |
| |
| /* add all the commands to command pool (instance->cmd_pool) */ |
| for (i = 0; i < max_cmd; i++) { |
| cmd = instance->cmd_list[i]; |
| cmd->index = i; |
| |
| mlist_add_tail(&cmd->list, &instance->cmd_pool_list); |
| } |
| |
| /* create a frame pool and assign one frame to each cmd */ |
| if (create_mfi_frame_pool(instance)) { |
| con_log(CL_ANN, (CE_NOTE, "error creating frame DMA pool\n")); |
| return (DDI_FAILURE); |
| } |
| |
| /* create a frame pool and assign one frame to each cmd */ |
| if (alloc_additional_dma_buffer(instance)) { |
| con_log(CL_ANN, (CE_NOTE, "error creating frame DMA pool\n")); |
| return (DDI_FAILURE); |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * get_ctrl_info |
| */ |
| static int |
| get_ctrl_info(struct megasas_instance *instance, |
| struct megasas_ctrl_info *ctrl_info) |
| { |
| int ret = 0; |
| |
| struct megasas_cmd *cmd; |
| struct megasas_dcmd_frame *dcmd; |
| struct megasas_ctrl_info *ci; |
| |
| cmd = get_mfi_pkt(instance); |
| |
| if (!cmd) { |
| con_log(CL_ANN, (CE_WARN, |
| "Failed to get a cmd for ctrl info\n")); |
| return (DDI_FAILURE); |
| } |
| |
| dcmd = &cmd->frame->dcmd; |
| |
| ci = (struct megasas_ctrl_info *)instance->internal_buf; |
| |
| if (!ci) { |
| con_log(CL_ANN, (CE_WARN, |
| "Failed to alloc mem for ctrl info\n")); |
| return_mfi_pkt(instance, cmd); |
| return (DDI_FAILURE); |
| } |
| |
| (void) memset(ci, 0, sizeof (struct megasas_ctrl_info)); |
| |
| /* for( i = 0; i < DCMD_MBOX_SZ; i++ ) dcmd->mbox.b[i] = 0; */ |
| (void) memset(dcmd->mbox.b, 0, DCMD_MBOX_SZ); |
| |
| dcmd->cmd = MFI_CMD_OP_DCMD; |
| dcmd->cmd_status = MFI_CMD_STATUS_POLL_MODE; |
| dcmd->sge_count = 1; |
| dcmd->flags = MFI_FRAME_DIR_READ; |
| dcmd->timeout = 0; |
| dcmd->data_xfer_len = sizeof (struct megasas_ctrl_info); |
| dcmd->opcode = MR_DCMD_CTRL_GET_INFO; |
| dcmd->sgl.sge32[0].phys_addr = instance->internal_buf_dmac_add; |
| dcmd->sgl.sge32[0].length = sizeof (struct megasas_ctrl_info); |
| |
| cmd->frame_count = 1; |
| |
| if (!instance->func_ptr->issue_cmd_in_poll_mode(instance, cmd)) { |
| ret = 0; |
| (void) memcpy(ctrl_info, ci, sizeof (struct megasas_ctrl_info)); |
| } else { |
| con_log(CL_ANN, (CE_WARN, "get_ctrl_info: Ctrl info failed\n")); |
| ret = -1; |
| } |
| |
| return_mfi_pkt(instance, cmd); |
| if (megasas_common_check(instance, cmd) != DDI_SUCCESS) { |
| ret = -1; |
| } |
| |
| return (ret); |
| } |
| |
| /* |
| * abort_aen_cmd |
| */ |
| static int |
| abort_aen_cmd(struct megasas_instance *instance, |
| struct megasas_cmd *cmd_to_abort) |
| { |
| int ret = 0; |
| |
| struct megasas_cmd *cmd; |
| struct megasas_abort_frame *abort_fr; |
| |
| cmd = get_mfi_pkt(instance); |
| |
| if (!cmd) { |
| con_log(CL_ANN, (CE_WARN, |
| "Failed to get a cmd for ctrl info\n")); |
| return (DDI_FAILURE); |
| } |
| |
| abort_fr = &cmd->frame->abort; |
| |
| /* prepare and issue the abort frame */ |
| abort_fr->cmd = MFI_CMD_OP_ABORT; |
| abort_fr->cmd_status = MFI_CMD_STATUS_SYNC_MODE; |
| abort_fr->flags = 0; |
| abort_fr->abort_context = cmd_to_abort->index; |
| abort_fr->abort_mfi_phys_addr_lo = cmd_to_abort->frame_phys_addr; |
| abort_fr->abort_mfi_phys_addr_hi = 0; |
| |
| instance->aen_cmd->abort_aen = 1; |
| |
| cmd->sync_cmd = MEGASAS_TRUE; |
| cmd->frame_count = 1; |
| |
| if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { |
| con_log(CL_ANN, (CE_WARN, |
| "abort_aen_cmd: issue_cmd_in_sync_mode failed\n")); |
| ret = -1; |
| } else { |
| ret = 0; |
| } |
| |
| instance->aen_cmd->abort_aen = 1; |
| instance->aen_cmd = 0; |
| |
| return_mfi_pkt(instance, cmd); |
| (void) megasas_common_check(instance, cmd); |
| |
| return (ret); |
| } |
| |
| /* |
| * init_mfi |
| */ |
| static int |
| init_mfi(struct megasas_instance *instance) |
| { |
| off_t reglength; |
| struct megasas_cmd *cmd; |
| struct megasas_ctrl_info ctrl_info; |
| struct megasas_init_frame *init_frame; |
| struct megasas_init_queue_info *initq_info; |
| |
| if ((ddi_dev_regsize(instance->dip, REGISTER_SET_IO, ®length) |
| != DDI_SUCCESS) || reglength < MINIMUM_MFI_MEM_SZ) { |
| return (DDI_FAILURE); |
| } |
| |
| if (reglength > DEFAULT_MFI_MEM_SZ) { |
| reglength = DEFAULT_MFI_MEM_SZ; |
| con_log(CL_DLEVEL1, (CE_NOTE, |
| "mega: register length to map is 0x%lx bytes", reglength)); |
| } |
| |
| if (ddi_regs_map_setup(instance->dip, REGISTER_SET_IO, |
| &instance->regmap, 0, reglength, &endian_attr, |
| &instance->regmap_handle) != DDI_SUCCESS) { |
| con_log(CL_ANN, (CE_NOTE, |
| "megaraid: couldn't map control registers")); |
| |
| goto fail_mfi_reg_setup; |
| } |
| |
| /* we expect the FW state to be READY */ |
| if (mfi_state_transition_to_ready(instance)) { |
| con_log(CL_ANN, (CE_WARN, "megaraid: F/W is not ready")); |
| goto fail_ready_state; |
| } |
| |
| /* get various operational parameters from status register */ |
| instance->max_num_sge = |
| (instance->func_ptr->read_fw_status_reg(instance) & |
| 0xFF0000) >> 0x10; |
| /* |
| * Reduce the max supported cmds by 1. This is to ensure that the |
| * reply_q_sz (1 more than the max cmd that driver may send) |
| * does not exceed max cmds that the FW can support |
| */ |
| instance->max_fw_cmds = |
| instance->func_ptr->read_fw_status_reg(instance) & 0xFFFF; |
| instance->max_fw_cmds = instance->max_fw_cmds - 1; |
| |
| instance->max_num_sge = |
| (instance->max_num_sge > MEGASAS_MAX_SGE_CNT) ? |
| MEGASAS_MAX_SGE_CNT : instance->max_num_sge; |
| |
| /* create a pool of commands */ |
| if (alloc_space_for_mfi(instance)) |
| goto fail_alloc_fw_space; |
| |
| /* disable interrupt for initial preparation */ |
| instance->func_ptr->disable_intr(instance); |
| |
| /* |
| * Prepare a init frame. Note the init frame points to queue info |
| * structure. Each frame has SGL allocated after first 64 bytes. For |
| * this frame - since we don't need any SGL - we use SGL's space as |
| * queue info structure |
| */ |
| cmd = get_mfi_pkt(instance); |
| |
| init_frame = (struct megasas_init_frame *)cmd->frame; |
| initq_info = (struct megasas_init_queue_info *) |
| ((unsigned long)init_frame + 64); |
| |
| (void) memset(init_frame, 0, MEGAMFI_FRAME_SIZE); |
| (void) memset(initq_info, 0, sizeof (struct megasas_init_queue_info)); |
| |
| initq_info->init_flags = 0; |
| |
| initq_info->reply_queue_entries = instance->max_fw_cmds + 1; |
| |
| initq_info->producer_index_phys_addr_hi = 0; |
| initq_info->producer_index_phys_addr_lo = |
| instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address; |
| |
| initq_info->consumer_index_phys_addr_hi = 0; |
| initq_info->consumer_index_phys_addr_lo = |
| instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address + 4; |
| |
| initq_info->reply_queue_start_phys_addr_hi = 0; |
| initq_info->reply_queue_start_phys_addr_lo = |
| instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address + 8; |
| |
| init_frame->cmd = MFI_CMD_OP_INIT; |
| init_frame->cmd_status = MFI_CMD_STATUS_POLL_MODE; |
| init_frame->flags = 0; |
| init_frame->queue_info_new_phys_addr_lo = |
| cmd->frame_phys_addr + 64; |
| init_frame->queue_info_new_phys_addr_hi = 0; |
| |
| init_frame->data_xfer_len = sizeof (struct megasas_init_queue_info); |
| |
| cmd->frame_count = 1; |
| |
| /* issue the init frame in polled mode */ |
| if (instance->func_ptr->issue_cmd_in_poll_mode(instance, cmd)) { |
| con_log(CL_ANN, (CE_WARN, "failed to init firmware")); |
| goto fail_fw_init; |
| } |
| |
| return_mfi_pkt(instance, cmd); |
| if (megasas_common_check(instance, cmd) != DDI_SUCCESS) { |
| goto fail_fw_init; |
| } |
| |
| /* gather misc FW related information */ |
| if (!get_ctrl_info(instance, &ctrl_info)) { |
| instance->max_sectors_per_req = ctrl_info.max_request_size; |
| con_log(CL_ANN1, (CE_NOTE, "product name %s ld present %d", |
| ctrl_info.product_name, ctrl_info.ld_present_count)); |
| } else { |
| instance->max_sectors_per_req = instance->max_num_sge * |
| PAGESIZE / 512; |
| } |
| |
| if (megasas_check_acc_handle(instance->regmap_handle) != DDI_SUCCESS) { |
| goto fail_fw_init; |
| } |
| |
| return (0); |
| |
| fail_fw_init: |
| fail_alloc_fw_space: |
| |
| free_space_for_mfi(instance); |
| |
| fail_ready_state: |
| ddi_regs_map_free(&instance->regmap_handle); |
| |
| fail_mfi_reg_setup: |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * mfi_state_transition_to_ready : Move the FW to READY state |
| * |
| * @reg_set : MFI register set |
| */ |
| static int |
| mfi_state_transition_to_ready(struct megasas_instance *instance) |
| { |
| int i; |
| uint8_t max_wait; |
| uint32_t fw_ctrl; |
| uint32_t fw_state; |
| uint32_t cur_state; |
| |
| fw_state = |
| instance->func_ptr->read_fw_status_reg(instance) & MFI_STATE_MASK; |
| con_log(CL_ANN1, (CE_NOTE, |
| "mfi_state_transition_to_ready:FW state = 0x%x", fw_state)); |
| |
| while (fw_state != MFI_STATE_READY) { |
| con_log(CL_ANN, (CE_NOTE, |
| "mfi_state_transition_to_ready:FW state%x", fw_state)); |
| |
| switch (fw_state) { |
| case MFI_STATE_FAULT: |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: FW in FAULT state!!")); |
| |
| return (-ENODEV); |
| case MFI_STATE_WAIT_HANDSHAKE: |
| /* set the CLR bit in IMR0 */ |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: FW waiting for HANDSHAKE")); |
| /* |
| * PCI_Hot Plug: MFI F/W requires |
| * (MFI_INIT_CLEAR_HANDSHAKE|MFI_INIT_HOTPLUG) |
| * to be set |
| */ |
| /* WR_IB_MSG_0(MFI_INIT_CLEAR_HANDSHAKE, instance); */ |
| WR_IB_DOORBELL(MFI_INIT_CLEAR_HANDSHAKE | |
| MFI_INIT_HOTPLUG, instance); |
| |
| max_wait = 2; |
| cur_state = MFI_STATE_WAIT_HANDSHAKE; |
| break; |
| case MFI_STATE_BOOT_MESSAGE_PENDING: |
| /* set the CLR bit in IMR0 */ |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: FW state boot message pending")); |
| /* |
| * PCI_Hot Plug: MFI F/W requires |
| * (MFI_INIT_CLEAR_HANDSHAKE|MFI_INIT_HOTPLUG) |
| * to be set |
| */ |
| WR_IB_DOORBELL(MFI_INIT_HOTPLUG, instance); |
| |
| max_wait = 10; |
| cur_state = MFI_STATE_BOOT_MESSAGE_PENDING; |
| break; |
| case MFI_STATE_OPERATIONAL: |
| /* bring it to READY state; assuming max wait 2 secs */ |
| instance->func_ptr->disable_intr(instance); |
| con_log(CL_ANN1, (CE_NOTE, |
| "megasas: FW in OPERATIONAL state")); |
| /* |
| * PCI_Hot Plug: MFI F/W requires |
| * (MFI_INIT_READY | MFI_INIT_MFIMODE | MFI_INIT_ABORT) |
| * to be set |
| */ |
| /* WR_IB_DOORBELL(MFI_INIT_READY, instance); */ |
| WR_IB_DOORBELL(MFI_RESET_FLAGS, instance); |
| |
| max_wait = 10; |
| cur_state = MFI_STATE_OPERATIONAL; |
| break; |
| case MFI_STATE_UNDEFINED: |
| /* this state should not last for more than 2 seconds */ |
| con_log(CL_ANN, (CE_NOTE, "FW state undefined\n")); |
| |
| max_wait = 2; |
| cur_state = MFI_STATE_UNDEFINED; |
| break; |
| case MFI_STATE_BB_INIT: |
| max_wait = 2; |
| cur_state = MFI_STATE_BB_INIT; |
| break; |
| case MFI_STATE_FW_INIT: |
| max_wait = 2; |
| cur_state = MFI_STATE_FW_INIT; |
| break; |
| case MFI_STATE_DEVICE_SCAN: |
| max_wait = 10; |
| cur_state = MFI_STATE_DEVICE_SCAN; |
| break; |
| default: |
| con_log(CL_ANN, (CE_NOTE, |
| "megasas: Unknown state 0x%x\n", fw_state)); |
| return (-ENODEV); |
| } |
| |
| /* the cur_state should not last for more than max_wait secs */ |
| for (i = 0; i < (max_wait * MILLISEC); i++) { |
| /* fw_state = RD_OB_MSG_0(instance) & MFI_STATE_MASK; */ |
| fw_state = |
| instance->func_ptr->read_fw_status_reg(instance) & |
| MFI_STATE_MASK; |
| |
| if (fw_state == cur_state) { |
| delay(1 * drv_usectohz(MILLISEC)); |
| } else { |
| break; |
| } |
| } |
| |
| /* return error if fw_state hasn't changed after max_wait */ |
| if (fw_state == cur_state) { |
| con_log(CL_ANN, (CE_NOTE, |
| "FW state hasn't changed in %d secs\n", max_wait)); |
| return (-ENODEV); |
| } |
| }; |
| |
| fw_ctrl = RD_IB_DOORBELL(instance); |
| |
| con_log(CL_ANN1, (CE_NOTE, |
| "mfi_state_transition_to_ready:FW ctrl = 0x%x", fw_ctrl)); |
| |
| /* |
| * Write 0xF to the doorbell register to do the following. |
| * - Abort all outstanding commands (bit 0). |
| * - Transition from OPERATIONAL to READY state (bit 1). |
| * - Discard (possible) low MFA posted in 64-bit mode (bit-2). |
| * - Set to release FW to continue running (i.e. BIOS handshake |
| * (bit 3). |
| */ |
| WR_IB_DOORBELL(0xF, instance); |
| |
| if (megasas_check_acc_handle(instance->regmap_handle) != DDI_SUCCESS) { |
| return (-ENODEV); |
| } |
| return (0); |
| } |
| |
| /* |
| * get_seq_num |
| */ |
| static int |
| get_seq_num(struct megasas_instance *instance, |
| struct megasas_evt_log_info *eli) |
| { |
| int ret = 0; |
| |
| dma_obj_t dcmd_dma_obj; |
| struct megasas_cmd *cmd; |
| struct megasas_dcmd_frame *dcmd; |
| |
| cmd = get_mfi_pkt(instance); |
| |
| if (!cmd) { |
| cmn_err(CE_WARN, "megasas: failed to get a cmd\n"); |
| return (-ENOMEM); |
| } |
| |
| dcmd = &cmd->frame->dcmd; |
| |
| /* allocate the data transfer buffer */ |
| dcmd_dma_obj.size = sizeof (struct megasas_evt_log_info); |
| dcmd_dma_obj.dma_attr = megasas_generic_dma_attr; |
| dcmd_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; |
| dcmd_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; |
| dcmd_dma_obj.dma_attr.dma_attr_sgllen = 1; |
| dcmd_dma_obj.dma_attr.dma_attr_align = 1; |
| |
| if (mega_alloc_dma_obj(instance, &dcmd_dma_obj) != 1) { |
| con_log(CL_ANN, (CE_WARN, |
| "get_seq_num: could not data transfer buffer alloc.")); |
| return (DDI_FAILURE); |
| } |
| |
| (void) memset(dcmd_dma_obj.buffer, 0, |
| sizeof (struct megasas_evt_log_info)); |
| |
| (void) memset(dcmd->mbox.b, 0, DCMD_MBOX_SZ); |
| |
| dcmd->cmd = MFI_CMD_OP_DCMD; |
| dcmd->cmd_status = 0; |
| dcmd->sge_count = 1; |
| dcmd->flags = MFI_FRAME_DIR_READ; |
| dcmd->timeout = 0; |
| dcmd->data_xfer_len = sizeof (struct megasas_evt_log_info); |
| dcmd->opcode = MR_DCMD_CTRL_EVENT_GET_INFO; |
| dcmd->sgl.sge32[0].length = sizeof (struct megasas_evt_log_info); |
| dcmd->sgl.sge32[0].phys_addr = dcmd_dma_obj.dma_cookie[0].dmac_address; |
| |
| cmd->sync_cmd = MEGASAS_TRUE; |
| cmd->frame_count = 1; |
| |
| if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { |
| cmn_err(CE_WARN, "get_seq_num: " |
| "failed to issue MR_DCMD_CTRL_EVENT_GET_INFO\n"); |
| ret = -1; |
| } else { |
| /* copy the data back into callers buffer */ |
| bcopy(dcmd_dma_obj.buffer, eli, |
| sizeof (struct megasas_evt_log_info)); |
| ret = 0; |
| } |
| |
| if (mega_free_dma_obj(instance, dcmd_dma_obj) != DDI_SUCCESS) |
| ret = -1; |
| |
| return_mfi_pkt(instance, cmd); |
| if (megasas_common_check(instance, cmd) != DDI_SUCCESS) { |
| ret = -1; |
| } |
| return (ret); |
| } |
| |
| /* |
| * start_mfi_aen |
| */ |
| static int |
| start_mfi_aen(struct megasas_instance *instance) |
| { |
| int ret = 0; |
| |
| struct megasas_evt_log_info eli; |
| union megasas_evt_class_locale class_locale; |
| |
| /* get the latest sequence number from FW */ |
| (void) memset(&eli, 0, sizeof (struct megasas_evt_log_info)); |
| |
| if (get_seq_num(instance, &eli)) { |
| cmn_err(CE_WARN, "start_mfi_aen: failed to get seq num\n"); |
| return (-1); |
| } |
| |
| /* register AEN with FW for latest sequence number plus 1 */ |
| class_locale.members.reserved = 0; |
| class_locale.members.locale = MR_EVT_LOCALE_ALL; |
| class_locale.members.class = MR_EVT_CLASS_CRITICAL; |
| |
| ret = register_mfi_aen(instance, eli.newest_seq_num + 1, |
| class_locale.word); |
| |
| if (ret) { |
| cmn_err(CE_WARN, "start_mfi_aen: aen registration failed\n"); |
| return (-1); |
| } |
| |
| return (ret); |
| } |
| |
| /* |
| * flush_cache |
| */ |
| static void |
| flush_cache(struct megasas_instance *instance) |
| { |
| struct megasas_cmd *cmd; |
| struct megasas_dcmd_frame *dcmd; |
| |
| if (!(cmd = get_mfi_pkt(instance))) |
| return; |
| |
| dcmd = &cmd->frame->dcmd; |
| |
| (void) memset(dcmd->mbox.b, 0, DCMD_MBOX_SZ); |
| |
| dcmd->cmd = MFI_CMD_OP_DCMD; |
| dcmd->cmd_status = 0x0; |
| dcmd->sge_count = 0; |
| dcmd->flags = MFI_FRAME_DIR_NONE; |
| dcmd->timeout = 0; |
| dcmd->data_xfer_len = 0; |
| dcmd->opcode = MR_DCMD_CTRL_CACHE_FLUSH; |
| dcmd->mbox.b[0] = MR_FLUSH_CTRL_CACHE | MR_FLUSH_DISK_CACHE; |
| |
| cmd->frame_count = 1; |
| |
| if (instance->func_ptr->issue_cmd_in_poll_mode(instance, cmd)) { |
| cmn_err(CE_WARN, |
| "flush_cache: failed to issue MFI_DCMD_CTRL_CACHE_FLUSH\n"); |
| } |
| con_log(CL_DLEVEL1, (CE_NOTE, "done")); |
| return_mfi_pkt(instance, cmd); |
| (void) megasas_common_check(instance, cmd); |
| } |
| |
| /* |
| * service_mfi_aen- Completes an AEN command |
| * @instance: Adapter soft state |
| * @cmd: Command to be completed |
| * |
| */ |
| static void |
| service_mfi_aen(struct megasas_instance *instance, struct megasas_cmd *cmd) |
| { |
| uint32_t seq_num; |
| struct megasas_evt_detail *evt_detail = |
| (struct megasas_evt_detail *)instance->mfi_evt_detail_obj.buffer; |
| |
| cmd->cmd_status = cmd->frame->io.cmd_status; |
| |
| if (cmd->cmd_status == ENODATA) { |
| cmd->cmd_status = 0; |
| } |
| |
| /* |
| * log the MFI AEN event to the sysevent queue so that |
| * application will get noticed |
| */ |
| if (ddi_log_sysevent(instance->dip, DDI_VENDOR_LSI, "LSIMEGA", "SAS", |
| NULL, NULL, DDI_NOSLEEP) != DDI_SUCCESS) { |
| int instance_no = ddi_get_instance(instance->dip); |
| con_log(CL_ANN, (CE_WARN, |
| "mega%d: Failed to log AEN event", instance_no)); |
| } |
| |
| /* get copy of seq_num and class/locale for re-registration */ |
| seq_num = evt_detail->seq_num; |
| seq_num++; |
| (void) memset(instance->mfi_evt_detail_obj.buffer, 0, |
| sizeof (struct megasas_evt_detail)); |
| |
| cmd->frame->dcmd.cmd_status = 0x0; |
| cmd->frame->dcmd.mbox.w[0] = seq_num; |
| |
| instance->aen_seq_num = seq_num; |
| |
| cmd->frame_count = 1; |
| |
| /* Issue the aen registration frame */ |
| instance->func_ptr->issue_cmd(cmd, instance); |
| } |
| |
| /* |
| * complete_cmd_in_sync_mode - Completes an internal command |
| * @instance: Adapter soft state |
| * @cmd: Command to be completed |
| * |
| * The issue_cmd_in_sync_mode() function waits for a command to complete |
| * after it issues a command. This function wakes up that waiting routine by |
| * calling wake_up() on the wait queue. |
| */ |
| static void |
| complete_cmd_in_sync_mode(struct megasas_instance *instance, |
| struct megasas_cmd *cmd) |
| { |
| cmd->cmd_status = cmd->frame->io.cmd_status; |
| |
| cmd->sync_cmd = MEGASAS_FALSE; |
| |
| if (cmd->cmd_status == ENODATA) { |
| cmd->cmd_status = 0; |
| } |
| |
| cv_broadcast(&instance->int_cmd_cv); |
| } |
| |
| /* |
| * megasas_softintr - The Software ISR |
| * @param arg : HBA soft state |
| * |
| * called from high-level interrupt if hi-level interrupt are not there, |
| * otherwise triggered as a soft interrupt |
| */ |
| static uint_t |
| megasas_softintr(struct megasas_instance *instance) |
| { |
| struct scsi_pkt *pkt; |
| struct scsa_cmd *acmd; |
| struct megasas_cmd *cmd; |
| struct mlist_head *pos, *next; |
| mlist_t process_list; |
| struct megasas_header *hdr; |
| struct scsi_arq_status *arqstat; |
| |
| con_log(CL_ANN1, (CE_CONT, "megasas_softintr called")); |
| |
| ASSERT(instance); |
| mutex_enter(&instance->completed_pool_mtx); |
| |
| if (mlist_empty(&instance->completed_pool_list)) { |
| mutex_exit(&instance->completed_pool_mtx); |
| return (DDI_INTR_UNCLAIMED); |
| } |
| |
| instance->softint_running = 1; |
| |
| INIT_LIST_HEAD(&process_list); |
| mlist_splice(&instance->completed_pool_list, &process_list); |
| INIT_LIST_HEAD(&instance->completed_pool_list); |
| |
| mutex_exit(&instance->completed_pool_mtx); |
| |
| /* perform all callbacks first, before releasing the SCBs */ |
| mlist_for_each_safe(pos, next, &process_list) { |
| cmd = mlist_entry(pos, struct megasas_cmd, list); |
| |
| /* syncronize the Cmd frame for the controller */ |
| (void) ddi_dma_sync(cmd->frame_dma_obj.dma_handle, |
| 0, 0, DDI_DMA_SYNC_FORCPU); |
| |
| if (megasas_check_dma_handle(cmd->frame_dma_obj.dma_handle) != |
| DDI_SUCCESS) { |
| megasas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); |
| ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); |
| return (DDI_INTR_UNCLAIMED); |
| } |
| |
| hdr = &cmd->frame->hdr; |
| |
| /* remove the internal command from the process list */ |
| mlist_del_init(&cmd->list); |
| |
| switch (hdr->cmd) { |
| case MFI_CMD_OP_PD_SCSI: |
| case MFI_CMD_OP_LD_SCSI: |
| case MFI_CMD_OP_LD_READ: |
| case MFI_CMD_OP_LD_WRITE: |
| /* |
| * MFI_CMD_OP_PD_SCSI and MFI_CMD_OP_LD_SCSI |
| * could have been issued either through an |
| * IO path or an IOCTL path. If it was via IOCTL, |
| * we will send it to internal completion. |
| */ |
| if (cmd->sync_cmd == MEGASAS_TRUE) { |
| complete_cmd_in_sync_mode(instance, cmd); |
| break; |
| } |
| |
| /* regular commands */ |
| acmd = cmd->cmd; |
| pkt = CMD2PKT(acmd); |
| |
| if (acmd->cmd_flags & CFLAG_DMAVALID) { |
| if (acmd->cmd_flags & CFLAG_CONSISTENT) { |
| (void) ddi_dma_sync(acmd->cmd_dmahandle, |
| acmd->cmd_dma_offset, |
| acmd->cmd_dma_len, |
| DDI_DMA_SYNC_FORCPU); |
| } |
| } |
| |
| pkt->pkt_reason = CMD_CMPLT; |
| pkt->pkt_statistics = 0; |
| pkt->pkt_state = STATE_GOT_BUS |
| | STATE_GOT_TARGET | STATE_SENT_CMD |
| | STATE_XFERRED_DATA | STATE_GOT_STATUS; |
| |
| con_log(CL_ANN1, (CE_CONT, |
| "CDB[0] = %x completed for %s: size %lx context %x", |
| pkt->pkt_cdbp[0], ((acmd->islogical) ? "LD" : "PD"), |
| acmd->cmd_dmacount, hdr->context)); |
| |
| if (pkt->pkt_cdbp[0] == SCMD_INQUIRY) { |
| struct scsi_inquiry *inq; |
| |
| if (acmd->cmd_dmacount != 0) { |
| bp_mapin(acmd->cmd_buf); |
| inq = (struct scsi_inquiry *) |
| acmd->cmd_buf->b_un.b_addr; |
| |
| /* don't expose physical drives to OS */ |
| if (acmd->islogical && |
| (hdr->cmd_status == MFI_STAT_OK)) { |
| display_scsi_inquiry( |
| (caddr_t)inq); |
| } else if ((hdr->cmd_status == |
| MFI_STAT_OK) && inq->inq_dtype == |
| DTYPE_DIRECT) { |
| |
| display_scsi_inquiry( |
| (caddr_t)inq); |
| |
| /* for physical disk */ |
| hdr->cmd_status = |
| MFI_STAT_DEVICE_NOT_FOUND; |
| } |
| } |
| } |
| |
| switch (hdr->cmd_status) { |
| case MFI_STAT_OK: |
| pkt->pkt_scbp[0] = STATUS_GOOD; |
| break; |
| case MFI_STAT_LD_CC_IN_PROGRESS: |
| case MFI_STAT_LD_RECON_IN_PROGRESS: |
| /* SJ - these are not correct way */ |
| pkt->pkt_scbp[0] = STATUS_GOOD; |
| break; |
| case MFI_STAT_LD_INIT_IN_PROGRESS: |
| con_log(CL_ANN, |
| (CE_WARN, "Initialization in Progress")); |
| pkt->pkt_reason = CMD_TRAN_ERR; |
| |
| break; |
| case MFI_STAT_SCSI_DONE_WITH_ERROR: |
| con_log(CL_ANN1, (CE_CONT, "scsi_done error")); |
| |
| pkt->pkt_reason = CMD_CMPLT; |
| ((struct scsi_status *) |
| pkt->pkt_scbp)->sts_chk = 1; |
| |
| if (pkt->pkt_cdbp[0] == SCMD_TEST_UNIT_READY) { |
| |
| con_log(CL_ANN, |
| (CE_WARN, "TEST_UNIT_READY fail")); |
| |
| } else { |
| pkt->pkt_state |= STATE_ARQ_DONE; |
| arqstat = (void *)(pkt->pkt_scbp); |
| 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; |
| *(uint8_t *)&arqstat->sts_rqpkt_status = |
| STATUS_GOOD; |
| |
| bcopy(cmd->sense, |
| &(arqstat->sts_sensedata), |
| acmd->cmd_scblen - |
| offsetof(struct scsi_arq_status, |
| sts_sensedata)); |
| } |
| break; |
| case MFI_STAT_LD_OFFLINE: |
| case MFI_STAT_DEVICE_NOT_FOUND: |
| con_log(CL_ANN1, (CE_CONT, |
| "device not found error")); |
| pkt->pkt_reason = CMD_DEV_GONE; |
| pkt->pkt_statistics = STAT_DISCON; |
| break; |
| case MFI_STAT_LD_LBA_OUT_OF_RANGE: |
| pkt->pkt_state |= STATE_ARQ_DONE; |
| pkt->pkt_reason = CMD_CMPLT; |
| ((struct scsi_status *) |
| pkt->pkt_scbp)->sts_chk = 1; |
| |
| arqstat = (void *)(pkt->pkt_scbp); |
| 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; |
| *(uint8_t *)&arqstat->sts_rqpkt_status = |
| STATUS_GOOD; |
| |
| arqstat->sts_sensedata.es_valid = 1; |
| arqstat->sts_sensedata.es_key = |
| KEY_ILLEGAL_REQUEST; |
| arqstat->sts_sensedata.es_class = |
| CLASS_EXTENDED_SENSE; |
| |
| /* |
| * LOGICAL BLOCK ADDRESS OUT OF RANGE: |
| * ASC: 0x21h; ASCQ: 0x00h; |
| */ |
| arqstat->sts_sensedata.es_add_code = 0x21; |
| arqstat->sts_sensedata.es_qual_code = 0x00; |
| |
| break; |
| |
| default: |
| con_log(CL_ANN, (CE_CONT, "Unknown status!")); |
| pkt->pkt_reason = CMD_TRAN_ERR; |
| |
| break; |
| } |
| |
| atomic_add_16(&instance->fw_outstanding, (-1)); |
| |
| return_mfi_pkt(instance, cmd); |
| |
| (void) megasas_common_check(instance, cmd); |
| |
| if (acmd->cmd_dmahandle) { |
| if (megasas_check_dma_handle( |
| acmd->cmd_dmahandle) != DDI_SUCCESS) { |
| ddi_fm_service_impact(instance->dip, |
| DDI_SERVICE_UNAFFECTED); |
| pkt->pkt_reason = CMD_TRAN_ERR; |
| pkt->pkt_statistics = 0; |
| } |
| } |
| |