| /* |
| * mr_sas.c: source for mr_sas driver |
| * |
| * Solaris MegaRAID device driver for SAS2.0 controllers |
| * Copyright (c) 2008-2012, LSI Logic Corporation. |
| * All rights reserved. |
| * |
| * Version: |
| * Author: |
| * Swaminathan K S |
| * Arun Chandrashekhar |
| * Manju R |
| * Rasheed |
| * Shakeel Bukhari |
| * |
| * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2011 Bayard G. Bell. All rights reserved. |
| * Copyright 2013 Nexenta Systems, Inc. All rights reserved. |
| * Copyright 2015, 2017 Citrus IT Limited. All rights reserved. |
| * Copyright 2015 Garrett D'Amore <garrett@damore.org> |
| */ |
| |
| #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 <sys/byteorder.h> |
| #include <sys/sdt.h> |
| #include <sys/fs/dv_node.h> /* devfs_clean */ |
| |
| #include "mr_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> |
| |
| /* Macros to help Skinny and stock 2108/MFI live together. */ |
| #define WR_IB_PICK_QPORT(addr, instance) \ |
| if ((instance)->skinny) { \ |
| WR_IB_LOW_QPORT((addr), (instance)); \ |
| WR_IB_HIGH_QPORT(0, (instance)); \ |
| } else { \ |
| WR_IB_QPORT((addr), (instance)); \ |
| } |
| |
| /* |
| * Local static data |
| */ |
| static void *mrsas_state = NULL; |
| static volatile boolean_t mrsas_relaxed_ordering = B_TRUE; |
| volatile int debug_level_g = CL_NONE; |
| static volatile int msi_enable = 1; |
| static volatile int ctio_enable = 1; |
| |
| /* Default Timeout value to issue online controller reset */ |
| volatile int debug_timeout_g = 0xF0; /* 0xB4; */ |
| /* Simulate consecutive firmware fault */ |
| static volatile int debug_fw_faults_after_ocr_g = 0; |
| #ifdef OCRDEBUG |
| /* Simulate three consecutive timeout for an IO */ |
| static volatile int debug_consecutive_timeout_after_ocr_g = 0; |
| #endif |
| |
| #pragma weak scsi_hba_open |
| #pragma weak scsi_hba_close |
| #pragma weak scsi_hba_ioctl |
| |
| /* Local static prototypes. */ |
| static int mrsas_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); |
| static int mrsas_attach(dev_info_t *, ddi_attach_cmd_t); |
| #ifdef __sparc |
| static int mrsas_reset(dev_info_t *, ddi_reset_cmd_t); |
| #else |
| static int mrsas_quiesce(dev_info_t *); |
| #endif |
| static int mrsas_detach(dev_info_t *, ddi_detach_cmd_t); |
| static int mrsas_open(dev_t *, int, int, cred_t *); |
| static int mrsas_close(dev_t, int, int, cred_t *); |
| static int mrsas_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); |
| |
| static int mrsas_tran_tgt_init(dev_info_t *, dev_info_t *, |
| scsi_hba_tran_t *, struct scsi_device *); |
| static struct scsi_pkt *mrsas_tran_init_pkt(struct scsi_address *, register |
| struct scsi_pkt *, struct buf *, int, int, int, int, |
| int (*)(), caddr_t); |
| static int mrsas_tran_start(struct scsi_address *, |
| register struct scsi_pkt *); |
| static int mrsas_tran_abort(struct scsi_address *, struct scsi_pkt *); |
| static int mrsas_tran_reset(struct scsi_address *, int); |
| static int mrsas_tran_getcap(struct scsi_address *, char *, int); |
| static int mrsas_tran_setcap(struct scsi_address *, char *, int, int); |
| static void mrsas_tran_destroy_pkt(struct scsi_address *, |
| struct scsi_pkt *); |
| static void mrsas_tran_dmafree(struct scsi_address *, struct scsi_pkt *); |
| static void mrsas_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); |
| static int mrsas_tran_quiesce(dev_info_t *dip); |
| static int mrsas_tran_unquiesce(dev_info_t *dip); |
| static uint_t mrsas_isr(); |
| static uint_t mrsas_softintr(); |
| static void mrsas_undo_resources(dev_info_t *, struct mrsas_instance *); |
| |
| static void free_space_for_mfi(struct mrsas_instance *); |
| static uint32_t read_fw_status_reg_ppc(struct mrsas_instance *); |
| static void issue_cmd_ppc(struct mrsas_cmd *, struct mrsas_instance *); |
| static int issue_cmd_in_poll_mode_ppc(struct mrsas_instance *, |
| struct mrsas_cmd *); |
| static int issue_cmd_in_sync_mode_ppc(struct mrsas_instance *, |
| struct mrsas_cmd *); |
| static void enable_intr_ppc(struct mrsas_instance *); |
| static void disable_intr_ppc(struct mrsas_instance *); |
| static int intr_ack_ppc(struct mrsas_instance *); |
| static void flush_cache(struct mrsas_instance *instance); |
| void display_scsi_inquiry(caddr_t); |
| static int start_mfi_aen(struct mrsas_instance *instance); |
| static int handle_drv_ioctl(struct mrsas_instance *instance, |
| struct mrsas_ioctl *ioctl, int mode); |
| static int handle_mfi_ioctl(struct mrsas_instance *instance, |
| struct mrsas_ioctl *ioctl, int mode); |
| static int handle_mfi_aen(struct mrsas_instance *instance, |
| struct mrsas_aen *aen); |
| static struct mrsas_cmd *build_cmd(struct mrsas_instance *, |
| struct scsi_address *, struct scsi_pkt *, uchar_t *); |
| static int alloc_additional_dma_buffer(struct mrsas_instance *); |
| static void complete_cmd_in_sync_mode(struct mrsas_instance *, |
| struct mrsas_cmd *); |
| static int mrsas_kill_adapter(struct mrsas_instance *); |
| static int mrsas_issue_init_mfi(struct mrsas_instance *); |
| static int mrsas_reset_ppc(struct mrsas_instance *); |
| static uint32_t mrsas_initiate_ocr_if_fw_is_faulty(struct mrsas_instance *); |
| static int wait_for_outstanding(struct mrsas_instance *instance); |
| static int register_mfi_aen(struct mrsas_instance *instance, |
| uint32_t seq_num, uint32_t class_locale_word); |
| static int issue_mfi_pthru(struct mrsas_instance *instance, struct |
| mrsas_ioctl *ioctl, struct mrsas_cmd *cmd, int mode); |
| static int issue_mfi_dcmd(struct mrsas_instance *instance, struct |
| mrsas_ioctl *ioctl, struct mrsas_cmd *cmd, int mode); |
| static int issue_mfi_smp(struct mrsas_instance *instance, struct |
| mrsas_ioctl *ioctl, struct mrsas_cmd *cmd, int mode); |
| static int issue_mfi_stp(struct mrsas_instance *instance, struct |
| mrsas_ioctl *ioctl, struct mrsas_cmd *cmd, int mode); |
| static int abort_aen_cmd(struct mrsas_instance *instance, |
| struct mrsas_cmd *cmd_to_abort); |
| |
| static void mrsas_rem_intrs(struct mrsas_instance *instance); |
| static int mrsas_add_intrs(struct mrsas_instance *instance, int intr_type); |
| |
| static void mrsas_tran_tgt_free(dev_info_t *, dev_info_t *, |
| scsi_hba_tran_t *, struct scsi_device *); |
| static int mrsas_tran_bus_config(dev_info_t *, uint_t, |
| ddi_bus_config_op_t, void *, dev_info_t **); |
| static int mrsas_parse_devname(char *, int *, int *); |
| static int mrsas_config_all_devices(struct mrsas_instance *); |
| static int mrsas_config_ld(struct mrsas_instance *, uint16_t, |
| uint8_t, dev_info_t **); |
| static int mrsas_name_node(dev_info_t *, char *, int); |
| static void mrsas_issue_evt_taskq(struct mrsas_eventinfo *); |
| static void free_additional_dma_buffer(struct mrsas_instance *); |
| static void io_timeout_checker(void *); |
| static void mrsas_fm_init(struct mrsas_instance *); |
| static void mrsas_fm_fini(struct mrsas_instance *); |
| |
| static struct mrsas_function_template mrsas_function_template_ppc = { |
| .read_fw_status_reg = read_fw_status_reg_ppc, |
| .issue_cmd = issue_cmd_ppc, |
| .issue_cmd_in_sync_mode = issue_cmd_in_sync_mode_ppc, |
| .issue_cmd_in_poll_mode = issue_cmd_in_poll_mode_ppc, |
| .enable_intr = enable_intr_ppc, |
| .disable_intr = disable_intr_ppc, |
| .intr_ack = intr_ack_ppc, |
| .init_adapter = mrsas_init_adapter_ppc |
| }; |
| |
| |
| static struct mrsas_function_template mrsas_function_template_fusion = { |
| .read_fw_status_reg = tbolt_read_fw_status_reg, |
| .issue_cmd = tbolt_issue_cmd, |
| .issue_cmd_in_sync_mode = tbolt_issue_cmd_in_sync_mode, |
| .issue_cmd_in_poll_mode = tbolt_issue_cmd_in_poll_mode, |
| .enable_intr = tbolt_enable_intr, |
| .disable_intr = tbolt_disable_intr, |
| .intr_ack = tbolt_intr_ack, |
| .init_adapter = mrsas_init_adapter_tbolt |
| }; |
| |
| |
| ddi_dma_attr_t mrsas_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 */ |
| MRSAS_MAX_SGE_CNT, /* dma_attr_sglen */ |
| 512, /* granularity of device */ |
| 0 /* bus specific DMA flags */ |
| }; |
| |
| int32_t mrsas_max_cap_maxxfer = 0x1000000; |
| |
| /* |
| * Fix for: Thunderbolt controller IO timeout when IO write size is 1MEG, |
| * Limit size to 256K |
| */ |
| uint32_t mrsas_tbolt_max_cap_maxxfer = (512 * 512); |
| |
| /* |
| * cb_ops contains base level routines |
| */ |
| static struct cb_ops mrsas_cb_ops = { |
| mrsas_open, /* open */ |
| mrsas_close, /* close */ |
| nodev, /* strategy */ |
| nodev, /* print */ |
| nodev, /* dump */ |
| nodev, /* read */ |
| nodev, /* write */ |
| mrsas_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 mrsas_ops = { |
| DEVO_REV, /* rev, */ |
| 0, /* refcnt */ |
| mrsas_getinfo, /* getinfo */ |
| nulldev, /* identify */ |
| nulldev, /* probe */ |
| mrsas_attach, /* attach */ |
| mrsas_detach, /* detach */ |
| #ifdef __sparc |
| mrsas_reset, /* reset */ |
| #else /* __sparc */ |
| nodev, |
| #endif /* __sparc */ |
| &mrsas_cb_ops, /* char/block ops */ |
| NULL, /* bus ops */ |
| NULL, /* power */ |
| #ifdef __sparc |
| ddi_quiesce_not_needed |
| #else /* __sparc */ |
| mrsas_quiesce /* quiesce */ |
| #endif /* __sparc */ |
| }; |
| |
| static struct modldrv modldrv = { |
| &mod_driverops, /* module type - driver */ |
| MRSAS_VERSION, |
| &mrsas_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 |
| }; |
| |
| /* Use the LSI Fast Path for the 2208 (tbolt) commands. */ |
| unsigned int enable_fp = 1; |
| |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * 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(&mrsas_state, |
| sizeof (struct mrsas_instance), 0); |
| |
| if (ret != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "mr_sas: could not init state"); |
| return (ret); |
| } |
| |
| if ((ret = scsi_hba_init(&modlinkage)) != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "mr_sas: could not init scsi hba"); |
| ddi_soft_state_fini(&mrsas_state); |
| return (ret); |
| } |
| |
| ret = mod_install(&modlinkage); |
| |
| if (ret != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "mr_sas: mod_install failed"); |
| scsi_hba_fini(&modlinkage); |
| ddi_soft_state_fini(&mrsas_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)) != DDI_SUCCESS) { |
| con_log(CL_ANN1, |
| (CE_WARN, "_fini: mod_remove() failed, error 0x%X", ret)); |
| return (ret); |
| } |
| |
| scsi_hba_fini(&modlinkage); |
| con_log(CL_DLEVEL1, (CE_NOTE, "_fini: scsi_hba_fini() done.")); |
| |
| ddi_soft_state_fini(&mrsas_state); |
| con_log(CL_DLEVEL1, (CE_NOTE, "_fini: ddi_soft_state_fini() done.")); |
| |
| 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 |
| mrsas_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) |
| { |
| int instance_no; |
| int nregs; |
| int i = 0; |
| uint8_t irq; |
| uint16_t vendor_id; |
| uint16_t device_id; |
| uint16_t subsysvid; |
| uint16_t subsysid; |
| uint16_t command; |
| off_t reglength = 0; |
| int intr_types = 0; |
| char *data; |
| |
| scsi_hba_tran_t *tran; |
| ddi_dma_attr_t tran_dma_attr; |
| struct mrsas_instance *instance; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* CONSTCOND */ |
| ASSERT(NO_COMPETING_THREADS); |
| |
| instance_no = ddi_get_instance(dip); |
| |
| /* |
| * check to see whether this device is in a DMA-capable slot. |
| */ |
| if (ddi_slaveonly(dip) == DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, "Device in slave-only slot, unused"); |
| return (DDI_FAILURE); |
| } |
| |
| switch (cmd) { |
| case DDI_ATTACH: |
| /* allocate the soft state for the instance */ |
| if (ddi_soft_state_zalloc(mrsas_state, instance_no) |
| != DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, "Failed to allocate soft state"); |
| return (DDI_FAILURE); |
| } |
| |
| instance = (struct mrsas_instance *)ddi_get_soft_state |
| (mrsas_state, instance_no); |
| |
| if (instance == NULL) { |
| dev_err(dip, CE_WARN, "Bad soft state"); |
| ddi_soft_state_free(mrsas_state, instance_no); |
| return (DDI_FAILURE); |
| } |
| |
| instance->unroll.softs = 1; |
| |
| /* Setup the PCI configuration space handles */ |
| if (pci_config_setup(dip, &instance->pci_handle) != |
| DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, "pci config setup failed"); |
| |
| ddi_soft_state_free(mrsas_state, instance_no); |
| return (DDI_FAILURE); |
| } |
| |
| if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, "Failed to get registers"); |
| |
| pci_config_teardown(&instance->pci_handle); |
| ddi_soft_state_free(mrsas_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); |
| |
| dev_err(dip, CE_CONT, |
| "?0x%x:0x%x 0x%x:0x%x, irq:%d drv-ver:%s\n", |
| vendor_id, device_id, subsysvid, |
| subsysid, irq, MRSAS_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, "mr_sas%d: " |
| "enable bus-mastering", instance_no)); |
| } else { |
| con_log(CL_DLEVEL1, (CE_CONT, "mr_sas%d: " |
| "bus-mastering already set", instance_no)); |
| } |
| |
| /* initialize function pointers */ |
| switch (device_id) { |
| case PCI_DEVICE_ID_LSI_INVADER: |
| case PCI_DEVICE_ID_LSI_FURY: |
| case PCI_DEVICE_ID_LSI_INTRUDER: |
| case PCI_DEVICE_ID_LSI_INTRUDER_24: |
| case PCI_DEVICE_ID_LSI_CUTLASS_52: |
| case PCI_DEVICE_ID_LSI_CUTLASS_53: |
| dev_err(dip, CE_CONT, "?Gen3 device detected\n"); |
| instance->gen3 = 1; |
| /* FALLTHROUGH */ |
| case PCI_DEVICE_ID_LSI_TBOLT: |
| dev_err(dip, CE_CONT, "?TBOLT device detected\n"); |
| |
| instance->func_ptr = |
| &mrsas_function_template_fusion; |
| instance->tbolt = 1; |
| break; |
| |
| case PCI_DEVICE_ID_LSI_SKINNY: |
| case PCI_DEVICE_ID_LSI_SKINNY_NEW: |
| /* |
| * FALLTHRU to PPC-style functions, but mark this |
| * instance as Skinny, because the register set is |
| * slightly different (See WR_IB_PICK_QPORT), and |
| * certain other features are available to a Skinny |
| * HBA. |
| */ |
| dev_err(dip, CE_CONT, "?Skinny device detected\n"); |
| instance->skinny = 1; |
| /* FALLTHRU */ |
| |
| case PCI_DEVICE_ID_LSI_2108VDE: |
| case PCI_DEVICE_ID_LSI_2108V: |
| dev_err(dip, CE_CONT, |
| "?2108 Liberator device detected\n"); |
| |
| instance->func_ptr = |
| &mrsas_function_template_ppc; |
| break; |
| |
| default: |
| dev_err(dip, CE_WARN, "Invalid device detected"); |
| |
| pci_config_teardown(&instance->pci_handle); |
| ddi_soft_state_free(mrsas_state, instance_no); |
| return (DDI_FAILURE); |
| } |
| |
| 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; |
| instance->instance = instance_no; |
| |
| /* 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); |
| |
| mrsas_fm_init(instance); |
| |
| /* Setup register map */ |
| if ((ddi_dev_regsize(instance->dip, |
| REGISTER_SET_IO_2108, ®length) != DDI_SUCCESS) || |
| reglength < MINIMUM_MFI_MEM_SZ) { |
| goto fail_attach; |
| } |
| if (reglength > DEFAULT_MFI_MEM_SZ) { |
| reglength = DEFAULT_MFI_MEM_SZ; |
| con_log(CL_DLEVEL1, (CE_NOTE, |
| "mr_sas: register length to map is 0x%lx bytes", |
| reglength)); |
| } |
| if (ddi_regs_map_setup(instance->dip, |
| REGISTER_SET_IO_2108, &instance->regmap, 0, |
| reglength, &endian_attr, &instance->regmap_handle) |
| != DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, "couldn't map control registers"); |
| goto fail_attach; |
| } |
| |
| instance->unroll.regs = 1; |
| |
| /* |
| * Disable Interrupt Now. |
| * Setup Software interrupt |
| */ |
| instance->func_ptr->disable_intr(instance); |
| |
| if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0, |
| "mrsas-enable-msi", &data) == DDI_SUCCESS) { |
| if (strncmp(data, "no", 3) == 0) { |
| msi_enable = 0; |
| con_log(CL_ANN1, (CE_WARN, |
| "msi_enable = %d disabled", msi_enable)); |
| } |
| ddi_prop_free(data); |
| } |
| |
| dev_err(dip, CE_CONT, "?msi_enable = %d\n", msi_enable); |
| |
| if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0, |
| "mrsas-enable-fp", &data) == DDI_SUCCESS) { |
| if (strncmp(data, "no", 3) == 0) { |
| enable_fp = 0; |
| dev_err(dip, CE_NOTE, |
| "enable_fp = %d, Fast-Path disabled.\n", |
| enable_fp); |
| } |
| |
| ddi_prop_free(data); |
| } |
| |
| dev_err(dip, CE_CONT, "?enable_fp = %d\n", enable_fp); |
| |
| /* Check for all supported interrupt types */ |
| if (ddi_intr_get_supported_types( |
| dip, &intr_types) != DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, |
| "ddi_intr_get_supported_types() failed"); |
| goto fail_attach; |
| } |
| |
| con_log(CL_DLEVEL1, (CE_NOTE, |
| "ddi_intr_get_supported_types() ret: 0x%x", intr_types)); |
| |
| /* Initialize and Setup Interrupt handler */ |
| if (msi_enable && (intr_types & DDI_INTR_TYPE_MSIX)) { |
| if (mrsas_add_intrs(instance, DDI_INTR_TYPE_MSIX) != |
| DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, |
| "MSIX interrupt query failed"); |
| goto fail_attach; |
| } |
| instance->intr_type = DDI_INTR_TYPE_MSIX; |
| } else if (msi_enable && (intr_types & DDI_INTR_TYPE_MSI)) { |
| if (mrsas_add_intrs(instance, DDI_INTR_TYPE_MSI) != |
| DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, |
| "MSI interrupt query failed"); |
| goto fail_attach; |
| } |
| instance->intr_type = DDI_INTR_TYPE_MSI; |
| } else if (intr_types & DDI_INTR_TYPE_FIXED) { |
| msi_enable = 0; |
| if (mrsas_add_intrs(instance, DDI_INTR_TYPE_FIXED) != |
| DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, |
| "FIXED interrupt query failed"); |
| goto fail_attach; |
| } |
| instance->intr_type = DDI_INTR_TYPE_FIXED; |
| } else { |
| dev_err(dip, CE_WARN, "Device cannot " |
| "suppport either FIXED or MSI/X " |
| "interrupts"); |
| goto fail_attach; |
| } |
| |
| instance->unroll.intr = 1; |
| |
| if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0, |
| "mrsas-enable-ctio", &data) == DDI_SUCCESS) { |
| if (strncmp(data, "no", 3) == 0) { |
| ctio_enable = 0; |
| con_log(CL_ANN1, (CE_WARN, |
| "ctio_enable = %d disabled", ctio_enable)); |
| } |
| ddi_prop_free(data); |
| } |
| |
| dev_err(dip, CE_CONT, "?ctio_enable = %d\n", ctio_enable); |
| |
| /* setup the mfi based low level driver */ |
| if (mrsas_init_adapter(instance) != DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, |
| "could not initialize the low level driver"); |
| |
| goto fail_attach; |
| } |
| |
| /* Initialize all Mutex */ |
| INIT_LIST_HEAD(&instance->completed_pool_list); |
| mutex_init(&instance->completed_pool_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->sync_map_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->app_cmd_pool_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->config_dev_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->cmd_pend_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->ocr_flags_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->int_cmd_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| cv_init(&instance->int_cmd_cv, NULL, CV_DRIVER, NULL); |
| |
| mutex_init(&instance->cmd_pool_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->reg_write_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| if (instance->tbolt) { |
| mutex_init(&instance->cmd_app_pool_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| mutex_init(&instance->chip_mtx, NULL, |
| MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); |
| |
| } |
| |
| instance->unroll.mutexs = 1; |
| |
| instance->timeout_id = (timeout_id_t)-1; |
| |
| /* Register our soft-isr for highlevel interrupts. */ |
| instance->isr_level = instance->intr_pri; |
| if (!(instance->tbolt)) { |
| if (instance->isr_level == HIGH_LEVEL_INTR) { |
| if (ddi_add_softintr(dip, |
| DDI_SOFTINT_HIGH, |
| &instance->soft_intr_id, NULL, NULL, |
| mrsas_softintr, (caddr_t)instance) != |
| DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, |
| "Software ISR did not register"); |
| |
| goto fail_attach; |
| } |
| |
| instance->unroll.soft_isr = 1; |
| |
| } |
| } |
| |
| instance->softint_running = 0; |
| |
| /* Allocate a transport structure */ |
| tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); |
| |
| if (tran == NULL) { |
| dev_err(dip, CE_WARN, |
| "scsi_hba_tran_alloc failed"); |
| goto fail_attach; |
| } |
| |
| instance->tran = tran; |
| instance->unroll.tran = 1; |
| |
| tran->tran_hba_private = instance; |
| tran->tran_tgt_init = mrsas_tran_tgt_init; |
| tran->tran_tgt_probe = scsi_hba_probe; |
| tran->tran_tgt_free = mrsas_tran_tgt_free; |
| tran->tran_init_pkt = mrsas_tran_init_pkt; |
| if (instance->tbolt) |
| tran->tran_start = mrsas_tbolt_tran_start; |
| else |
| tran->tran_start = mrsas_tran_start; |
| tran->tran_abort = mrsas_tran_abort; |
| tran->tran_reset = mrsas_tran_reset; |
| tran->tran_getcap = mrsas_tran_getcap; |
| tran->tran_setcap = mrsas_tran_setcap; |
| tran->tran_destroy_pkt = mrsas_tran_destroy_pkt; |
| tran->tran_dmafree = mrsas_tran_dmafree; |
| tran->tran_sync_pkt = mrsas_tran_sync_pkt; |
| tran->tran_quiesce = mrsas_tran_quiesce; |
| tran->tran_unquiesce = mrsas_tran_unquiesce; |
| tran->tran_bus_config = mrsas_tran_bus_config; |
| |
| if (mrsas_relaxed_ordering) |
| mrsas_generic_dma_attr.dma_attr_flags |= |
| DDI_DMA_RELAXED_ORDERING; |
| |
| |
| tran_dma_attr = mrsas_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) { |
| dev_err(dip, CE_WARN, |
| "scsi_hba_attach failed"); |
| |
| goto fail_attach; |
| } |
| instance->unroll.tranSetup = 1; |
| con_log(CL_ANN1, |
| (CE_CONT, "scsi_hba_attach_setup() done.")); |
| |
| /* 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) { |
| dev_err(dip, CE_WARN, "failed to create devctl node."); |
| |
| goto fail_attach; |
| } |
| |
| instance->unroll.devctl = 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) { |
| dev_err(dip, CE_WARN, "failed to create scsi node."); |
| |
| goto fail_attach; |
| } |
| |
| instance->unroll.scsictl = 1; |
| |
| (void) snprintf(instance->iocnode, sizeof (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) { |
| dev_err(dip, CE_WARN, "failed to create ioctl node."); |
| |
| goto fail_attach; |
| } |
| |
| instance->unroll.ioctl = 1; |
| |
| /* Create a taskq to handle dr events */ |
| if ((instance->taskq = ddi_taskq_create(dip, |
| "mrsas_dr_taskq", 1, TASKQ_DEFAULTPRI, 0)) == NULL) { |
| dev_err(dip, CE_WARN, "failed to create taskq."); |
| instance->taskq = NULL; |
| goto fail_attach; |
| } |
| instance->unroll.taskq = 1; |
| con_log(CL_ANN1, (CE_CONT, "ddi_taskq_create() done.")); |
| |
| /* enable interrupt */ |
| instance->func_ptr->enable_intr(instance); |
| |
| /* initiate AEN */ |
| if (start_mfi_aen(instance)) { |
| dev_err(dip, CE_WARN, "failed to initiate AEN."); |
| goto fail_attach; |
| } |
| instance->unroll.aenPend = 1; |
| con_log(CL_ANN1, |
| (CE_CONT, "AEN started for instance %d.", instance_no)); |
| |
| /* Finally! We are on the air. */ |
| ddi_report_dev(dip); |
| |
| /* FMA handle checking. */ |
| if (mrsas_check_acc_handle(instance->regmap_handle) != |
| DDI_SUCCESS) { |
| goto fail_attach; |
| } |
| if (mrsas_check_acc_handle(instance->pci_handle) != |
| DDI_SUCCESS) { |
| goto fail_attach; |
| } |
| |
| instance->mr_ld_list = |
| kmem_zalloc(MRDRV_MAX_LD * sizeof (struct mrsas_ld), |
| KM_SLEEP); |
| instance->unroll.ldlist_buff = 1; |
| |
| if (instance->tbolt || instance->skinny) { |
| instance->mr_tbolt_pd_max = MRSAS_TBOLT_PD_TGT_MAX; |
| instance->mr_tbolt_pd_list = |
| kmem_zalloc(MRSAS_TBOLT_GET_PD_MAX(instance) * |
| sizeof (struct mrsas_tbolt_pd), KM_SLEEP); |
| ASSERT(instance->mr_tbolt_pd_list); |
| for (i = 0; i < instance->mr_tbolt_pd_max; i++) { |
| instance->mr_tbolt_pd_list[i].lun_type = |
| MRSAS_TBOLT_PD_LUN; |
| instance->mr_tbolt_pd_list[i].dev_id = |
| (uint8_t)i; |
| } |
| |
| instance->unroll.pdlist_buff = 1; |
| } |
| break; |
| case DDI_PM_RESUME: |
| con_log(CL_ANN, (CE_NOTE, "mr_sas: DDI_PM_RESUME")); |
| break; |
| case DDI_RESUME: |
| con_log(CL_ANN, (CE_NOTE, "mr_sas: DDI_RESUME")); |
| break; |
| default: |
| con_log(CL_ANN, |
| (CE_WARN, "mr_sas: invalid attach cmd=%x", cmd)); |
| return (DDI_FAILURE); |
| } |
| |
| |
| con_log(CL_DLEVEL1, |
| (CE_NOTE, "mrsas_attach() return SUCCESS instance_num %d", |
| instance_no)); |
| return (DDI_SUCCESS); |
| |
| fail_attach: |
| |
| mrsas_undo_resources(dip, instance); |
| |
| mrsas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); |
| ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); |
| |
| mrsas_fm_fini(instance); |
| |
| pci_config_teardown(&instance->pci_handle); |
| ddi_soft_state_free(mrsas_state, instance_no); |
| |
| 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 |
| mrsas_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) |
| { |
| int rval; |
| int mrsas_minor = getminor((dev_t)arg); |
| |
| struct mrsas_instance *instance; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| switch (cmd) { |
| case DDI_INFO_DEVT2DEVINFO: |
| instance = (struct mrsas_instance *) |
| ddi_get_soft_state(mrsas_state, |
| MINOR2INST(mrsas_minor)); |
| |
| if (instance == NULL) { |
| *resultp = NULL; |
| rval = DDI_FAILURE; |
| } else { |
| *resultp = instance->dip; |
| rval = DDI_SUCCESS; |
| } |
| break; |
| case DDI_INFO_DEVT2INSTANCE: |
| *resultp = (void *)(intptr_t) |
| (MINOR2INST(getminor((dev_t)arg))); |
| 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 |
| mrsas_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) |
| { |
| int instance_no; |
| |
| struct mrsas_instance *instance; |
| |
| con_log(CL_ANN, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| |
| /* CONSTCOND */ |
| ASSERT(NO_COMPETING_THREADS); |
| |
| instance_no = ddi_get_instance(dip); |
| |
| instance = (struct mrsas_instance *)ddi_get_soft_state(mrsas_state, |
| instance_no); |
| |
| if (!instance) { |
| dev_err(dip, CE_WARN, "could not get instance in detach"); |
| |
| return (DDI_FAILURE); |
| } |
| |
| switch (cmd) { |
| case DDI_DETACH: |
| con_log(CL_ANN, (CE_NOTE, |
| "mrsas_detach: DDI_DETACH")); |
| |
| mutex_enter(&instance->config_dev_mtx); |
| if (instance->timeout_id != (timeout_id_t)-1) { |
| mutex_exit(&instance->config_dev_mtx); |
| (void) untimeout(instance->timeout_id); |
| instance->timeout_id = (timeout_id_t)-1; |
| mutex_enter(&instance->config_dev_mtx); |
| instance->unroll.timer = 0; |
| } |
| mutex_exit(&instance->config_dev_mtx); |
| |
| if (instance->unroll.tranSetup == 1) { |
| if (scsi_hba_detach(dip) != DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, |
| "failed to detach"); |
| return (DDI_FAILURE); |
| } |
| instance->unroll.tranSetup = 0; |
| con_log(CL_ANN1, |
| (CE_CONT, "scsi_hba_dettach() done.")); |
| } |
| |
| flush_cache(instance); |
| |
| mrsas_undo_resources(dip, instance); |
| |
| mrsas_fm_fini(instance); |
| |
| pci_config_teardown(&instance->pci_handle); |
| ddi_soft_state_free(mrsas_state, instance_no); |
| break; |
| |
| case DDI_PM_SUSPEND: |
| con_log(CL_ANN, (CE_NOTE, |
| "mrsas_detach: DDI_PM_SUSPEND")); |
| |
| break; |
| case DDI_SUSPEND: |
| con_log(CL_ANN, (CE_NOTE, |
| "mrsas_detach: DDI_SUSPEND")); |
| |
| break; |
| default: |
| con_log(CL_ANN, (CE_WARN, |
| "invalid detach command:0x%x", cmd)); |
| return (DDI_FAILURE); |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| |
| static void |
| mrsas_undo_resources(dev_info_t *dip, struct mrsas_instance *instance) |
| { |
| con_log(CL_ANN, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (instance->unroll.ioctl == 1) { |
| ddi_remove_minor_node(dip, instance->iocnode); |
| instance->unroll.ioctl = 0; |
| } |
| |
| if (instance->unroll.scsictl == 1) { |
| ddi_remove_minor_node(dip, "scsi"); |
| instance->unroll.scsictl = 0; |
| } |
| |
| if (instance->unroll.devctl == 1) { |
| ddi_remove_minor_node(dip, "devctl"); |
| instance->unroll.devctl = 0; |
| } |
| |
| if (instance->unroll.tranSetup == 1) { |
| if (scsi_hba_detach(dip) != DDI_SUCCESS) { |
| dev_err(dip, CE_WARN, "failed to detach"); |
| return; /* DDI_FAILURE */ |
| } |
| instance->unroll.tranSetup = 0; |
| con_log(CL_ANN1, (CE_CONT, "scsi_hba_dettach() done.")); |
| } |
| |
| if (instance->unroll.tran == 1) { |
| scsi_hba_tran_free(instance->tran); |
| instance->unroll.tran = 0; |
| con_log(CL_ANN1, (CE_CONT, "scsi_hba_tran_free() done.")); |
| } |
| |
| if (instance->unroll.syncCmd == 1) { |
| if (instance->tbolt) { |
| if (abort_syncmap_cmd(instance, |
| instance->map_update_cmd)) { |
| dev_err(dip, CE_WARN, "mrsas_detach: " |
| "failed to abort previous syncmap command"); |
| } |
| |
| instance->unroll.syncCmd = 0; |
| con_log(CL_ANN1, (CE_CONT, "sync cmd aborted, done.")); |
| } |
| } |
| |
| if (instance->unroll.aenPend == 1) { |
| if (abort_aen_cmd(instance, instance->aen_cmd)) |
| dev_err(dip, CE_WARN, "mrsas_detach: " |
| "failed to abort prevous AEN command"); |
| |
| instance->unroll.aenPend = 0; |
| con_log(CL_ANN1, (CE_CONT, "aen cmd aborted, done.")); |
| /* This means the controller is fully initialized and running */ |
| /* Shutdown should be a last command to controller. */ |
| /* shutdown_controller(); */ |
| } |
| |
| |
| if (instance->unroll.timer == 1) { |
| if (instance->timeout_id != (timeout_id_t)-1) { |
| (void) untimeout(instance->timeout_id); |
| instance->timeout_id = (timeout_id_t)-1; |
| |
| instance->unroll.timer = 0; |
| } |
| } |
| |
| instance->func_ptr->disable_intr(instance); |
| |
| |
| if (instance->unroll.mutexs == 1) { |
| mutex_destroy(&instance->cmd_pool_mtx); |
| mutex_destroy(&instance->app_cmd_pool_mtx); |
| mutex_destroy(&instance->cmd_pend_mtx); |
| mutex_destroy(&instance->completed_pool_mtx); |
| mutex_destroy(&instance->sync_map_mtx); |
| mutex_destroy(&instance->int_cmd_mtx); |
| cv_destroy(&instance->int_cmd_cv); |
| mutex_destroy(&instance->config_dev_mtx); |
| mutex_destroy(&instance->ocr_flags_mtx); |
| mutex_destroy(&instance->reg_write_mtx); |
| |
| if (instance->tbolt) { |
| mutex_destroy(&instance->cmd_app_pool_mtx); |
| mutex_destroy(&instance->chip_mtx); |
| } |
| |
| instance->unroll.mutexs = 0; |
| con_log(CL_ANN1, (CE_CONT, "Destroy mutex & cv, done.")); |
| } |
| |
| |
| if (instance->unroll.soft_isr == 1) { |
| ddi_remove_softintr(instance->soft_intr_id); |
| instance->unroll.soft_isr = 0; |
| } |
| |
| if (instance->unroll.intr == 1) { |
| mrsas_rem_intrs(instance); |
| instance->unroll.intr = 0; |
| } |
| |
| |
| if (instance->unroll.taskq == 1) { |
| if (instance->taskq) { |
| ddi_taskq_destroy(instance->taskq); |
| instance->unroll.taskq = 0; |
| } |
| |
| } |
| |
| /* |
| * free dma memory allocated for |
| * cmds/frames/queues/driver version etc |
| */ |
| if (instance->unroll.verBuff == 1) { |
| (void) mrsas_free_dma_obj(instance, instance->drv_ver_dma_obj); |
| instance->unroll.verBuff = 0; |
| } |
| |
| if (instance->unroll.pdlist_buff == 1) { |
| if (instance->mr_tbolt_pd_list != NULL) { |
| kmem_free(instance->mr_tbolt_pd_list, |
| MRSAS_TBOLT_GET_PD_MAX(instance) * |
| sizeof (struct mrsas_tbolt_pd)); |
| } |
| |
| instance->mr_tbolt_pd_list = NULL; |
| instance->unroll.pdlist_buff = 0; |
| } |
| |
| if (instance->unroll.ldlist_buff == 1) { |
| if (instance->mr_ld_list != NULL) { |
| kmem_free(instance->mr_ld_list, MRDRV_MAX_LD |
| * sizeof (struct mrsas_ld)); |
| } |
| |
| instance->mr_ld_list = NULL; |
| instance->unroll.ldlist_buff = 0; |
| } |
| |
| if (instance->tbolt) { |
| if (instance->unroll.alloc_space_mpi2 == 1) { |
| free_space_for_mpi2(instance); |
| instance->unroll.alloc_space_mpi2 = 0; |
| } |
| } else { |
| if (instance->unroll.alloc_space_mfi == 1) { |
| free_space_for_mfi(instance); |
| instance->unroll.alloc_space_mfi = 0; |
| } |
| } |
| |
| if (instance->unroll.regs == 1) { |
| ddi_regs_map_free(&instance->regmap_handle); |
| instance->unroll.regs = 0; |
| con_log(CL_ANN1, (CE_CONT, "ddi_regs_map_free() done.")); |
| } |
| } |
| |
| |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * 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 |
| mrsas_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, |
| "mr_sas: Non-root ioctl access denied!")); |
| return (EPERM); |
| } |
| |
| /* Verify we are being opened as a character device */ |
| if (otyp != OTYP_CHR) { |
| con_log(CL_ANN, (CE_WARN, |
| "mr_sas: ioctl node must be a char node")); |
| return (EINVAL); |
| } |
| |
| if (ddi_get_soft_state(mrsas_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 |
| mrsas_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 |
| mrsas_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, |
| int *rvalp) |
| { |
| int rval = 0; |
| |
| struct mrsas_instance *instance; |
| struct mrsas_ioctl *ioctl; |
| struct mrsas_aen aen; |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| instance = ddi_get_soft_state(mrsas_state, MINOR2INST(getminor(dev))); |
| |
| if (instance == NULL) { |
| /* invalid minor number */ |
| con_log(CL_ANN, (CE_WARN, "mr_sas: adapter not found.")); |
| return (ENXIO); |
| } |
| |
| ioctl = (struct mrsas_ioctl *)kmem_zalloc(sizeof (struct mrsas_ioctl), |
| KM_SLEEP); |
| ASSERT(ioctl); |
| |
| switch ((uint_t)cmd) { |
| case MRSAS_IOCTL_FIRMWARE: |
| if (ddi_copyin((void *)arg, ioctl, |
| sizeof (struct mrsas_ioctl), mode)) { |
| con_log(CL_ANN, (CE_WARN, "mrsas_ioctl: " |
| "ERROR IOCTL copyin")); |
| kmem_free(ioctl, sizeof (struct mrsas_ioctl)); |
| return (EFAULT); |
| } |
| |
| if (ioctl->control_code == MRSAS_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 mrsas_ioctl) - 1), mode)) { |
| con_log(CL_ANN, (CE_WARN, |
| "mrsas_ioctl: copy_to_user failed")); |
| rval = 1; |
| } |
| |
| break; |
| case MRSAS_IOCTL_AEN: |
| if (ddi_copyin((void *) arg, &aen, |
| sizeof (struct mrsas_aen), mode)) { |
| con_log(CL_ANN, (CE_WARN, |
| "mrsas_ioctl: ERROR AEN copyin")); |
| kmem_free(ioctl, sizeof (struct mrsas_ioctl)); |
| return (EFAULT); |
| } |
| |
| rval = handle_mfi_aen(instance, &aen); |
| |
| if (ddi_copyout((void *) &aen, (void *)arg, |
| sizeof (struct mrsas_aen), mode)) { |
| con_log(CL_ANN, (CE_WARN, |
| "mrsas_ioctl: copy_to_user failed")); |
| rval = 1; |
| } |
| |
| break; |
| default: |
| rval = scsi_hba_ioctl(dev, cmd, arg, |
| mode, credp, rvalp); |
| |
| con_log(CL_DLEVEL1, (CE_NOTE, "mrsas_ioctl: " |
| "scsi_hba_ioctl called, ret = %x.", rval)); |
| } |
| |
| kmem_free(ioctl, sizeof (struct mrsas_ioctl)); |
| return (rval); |
| } |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * common entry points - for block driver types * |
| * * |
| * ************************************************************************** * |
| */ |
| #ifdef __sparc |
| /* |
| * reset - TBD |
| * @dip: |
| * @cmd: |
| * |
| * TBD |
| */ |
| /*ARGSUSED*/ |
| static int |
| mrsas_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) |
| { |
| int instance_no; |
| |
| struct mrsas_instance *instance; |
| |
| instance_no = ddi_get_instance(dip); |
| instance = (struct mrsas_instance *)ddi_get_soft_state |
| (mrsas_state, instance_no); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (!instance) { |
| con_log(CL_ANN, (CE_WARN, "mr_sas:%d could not get adapter " |
| "in reset", instance_no)); |
| return (DDI_FAILURE); |
| } |
| |
| instance->func_ptr->disable_intr(instance); |
| |
| con_log(CL_ANN1, (CE_CONT, "flushing cache for instance %d", |
| instance_no)); |
| |
| flush_cache(instance); |
| |
| return (DDI_SUCCESS); |
| } |
| #else /* __sparc */ |
| /*ARGSUSED*/ |
| static int |
| mrsas_quiesce(dev_info_t *dip) |
| { |
| int instance_no; |
| |
| struct mrsas_instance *instance; |
| |
| instance_no = ddi_get_instance(dip); |
| instance = (struct mrsas_instance *)ddi_get_soft_state |
| (mrsas_state, instance_no); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (!instance) { |
| con_log(CL_ANN1, (CE_WARN, "mr_sas:%d could not get adapter " |
| "in quiesce", instance_no)); |
| return (DDI_FAILURE); |
| } |
| if (instance->deadadapter || instance->adapterresetinprogress) { |
| con_log(CL_ANN1, (CE_WARN, "mr_sas:%d adapter is not in " |
| "healthy state", instance_no)); |
| return (DDI_FAILURE); |
| } |
| |
| if (abort_aen_cmd(instance, instance->aen_cmd)) { |
| con_log(CL_ANN1, (CE_WARN, "mrsas_quiesce: " |
| "failed to abort prevous AEN command QUIESCE")); |
| } |
| |
| if (instance->tbolt) { |
| if (abort_syncmap_cmd(instance, |
| instance->map_update_cmd)) { |
| dev_err(dip, CE_WARN, |
| "mrsas_detach: failed to abort " |
| "previous syncmap command"); |
| return (DDI_FAILURE); |
| } |
| } |
| |
| instance->func_ptr->disable_intr(instance); |
| |
| con_log(CL_ANN1, (CE_CONT, "flushing cache for instance %d", |
| instance_no)); |
| |
| flush_cache(instance); |
| |
| if (wait_for_outstanding(instance)) { |
| con_log(CL_ANN1, |
| (CE_CONT, "wait_for_outstanding: return FAIL.\n")); |
| return (DDI_FAILURE); |
| } |
| return (DDI_SUCCESS); |
| } |
| #endif /* __sparc */ |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * 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 |
| mrsas_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, |
| scsi_hba_tran_t *tran, struct scsi_device *sd) |
| { |
| struct mrsas_instance *instance; |
| uint16_t tgt = sd->sd_address.a_target; |
| uint8_t lun = sd->sd_address.a_lun; |
| dev_info_t *child = NULL; |
| |
| con_log(CL_DLEVEL2, (CE_NOTE, "mrsas_tgt_init target %d lun %d", |
| tgt, lun)); |
| |
| instance = ADDR2MR(&sd->sd_address); |
| |
| if (ndi_dev_is_persistent_node(tgt_dip) == 0) { |
| /* |
| * If no persistent node exists, we don't allow .conf node |
| * to be created. |
| */ |
| if ((child = mrsas_find_child(instance, tgt, lun)) != NULL) { |
| con_log(CL_DLEVEL2, |
| (CE_NOTE, "mrsas_tgt_init find child =" |
| " %p t = %d l = %d", (void *)child, tgt, lun)); |
| if (ndi_merge_node(tgt_dip, mrsas_name_node) != |
| DDI_SUCCESS) |
| /* Create this .conf node */ |
| return (DDI_SUCCESS); |
| } |
| con_log(CL_DLEVEL2, (CE_NOTE, "mrsas_tgt_init in ndi_per " |
| "DDI_FAILURE t = %d l = %d", tgt, lun)); |
| return (DDI_FAILURE); |
| |
| } |
| |
| con_log(CL_DLEVEL2, (CE_NOTE, "mrsas_tgt_init dev_dip %p tgt_dip %p", |
| (void *)instance->mr_ld_list[tgt].dip, (void *)tgt_dip)); |
| |
| if (tgt < MRDRV_MAX_LD && lun == 0) { |
| if (instance->mr_ld_list[tgt].dip == NULL && |
| strcmp(ddi_driver_name(sd->sd_dev), "sd") == 0) { |
| mutex_enter(&instance->config_dev_mtx); |
| instance->mr_ld_list[tgt].dip = tgt_dip; |
| instance->mr_ld_list[tgt].lun_type = MRSAS_LD_LUN; |
| instance->mr_ld_list[tgt].flag = MRDRV_TGT_VALID; |
| mutex_exit(&instance->config_dev_mtx); |
| } |
| } else if (instance->tbolt || instance->skinny) { |
| if (instance->mr_tbolt_pd_list[tgt].dip == NULL) { |
| mutex_enter(&instance->config_dev_mtx); |
| instance->mr_tbolt_pd_list[tgt].dip = tgt_dip; |
| instance->mr_tbolt_pd_list[tgt].flag = |
| MRDRV_TGT_VALID; |
| mutex_exit(&instance->config_dev_mtx); |
| con_log(CL_ANN1, (CE_NOTE, "mrsas_tran_tgt_init:" |
| "t%xl%x", tgt, lun)); |
| } |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /*ARGSUSED*/ |
| static void |
| mrsas_tran_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip, |
| scsi_hba_tran_t *hba_tran, struct scsi_device *sd) |
| { |
| struct mrsas_instance *instance; |
| int tgt = sd->sd_address.a_target; |
| int lun = sd->sd_address.a_lun; |
| |
| instance = ADDR2MR(&sd->sd_address); |
| |
| con_log(CL_DLEVEL2, (CE_NOTE, "tgt_free t = %d l = %d", tgt, lun)); |
| |
| if (tgt < MRDRV_MAX_LD && lun == 0) { |
| if (instance->mr_ld_list[tgt].dip == tgt_dip) { |
| mutex_enter(&instance->config_dev_mtx); |
| instance->mr_ld_list[tgt].dip = NULL; |
| mutex_exit(&instance->config_dev_mtx); |
| } |
| } else if (instance->tbolt || instance->skinny) { |
| mutex_enter(&instance->config_dev_mtx); |
| instance->mr_tbolt_pd_list[tgt].dip = NULL; |
| mutex_exit(&instance->config_dev_mtx); |
| con_log(CL_ANN1, (CE_NOTE, "tgt_free: Setting dip = NULL" |
| "for tgt:%x", tgt)); |
| } |
| } |
| |
| dev_info_t * |
| mrsas_find_child(struct mrsas_instance *instance, uint16_t tgt, uint8_t lun) |
| { |
| dev_info_t *child = NULL; |
| char addr[SCSI_MAXNAMELEN]; |
| char tmp[MAXNAMELEN]; |
| |
| (void) snprintf(addr, sizeof (addr), "%x,%x", tgt, lun); |
| for (child = ddi_get_child(instance->dip); child; |
| child = ddi_get_next_sibling(child)) { |
| |
| if (ndi_dev_is_persistent_node(child) == 0) { |
| continue; |
| } |
| |
| if (mrsas_name_node(child, tmp, MAXNAMELEN) != |
| DDI_SUCCESS) { |
| continue; |
| } |
| |
| if (strcmp(addr, tmp) == 0) { |
| break; |
| } |
| } |
| con_log(CL_DLEVEL2, (CE_NOTE, "mrsas_find_child: return child = %p", |
| (void *)child)); |
| return (child); |
| } |
| |
| /* |
| * mrsas_name_node - |
| * @dip: |
| * @name: |
| * @len: |
| */ |
| static int |
| mrsas_name_node(dev_info_t *dip, char *name, int len) |
| { |
| int tgt, lun; |
| |
| tgt = ddi_prop_get_int(DDI_DEV_T_ANY, dip, |
| DDI_PROP_DONTPASS, "target", -1); |
| con_log(CL_DLEVEL2, (CE_NOTE, |
| "mrsas_name_node: dip %p tgt %d", (void *)dip, tgt)); |
| if (tgt == -1) { |
| return (DDI_FAILURE); |
| } |
| lun = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, |
| "lun", -1); |
| con_log(CL_DLEVEL2, |
| (CE_NOTE, "mrsas_name_node: tgt %d lun %d", tgt, lun)); |
| if (lun == -1) { |
| return (DDI_FAILURE); |
| } |
| (void) snprintf(name, len, "%x,%x", tgt, lun); |
| 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 * |
| mrsas_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 mrsas_instance *instance; |
| struct scsi_pkt *new_pkt; |
| |
| con_log(CL_DLEVEL1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| instance = ADDR2MR(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 (mrsas_dma_alloc(instance, pkt, bp, flags, |
| callback) == DDI_FAILURE) { |
| if (new_pkt) { |
| scsi_hba_pkt_free(ap, new_pkt); |
| } |
| return ((struct scsi_pkt *)NULL); |
| } |
| } else { |
| if (mrsas_dma_move(instance, pkt, bp) == DDI_FAILURE) { |
| 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 |
| mrsas_tran_start(struct scsi_address *ap, register struct scsi_pkt *pkt) |
| { |
| uchar_t cmd_done = 0; |
| |
| struct mrsas_instance *instance = ADDR2MR(ap); |
| struct mrsas_cmd *cmd; |
| |
| con_log(CL_DLEVEL1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| if (instance->deadadapter == 1) { |
| con_log(CL_ANN1, (CE_WARN, |
| "mrsas_tran_start: return TRAN_FATAL_ERROR " |
| "for IO, as the HBA doesnt take any more IOs")); |
| if (pkt) { |
| pkt->pkt_reason = CMD_DEV_GONE; |
| pkt->pkt_statistics = STAT_DISCON; |
| } |
| return (TRAN_FATAL_ERROR); |
| } |
| |
| if (instance->adapterresetinprogress) { |
| con_log(CL_ANN1, (CE_NOTE, "mrsas_tran_start: Reset flag set, " |
| "returning mfi_pkt and setting TRAN_BUSY\n")); |
| return (TRAN_BUSY); |
| } |
| |
| con_log(CL_ANN1, (CE_CONT, "chkpnt:%s:%d:SCSI CDB[0]=0x%x time:%x", |
| __func__, __LINE__, pkt->pkt_cdbp[0], pkt->pkt_time)); |
| |
| 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 mrsas_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) { |
| pkt->pkt_reason = CMD_CMPLT; |
| pkt->pkt_scbp[0] = STATUS_GOOD; |
| pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET |
| | STATE_SENT_CMD; |
| if (((pkt->pkt_flags & FLAG_NOINTR) == 0) && pkt->pkt_comp) { |
| (*pkt->pkt_comp)(pkt); |
| } |
| |
| 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, "mr_sas:Firmware busy")); |
| DTRACE_PROBE2(start_tran_err, |
| uint16_t, instance->fw_outstanding, |
| uint16_t, instance->max_fw_cmds); |
| mrsas_return_mfi_pkt(instance, cmd); |
| return (TRAN_BUSY); |
| } |
| |
| /* Synchronize the Cmd frame for the controller */ |
| (void) ddi_dma_sync(cmd->frame_dma_obj.dma_handle, 0, 0, |
| DDI_DMA_SYNC_FORDEV); |
| con_log(CL_ANN, (CE_CONT, "issue_cmd_ppc: SCSI CDB[0]=0x%x" |
| "cmd->index:%x\n", pkt->pkt_cdbp[0], cmd->index)); |
| instance->func_ptr->issue_cmd(cmd, instance); |
| |
| } else { |
| struct mrsas_header *hdr = &cmd->frame->hdr; |
| |
| 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 (ddi_get8(cmd->frame_dma_obj.acc_handle, |
| &hdr->cmd_status)) { |
| case MFI_STAT_OK: |
| pkt->pkt_scbp[0] = STATUS_GOOD; |
| break; |
| |
| case MFI_STAT_SCSI_DONE_WITH_ERROR: |
| con_log(CL_ANN, (CE_CONT, |
| "mrsas_tran_start: 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: |
| con_log(CL_ANN, (CE_CONT, |
| "mrsas_tran_start: device not found error")); |
| pkt->pkt_reason = CMD_DEV_GONE; |
| pkt->pkt_statistics = STAT_DISCON; |
| break; |
| |
| default: |
| ((struct scsi_status *)pkt->pkt_scbp)->sts_busy = 1; |
| } |
| |
| (void) mrsas_common_check(instance, cmd); |
| DTRACE_PROBE2(start_nointr_done, uint8_t, hdr->cmd, |
| uint8_t, hdr->cmd_status); |
| mrsas_return_mfi_pkt(instance, cmd); |
| |
| if (pkt->pkt_comp) { |
| (*pkt->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 |
| mrsas_tran_abort(struct scsi_address *ap, struct scsi_pkt *pkt) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| /* abort 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 |
| mrsas_tran_reset(struct scsi_address *ap, int level) |
| { |
| struct mrsas_instance *instance = ADDR2MR(ap); |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| if (wait_for_outstanding(instance)) { |
| con_log(CL_ANN1, |
| (CE_CONT, "wait_for_outstanding: return FAIL.\n")); |
| 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 |
| mrsas_tran_getcap(struct scsi_address *ap, char *cap, int whom) |
| { |
| int rval = 0; |
| |
| struct mrsas_instance *instance = ADDR2MR(ap); |
| |
| con_log(CL_DLEVEL2, (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: |
| if (instance->tbolt) { |
| /* Limit to 256k max transfer */ |
| rval = mrsas_tbolt_max_cap_maxxfer; |
| } else { |
| /* Limit to 16MB max transfer */ |
| rval = mrsas_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 |
| mrsas_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) |
| { |
| int rval = 1; |
| |
| con_log(CL_DLEVEL2, (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 |
| mrsas_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) |
| { |
| struct scsa_cmd *acmd = PKT2CMD(pkt); |
| |
| con_log(CL_DLEVEL2, (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 |
| mrsas_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 |
| mrsas_tran_sync_pkt(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) { |
| (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); |
| } |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| mrsas_tran_quiesce(dev_info_t *dip) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| return (1); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| mrsas_tran_unquiesce(dev_info_t *dip) |
| { |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| return (1); |
| } |
| |
| |
| /* |
| * mrsas_isr(caddr_t) |
| * |
| * The Interrupt Service Routine |
| * |
| * Collect status for all completed commands and do callback |
| * |
| */ |
| static uint_t |
| mrsas_isr(struct mrsas_instance *instance) |
| { |
| int need_softintr; |
| uint32_t producer; |
| uint32_t consumer; |
| uint32_t context; |
| int retval; |
| |
| struct mrsas_cmd *cmd; |
| struct mrsas_header *hdr; |
| struct scsi_pkt *pkt; |
| |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| ASSERT(instance); |
| if (instance->tbolt) { |
| mutex_enter(&instance->chip_mtx); |
| if ((instance->intr_type == DDI_INTR_TYPE_FIXED) && |
| !(instance->func_ptr->intr_ack(instance))) { |
| mutex_exit(&instance->chip_mtx); |
| return (DDI_INTR_UNCLAIMED); |
| } |
| retval = mr_sas_tbolt_process_outstanding_cmd(instance); |
| mutex_exit(&instance->chip_mtx); |
| return (retval); |
| } else { |
| if ((instance->intr_type == DDI_INTR_TYPE_FIXED) && |
| !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 (mrsas_check_dma_handle(instance->mfi_internal_dma_obj.dma_handle) |
| != DDI_SUCCESS) { |
| mrsas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); |
| ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); |
| con_log(CL_ANN1, (CE_WARN, |
| "mr_sas_isr(): FMA check, returning DDI_INTR_UNCLAIMED")); |
| return (DDI_INTR_CLAIMED); |
| } |
| con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); |
| |
| #ifdef OCRDEBUG |
| if (debug_consecutive_timeout_after_ocr_g == 1) { |
| con_log(CL_ANN1, (CE_NOTE, |
| "simulating consecutive timeout after ocr")); |
| return (DDI_INTR_CLAIMED); |
| } |
| #endif |
| |
| mutex_enter(&instance->completed_pool_mtx); |
| mutex_enter(&instance->cmd_pend_mtx); |
| |
| producer = ddi_get32(instance->mfi_internal_dma_obj.acc_handle, |
| instance->producer); |
| consumer = ddi_get32(instance->mfi_internal_dma_obj.acc_handle, |
| instance->consumer); |
| |
| con_log(CL_ANN, (CE_CONT, " producer %x consumer %x ", |
| producer, consumer)); |
| if (producer == consumer) { |
| con_log(CL_ANN, (CE_WARN, "producer == consumer case")); |
| DTRACE_PROBE2(isr_pc_err, uint32_t, producer, |
| uint32_t, consumer); |
| mutex_exit(&instance->cmd_pend_mtx); |
| mutex_exit(&instance->completed_pool_mtx); |
| return (DDI_INTR_CLAIMED); |
| } |
| |
| while (consumer != producer) { |
| context = ddi_get32(instance->mfi_internal_dma_obj.acc_handle, |
| &instance->reply_queue[consumer]); |
| cmd = instance->cmd_list[context]; |
| |
| if (cmd->sync_cmd == MRSAS_TRUE) { |
| hdr = (struct mrsas_header *)&cmd->frame->hdr; |
| if (hdr) { |
| mlist_del_init(&cmd->list); |
| } |
| } else { |
| pkt = cmd->pkt; |
| if (pkt) { |
| mlist_del_init(&cmd->list); |
| } |
| } |
| |
| mlist_add_tail(&cmd->list, &instance->completed_pool_list); |
| |
| consumer++; |
| if (consumer == (instance->max_fw_cmds + 1)) { |
| consumer = 0; |
| } |
| } |
| ddi_put32(instance->mfi_internal_dma_obj.acc_handle, |
| instance->consumer, consumer); |
| mutex_exit(&instance->cmd_pend_mtx); |
| mutex_exit(&instance->completed_pool_mtx); |
| |
| (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) mrsas_softintr(instance); |
| } |
| |
| return (DDI_INTR_CLAIMED); |
| } |
| |
| |
| /* |
| * ************************************************************************** * |
| * * |
| * libraries * |
| * * |
| * ************************************************************************** * |
| */ |
| /* |
| * get_mfi_pkt : Get a command from the free pool |
| * After successful allocation, the caller of this routine |
| * must clear the frame buffer (memset to zero) before |
| * using the packet further. |
| * |
| * ***** Note ***** |
| * After clearing the frame buffer the context id of the |
| * frame buffer SHOULD be restored back. |
| */ |
| struct mrsas_cmd * |
| mrsas_get_mfi_pkt(struct mrsas_instance *instance) |
| { |
| mlist_t *head = &instance->cmd_pool_list; |
| struct mrsas_cmd *cmd = NULL; |
| |
| mutex_enter(&instance->cmd_pool_mtx); |
| |
| if (!mlist_empty(head)) { |
| cmd = mlist_entry(head->next, struct mrsas_cmd, list); |
| mlist_del_init(head->next); |
| } |
| if (cmd != NULL) { |
| cmd->pkt = NULL; |
| cmd->retry_count_for_ocr = 0; |
| cmd->drv_pkt_time = 0; |
| |
| } |
| mutex_exit(&instance->cmd_pool_mtx); |
| |
| return (cmd); |
| } |
| |
| static struct mrsas_cmd * |
| get_mfi_app_pkt(struct mrsas_instance *instance) |
| { |
| mlist_t *head = &instance->app_cmd_pool_list; |
| struct mrsas_cmd *cmd = NULL; |
| |
| mutex_enter(&instance->app_cmd_pool_mtx); |
| |
| if (!mlist_empty(head)) { |
| cmd = mlist_entry(head->next, struct mrsas_cmd, list); |
| mlist_del_init(head->next); |
| } |
| if (cmd != NULL) { |
| cmd->pkt = NULL; |
| cmd->retry_count_for_ocr = 0; |
| cmd->drv_pkt_time = 0; |
| } |
| |
| mutex_exit(&instance->app_cmd_pool_mtx); |
| |
| return (cmd); |
| } |
| /* |
| * return_mfi_pkt : Return a cmd to free command pool |
| */ |
| void |
| mrsas_return_mfi_pkt(struct mrsas_instance *instance, struct mrsas_cmd *cmd) |
| { |
| mutex_enter(&instance->cmd_pool_mtx); |
| /* use mlist_add_tail for debug assistance */ |
| mlist_add_tail(&cmd->list, &instance->cmd_pool_list); |
| |
| mutex_exit(&instance->cmd_pool_mtx); |
| } |
| |
| static void |
| return_mfi_app_pkt(struct mrsas_instance *instance, struct mrsas_cmd *cmd) |
| { |
| mutex_enter(&instance->app_cmd_pool_mtx); |
| |
| mlist_add(&cmd->list, &instance->app_cmd_pool_list); |
| |
| mutex_exit(&instance->app_cmd_pool_mtx); |
| } |
| void |
| push_pending_mfi_pkt(struct mrsas_instance *instance, struct mrsas_cmd *cmd) |
| { |
| struct scsi_pkt *pkt; |
| struct mrsas_header *hdr; |
| con_log(CL_DLEVEL2, (CE_NOTE, "push_pending_pkt(): Called\n")); |
| mutex_enter(&instance->cmd_pend_mtx); |
| mlist_del_init(&cmd->list); |
| mlist_add_tail(&cmd->list, &instance->cmd_pend_list); |
| if (cmd->sync_cmd == MRSAS_TRUE) { |
| hdr = (struct mrsas_header *)&cmd->frame->hdr; |
| if (hdr) { |
| con_log(CL_ANN1, (CE_CONT, |
| "push_pending_mfi_pkt: " |
| "cmd %p index %x " |
| "time %llx", |
| (void *)cmd, cmd->index, |
| gethrtime())); |
| /* Wait for specified interval */ |
| cmd->drv_pkt_time = ddi_get16( |
| cmd->frame_dma_obj.acc_handle, &hdr->timeout); |
| if (cmd->drv_pkt_time < debug_timeout_g) |
| cmd->drv_pkt_time = (uint16_t)debug_timeout_g; |
| con_log(CL_ANN1, (CE_CONT, |
| "push_pending_pkt(): " |
| "Called IO Timeout Value %x\n", |
| cmd->drv_pkt_time)); |
| } |
| if (hdr && instance->timeout_id == (timeout_id_t)-1) { |
| instance->timeout_id = timeout(io_timeout_checker, |
| (void *) instance, drv_usectohz(MRSAS_1_SECOND)); |
| } |
| } else { |
| pkt = cmd->pkt; |
| if (pkt) { |
| con_log(CL_ANN1, (CE_CONT, |
| "push_pending_mfi_pkt: " |
| "cmd %p index %x pkt %p, " |
| "time %llx", |
| (void *)cmd, cmd->index, (void *)pkt, |
| gethrtime())); |
| cmd->drv_pkt_time = (uint16_t)debug_timeout_g; |
| } |
| if (pkt && instance->timeout_id == (timeout_id_t)-1) { |
| instance->timeout_id = timeout(io_timeout_checker, |
| (void *) instance, drv_usectohz(MRSAS_1_SECOND)); |
| } |
| } |
| |
| mutex_exit(&instance->cmd_pend_mtx); |
| |
| } |
| |
| int |
| mrsas_print_pending_cmds(struct mrsas_instance *instance) |
| { |
| mlist_t *head = &instance->cmd_pend_list; |
| mlist_t *tmp = head; |
| struct mrsas_cmd *cmd = NULL; |
| struct mrsas_header *hdr; |
| unsigned int flag = 1; |
| struct scsi_pkt *pkt; |
| int saved_level; |
| int cmd_count = 0; |
| |
| saved_level = debug_level_g; |
| debug_level_g = CL_ANN1; |
| |
| dev_err(instance->dip, CE_NOTE, |
| "mrsas_print_pending_cmds(): Called"); |
| |
| while (flag) { |
| mutex_enter(&instance->cmd_pend_mtx); |
| tmp = tmp->next; |
| if (tmp == head) { |
| mutex_exit(&instance->cmd_pend_mtx); |
| flag = 0; |
| con_log(CL_ANN1, (CE_CONT, "mrsas_print_pending_cmds():" |
| " NO MORE CMDS PENDING....\n")); |
| break; |
| } else { |
| cmd = mlist_entry(tmp, struct mrsas_cmd, list); |
| mutex_exit(&instance->cmd_pend_mtx); |
| if (cmd) { |
| if (cmd->sync_cmd == MRSAS_TRUE) { |
| hdr = (struct mrsas_header *) |
| &cmd->frame->hdr; |
| if (hdr) { |
| con_log(CL_ANN1, (CE_CONT, |
| "print: cmd %p index 0x%x " |
| "drv_pkt_time 0x%x (NO-PKT)" |
| " hdr %p\n", (void *)cmd, |
| cmd->index, |
| cmd->drv_pkt_time, |
| (void *)hdr)); |
| } |
| } else { |
| pkt = cmd->pkt; |
| if (pkt) { |
| con_log(CL_ANN1, (CE_CONT, |
| "print: cmd %p index 0x%x " |
| "drv_pkt_time 0x%x pkt %p \n", |
| (void *)cmd, cmd->index, |
| cmd->drv_pkt_time, (void *)pkt)); |
| } |
| } |
| |
| if (++cmd_count == 1) { |
| mrsas_print_cmd_details(instance, cmd, |
| 0xDD); |
| } else { |
| mrsas_print_cmd_details(instance, cmd, |
| 1); |
| } |
| |
| } |
| } |
| } |
| con_log(CL_ANN1, (CE_CONT, "mrsas_print_pending_cmds(): Done\n")); |
| |
| |
| debug_level_g = saved_level; |
| |
| return (DDI_SUCCESS); |
| } |
| |
| |
| int |
| mrsas_complete_pending_cmds(struct mrsas_instance *instance) |
| { |
| |
| struct mrsas_cmd *cmd = NULL; |
| struct scsi_pkt *pkt; |
| struct mrsas_header *hdr; |
| |
| struct mlist_head *pos, *next; |
| |
| con_log(CL_ANN1, (CE_NOTE, |
| "mrsas_complete_pending_cmds(): Called")); |
| |
| mutex_enter(&instance->cmd_pend_mtx); |
| mlist_for_each_safe(pos, next, &instance->cmd_pend_list) { |
| cmd = mlist_entry(pos, struct mrsas_cmd, list); |
| if (cmd) { |
| pkt = cmd->pkt; |
| if (pkt) { /* for IO */ |
| if (((pkt->pkt_flags & FLAG_NOINTR) |
| == 0) && pkt->pkt_comp) { |
| pkt->pkt_reason |
| = CMD_DEV_GONE; |
| pkt->pkt_statistics |
| = STAT_DISCON; |
| con_log(CL_ANN1, (CE_CONT, |
| "fail and posting to scsa " |
| "cmd %p index %x" |
| " pkt %p " |
| "time : %llx", |
| (void *)cmd, cmd->index, |
| (void *)pkt, gethrtime())); |
| (*pkt->pkt_comp)(pkt); |
| } |
| } else { /* for DCMDS */ |
| if (cmd->sync_cmd == MRSAS_TRUE) { |
| hdr = (struct mrsas_header *)&cmd->frame->hdr; |
| con_log(CL_ANN1, (CE_CONT, |
| "posting invalid status to application " |
| "cmd %p index %x" |
| " hdr %p " |
| "time : %llx", |
| (void *)cmd, cmd->index, |
| (void *)hdr, gethrtime())); |
| hdr->cmd_status = MFI_STAT_INVALID_STATUS; |
| complete_cmd_in_sync_mode(instance, cmd); |
| } |
| } |
| mlist_del_init(&cmd->list); |
| } else { |
| con_log(CL_ANN1, (CE_CONT, |
| "mrsas_complete_pending_cmds:" |
| "NULL command\n")); |
| } |
| con_log(CL_ANN1, (CE_CONT, |
| "mrsas_complete_pending_cmds:" |
| "looping for more commands\n")); |
| } |
| mutex_exit(&instance->cmd_pend_mtx); |
| |
| con_log(CL_ANN1, (CE_CONT, "mrsas_complete_pending_cmds(): DONE\n")); |
| return (DDI_SUCCESS); |
| } |
| |
| void |
| mrsas_print_cmd_details(struct mrsas_instance *instance, struct mrsas_cmd *cmd, |
| int detail) |
| { |
| struct scsi_pkt *pkt = cmd->pkt; |
| Mpi2RaidSCSIIORequest_t *scsi_io = cmd->scsi_io_request; |
| int i; |
| int saved_level; |
| ddi_acc_handle_t acc_handle = |
| instance->mpi2_frame_pool_dma_obj.acc_handle; |
| |
| if (detail == 0xDD) { |
| saved_level = debug_level_g; |
| debug_level_g = CL_ANN1; |
| } |
| |
| |
| if (instance->tbolt) { |
| con_log(CL_ANN1, (CE_CONT, "print_cmd_details: cmd %p " |
| "cmd->index 0x%x SMID 0x%x timer 0x%x sec\n", |
| (void *)cmd, cmd->index, cmd->SMID, cmd->drv_pkt_time)); |
| } else { |
| con_log(CL_ANN1, (CE_CONT, "print_cmd_details: cmd %p " |
| "cmd->index 0x%x timer 0x%x sec\n", |
| (void *)cmd, cmd->index, cmd->drv_pkt_time)); |
| } |
| |
| if (pkt) { |
| con_log(CL_ANN1, (CE_CONT, "scsi_pkt CDB[0]=0x%x", |
| pkt->pkt_cdbp[0])); |
| } else { |
| con_log(CL_ANN1, (CE_CONT, "NO-PKT")); |
| } |
| |
| if ((detail == 0xDD) && instance->tbolt) { |
| con_log(CL_ANN1, (CE_CONT, "RAID_SCSI_IO_REQUEST\n")); |
| con_log(CL_ANN1, (CE_CONT, "DevHandle=0x%X Function=0x%X " |
| "IoFlags=0x%X SGLFlags=0x%X DataLength=0x%X\n", |
| ddi_get16(acc_handle, &scsi_io->DevHandle), |
| ddi_get8(acc_handle, &scsi_io->Function), |
| ddi_get16(acc_handle, &scsi_io->IoFlags), |
| ddi_get16(acc_handle, &scsi_io->SGLFlags), |
| ddi_get32(acc_handle, &scsi_io->DataLength))); |
| |
| for (i = 0; i < 32; i++) { |
| con_log(CL_ANN1, (CE_CONT, "CDB[%d]=0x%x ", i, |
| ddi_get8(acc_handle, &scsi_io->CDB.CDB32[i]))); |
| } |
| |
| con_log(CL_ANN1, (CE_CONT, "RAID-CONTEXT\n")); |
| con_log(CL_ANN1, (CE_CONT, "status=0x%X extStatus=0x%X " |
| "ldTargetId=0x%X timeoutValue=0x%X regLockFlags=0x%X " |
| "RAIDFlags=0x%X regLockRowLBA=0x%" PRIu64 |
| " regLockLength=0x%X spanArm=0x%X\n", |
| ddi_get8(acc_handle, &scsi_io->RaidContext.status), |
| ddi_get8(acc_handle, &scsi_io->RaidContext.extStatus), |
| ddi_get16(acc_handle, &scsi_io->RaidContext.ldTargetId), |
| ddi_get16(acc_handle, &scsi_io->RaidContext.timeoutValue), |
| ddi_get8(acc_handle, &scsi_io->RaidContext.regLockFlags), |
| ddi_get8(acc_handle, &scsi_io->RaidContext.RAIDFlags), |
| ddi_get64(acc_handle, &scsi_io->RaidContext.regLockRowLBA), |
| ddi_get32(acc_handle, &scsi_io->RaidContext.regLockLength), |
| ddi_get8(acc_handle, &scsi_io->RaidContext.spanArm))); |
| } |
| |
| if (detail == 0xDD) { |
| debug_level_g = saved_level; |
| } |
| } |
| |
| |
| int |
| mrsas_issue_pending_cmds(struct mrsas_instance *instance) |
| { |
| mlist_t *head = &instance->cmd_pend_list; |
| mlist_t *tmp = head->next; |
| struct mrsas_cmd *cmd = NULL; |
| struct scsi_pkt *pkt; |
| |
| con_log(CL_ANN1, (CE_NOTE, "mrsas_issue_pending_cmds(): Called")); |
| while (tmp != head) { |
| mutex_enter(&instance->cmd_pend_mtx); |
| cmd = mlist_entry(tmp, struct mrsas_cmd, list); |
| tmp = tmp->next; |
| mutex_exit(&instance->cmd_pend_mtx); |
| if (cmd) { |
| con_log(CL_ANN1, (CE_CONT, |
| "mrsas_issue_pending_cmds(): " |
| "Got a cmd: cmd %p index 0x%x drv_pkt_time 0x%x ", |
| (void *)cmd, cmd->index, cmd->drv_pkt_time)); |
| |
| /* Reset command timeout value */ |
| if (cmd->drv_pkt_time < debug_timeout_g) |
| cmd->drv_pkt_time = (uint16_t)debug_timeout_g; |
| |
| cmd->retry_count_for_ocr++; |
| |
| dev_err(instance->dip, CE_CONT, |
| "cmd retry count = %d\n", |
| cmd->retry_count_for_ocr); |
| |
| if (cmd->retry_count_for_ocr > IO_RETRY_COUNT) { |
| dev_err(instance->dip, |
|