diff options
Diffstat (limited to 'drivers/scsi/megaraid/megaraid_sas.c')
-rw-r--r-- | drivers/scsi/megaraid/megaraid_sas.c | 366 |
1 files changed, 278 insertions, 88 deletions
diff --git a/drivers/scsi/megaraid/megaraid_sas.c b/drivers/scsi/megaraid/megaraid_sas.c index 4cab5b534b2..977b6e8d852 100644 --- a/drivers/scsi/megaraid/megaraid_sas.c +++ b/drivers/scsi/megaraid/megaraid_sas.c @@ -10,7 +10,7 @@ * 2 of the License, or (at your option) any later version. * * FILE : megaraid_sas.c - * Version : v00.00.03.01 + * Version : v00.00.03.05 * * Authors: * Sreenivas Bagalkote <Sreenivas.Bagalkote@lsil.com> @@ -71,6 +71,8 @@ static struct megasas_mgmt_info megasas_mgmt_info; static struct fasync_struct *megasas_async_queue; static DEFINE_MUTEX(megasas_async_queue_mutex); +static u32 megasas_dbg_lvl; + /** * megasas_get_cmd - Get a command from the free pool * @instance: Adapter soft state @@ -135,6 +137,19 @@ megasas_enable_intr_xscale(struct megasas_register_set __iomem * regs) } /** + * megasas_disable_intr_xscale -Disables interrupt + * @regs: MFI register set + */ +static inline void +megasas_disable_intr_xscale(struct megasas_register_set __iomem * regs) +{ + u32 mask = 0x1f; + writel(mask, ®s->outbound_intr_mask); + /* Dummy readl to force pci flush */ + readl(®s->outbound_intr_mask); +} + +/** * megasas_read_fw_status_reg_xscale - returns the current FW status value * @regs: MFI register set */ @@ -185,6 +200,7 @@ static struct megasas_instance_template megasas_instance_template_xscale = { .fire_cmd = megasas_fire_cmd_xscale, .enable_intr = megasas_enable_intr_xscale, + .disable_intr = megasas_disable_intr_xscale, .clear_intr = megasas_clear_intr_xscale, .read_fw_status_reg = megasas_read_fw_status_reg_xscale, }; @@ -215,6 +231,19 @@ megasas_enable_intr_ppc(struct megasas_register_set __iomem * regs) } /** + * megasas_disable_intr_ppc - Disable interrupt + * @regs: MFI register set + */ +static inline void +megasas_disable_intr_ppc(struct megasas_register_set __iomem * regs) +{ + u32 mask = 0xFFFFFFFF; + writel(mask, ®s->outbound_intr_mask); + /* Dummy readl to force pci flush */ + readl(®s->outbound_intr_mask); +} + +/** * megasas_read_fw_status_reg_ppc - returns the current FW status value * @regs: MFI register set */ @@ -265,6 +294,7 @@ static struct megasas_instance_template megasas_instance_template_ppc = { .fire_cmd = megasas_fire_cmd_ppc, .enable_intr = megasas_enable_intr_ppc, + .disable_intr = megasas_disable_intr_ppc, .clear_intr = megasas_clear_intr_ppc, .read_fw_status_reg = megasas_read_fw_status_reg_ppc, }; @@ -275,25 +305,6 @@ static struct megasas_instance_template megasas_instance_template_ppc = { */ /** - * megasas_disable_intr - Disables interrupts - * @regs: MFI register set - */ -static inline void -megasas_disable_intr(struct megasas_instance *instance) -{ - u32 mask = 0x1f; - struct megasas_register_set __iomem *regs = instance->reg_set; - - if(instance->pdev->device == PCI_DEVICE_ID_LSI_SAS1078R) - mask = 0xffffffff; - - writel(mask, ®s->outbound_intr_mask); - - /* Dummy readl to force pci flush */ - readl(®s->outbound_intr_mask); -} - -/** * megasas_issue_polled - Issues a polling command * @instance: Adapter soft state * @cmd: Command packet to be issued @@ -336,6 +347,7 @@ megasas_issue_polled(struct megasas_instance *instance, struct megasas_cmd *cmd) * @cmd: Command to be issued * * This function waits on an event for the command to be returned from ISR. + * Max wait time is MEGASAS_INTERNAL_CMD_WAIT_TIME secs * Used to issue ioctl commands. */ static int @@ -346,7 +358,8 @@ megasas_issue_blocked_cmd(struct megasas_instance *instance, instance->instancet->fire_cmd(cmd->frame_phys_addr ,0,instance->reg_set); - wait_event(instance->int_cmd_wait_q, (cmd->cmd_status != ENODATA)); + wait_event_timeout(instance->int_cmd_wait_q, (cmd->cmd_status != ENODATA), + MEGASAS_INTERNAL_CMD_WAIT_TIME*HZ); return 0; } @@ -358,7 +371,8 @@ megasas_issue_blocked_cmd(struct megasas_instance *instance, * * MFI firmware can abort previously issued AEN comamnd (automatic event * notification). The megasas_issue_blocked_abort_cmd() issues such abort - * cmd and blocks till it is completed. + * cmd and waits for return status. + * Max wait time is MEGASAS_INTERNAL_CMD_WAIT_TIME secs */ static int megasas_issue_blocked_abort_cmd(struct megasas_instance *instance, @@ -392,7 +406,8 @@ megasas_issue_blocked_abort_cmd(struct megasas_instance *instance, /* * Wait for this cmd to complete */ - wait_event(instance->abort_cmd_wait_q, (cmd->cmd_status != 0xFF)); + wait_event_timeout(instance->abort_cmd_wait_q, (cmd->cmd_status != 0xFF), + MEGASAS_INTERNAL_CMD_WAIT_TIME*HZ); megasas_return_cmd(instance, cmd); return 0; @@ -495,6 +510,46 @@ megasas_make_sgl64(struct megasas_instance *instance, struct scsi_cmnd *scp, return sge_count; } + /** + * megasas_get_frame_count - Computes the number of frames + * @sge_count : number of sg elements + * + * Returns the number of frames required for numnber of sge's (sge_count) + */ + +u32 megasas_get_frame_count(u8 sge_count) +{ + int num_cnt; + int sge_bytes; + u32 sge_sz; + u32 frame_count=0; + + sge_sz = (IS_DMA64) ? sizeof(struct megasas_sge64) : + sizeof(struct megasas_sge32); + + /* + * Main frame can contain 2 SGEs for 64-bit SGLs and + * 3 SGEs for 32-bit SGLs + */ + if (IS_DMA64) + num_cnt = sge_count - 2; + else + num_cnt = sge_count - 3; + + if(num_cnt>0){ + sge_bytes = sge_sz * num_cnt; + + frame_count = (sge_bytes / MEGAMFI_FRAME_SIZE) + + ((sge_bytes % MEGAMFI_FRAME_SIZE) ? 1 : 0) ; + } + /* Main frame */ + frame_count +=1; + + if (frame_count > 7) + frame_count = 8; + return frame_count; +} + /** * megasas_build_dcdb - Prepares a direct cdb (DCDB) command * @instance: Adapter soft state @@ -508,8 +563,6 @@ static int megasas_build_dcdb(struct megasas_instance *instance, struct scsi_cmnd *scp, struct megasas_cmd *cmd) { - u32 sge_sz; - int sge_bytes; u32 is_logical; u32 device_id; u16 flags = 0; @@ -544,9 +597,6 @@ megasas_build_dcdb(struct megasas_instance *instance, struct scsi_cmnd *scp, /* * Construct SGL */ - sge_sz = (IS_DMA64) ? sizeof(struct megasas_sge64) : - sizeof(struct megasas_sge32); - if (IS_DMA64) { pthru->flags |= MFI_FRAME_SGL64; pthru->sge_count = megasas_make_sgl64(instance, scp, @@ -562,17 +612,11 @@ megasas_build_dcdb(struct megasas_instance *instance, struct scsi_cmnd *scp, pthru->sense_buf_phys_addr_hi = 0; pthru->sense_buf_phys_addr_lo = cmd->sense_phys_addr; - sge_bytes = sge_sz * pthru->sge_count; - /* * Compute the total number of frames this command consumes. FW uses * this number to pull sufficient number of frames from host memory. */ - cmd->frame_count = (sge_bytes / MEGAMFI_FRAME_SIZE) + - ((sge_bytes % MEGAMFI_FRAME_SIZE) ? 1 : 0) + 1; - - if (cmd->frame_count > 7) - cmd->frame_count = 8; + cmd->frame_count = megasas_get_frame_count(pthru->sge_count); return cmd->frame_count; } @@ -589,8 +633,6 @@ static int megasas_build_ldio(struct megasas_instance *instance, struct scsi_cmnd *scp, struct megasas_cmd *cmd) { - u32 sge_sz; - int sge_bytes; u32 device_id; u8 sc = scp->cmnd[0]; u16 flags = 0; @@ -605,7 +647,7 @@ megasas_build_ldio(struct megasas_instance *instance, struct scsi_cmnd *scp, flags = MFI_FRAME_DIR_READ; /* - * Preare the Logical IO frame: 2nd bit is zero for all read cmds + * Prepare the Logical IO frame: 2nd bit is zero for all read cmds */ ldio->cmd = (sc & 0x02) ? MFI_CMD_LD_WRITE : MFI_CMD_LD_READ; ldio->cmd_status = 0x0; @@ -674,9 +716,6 @@ megasas_build_ldio(struct megasas_instance *instance, struct scsi_cmnd *scp, /* * Construct SGL */ - sge_sz = (IS_DMA64) ? sizeof(struct megasas_sge64) : - sizeof(struct megasas_sge32); - if (IS_DMA64) { ldio->flags |= MFI_FRAME_SGL64; ldio->sge_count = megasas_make_sgl64(instance, scp, &ldio->sgl); @@ -690,13 +729,11 @@ megasas_build_ldio(struct megasas_instance *instance, struct scsi_cmnd *scp, ldio->sense_buf_phys_addr_hi = 0; ldio->sense_buf_phys_addr_lo = cmd->sense_phys_addr; - sge_bytes = sge_sz * ldio->sge_count; - - cmd->frame_count = (sge_bytes / MEGAMFI_FRAME_SIZE) + - ((sge_bytes % MEGAMFI_FRAME_SIZE) ? 1 : 0) + 1; - - if (cmd->frame_count > 7) - cmd->frame_count = 8; + /* + * Compute the total number of frames this command consumes. FW uses + * this number to pull sufficient number of frames from host memory. + */ + cmd->frame_count = megasas_get_frame_count(ldio->sge_count); return cmd->frame_count; } @@ -727,6 +764,69 @@ static inline int megasas_is_ldio(struct scsi_cmnd *cmd) } } + /** + * megasas_dump_pending_frames - Dumps the frame address of all pending cmds + * in FW + * @instance: Adapter soft state + */ +static inline void +megasas_dump_pending_frames(struct megasas_instance *instance) +{ + struct megasas_cmd *cmd; + int i,n; + union megasas_sgl *mfi_sgl; + struct megasas_io_frame *ldio; + struct megasas_pthru_frame *pthru; + u32 sgcount; + u32 max_cmd = instance->max_fw_cmds; + + printk(KERN_ERR "\nmegasas[%d]: Dumping Frame Phys Address of all pending cmds in FW\n",instance->host->host_no); + printk(KERN_ERR "megasas[%d]: Total OS Pending cmds : %d\n",instance->host->host_no,atomic_read(&instance->fw_outstanding)); + if (IS_DMA64) + printk(KERN_ERR "\nmegasas[%d]: 64 bit SGLs were sent to FW\n",instance->host->host_no); + else + printk(KERN_ERR "\nmegasas[%d]: 32 bit SGLs were sent to FW\n",instance->host->host_no); + + printk(KERN_ERR "megasas[%d]: Pending OS cmds in FW : \n",instance->host->host_no); + for (i = 0; i < max_cmd; i++) { + cmd = instance->cmd_list[i]; + if(!cmd->scmd) + continue; + printk(KERN_ERR "megasas[%d]: Frame addr :0x%08lx : ",instance->host->host_no,(unsigned long)cmd->frame_phys_addr); + if (megasas_is_ldio(cmd->scmd)){ + ldio = (struct megasas_io_frame *)cmd->frame; + mfi_sgl = &ldio->sgl; + sgcount = ldio->sge_count; + printk(KERN_ERR "megasas[%d]: frame count : 0x%x, Cmd : 0x%x, Tgt id : 0x%x, lba lo : 0x%x, lba_hi : 0x%x, sense_buf addr : 0x%x,sge count : 0x%x\n",instance->host->host_no, cmd->frame_count,ldio->cmd,ldio->target_id, ldio->start_lba_lo,ldio->start_lba_hi,ldio->sense_buf_phys_addr_lo,sgcount); + } + else { + pthru = (struct megasas_pthru_frame *) cmd->frame; + mfi_sgl = &pthru->sgl; + sgcount = pthru->sge_count; + printk(KERN_ERR "megasas[%d]: frame count : 0x%x, Cmd : 0x%x, Tgt id : 0x%x, lun : 0x%x, cdb_len : 0x%x, data xfer len : 0x%x, sense_buf addr : 0x%x,sge count : 0x%x\n",instance->host->host_no,cmd->frame_count,pthru->cmd,pthru->target_id,pthru->lun,pthru->cdb_len , pthru->data_xfer_len,pthru->sense_buf_phys_addr_lo,sgcount); + } + if(megasas_dbg_lvl & MEGASAS_DBG_LVL){ + for (n = 0; n < sgcount; n++){ + if (IS_DMA64) + printk(KERN_ERR "megasas: sgl len : 0x%x, sgl addr : 0x%08lx ",mfi_sgl->sge64[n].length , (unsigned long)mfi_sgl->sge64[n].phys_addr) ; + else + printk(KERN_ERR "megasas: sgl len : 0x%x, sgl addr : 0x%x ",mfi_sgl->sge32[n].length , mfi_sgl->sge32[n].phys_addr) ; + } + } + printk(KERN_ERR "\n"); + } /*for max_cmd*/ + printk(KERN_ERR "\nmegasas[%d]: Pending Internal cmds in FW : \n",instance->host->host_no); + for (i = 0; i < max_cmd; i++) { + + cmd = instance->cmd_list[i]; + + if(cmd->sync_cmd == 1){ + printk(KERN_ERR "0x%08lx : ", (unsigned long)cmd->frame_phys_addr); + } + } + printk(KERN_ERR "megasas[%d]: Dumping Done.\n\n",instance->host->host_no); +} + /** * megasas_queue_command - Queue entry point * @scmd: SCSI command to be queued @@ -832,6 +932,13 @@ static int megasas_wait_for_outstanding(struct megasas_instance *instance) } if (atomic_read(&instance->fw_outstanding)) { + /* + * Send signal to FW to stop processing any pending cmds. + * The controller will be taken offline by the OS now. + */ + writel(MFI_STOP_ADP, + &instance->reg_set->inbound_doorbell); + megasas_dump_pending_frames(instance); instance->hw_crit_error = 1; return FAILED; } @@ -1168,11 +1275,6 @@ megasas_complete_cmd(struct megasas_instance *instance, struct megasas_cmd *cmd, static int megasas_deplete_reply_queue(struct megasas_instance *instance, u8 alt_status) { - u32 producer; - u32 consumer; - u32 context; - struct megasas_cmd *cmd; - /* * Check if it is our interrupt * Clear the interrupt @@ -1180,23 +1282,10 @@ megasas_deplete_reply_queue(struct megasas_instance *instance, u8 alt_status) if(instance->instancet->clear_intr(instance->reg_set)) return IRQ_NONE; - producer = *instance->producer; - consumer = *instance->consumer; - - while (consumer != producer) { - context = instance->reply_queue[consumer]; - - cmd = instance->cmd_list[context]; - - megasas_complete_cmd(instance, cmd, alt_status); - - consumer++; - if (consumer == (instance->max_fw_cmds + 1)) { - consumer = 0; - } - } - - *instance->consumer = producer; + /* + * Schedule the tasklet for cmd completion + */ + tasklet_schedule(&instance->isr_tasklet); return IRQ_HANDLED; } @@ -1229,10 +1318,12 @@ megasas_transition_to_ready(struct megasas_instance* instance) fw_state = instance->instancet->read_fw_status_reg(instance->reg_set) & MFI_STATE_MASK; + if (fw_state != MFI_STATE_READY) + printk(KERN_INFO "megasas: Waiting for FW to come to ready" + " state\n"); + while (fw_state != MFI_STATE_READY) { - printk(KERN_INFO "megasas: Waiting for FW to come to ready" - " state\n"); switch (fw_state) { case MFI_STATE_FAULT: @@ -1244,19 +1335,27 @@ megasas_transition_to_ready(struct megasas_instance* instance) /* * Set the CLR bit in inbound doorbell */ - writel(MFI_INIT_CLEAR_HANDSHAKE, + writel(MFI_INIT_CLEAR_HANDSHAKE|MFI_INIT_HOTPLUG, &instance->reg_set->inbound_doorbell); max_wait = 2; cur_state = MFI_STATE_WAIT_HANDSHAKE; break; + case MFI_STATE_BOOT_MESSAGE_PENDING: + writel(MFI_INIT_HOTPLUG, + &instance->reg_set->inbound_doorbell); + + 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 + * Bring it to READY state; assuming max wait 10 secs */ - megasas_disable_intr(instance); - writel(MFI_INIT_READY, &instance->reg_set->inbound_doorbell); + instance->instancet->disable_intr(instance->reg_set); + writel(MFI_RESET_FLAGS, &instance->reg_set->inbound_doorbell); max_wait = 10; cur_state = MFI_STATE_OPERATIONAL; @@ -1323,6 +1422,7 @@ megasas_transition_to_ready(struct megasas_instance* instance) return -ENODEV; } }; + printk(KERN_INFO "megasas: FW now in Ready state\n"); return 0; } @@ -1352,7 +1452,7 @@ static void megasas_teardown_frame_pool(struct megasas_instance *instance) cmd->frame_phys_addr); if (cmd->sense) - pci_pool_free(instance->sense_dma_pool, cmd->frame, + pci_pool_free(instance->sense_dma_pool, cmd->sense, cmd->sense_phys_addr); } @@ -1628,6 +1728,39 @@ megasas_get_ctrl_info(struct megasas_instance *instance, } /** + * megasas_complete_cmd_dpc - Returns FW's controller structure + * @instance_addr: Address of adapter soft state + * + * Tasklet to complete cmds + */ +void megasas_complete_cmd_dpc(unsigned long instance_addr) +{ + u32 producer; + u32 consumer; + u32 context; + struct megasas_cmd *cmd; + struct megasas_instance *instance = (struct megasas_instance *)instance_addr; + + producer = *instance->producer; + consumer = *instance->consumer; + + while (consumer != producer) { + context = instance->reply_queue[consumer]; + + cmd = instance->cmd_list[context]; + + megasas_complete_cmd(instance, cmd, DID_OK); + + consumer++; + if (consumer == (instance->max_fw_cmds + 1)) { + consumer = 0; + } + } + + *instance->consumer = producer; +} + +/** * megasas_init_mfi - Initializes the FW * @instance: Adapter soft state * @@ -1690,6 +1823,12 @@ static int megasas_init_mfi(struct megasas_instance *instance) * Get various operational parameters from status register */ instance->max_fw_cmds = instance->instancet->read_fw_status_reg(reg_set) & 0x00FFFF; + /* + * 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->max_fw_cmds-1; instance->max_num_sge = (instance->instancet->read_fw_status_reg(reg_set) & 0xFF0000) >> 0x10; /* @@ -1754,7 +1893,7 @@ static int megasas_init_mfi(struct megasas_instance *instance) /* * disable the intr before firing the init frame to FW */ - megasas_disable_intr(instance); + instance->instancet->disable_intr(instance->reg_set); /* * Issue the init frame in polled mode @@ -1791,6 +1930,12 @@ static int megasas_init_mfi(struct megasas_instance *instance) kfree(ctrl_info); + /* + * Setup tasklet for cmd completion + */ + + tasklet_init(&instance->isr_tasklet, megasas_complete_cmd_dpc, + (unsigned long)instance); return 0; fail_fw_init: @@ -2182,6 +2327,8 @@ megasas_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) instance->unique_id = pdev->bus->number << 8 | pdev->devfn; instance->init_id = MEGASAS_DEFAULT_INIT_ID; + megasas_dbg_lvl = 0; + /* * Initialize MFI Firmware */ @@ -2234,7 +2381,7 @@ megasas_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) megasas_mgmt_info.max_index--; pci_set_drvdata(pdev, NULL); - megasas_disable_intr(instance); + instance->instancet->disable_intr(instance->reg_set); free_irq(instance->pdev->irq, instance); megasas_release_mfi(instance); @@ -2348,6 +2495,7 @@ static void megasas_detach_one(struct pci_dev *pdev) scsi_remove_host(instance->host); megasas_flush_cache(instance); megasas_shutdown_controller(instance); + tasklet_kill(&instance->isr_tasklet); /* * Take the instance off the instance array. Note that we will not @@ -2364,7 +2512,7 @@ static void megasas_detach_one(struct pci_dev *pdev) pci_set_drvdata(instance->pdev, NULL); - megasas_disable_intr(instance); + instance->instancet->disable_intr(instance->reg_set); free_irq(instance->pdev->irq, instance); @@ -2716,7 +2864,8 @@ static int megasas_mgmt_compat_ioctl_fw(struct file *file, unsigned long arg) int i; int error = 0; - clear_user(ioc, sizeof(*ioc)); + if (clear_user(ioc, sizeof(*ioc))) + return -EFAULT; if (copy_in_user(&ioc->host_no, &cioc->host_no, sizeof(u16)) || copy_in_user(&ioc->sgl_off, &cioc->sgl_off, sizeof(u32)) || @@ -2808,6 +2957,26 @@ megasas_sysfs_show_release_date(struct device_driver *dd, char *buf) static DRIVER_ATTR(release_date, S_IRUGO, megasas_sysfs_show_release_date, NULL); +static ssize_t +megasas_sysfs_show_dbg_lvl(struct device_driver *dd, char *buf) +{ + return sprintf(buf,"%u",megasas_dbg_lvl); +} + +static ssize_t +megasas_sysfs_set_dbg_lvl(struct device_driver *dd, const char *buf, size_t count) +{ + int retval = count; + if(sscanf(buf,"%u",&megasas_dbg_lvl)<1){ + printk(KERN_ERR "megasas: could not set dbg_lvl\n"); + retval = -EINVAL; + } + return retval; +} + +static DRIVER_ATTR(dbg_lvl, S_IRUGO|S_IWUGO, megasas_sysfs_show_dbg_lvl, + megasas_sysfs_set_dbg_lvl); + /** * megasas_init - Driver load entry point */ @@ -2842,14 +3011,33 @@ static int __init megasas_init(void) if (rval) { printk(KERN_DEBUG "megasas: PCI hotplug regisration failed \n"); - unregister_chrdev(megasas_mgmt_majorno, "megaraid_sas_ioctl"); - } - - driver_create_file(&megasas_pci_driver.driver, &driver_attr_version); - driver_create_file(&megasas_pci_driver.driver, - &driver_attr_release_date); + goto err_pcidrv; + } + + rval = driver_create_file(&megasas_pci_driver.driver, + &driver_attr_version); + if (rval) + goto err_dcf_attr_ver; + rval = driver_create_file(&megasas_pci_driver.driver, + &driver_attr_release_date); + if (rval) + goto err_dcf_rel_date; + rval = driver_create_file(&megasas_pci_driver.driver, + &driver_attr_dbg_lvl); + if (rval) + goto err_dcf_dbg_lvl; return rval; +err_dcf_dbg_lvl: + driver_remove_file(&megasas_pci_driver.driver, + &driver_attr_release_date); +err_dcf_rel_date: + driver_remove_file(&megasas_pci_driver.driver, &driver_attr_version); +err_dcf_attr_ver: + pci_unregister_driver(&megasas_pci_driver); +err_pcidrv: + unregister_chrdev(megasas_mgmt_majorno, "megaraid_sas_ioctl"); + return rval; } /** @@ -2857,9 +3045,11 @@ static int __init megasas_init(void) */ static void __exit megasas_exit(void) { - driver_remove_file(&megasas_pci_driver.driver, &driver_attr_version); + driver_remove_file(&megasas_pci_driver.driver, + &driver_attr_dbg_lvl); driver_remove_file(&megasas_pci_driver.driver, &driver_attr_release_date); + driver_remove_file(&megasas_pci_driver.driver, &driver_attr_version); pci_unregister_driver(&megasas_pci_driver); unregister_chrdev(megasas_mgmt_majorno, "megaraid_sas_ioctl"); |