| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Linux Driver for Mylex DAC960/AcceleRAID/eXtremeRAID PCI RAID Controllers |
| * |
| * Copyright 2017 Hannes Reinecke, SUSE Linux GmbH <hare@suse.com> |
| * |
| * Based on the original DAC960 driver, |
| * Copyright 1998-2001 by Leonard N. Zubkoff <lnz@dandelion.com> |
| * Portions Copyright 2002 by Mylex (An IBM Business Unit) |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/pci.h> |
| #include <linux/raid_class.h> |
| #include <asm/unaligned.h> |
| #include <scsi/scsi.h> |
| #include <scsi/scsi_host.h> |
| #include <scsi/scsi_device.h> |
| #include <scsi/scsi_cmnd.h> |
| #include <scsi/scsi_tcq.h> |
| #include "myrb.h" |
| |
| static struct raid_template *myrb_raid_template; |
| |
| static void myrb_monitor(struct work_struct *work); |
| static inline void myrb_translate_devstate(void *DeviceState); |
| |
| static inline int myrb_logical_channel(struct Scsi_Host *shost) |
| { |
| return shost->max_channel - 1; |
| } |
| |
| static struct myrb_devstate_name_entry { |
| enum myrb_devstate state; |
| const char *name; |
| } myrb_devstate_name_list[] = { |
| { MYRB_DEVICE_DEAD, "Dead" }, |
| { MYRB_DEVICE_WO, "WriteOnly" }, |
| { MYRB_DEVICE_ONLINE, "Online" }, |
| { MYRB_DEVICE_CRITICAL, "Critical" }, |
| { MYRB_DEVICE_STANDBY, "Standby" }, |
| { MYRB_DEVICE_OFFLINE, "Offline" }, |
| }; |
| |
| static const char *myrb_devstate_name(enum myrb_devstate state) |
| { |
| struct myrb_devstate_name_entry *entry = myrb_devstate_name_list; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(myrb_devstate_name_list); i++) { |
| if (entry[i].state == state) |
| return entry[i].name; |
| } |
| return "Unknown"; |
| } |
| |
| static struct myrb_raidlevel_name_entry { |
| enum myrb_raidlevel level; |
| const char *name; |
| } myrb_raidlevel_name_list[] = { |
| { MYRB_RAID_LEVEL0, "RAID0" }, |
| { MYRB_RAID_LEVEL1, "RAID1" }, |
| { MYRB_RAID_LEVEL3, "RAID3" }, |
| { MYRB_RAID_LEVEL5, "RAID5" }, |
| { MYRB_RAID_LEVEL6, "RAID6" }, |
| { MYRB_RAID_JBOD, "JBOD" }, |
| }; |
| |
| static const char *myrb_raidlevel_name(enum myrb_raidlevel level) |
| { |
| struct myrb_raidlevel_name_entry *entry = myrb_raidlevel_name_list; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(myrb_raidlevel_name_list); i++) { |
| if (entry[i].level == level) |
| return entry[i].name; |
| } |
| return NULL; |
| } |
| |
| /** |
| * myrb_create_mempools - allocates auxiliary data structures |
| * |
| * Return: true on success, false otherwise. |
| */ |
| static bool myrb_create_mempools(struct pci_dev *pdev, struct myrb_hba *cb) |
| { |
| size_t elem_size, elem_align; |
| |
| elem_align = sizeof(struct myrb_sge); |
| elem_size = cb->host->sg_tablesize * elem_align; |
| cb->sg_pool = dma_pool_create("myrb_sg", &pdev->dev, |
| elem_size, elem_align, 0); |
| if (cb->sg_pool == NULL) { |
| shost_printk(KERN_ERR, cb->host, |
| "Failed to allocate SG pool\n"); |
| return false; |
| } |
| |
| cb->dcdb_pool = dma_pool_create("myrb_dcdb", &pdev->dev, |
| sizeof(struct myrb_dcdb), |
| sizeof(unsigned int), 0); |
| if (!cb->dcdb_pool) { |
| dma_pool_destroy(cb->sg_pool); |
| cb->sg_pool = NULL; |
| shost_printk(KERN_ERR, cb->host, |
| "Failed to allocate DCDB pool\n"); |
| return false; |
| } |
| |
| snprintf(cb->work_q_name, sizeof(cb->work_q_name), |
| "myrb_wq_%d", cb->host->host_no); |
| cb->work_q = create_singlethread_workqueue(cb->work_q_name); |
| if (!cb->work_q) { |
| dma_pool_destroy(cb->dcdb_pool); |
| cb->dcdb_pool = NULL; |
| dma_pool_destroy(cb->sg_pool); |
| cb->sg_pool = NULL; |
| shost_printk(KERN_ERR, cb->host, |
| "Failed to create workqueue\n"); |
| return false; |
| } |
| |
| /* |
| * Initialize the Monitoring Timer. |
| */ |
| INIT_DELAYED_WORK(&cb->monitor_work, myrb_monitor); |
| queue_delayed_work(cb->work_q, &cb->monitor_work, 1); |
| |
| return true; |
| } |
| |
| /** |
| * myrb_destroy_mempools - tears down the memory pools for the controller |
| */ |
| static void myrb_destroy_mempools(struct myrb_hba *cb) |
| { |
| cancel_delayed_work_sync(&cb->monitor_work); |
| destroy_workqueue(cb->work_q); |
| |
| dma_pool_destroy(cb->sg_pool); |
| dma_pool_destroy(cb->dcdb_pool); |
| } |
| |
| /** |
| * myrb_reset_cmd - reset command block |
| */ |
| static inline void myrb_reset_cmd(struct myrb_cmdblk *cmd_blk) |
| { |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| |
| memset(mbox, 0, sizeof(union myrb_cmd_mbox)); |
| cmd_blk->status = 0; |
| } |
| |
| /** |
| * myrb_qcmd - queues command block for execution |
| */ |
| static void myrb_qcmd(struct myrb_hba *cb, struct myrb_cmdblk *cmd_blk) |
| { |
| void __iomem *base = cb->io_base; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| union myrb_cmd_mbox *next_mbox = cb->next_cmd_mbox; |
| |
| cb->write_cmd_mbox(next_mbox, mbox); |
| if (cb->prev_cmd_mbox1->words[0] == 0 || |
| cb->prev_cmd_mbox2->words[0] == 0) |
| cb->get_cmd_mbox(base); |
| cb->prev_cmd_mbox2 = cb->prev_cmd_mbox1; |
| cb->prev_cmd_mbox1 = next_mbox; |
| if (++next_mbox > cb->last_cmd_mbox) |
| next_mbox = cb->first_cmd_mbox; |
| cb->next_cmd_mbox = next_mbox; |
| } |
| |
| /** |
| * myrb_exec_cmd - executes command block and waits for completion. |
| * |
| * Return: command status |
| */ |
| static unsigned short myrb_exec_cmd(struct myrb_hba *cb, |
| struct myrb_cmdblk *cmd_blk) |
| { |
| DECLARE_COMPLETION_ONSTACK(cmpl); |
| unsigned long flags; |
| |
| cmd_blk->completion = &cmpl; |
| |
| spin_lock_irqsave(&cb->queue_lock, flags); |
| cb->qcmd(cb, cmd_blk); |
| spin_unlock_irqrestore(&cb->queue_lock, flags); |
| |
| WARN_ON(in_interrupt()); |
| wait_for_completion(&cmpl); |
| return cmd_blk->status; |
| } |
| |
| /** |
| * myrb_exec_type3 - executes a type 3 command and waits for completion. |
| * |
| * Return: command status |
| */ |
| static unsigned short myrb_exec_type3(struct myrb_hba *cb, |
| enum myrb_cmd_opcode op, dma_addr_t addr) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| unsigned short status; |
| |
| mutex_lock(&cb->dcmd_mutex); |
| myrb_reset_cmd(cmd_blk); |
| mbox->type3.id = MYRB_DCMD_TAG; |
| mbox->type3.opcode = op; |
| mbox->type3.addr = addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| mutex_unlock(&cb->dcmd_mutex); |
| return status; |
| } |
| |
| /** |
| * myrb_exec_type3D - executes a type 3D command and waits for completion. |
| * |
| * Return: command status |
| */ |
| static unsigned short myrb_exec_type3D(struct myrb_hba *cb, |
| enum myrb_cmd_opcode op, struct scsi_device *sdev, |
| struct myrb_pdev_state *pdev_info) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| unsigned short status; |
| dma_addr_t pdev_info_addr; |
| |
| pdev_info_addr = dma_map_single(&cb->pdev->dev, pdev_info, |
| sizeof(struct myrb_pdev_state), |
| DMA_FROM_DEVICE); |
| if (dma_mapping_error(&cb->pdev->dev, pdev_info_addr)) |
| return MYRB_STATUS_SUBSYS_FAILED; |
| |
| mutex_lock(&cb->dcmd_mutex); |
| myrb_reset_cmd(cmd_blk); |
| mbox->type3D.id = MYRB_DCMD_TAG; |
| mbox->type3D.opcode = op; |
| mbox->type3D.channel = sdev->channel; |
| mbox->type3D.target = sdev->id; |
| mbox->type3D.addr = pdev_info_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| mutex_unlock(&cb->dcmd_mutex); |
| dma_unmap_single(&cb->pdev->dev, pdev_info_addr, |
| sizeof(struct myrb_pdev_state), DMA_FROM_DEVICE); |
| if (status == MYRB_STATUS_SUCCESS && |
| mbox->type3D.opcode == MYRB_CMD_GET_DEVICE_STATE_OLD) |
| myrb_translate_devstate(pdev_info); |
| |
| return status; |
| } |
| |
| static char *myrb_event_msg[] = { |
| "killed because write recovery failed", |
| "killed because of SCSI bus reset failure", |
| "killed because of double check condition", |
| "killed because it was removed", |
| "killed because of gross error on SCSI chip", |
| "killed because of bad tag returned from drive", |
| "killed because of timeout on SCSI command", |
| "killed because of reset SCSI command issued from system", |
| "killed because busy or parity error count exceeded limit", |
| "killed because of 'kill drive' command from system", |
| "killed because of selection timeout", |
| "killed due to SCSI phase sequence error", |
| "killed due to unknown status", |
| }; |
| |
| /** |
| * myrb_get_event - get event log from HBA |
| * @cb: pointer to the hba structure |
| * @event: number of the event |
| * |
| * Execute a type 3E command and logs the event message |
| */ |
| static void myrb_get_event(struct myrb_hba *cb, unsigned int event) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| struct myrb_log_entry *ev_buf; |
| dma_addr_t ev_addr; |
| unsigned short status; |
| |
| ev_buf = dma_alloc_coherent(&cb->pdev->dev, |
| sizeof(struct myrb_log_entry), |
| &ev_addr, GFP_KERNEL); |
| if (!ev_buf) |
| return; |
| |
| myrb_reset_cmd(cmd_blk); |
| mbox->type3E.id = MYRB_MCMD_TAG; |
| mbox->type3E.opcode = MYRB_CMD_EVENT_LOG_OPERATION; |
| mbox->type3E.optype = DAC960_V1_GetEventLogEntry; |
| mbox->type3E.opqual = 1; |
| mbox->type3E.ev_seq = event; |
| mbox->type3E.addr = ev_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| if (status != MYRB_STATUS_SUCCESS) |
| shost_printk(KERN_INFO, cb->host, |
| "Failed to get event log %d, status %04x\n", |
| event, status); |
| |
| else if (ev_buf->seq_num == event) { |
| struct scsi_sense_hdr sshdr; |
| |
| memset(&sshdr, 0, sizeof(sshdr)); |
| scsi_normalize_sense(ev_buf->sense, 32, &sshdr); |
| |
| if (sshdr.sense_key == VENDOR_SPECIFIC && |
| sshdr.asc == 0x80 && |
| sshdr.ascq < ARRAY_SIZE(myrb_event_msg)) |
| shost_printk(KERN_CRIT, cb->host, |
| "Physical drive %d:%d: %s\n", |
| ev_buf->channel, ev_buf->target, |
| myrb_event_msg[sshdr.ascq]); |
| else |
| shost_printk(KERN_CRIT, cb->host, |
| "Physical drive %d:%d: Sense: %X/%02X/%02X\n", |
| ev_buf->channel, ev_buf->target, |
| sshdr.sense_key, sshdr.asc, sshdr.ascq); |
| } |
| |
| dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_log_entry), |
| ev_buf, ev_addr); |
| } |
| |
| /** |
| * myrb_get_errtable - retrieves the error table from the controller |
| * |
| * Executes a type 3 command and logs the error table from the controller. |
| */ |
| static void myrb_get_errtable(struct myrb_hba *cb) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| unsigned short status; |
| struct myrb_error_entry old_table[MYRB_MAX_CHANNELS * MYRB_MAX_TARGETS]; |
| |
| memcpy(&old_table, cb->err_table, sizeof(old_table)); |
| |
| myrb_reset_cmd(cmd_blk); |
| mbox->type3.id = MYRB_MCMD_TAG; |
| mbox->type3.opcode = MYRB_CMD_GET_ERROR_TABLE; |
| mbox->type3.addr = cb->err_table_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| if (status == MYRB_STATUS_SUCCESS) { |
| struct myrb_error_entry *table = cb->err_table; |
| struct myrb_error_entry *new, *old; |
| size_t err_table_offset; |
| struct scsi_device *sdev; |
| |
| shost_for_each_device(sdev, cb->host) { |
| if (sdev->channel >= myrb_logical_channel(cb->host)) |
| continue; |
| err_table_offset = sdev->channel * MYRB_MAX_TARGETS |
| + sdev->id; |
| new = table + err_table_offset; |
| old = &old_table[err_table_offset]; |
| if (new->parity_err == old->parity_err && |
| new->soft_err == old->soft_err && |
| new->hard_err == old->hard_err && |
| new->misc_err == old->misc_err) |
| continue; |
| sdev_printk(KERN_CRIT, sdev, |
| "Errors: Parity = %d, Soft = %d, Hard = %d, Misc = %d\n", |
| new->parity_err, new->soft_err, |
| new->hard_err, new->misc_err); |
| } |
| } |
| } |
| |
| /** |
| * myrb_get_ldev_info - retrieves the logical device table from the controller |
| * |
| * Executes a type 3 command and updates the logical device table. |
| * |
| * Return: command status |
| */ |
| static unsigned short myrb_get_ldev_info(struct myrb_hba *cb) |
| { |
| unsigned short status; |
| int ldev_num, ldev_cnt = cb->enquiry->ldev_count; |
| struct Scsi_Host *shost = cb->host; |
| |
| status = myrb_exec_type3(cb, MYRB_CMD_GET_LDEV_INFO, |
| cb->ldev_info_addr); |
| if (status != MYRB_STATUS_SUCCESS) |
| return status; |
| |
| for (ldev_num = 0; ldev_num < ldev_cnt; ldev_num++) { |
| struct myrb_ldev_info *old = NULL; |
| struct myrb_ldev_info *new = cb->ldev_info_buf + ldev_num; |
| struct scsi_device *sdev; |
| |
| sdev = scsi_device_lookup(shost, myrb_logical_channel(shost), |
| ldev_num, 0); |
| if (!sdev) { |
| if (new->state == MYRB_DEVICE_OFFLINE) |
| continue; |
| shost_printk(KERN_INFO, shost, |
| "Adding Logical Drive %d in state %s\n", |
| ldev_num, myrb_devstate_name(new->state)); |
| scsi_add_device(shost, myrb_logical_channel(shost), |
| ldev_num, 0); |
| continue; |
| } |
| old = sdev->hostdata; |
| if (new->state != old->state) |
| shost_printk(KERN_INFO, shost, |
| "Logical Drive %d is now %s\n", |
| ldev_num, myrb_devstate_name(new->state)); |
| if (new->wb_enabled != old->wb_enabled) |
| sdev_printk(KERN_INFO, sdev, |
| "Logical Drive is now WRITE %s\n", |
| (new->wb_enabled ? "BACK" : "THRU")); |
| memcpy(old, new, sizeof(*new)); |
| scsi_device_put(sdev); |
| } |
| return status; |
| } |
| |
| /** |
| * myrb_get_rbld_progress - get rebuild progress information |
| * |
| * Executes a type 3 command and returns the rebuild progress |
| * information. |
| * |
| * Return: command status |
| */ |
| static unsigned short myrb_get_rbld_progress(struct myrb_hba *cb, |
| struct myrb_rbld_progress *rbld) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| struct myrb_rbld_progress *rbld_buf; |
| dma_addr_t rbld_addr; |
| unsigned short status; |
| |
| rbld_buf = dma_alloc_coherent(&cb->pdev->dev, |
| sizeof(struct myrb_rbld_progress), |
| &rbld_addr, GFP_KERNEL); |
| if (!rbld_buf) |
| return MYRB_STATUS_RBLD_NOT_CHECKED; |
| |
| myrb_reset_cmd(cmd_blk); |
| mbox->type3.id = MYRB_MCMD_TAG; |
| mbox->type3.opcode = MYRB_CMD_GET_REBUILD_PROGRESS; |
| mbox->type3.addr = rbld_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| if (rbld) |
| memcpy(rbld, rbld_buf, sizeof(struct myrb_rbld_progress)); |
| dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_rbld_progress), |
| rbld_buf, rbld_addr); |
| return status; |
| } |
| |
| /** |
| * myrb_update_rbld_progress - updates the rebuild status |
| * |
| * Updates the rebuild status for the attached logical devices. |
| * |
| */ |
| static void myrb_update_rbld_progress(struct myrb_hba *cb) |
| { |
| struct myrb_rbld_progress rbld_buf; |
| unsigned short status; |
| |
| status = myrb_get_rbld_progress(cb, &rbld_buf); |
| if (status == MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS && |
| cb->last_rbld_status == MYRB_STATUS_SUCCESS) |
| status = MYRB_STATUS_RBLD_SUCCESS; |
| if (status != MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS) { |
| unsigned int blocks_done = |
| rbld_buf.ldev_size - rbld_buf.blocks_left; |
| struct scsi_device *sdev; |
| |
| sdev = scsi_device_lookup(cb->host, |
| myrb_logical_channel(cb->host), |
| rbld_buf.ldev_num, 0); |
| if (!sdev) |
| return; |
| |
| switch (status) { |
| case MYRB_STATUS_SUCCESS: |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild in Progress, %d%% completed\n", |
| (100 * (blocks_done >> 7)) |
| / (rbld_buf.ldev_size >> 7)); |
| break; |
| case MYRB_STATUS_RBLD_FAILED_LDEV_FAILURE: |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Failed due to Logical Drive Failure\n"); |
| break; |
| case MYRB_STATUS_RBLD_FAILED_BADBLOCKS: |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Failed due to Bad Blocks on Other Drives\n"); |
| break; |
| case MYRB_STATUS_RBLD_FAILED_NEW_DRIVE_FAILED: |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Failed due to Failure of Drive Being Rebuilt\n"); |
| break; |
| case MYRB_STATUS_RBLD_SUCCESS: |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Completed Successfully\n"); |
| break; |
| case MYRB_STATUS_RBLD_SUCCESS_TERMINATED: |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Successfully Terminated\n"); |
| break; |
| default: |
| break; |
| } |
| scsi_device_put(sdev); |
| } |
| cb->last_rbld_status = status; |
| } |
| |
| /** |
| * myrb_get_cc_progress - retrieve the rebuild status |
| * |
| * Execute a type 3 Command and fetch the rebuild / consistency check |
| * status. |
| */ |
| static void myrb_get_cc_progress(struct myrb_hba *cb) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| struct myrb_rbld_progress *rbld_buf; |
| dma_addr_t rbld_addr; |
| unsigned short status; |
| |
| rbld_buf = dma_alloc_coherent(&cb->pdev->dev, |
| sizeof(struct myrb_rbld_progress), |
| &rbld_addr, GFP_KERNEL); |
| if (!rbld_buf) { |
| cb->need_cc_status = true; |
| return; |
| } |
| myrb_reset_cmd(cmd_blk); |
| mbox->type3.id = MYRB_MCMD_TAG; |
| mbox->type3.opcode = MYRB_CMD_REBUILD_STAT; |
| mbox->type3.addr = rbld_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| if (status == MYRB_STATUS_SUCCESS) { |
| unsigned int ldev_num = rbld_buf->ldev_num; |
| unsigned int ldev_size = rbld_buf->ldev_size; |
| unsigned int blocks_done = |
| ldev_size - rbld_buf->blocks_left; |
| struct scsi_device *sdev; |
| |
| sdev = scsi_device_lookup(cb->host, |
| myrb_logical_channel(cb->host), |
| ldev_num, 0); |
| if (sdev) { |
| sdev_printk(KERN_INFO, sdev, |
| "Consistency Check in Progress: %d%% completed\n", |
| (100 * (blocks_done >> 7)) |
| / (ldev_size >> 7)); |
| scsi_device_put(sdev); |
| } |
| } |
| dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_rbld_progress), |
| rbld_buf, rbld_addr); |
| } |
| |
| /** |
| * myrb_bgi_control - updates background initialisation status |
| * |
| * Executes a type 3B command and updates the background initialisation status |
| */ |
| static void myrb_bgi_control(struct myrb_hba *cb) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| struct myrb_bgi_status *bgi, *last_bgi; |
| dma_addr_t bgi_addr; |
| struct scsi_device *sdev = NULL; |
| unsigned short status; |
| |
| bgi = dma_alloc_coherent(&cb->pdev->dev, sizeof(struct myrb_bgi_status), |
| &bgi_addr, GFP_KERNEL); |
| if (!bgi) { |
| shost_printk(KERN_ERR, cb->host, |
| "Failed to allocate bgi memory\n"); |
| return; |
| } |
| myrb_reset_cmd(cmd_blk); |
| mbox->type3B.id = MYRB_DCMD_TAG; |
| mbox->type3B.opcode = MYRB_CMD_BGI_CONTROL; |
| mbox->type3B.optype = 0x20; |
| mbox->type3B.addr = bgi_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| last_bgi = &cb->bgi_status; |
| sdev = scsi_device_lookup(cb->host, |
| myrb_logical_channel(cb->host), |
| bgi->ldev_num, 0); |
| switch (status) { |
| case MYRB_STATUS_SUCCESS: |
| switch (bgi->status) { |
| case MYRB_BGI_INVALID: |
| break; |
| case MYRB_BGI_STARTED: |
| if (!sdev) |
| break; |
| sdev_printk(KERN_INFO, sdev, |
| "Background Initialization Started\n"); |
| break; |
| case MYRB_BGI_INPROGRESS: |
| if (!sdev) |
| break; |
| if (bgi->blocks_done == last_bgi->blocks_done && |
| bgi->ldev_num == last_bgi->ldev_num) |
| break; |
| sdev_printk(KERN_INFO, sdev, |
| "Background Initialization in Progress: %d%% completed\n", |
| (100 * (bgi->blocks_done >> 7)) |
| / (bgi->ldev_size >> 7)); |
| break; |
| case MYRB_BGI_SUSPENDED: |
| if (!sdev) |
| break; |
| sdev_printk(KERN_INFO, sdev, |
| "Background Initialization Suspended\n"); |
| break; |
| case MYRB_BGI_CANCELLED: |
| if (!sdev) |
| break; |
| sdev_printk(KERN_INFO, sdev, |
| "Background Initialization Cancelled\n"); |
| break; |
| } |
| memcpy(&cb->bgi_status, bgi, sizeof(struct myrb_bgi_status)); |
| break; |
| case MYRB_STATUS_BGI_SUCCESS: |
| if (sdev && cb->bgi_status.status == MYRB_BGI_INPROGRESS) |
| sdev_printk(KERN_INFO, sdev, |
| "Background Initialization Completed Successfully\n"); |
| cb->bgi_status.status = MYRB_BGI_INVALID; |
| break; |
| case MYRB_STATUS_BGI_ABORTED: |
| if (sdev && cb->bgi_status.status == MYRB_BGI_INPROGRESS) |
| sdev_printk(KERN_INFO, sdev, |
| "Background Initialization Aborted\n"); |
| fallthrough; |
| case MYRB_STATUS_NO_BGI_INPROGRESS: |
| cb->bgi_status.status = MYRB_BGI_INVALID; |
| break; |
| } |
| if (sdev) |
| scsi_device_put(sdev); |
| dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_bgi_status), |
| bgi, bgi_addr); |
| } |
| |
| /** |
| * myrb_hba_enquiry - updates the controller status |
| * |
| * Executes a DAC_V1_Enquiry command and updates the controller status. |
| * |
| * Return: command status |
| */ |
| static unsigned short myrb_hba_enquiry(struct myrb_hba *cb) |
| { |
| struct myrb_enquiry old, *new; |
| unsigned short status; |
| |
| memcpy(&old, cb->enquiry, sizeof(struct myrb_enquiry)); |
| |
| status = myrb_exec_type3(cb, MYRB_CMD_ENQUIRY, cb->enquiry_addr); |
| if (status != MYRB_STATUS_SUCCESS) |
| return status; |
| |
| new = cb->enquiry; |
| if (new->ldev_count > old.ldev_count) { |
| int ldev_num = old.ldev_count - 1; |
| |
| while (++ldev_num < new->ldev_count) |
| shost_printk(KERN_CRIT, cb->host, |
| "Logical Drive %d Now Exists\n", |
| ldev_num); |
| } |
| if (new->ldev_count < old.ldev_count) { |
| int ldev_num = new->ldev_count - 1; |
| |
| while (++ldev_num < old.ldev_count) |
| shost_printk(KERN_CRIT, cb->host, |
| "Logical Drive %d No Longer Exists\n", |
| ldev_num); |
| } |
| if (new->status.deferred != old.status.deferred) |
| shost_printk(KERN_CRIT, cb->host, |
| "Deferred Write Error Flag is now %s\n", |
| (new->status.deferred ? "TRUE" : "FALSE")); |
| if (new->ev_seq != old.ev_seq) { |
| cb->new_ev_seq = new->ev_seq; |
| cb->need_err_info = true; |
| shost_printk(KERN_INFO, cb->host, |
| "Event log %d/%d (%d/%d) available\n", |
| cb->old_ev_seq, cb->new_ev_seq, |
| old.ev_seq, new->ev_seq); |
| } |
| if ((new->ldev_critical > 0 && |
| new->ldev_critical != old.ldev_critical) || |
| (new->ldev_offline > 0 && |
| new->ldev_offline != old.ldev_offline) || |
| (new->ldev_count != old.ldev_count)) { |
| shost_printk(KERN_INFO, cb->host, |
| "Logical drive count changed (%d/%d/%d)\n", |
| new->ldev_critical, |
| new->ldev_offline, |
| new->ldev_count); |
| cb->need_ldev_info = true; |
| } |
| if (new->pdev_dead > 0 || |
| new->pdev_dead != old.pdev_dead || |
| time_after_eq(jiffies, cb->secondary_monitor_time |
| + MYRB_SECONDARY_MONITOR_INTERVAL)) { |
| cb->need_bgi_status = cb->bgi_status_supported; |
| cb->secondary_monitor_time = jiffies; |
| } |
| if (new->rbld == MYRB_STDBY_RBLD_IN_PROGRESS || |
| new->rbld == MYRB_BG_RBLD_IN_PROGRESS || |
| old.rbld == MYRB_STDBY_RBLD_IN_PROGRESS || |
| old.rbld == MYRB_BG_RBLD_IN_PROGRESS) { |
| cb->need_rbld = true; |
| cb->rbld_first = (new->ldev_critical < old.ldev_critical); |
| } |
| if (old.rbld == MYRB_BG_CHECK_IN_PROGRESS) |
| switch (new->rbld) { |
| case MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS: |
| shost_printk(KERN_INFO, cb->host, |
| "Consistency Check Completed Successfully\n"); |
| break; |
| case MYRB_STDBY_RBLD_IN_PROGRESS: |
| case MYRB_BG_RBLD_IN_PROGRESS: |
| break; |
| case MYRB_BG_CHECK_IN_PROGRESS: |
| cb->need_cc_status = true; |
| break; |
| case MYRB_STDBY_RBLD_COMPLETED_WITH_ERROR: |
| shost_printk(KERN_INFO, cb->host, |
| "Consistency Check Completed with Error\n"); |
| break; |
| case MYRB_BG_RBLD_OR_CHECK_FAILED_DRIVE_FAILED: |
| shost_printk(KERN_INFO, cb->host, |
| "Consistency Check Failed - Physical Device Failed\n"); |
| break; |
| case MYRB_BG_RBLD_OR_CHECK_FAILED_LDEV_FAILED: |
| shost_printk(KERN_INFO, cb->host, |
| "Consistency Check Failed - Logical Drive Failed\n"); |
| break; |
| case MYRB_BG_RBLD_OR_CHECK_FAILED_OTHER: |
| shost_printk(KERN_INFO, cb->host, |
| "Consistency Check Failed - Other Causes\n"); |
| break; |
| case MYRB_BG_RBLD_OR_CHECK_SUCCESS_TERMINATED: |
| shost_printk(KERN_INFO, cb->host, |
| "Consistency Check Successfully Terminated\n"); |
| break; |
| } |
| else if (new->rbld == MYRB_BG_CHECK_IN_PROGRESS) |
| cb->need_cc_status = true; |
| |
| return MYRB_STATUS_SUCCESS; |
| } |
| |
| /** |
| * myrb_set_pdev_state - sets the device state for a physical device |
| * |
| * Return: command status |
| */ |
| static unsigned short myrb_set_pdev_state(struct myrb_hba *cb, |
| struct scsi_device *sdev, enum myrb_devstate state) |
| { |
| struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| unsigned short status; |
| |
| mutex_lock(&cb->dcmd_mutex); |
| mbox->type3D.opcode = MYRB_CMD_START_DEVICE; |
| mbox->type3D.id = MYRB_DCMD_TAG; |
| mbox->type3D.channel = sdev->channel; |
| mbox->type3D.target = sdev->id; |
| mbox->type3D.state = state & 0x1F; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| mutex_unlock(&cb->dcmd_mutex); |
| |
| return status; |
| } |
| |
| /** |
| * myrb_enable_mmio - enables the Memory Mailbox Interface |
| * |
| * PD and P controller types have no memory mailbox, but still need the |
| * other dma mapped memory. |
| * |
| * Return: true on success, false otherwise. |
| */ |
| static bool myrb_enable_mmio(struct myrb_hba *cb, mbox_mmio_init_t mmio_init_fn) |
| { |
| void __iomem *base = cb->io_base; |
| struct pci_dev *pdev = cb->pdev; |
| size_t err_table_size; |
| size_t ldev_info_size; |
| union myrb_cmd_mbox *cmd_mbox_mem; |
| struct myrb_stat_mbox *stat_mbox_mem; |
| union myrb_cmd_mbox mbox; |
| unsigned short status; |
| |
| memset(&mbox, 0, sizeof(union myrb_cmd_mbox)); |
| |
| if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) { |
| dev_err(&pdev->dev, "DMA mask out of range\n"); |
| return false; |
| } |
| |
| cb->enquiry = dma_alloc_coherent(&pdev->dev, |
| sizeof(struct myrb_enquiry), |
| &cb->enquiry_addr, GFP_KERNEL); |
| if (!cb->enquiry) |
| return false; |
| |
| err_table_size = sizeof(struct myrb_error_entry) * |
| MYRB_MAX_CHANNELS * MYRB_MAX_TARGETS; |
| cb->err_table = dma_alloc_coherent(&pdev->dev, err_table_size, |
| &cb->err_table_addr, GFP_KERNEL); |
| if (!cb->err_table) |
| return false; |
| |
| ldev_info_size = sizeof(struct myrb_ldev_info) * MYRB_MAX_LDEVS; |
| cb->ldev_info_buf = dma_alloc_coherent(&pdev->dev, ldev_info_size, |
| &cb->ldev_info_addr, GFP_KERNEL); |
| if (!cb->ldev_info_buf) |
| return false; |
| |
| /* |
| * Skip mailbox initialisation for PD and P Controllers |
| */ |
| if (!mmio_init_fn) |
| return true; |
| |
| /* These are the base addresses for the command memory mailbox array */ |
| cb->cmd_mbox_size = MYRB_CMD_MBOX_COUNT * sizeof(union myrb_cmd_mbox); |
| cb->first_cmd_mbox = dma_alloc_coherent(&pdev->dev, |
| cb->cmd_mbox_size, |
| &cb->cmd_mbox_addr, |
| GFP_KERNEL); |
| if (!cb->first_cmd_mbox) |
| return false; |
| |
| cmd_mbox_mem = cb->first_cmd_mbox; |
| cmd_mbox_mem += MYRB_CMD_MBOX_COUNT - 1; |
| cb->last_cmd_mbox = cmd_mbox_mem; |
| cb->next_cmd_mbox = cb->first_cmd_mbox; |
| cb->prev_cmd_mbox1 = cb->last_cmd_mbox; |
| cb->prev_cmd_mbox2 = cb->last_cmd_mbox - 1; |
| |
| /* These are the base addresses for the status memory mailbox array */ |
| cb->stat_mbox_size = MYRB_STAT_MBOX_COUNT * |
| sizeof(struct myrb_stat_mbox); |
| cb->first_stat_mbox = dma_alloc_coherent(&pdev->dev, |
| cb->stat_mbox_size, |
| &cb->stat_mbox_addr, |
| GFP_KERNEL); |
| if (!cb->first_stat_mbox) |
| return false; |
| |
| stat_mbox_mem = cb->first_stat_mbox; |
| stat_mbox_mem += MYRB_STAT_MBOX_COUNT - 1; |
| cb->last_stat_mbox = stat_mbox_mem; |
| cb->next_stat_mbox = cb->first_stat_mbox; |
| |
| /* Enable the Memory Mailbox Interface. */ |
| cb->dual_mode_interface = true; |
| mbox.typeX.opcode = 0x2B; |
| mbox.typeX.id = 0; |
| mbox.typeX.opcode2 = 0x14; |
| mbox.typeX.cmd_mbox_addr = cb->cmd_mbox_addr; |
| mbox.typeX.stat_mbox_addr = cb->stat_mbox_addr; |
| |
| status = mmio_init_fn(pdev, base, &mbox); |
| if (status != MYRB_STATUS_SUCCESS) { |
| cb->dual_mode_interface = false; |
| mbox.typeX.opcode2 = 0x10; |
| status = mmio_init_fn(pdev, base, &mbox); |
| if (status != MYRB_STATUS_SUCCESS) { |
| dev_err(&pdev->dev, |
| "Failed to enable mailbox, statux %02X\n", |
| status); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * myrb_get_hba_config - reads the configuration information |
| * |
| * Reads the configuration information from the controller and |
| * initializes the controller structure. |
| * |
| * Return: 0 on success, errno otherwise |
| */ |
| static int myrb_get_hba_config(struct myrb_hba *cb) |
| { |
| struct myrb_enquiry2 *enquiry2; |
| dma_addr_t enquiry2_addr; |
| struct myrb_config2 *config2; |
| dma_addr_t config2_addr; |
| struct Scsi_Host *shost = cb->host; |
| struct pci_dev *pdev = cb->pdev; |
| int pchan_max = 0, pchan_cur = 0; |
| unsigned short status; |
| int ret = -ENODEV, memsize = 0; |
| |
| enquiry2 = dma_alloc_coherent(&pdev->dev, sizeof(struct myrb_enquiry2), |
| &enquiry2_addr, GFP_KERNEL); |
| if (!enquiry2) { |
| shost_printk(KERN_ERR, cb->host, |
| "Failed to allocate V1 enquiry2 memory\n"); |
| return -ENOMEM; |
| } |
| config2 = dma_alloc_coherent(&pdev->dev, sizeof(struct myrb_config2), |
| &config2_addr, GFP_KERNEL); |
| if (!config2) { |
| shost_printk(KERN_ERR, cb->host, |
| "Failed to allocate V1 config2 memory\n"); |
| dma_free_coherent(&pdev->dev, sizeof(struct myrb_enquiry2), |
| enquiry2, enquiry2_addr); |
| return -ENOMEM; |
| } |
| mutex_lock(&cb->dma_mutex); |
| status = myrb_hba_enquiry(cb); |
| mutex_unlock(&cb->dma_mutex); |
| if (status != MYRB_STATUS_SUCCESS) { |
| shost_printk(KERN_WARNING, cb->host, |
| "Failed it issue V1 Enquiry\n"); |
| goto out_free; |
| } |
| |
| status = myrb_exec_type3(cb, MYRB_CMD_ENQUIRY2, enquiry2_addr); |
| if (status != MYRB_STATUS_SUCCESS) { |
| shost_printk(KERN_WARNING, cb->host, |
| "Failed to issue V1 Enquiry2\n"); |
| goto out_free; |
| } |
| |
| status = myrb_exec_type3(cb, MYRB_CMD_READ_CONFIG2, config2_addr); |
| if (status != MYRB_STATUS_SUCCESS) { |
| shost_printk(KERN_WARNING, cb->host, |
| "Failed to issue ReadConfig2\n"); |
| goto out_free; |
| } |
| |
| status = myrb_get_ldev_info(cb); |
| if (status != MYRB_STATUS_SUCCESS) { |
| shost_printk(KERN_WARNING, cb->host, |
| "Failed to get logical drive information\n"); |
| goto out_free; |
| } |
| |
| /* |
| * Initialize the Controller Model Name and Full Model Name fields. |
| */ |
| switch (enquiry2->hw.sub_model) { |
| case DAC960_V1_P_PD_PU: |
| if (enquiry2->scsi_cap.bus_speed == MYRB_SCSI_SPEED_ULTRA) |
| strcpy(cb->model_name, "DAC960PU"); |
| else |
| strcpy(cb->model_name, "DAC960PD"); |
| break; |
| case DAC960_V1_PL: |
| strcpy(cb->model_name, "DAC960PL"); |
| break; |
| case DAC960_V1_PG: |
| strcpy(cb->model_name, "DAC960PG"); |
| break; |
| case DAC960_V1_PJ: |
| strcpy(cb->model_name, "DAC960PJ"); |
| break; |
| case DAC960_V1_PR: |
| strcpy(cb->model_name, "DAC960PR"); |
| break; |
| case DAC960_V1_PT: |
| strcpy(cb->model_name, "DAC960PT"); |
| break; |
| case DAC960_V1_PTL0: |
| strcpy(cb->model_name, "DAC960PTL0"); |
| break; |
| case DAC960_V1_PRL: |
| strcpy(cb->model_name, "DAC960PRL"); |
| break; |
| case DAC960_V1_PTL1: |
| strcpy(cb->model_name, "DAC960PTL1"); |
| break; |
| case DAC960_V1_1164P: |
| strcpy(cb->model_name, "eXtremeRAID 1100"); |
| break; |
| default: |
| shost_printk(KERN_WARNING, cb->host, |
| "Unknown Model %X\n", |
| enquiry2->hw.sub_model); |
| goto out; |
| } |
| /* |
| * Initialize the Controller Firmware Version field and verify that it |
| * is a supported firmware version. |
| * The supported firmware versions are: |
| * |
| * DAC1164P 5.06 and above |
| * DAC960PTL/PRL/PJ/PG 4.06 and above |
| * DAC960PU/PD/PL 3.51 and above |
| * DAC960PU/PD/PL/P 2.73 and above |
| */ |
| #if defined(CONFIG_ALPHA) |
| /* |
| * DEC Alpha machines were often equipped with DAC960 cards that were |
| * OEMed from Mylex, and had their own custom firmware. Version 2.70, |
| * the last custom FW revision to be released by DEC for these older |
| * controllers, appears to work quite well with this driver. |
| * |
| * Cards tested successfully were several versions each of the PD and |
| * PU, called by DEC the KZPSC and KZPAC, respectively, and having |
| * the Manufacturer Numbers (from Mylex), usually on a sticker on the |
| * back of the board, of: |
| * |
| * KZPSC: D040347 (1-channel) or D040348 (2-channel) |
| * or D040349 (3-channel) |
| * KZPAC: D040395 (1-channel) or D040396 (2-channel) |
| * or D040397 (3-channel) |
| */ |
| # define FIRMWARE_27X "2.70" |
| #else |
| # define FIRMWARE_27X "2.73" |
| #endif |
| |
| if (enquiry2->fw.major_version == 0) { |
| enquiry2->fw.major_version = cb->enquiry->fw_major_version; |
| enquiry2->fw.minor_version = cb->enquiry->fw_minor_version; |
| enquiry2->fw.firmware_type = '0'; |
| enquiry2->fw.turn_id = 0; |
| } |
| snprintf(cb->fw_version, sizeof(cb->fw_version), |
| "%d.%02d-%c-%02d", |
| enquiry2->fw.major_version, |
| enquiry2->fw.minor_version, |
| enquiry2->fw.firmware_type, |
| enquiry2->fw.turn_id); |
| if (!((enquiry2->fw.major_version == 5 && |
| enquiry2->fw.minor_version >= 6) || |
| (enquiry2->fw.major_version == 4 && |
| enquiry2->fw.minor_version >= 6) || |
| (enquiry2->fw.major_version == 3 && |
| enquiry2->fw.minor_version >= 51) || |
| (enquiry2->fw.major_version == 2 && |
| strcmp(cb->fw_version, FIRMWARE_27X) >= 0))) { |
| shost_printk(KERN_WARNING, cb->host, |
| "Firmware Version '%s' unsupported\n", |
| cb->fw_version); |
| goto out; |
| } |
| /* |
| * Initialize the Channels, Targets, Memory Size, and SAF-TE |
| * Enclosure Management Enabled fields. |
| */ |
| switch (enquiry2->hw.model) { |
| case MYRB_5_CHANNEL_BOARD: |
| pchan_max = 5; |
| break; |
| case MYRB_3_CHANNEL_BOARD: |
| case MYRB_3_CHANNEL_ASIC_DAC: |
| pchan_max = 3; |
| break; |
| case MYRB_2_CHANNEL_BOARD: |
| pchan_max = 2; |
| break; |
| default: |
| pchan_max = enquiry2->cfg_chan; |
| break; |
| } |
| pchan_cur = enquiry2->cur_chan; |
| if (enquiry2->scsi_cap.bus_width == MYRB_WIDTH_WIDE_32BIT) |
| cb->bus_width = 32; |
| else if (enquiry2->scsi_cap.bus_width == MYRB_WIDTH_WIDE_16BIT) |
| cb->bus_width = 16; |
| else |
| cb->bus_width = 8; |
| cb->ldev_block_size = enquiry2->ldev_block_size; |
| shost->max_channel = pchan_cur; |
| shost->max_id = enquiry2->max_targets; |
| memsize = enquiry2->mem_size >> 20; |
| cb->safte_enabled = (enquiry2->fault_mgmt == MYRB_FAULT_SAFTE); |
| /* |
| * Initialize the Controller Queue Depth, Driver Queue Depth, |
| * Logical Drive Count, Maximum Blocks per Command, Controller |
| * Scatter/Gather Limit, and Driver Scatter/Gather Limit. |
| * The Driver Queue Depth must be at most one less than the |
| * Controller Queue Depth to allow for an automatic drive |
| * rebuild operation. |
| */ |
| shost->can_queue = cb->enquiry->max_tcq; |
| if (shost->can_queue < 3) |
| shost->can_queue = enquiry2->max_cmds; |
| if (shost->can_queue < 3) |
| /* Play safe and disable TCQ */ |
| shost->can_queue = 1; |
| |
| if (shost->can_queue > MYRB_CMD_MBOX_COUNT - 2) |
| shost->can_queue = MYRB_CMD_MBOX_COUNT - 2; |
| shost->max_sectors = enquiry2->max_sectors; |
| shost->sg_tablesize = enquiry2->max_sge; |
| if (shost->sg_tablesize > MYRB_SCATTER_GATHER_LIMIT) |
| shost->sg_tablesize = MYRB_SCATTER_GATHER_LIMIT; |
| /* |
| * Initialize the Stripe Size, Segment Size, and Geometry Translation. |
| */ |
| cb->stripe_size = config2->blocks_per_stripe * config2->block_factor |
| >> (10 - MYRB_BLKSIZE_BITS); |
| cb->segment_size = config2->blocks_per_cacheline * config2->block_factor |
| >> (10 - MYRB_BLKSIZE_BITS); |
| /* Assume 255/63 translation */ |
| cb->ldev_geom_heads = 255; |
| cb->ldev_geom_sectors = 63; |
| if (config2->drive_geometry) { |
| cb->ldev_geom_heads = 128; |
| cb->ldev_geom_sectors = 32; |
| } |
| |
| /* |
| * Initialize the Background Initialization Status. |
| */ |
| if ((cb->fw_version[0] == '4' && |
| strcmp(cb->fw_version, "4.08") >= 0) || |
| (cb->fw_version[0] == '5' && |
| strcmp(cb->fw_version, "5.08") >= 0)) { |
| cb->bgi_status_supported = true; |
| myrb_bgi_control(cb); |
| } |
| cb->last_rbld_status = MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS; |
| ret = 0; |
| |
| out: |
| shost_printk(KERN_INFO, cb->host, |
| "Configuring %s PCI RAID Controller\n", cb->model_name); |
| shost_printk(KERN_INFO, cb->host, |
| " Firmware Version: %s, Memory Size: %dMB\n", |
| cb->fw_version, memsize); |
| if (cb->io_addr == 0) |
| shost_printk(KERN_INFO, cb->host, |
| " I/O Address: n/a, PCI Address: 0x%lX, IRQ Channel: %d\n", |
| (unsigned long)cb->pci_addr, cb->irq); |
| else |
| shost_printk(KERN_INFO, cb->host, |
| " I/O Address: 0x%lX, PCI Address: 0x%lX, IRQ Channel: %d\n", |
| (unsigned long)cb->io_addr, (unsigned long)cb->pci_addr, |
| cb->irq); |
| shost_printk(KERN_INFO, cb->host, |
| " Controller Queue Depth: %d, Maximum Blocks per Command: %d\n", |
| cb->host->can_queue, cb->host->max_sectors); |
| shost_printk(KERN_INFO, cb->host, |
| " Driver Queue Depth: %d, Scatter/Gather Limit: %d of %d Segments\n", |
| cb->host->can_queue, cb->host->sg_tablesize, |
| MYRB_SCATTER_GATHER_LIMIT); |
| shost_printk(KERN_INFO, cb->host, |
| " Stripe Size: %dKB, Segment Size: %dKB, BIOS Geometry: %d/%d%s\n", |
| cb->stripe_size, cb->segment_size, |
| cb->ldev_geom_heads, cb->ldev_geom_sectors, |
| cb->safte_enabled ? |
| " SAF-TE Enclosure Management Enabled" : ""); |
| shost_printk(KERN_INFO, cb->host, |
| " Physical: %d/%d channels %d/%d/%d devices\n", |
| pchan_cur, pchan_max, 0, cb->enquiry->pdev_dead, |
| cb->host->max_id); |
| |
| shost_printk(KERN_INFO, cb->host, |
| " Logical: 1/1 channels, %d/%d disks\n", |
| cb->enquiry->ldev_count, MYRB_MAX_LDEVS); |
| |
| out_free: |
| dma_free_coherent(&pdev->dev, sizeof(struct myrb_enquiry2), |
| enquiry2, enquiry2_addr); |
| dma_free_coherent(&pdev->dev, sizeof(struct myrb_config2), |
| config2, config2_addr); |
| |
| return ret; |
| } |
| |
| /** |
| * myrb_unmap - unmaps controller structures |
| */ |
| static void myrb_unmap(struct myrb_hba *cb) |
| { |
| if (cb->ldev_info_buf) { |
| size_t ldev_info_size = sizeof(struct myrb_ldev_info) * |
| MYRB_MAX_LDEVS; |
| dma_free_coherent(&cb->pdev->dev, ldev_info_size, |
| cb->ldev_info_buf, cb->ldev_info_addr); |
| cb->ldev_info_buf = NULL; |
| } |
| if (cb->err_table) { |
| size_t err_table_size = sizeof(struct myrb_error_entry) * |
| MYRB_MAX_CHANNELS * MYRB_MAX_TARGETS; |
| dma_free_coherent(&cb->pdev->dev, err_table_size, |
| cb->err_table, cb->err_table_addr); |
| cb->err_table = NULL; |
| } |
| if (cb->enquiry) { |
| dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_enquiry), |
| cb->enquiry, cb->enquiry_addr); |
| cb->enquiry = NULL; |
| } |
| if (cb->first_stat_mbox) { |
| dma_free_coherent(&cb->pdev->dev, cb->stat_mbox_size, |
| cb->first_stat_mbox, cb->stat_mbox_addr); |
| cb->first_stat_mbox = NULL; |
| } |
| if (cb->first_cmd_mbox) { |
| dma_free_coherent(&cb->pdev->dev, cb->cmd_mbox_size, |
| cb->first_cmd_mbox, cb->cmd_mbox_addr); |
| cb->first_cmd_mbox = NULL; |
| } |
| } |
| |
| /** |
| * myrb_cleanup - cleanup controller structures |
| */ |
| static void myrb_cleanup(struct myrb_hba *cb) |
| { |
| struct pci_dev *pdev = cb->pdev; |
| |
| /* Free the memory mailbox, status, and related structures */ |
| myrb_unmap(cb); |
| |
| if (cb->mmio_base) { |
| cb->disable_intr(cb->io_base); |
| iounmap(cb->mmio_base); |
| } |
| if (cb->irq) |
| free_irq(cb->irq, cb); |
| if (cb->io_addr) |
| release_region(cb->io_addr, 0x80); |
| pci_set_drvdata(pdev, NULL); |
| pci_disable_device(pdev); |
| scsi_host_put(cb->host); |
| } |
| |
| static int myrb_host_reset(struct scsi_cmnd *scmd) |
| { |
| struct Scsi_Host *shost = scmd->device->host; |
| struct myrb_hba *cb = shost_priv(shost); |
| |
| cb->reset(cb->io_base); |
| return SUCCESS; |
| } |
| |
| static int myrb_pthru_queuecommand(struct Scsi_Host *shost, |
| struct scsi_cmnd *scmd) |
| { |
| struct myrb_hba *cb = shost_priv(shost); |
| struct myrb_cmdblk *cmd_blk = scsi_cmd_priv(scmd); |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| struct myrb_dcdb *dcdb; |
| dma_addr_t dcdb_addr; |
| struct scsi_device *sdev = scmd->device; |
| struct scatterlist *sgl; |
| unsigned long flags; |
| int nsge; |
| |
| myrb_reset_cmd(cmd_blk); |
| dcdb = dma_pool_alloc(cb->dcdb_pool, GFP_ATOMIC, &dcdb_addr); |
| if (!dcdb) |
| return SCSI_MLQUEUE_HOST_BUSY; |
| nsge = scsi_dma_map(scmd); |
| if (nsge > 1) { |
| dma_pool_free(cb->dcdb_pool, dcdb, dcdb_addr); |
| scmd->result = (DID_ERROR << 16); |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| |
| mbox->type3.opcode = MYRB_CMD_DCDB; |
| mbox->type3.id = scmd->request->tag + 3; |
| mbox->type3.addr = dcdb_addr; |
| dcdb->channel = sdev->channel; |
| dcdb->target = sdev->id; |
| switch (scmd->sc_data_direction) { |
| case DMA_NONE: |
| dcdb->data_xfer = MYRB_DCDB_XFER_NONE; |
| break; |
| case DMA_TO_DEVICE: |
| dcdb->data_xfer = MYRB_DCDB_XFER_SYSTEM_TO_DEVICE; |
| break; |
| case DMA_FROM_DEVICE: |
| dcdb->data_xfer = MYRB_DCDB_XFER_DEVICE_TO_SYSTEM; |
| break; |
| default: |
| dcdb->data_xfer = MYRB_DCDB_XFER_ILLEGAL; |
| break; |
| } |
| dcdb->early_status = false; |
| if (scmd->request->timeout <= 10) |
| dcdb->timeout = MYRB_DCDB_TMO_10_SECS; |
| else if (scmd->request->timeout <= 60) |
| dcdb->timeout = MYRB_DCDB_TMO_60_SECS; |
| else if (scmd->request->timeout <= 600) |
| dcdb->timeout = MYRB_DCDB_TMO_10_MINS; |
| else |
| dcdb->timeout = MYRB_DCDB_TMO_24_HRS; |
| dcdb->no_autosense = false; |
| dcdb->allow_disconnect = true; |
| sgl = scsi_sglist(scmd); |
| dcdb->dma_addr = sg_dma_address(sgl); |
| if (sg_dma_len(sgl) > USHRT_MAX) { |
| dcdb->xfer_len_lo = sg_dma_len(sgl) & 0xffff; |
| dcdb->xfer_len_hi4 = sg_dma_len(sgl) >> 16; |
| } else { |
| dcdb->xfer_len_lo = sg_dma_len(sgl); |
| dcdb->xfer_len_hi4 = 0; |
| } |
| dcdb->cdb_len = scmd->cmd_len; |
| dcdb->sense_len = sizeof(dcdb->sense); |
| memcpy(&dcdb->cdb, scmd->cmnd, scmd->cmd_len); |
| |
| spin_lock_irqsave(&cb->queue_lock, flags); |
| cb->qcmd(cb, cmd_blk); |
| spin_unlock_irqrestore(&cb->queue_lock, flags); |
| return 0; |
| } |
| |
| static void myrb_inquiry(struct myrb_hba *cb, |
| struct scsi_cmnd *scmd) |
| { |
| unsigned char inq[36] = { |
| 0x00, 0x00, 0x03, 0x02, 0x20, 0x00, 0x01, 0x00, |
| 0x4d, 0x59, 0x4c, 0x45, 0x58, 0x20, 0x20, 0x20, |
| 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, |
| 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, |
| 0x20, 0x20, 0x20, 0x20, |
| }; |
| |
| if (cb->bus_width > 16) |
| inq[7] |= 1 << 6; |
| if (cb->bus_width > 8) |
| inq[7] |= 1 << 5; |
| memcpy(&inq[16], cb->model_name, 16); |
| memcpy(&inq[32], cb->fw_version, 1); |
| memcpy(&inq[33], &cb->fw_version[2], 2); |
| memcpy(&inq[35], &cb->fw_version[7], 1); |
| |
| scsi_sg_copy_from_buffer(scmd, (void *)inq, 36); |
| } |
| |
| static void |
| myrb_mode_sense(struct myrb_hba *cb, struct scsi_cmnd *scmd, |
| struct myrb_ldev_info *ldev_info) |
| { |
| unsigned char modes[32], *mode_pg; |
| bool dbd; |
| size_t mode_len; |
| |
| dbd = (scmd->cmnd[1] & 0x08) == 0x08; |
| if (dbd) { |
| mode_len = 24; |
| mode_pg = &modes[4]; |
| } else { |
| mode_len = 32; |
| mode_pg = &modes[12]; |
| } |
| memset(modes, 0, sizeof(modes)); |
| modes[0] = mode_len - 1; |
| if (!dbd) { |
| unsigned char *block_desc = &modes[4]; |
| |
| modes[3] = 8; |
| put_unaligned_be32(ldev_info->size, &block_desc[0]); |
| put_unaligned_be32(cb->ldev_block_size, &block_desc[5]); |
| } |
| mode_pg[0] = 0x08; |
| mode_pg[1] = 0x12; |
| if (ldev_info->wb_enabled) |
| mode_pg[2] |= 0x04; |
| if (cb->segment_size) { |
| mode_pg[2] |= 0x08; |
| put_unaligned_be16(cb->segment_size, &mode_pg[14]); |
| } |
| |
| scsi_sg_copy_from_buffer(scmd, modes, mode_len); |
| } |
| |
| static void myrb_request_sense(struct myrb_hba *cb, |
| struct scsi_cmnd *scmd) |
| { |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| NO_SENSE, 0, 0); |
| scsi_sg_copy_from_buffer(scmd, scmd->sense_buffer, |
| SCSI_SENSE_BUFFERSIZE); |
| } |
| |
| static void myrb_read_capacity(struct myrb_hba *cb, struct scsi_cmnd *scmd, |
| struct myrb_ldev_info *ldev_info) |
| { |
| unsigned char data[8]; |
| |
| dev_dbg(&scmd->device->sdev_gendev, |
| "Capacity %u, blocksize %u\n", |
| ldev_info->size, cb->ldev_block_size); |
| put_unaligned_be32(ldev_info->size - 1, &data[0]); |
| put_unaligned_be32(cb->ldev_block_size, &data[4]); |
| scsi_sg_copy_from_buffer(scmd, data, 8); |
| } |
| |
| static int myrb_ldev_queuecommand(struct Scsi_Host *shost, |
| struct scsi_cmnd *scmd) |
| { |
| struct myrb_hba *cb = shost_priv(shost); |
| struct myrb_cmdblk *cmd_blk = scsi_cmd_priv(scmd); |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| struct myrb_ldev_info *ldev_info; |
| struct scsi_device *sdev = scmd->device; |
| struct scatterlist *sgl; |
| unsigned long flags; |
| u64 lba; |
| u32 block_cnt; |
| int nsge; |
| |
| ldev_info = sdev->hostdata; |
| if (ldev_info->state != MYRB_DEVICE_ONLINE && |
| ldev_info->state != MYRB_DEVICE_WO) { |
| dev_dbg(&shost->shost_gendev, "ldev %u in state %x, skip\n", |
| sdev->id, ldev_info ? ldev_info->state : 0xff); |
| scmd->result = (DID_BAD_TARGET << 16); |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| switch (scmd->cmnd[0]) { |
| case TEST_UNIT_READY: |
| scmd->result = (DID_OK << 16); |
| scmd->scsi_done(scmd); |
| return 0; |
| case INQUIRY: |
| if (scmd->cmnd[1] & 1) { |
| /* Illegal request, invalid field in CDB */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| ILLEGAL_REQUEST, 0x24, 0); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| } else { |
| myrb_inquiry(cb, scmd); |
| scmd->result = (DID_OK << 16); |
| } |
| scmd->scsi_done(scmd); |
| return 0; |
| case SYNCHRONIZE_CACHE: |
| scmd->result = (DID_OK << 16); |
| scmd->scsi_done(scmd); |
| return 0; |
| case MODE_SENSE: |
| if ((scmd->cmnd[2] & 0x3F) != 0x3F && |
| (scmd->cmnd[2] & 0x3F) != 0x08) { |
| /* Illegal request, invalid field in CDB */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| ILLEGAL_REQUEST, 0x24, 0); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| } else { |
| myrb_mode_sense(cb, scmd, ldev_info); |
| scmd->result = (DID_OK << 16); |
| } |
| scmd->scsi_done(scmd); |
| return 0; |
| case READ_CAPACITY: |
| if ((scmd->cmnd[1] & 1) || |
| (scmd->cmnd[8] & 1)) { |
| /* Illegal request, invalid field in CDB */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| ILLEGAL_REQUEST, 0x24, 0); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| lba = get_unaligned_be32(&scmd->cmnd[2]); |
| if (lba) { |
| /* Illegal request, invalid field in CDB */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| ILLEGAL_REQUEST, 0x24, 0); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| myrb_read_capacity(cb, scmd, ldev_info); |
| scmd->scsi_done(scmd); |
| return 0; |
| case REQUEST_SENSE: |
| myrb_request_sense(cb, scmd); |
| scmd->result = (DID_OK << 16); |
| return 0; |
| case SEND_DIAGNOSTIC: |
| if (scmd->cmnd[1] != 0x04) { |
| /* Illegal request, invalid field in CDB */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| ILLEGAL_REQUEST, 0x24, 0); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| } else { |
| /* Assume good status */ |
| scmd->result = (DID_OK << 16); |
| } |
| scmd->scsi_done(scmd); |
| return 0; |
| case READ_6: |
| if (ldev_info->state == MYRB_DEVICE_WO) { |
| /* Data protect, attempt to read invalid data */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| DATA_PROTECT, 0x21, 0x06); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| fallthrough; |
| case WRITE_6: |
| lba = (((scmd->cmnd[1] & 0x1F) << 16) | |
| (scmd->cmnd[2] << 8) | |
| scmd->cmnd[3]); |
| block_cnt = scmd->cmnd[4]; |
| break; |
| case READ_10: |
| if (ldev_info->state == MYRB_DEVICE_WO) { |
| /* Data protect, attempt to read invalid data */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| DATA_PROTECT, 0x21, 0x06); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| fallthrough; |
| case WRITE_10: |
| case VERIFY: /* 0x2F */ |
| case WRITE_VERIFY: /* 0x2E */ |
| lba = get_unaligned_be32(&scmd->cmnd[2]); |
| block_cnt = get_unaligned_be16(&scmd->cmnd[7]); |
| break; |
| case READ_12: |
| if (ldev_info->state == MYRB_DEVICE_WO) { |
| /* Data protect, attempt to read invalid data */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| DATA_PROTECT, 0x21, 0x06); |
| scmd->result = (DRIVER_SENSE << 24) | |
| SAM_STAT_CHECK_CONDITION; |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| fallthrough; |
| case WRITE_12: |
| case VERIFY_12: /* 0xAF */ |
| case WRITE_VERIFY_12: /* 0xAE */ |
| lba = get_unaligned_be32(&scmd->cmnd[2]); |
| block_cnt = get_unaligned_be32(&scmd->cmnd[6]); |
| break; |
| default: |
| /* Illegal request, invalid opcode */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| ILLEGAL_REQUEST, 0x20, 0); |
| scmd->result = (DRIVER_SENSE << 24) | SAM_STAT_CHECK_CONDITION; |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| |
| myrb_reset_cmd(cmd_blk); |
| mbox->type5.id = scmd->request->tag + 3; |
| if (scmd->sc_data_direction == DMA_NONE) |
| goto submit; |
| nsge = scsi_dma_map(scmd); |
| if (nsge == 1) { |
| sgl = scsi_sglist(scmd); |
| if (scmd->sc_data_direction == DMA_FROM_DEVICE) |
| mbox->type5.opcode = MYRB_CMD_READ; |
| else |
| mbox->type5.opcode = MYRB_CMD_WRITE; |
| |
| mbox->type5.ld.xfer_len = block_cnt; |
| mbox->type5.ld.ldev_num = sdev->id; |
| mbox->type5.lba = lba; |
| mbox->type5.addr = (u32)sg_dma_address(sgl); |
| } else { |
| struct myrb_sge *hw_sgl; |
| dma_addr_t hw_sgl_addr; |
| int i; |
| |
| hw_sgl = dma_pool_alloc(cb->sg_pool, GFP_ATOMIC, &hw_sgl_addr); |
| if (!hw_sgl) |
| return SCSI_MLQUEUE_HOST_BUSY; |
| |
| cmd_blk->sgl = hw_sgl; |
| cmd_blk->sgl_addr = hw_sgl_addr; |
| |
| if (scmd->sc_data_direction == DMA_FROM_DEVICE) |
| mbox->type5.opcode = MYRB_CMD_READ_SG; |
| else |
| mbox->type5.opcode = MYRB_CMD_WRITE_SG; |
| |
| mbox->type5.ld.xfer_len = block_cnt; |
| mbox->type5.ld.ldev_num = sdev->id; |
| mbox->type5.lba = lba; |
| mbox->type5.addr = hw_sgl_addr; |
| mbox->type5.sg_count = nsge; |
| |
| scsi_for_each_sg(scmd, sgl, nsge, i) { |
| hw_sgl->sge_addr = (u32)sg_dma_address(sgl); |
| hw_sgl->sge_count = (u32)sg_dma_len(sgl); |
| hw_sgl++; |
| } |
| } |
| submit: |
| spin_lock_irqsave(&cb->queue_lock, flags); |
| cb->qcmd(cb, cmd_blk); |
| spin_unlock_irqrestore(&cb->queue_lock, flags); |
| |
| return 0; |
| } |
| |
| static int myrb_queuecommand(struct Scsi_Host *shost, |
| struct scsi_cmnd *scmd) |
| { |
| struct scsi_device *sdev = scmd->device; |
| |
| if (sdev->channel > myrb_logical_channel(shost)) { |
| scmd->result = (DID_BAD_TARGET << 16); |
| scmd->scsi_done(scmd); |
| return 0; |
| } |
| if (sdev->channel == myrb_logical_channel(shost)) |
| return myrb_ldev_queuecommand(shost, scmd); |
| |
| return myrb_pthru_queuecommand(shost, scmd); |
| } |
| |
| static int myrb_ldev_slave_alloc(struct scsi_device *sdev) |
| { |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_ldev_info *ldev_info; |
| unsigned short ldev_num = sdev->id; |
| enum raid_level level; |
| |
| ldev_info = cb->ldev_info_buf + ldev_num; |
| if (!ldev_info) |
| return -ENXIO; |
| |
| sdev->hostdata = kzalloc(sizeof(*ldev_info), GFP_KERNEL); |
| if (!sdev->hostdata) |
| return -ENOMEM; |
| dev_dbg(&sdev->sdev_gendev, |
| "slave alloc ldev %d state %x\n", |
| ldev_num, ldev_info->state); |
| memcpy(sdev->hostdata, ldev_info, |
| sizeof(*ldev_info)); |
| switch (ldev_info->raid_level) { |
| case MYRB_RAID_LEVEL0: |
| level = RAID_LEVEL_LINEAR; |
| break; |
| case MYRB_RAID_LEVEL1: |
| level = RAID_LEVEL_1; |
| break; |
| case MYRB_RAID_LEVEL3: |
| level = RAID_LEVEL_3; |
| break; |
| case MYRB_RAID_LEVEL5: |
| level = RAID_LEVEL_5; |
| break; |
| case MYRB_RAID_LEVEL6: |
| level = RAID_LEVEL_6; |
| break; |
| case MYRB_RAID_JBOD: |
| level = RAID_LEVEL_JBOD; |
| break; |
| default: |
| level = RAID_LEVEL_UNKNOWN; |
| break; |
| } |
| raid_set_level(myrb_raid_template, &sdev->sdev_gendev, level); |
| return 0; |
| } |
| |
| static int myrb_pdev_slave_alloc(struct scsi_device *sdev) |
| { |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_pdev_state *pdev_info; |
| unsigned short status; |
| |
| if (sdev->id > MYRB_MAX_TARGETS) |
| return -ENXIO; |
| |
| pdev_info = kzalloc(sizeof(*pdev_info), GFP_KERNEL|GFP_DMA); |
| if (!pdev_info) |
| return -ENOMEM; |
| |
| status = myrb_exec_type3D(cb, MYRB_CMD_GET_DEVICE_STATE, |
| sdev, pdev_info); |
| if (status != MYRB_STATUS_SUCCESS) { |
| dev_dbg(&sdev->sdev_gendev, |
| "Failed to get device state, status %x\n", |
| status); |
| kfree(pdev_info); |
| return -ENXIO; |
| } |
| if (!pdev_info->present) { |
| dev_dbg(&sdev->sdev_gendev, |
| "device not present, skip\n"); |
| kfree(pdev_info); |
| return -ENXIO; |
| } |
| dev_dbg(&sdev->sdev_gendev, |
| "slave alloc pdev %d:%d state %x\n", |
| sdev->channel, sdev->id, pdev_info->state); |
| sdev->hostdata = pdev_info; |
| |
| return 0; |
| } |
| |
| static int myrb_slave_alloc(struct scsi_device *sdev) |
| { |
| if (sdev->channel > myrb_logical_channel(sdev->host)) |
| return -ENXIO; |
| |
| if (sdev->lun > 0) |
| return -ENXIO; |
| |
| if (sdev->channel == myrb_logical_channel(sdev->host)) |
| return myrb_ldev_slave_alloc(sdev); |
| |
| return myrb_pdev_slave_alloc(sdev); |
| } |
| |
| static int myrb_slave_configure(struct scsi_device *sdev) |
| { |
| struct myrb_ldev_info *ldev_info; |
| |
| if (sdev->channel > myrb_logical_channel(sdev->host)) |
| return -ENXIO; |
| |
| if (sdev->channel < myrb_logical_channel(sdev->host)) { |
| sdev->no_uld_attach = 1; |
| return 0; |
| } |
| if (sdev->lun != 0) |
| return -ENXIO; |
| |
| ldev_info = sdev->hostdata; |
| if (!ldev_info) |
| return -ENXIO; |
| if (ldev_info->state != MYRB_DEVICE_ONLINE) |
| sdev_printk(KERN_INFO, sdev, |
| "Logical drive is %s\n", |
| myrb_devstate_name(ldev_info->state)); |
| |
| sdev->tagged_supported = 1; |
| return 0; |
| } |
| |
| static void myrb_slave_destroy(struct scsi_device *sdev) |
| { |
| kfree(sdev->hostdata); |
| } |
| |
| static int myrb_biosparam(struct scsi_device *sdev, struct block_device *bdev, |
| sector_t capacity, int geom[]) |
| { |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| |
| geom[0] = cb->ldev_geom_heads; |
| geom[1] = cb->ldev_geom_sectors; |
| geom[2] = sector_div(capacity, geom[0] * geom[1]); |
| |
| return 0; |
| } |
| |
| static ssize_t raid_state_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| int ret; |
| |
| if (!sdev->hostdata) |
| return snprintf(buf, 16, "Unknown\n"); |
| |
| if (sdev->channel == myrb_logical_channel(sdev->host)) { |
| struct myrb_ldev_info *ldev_info = sdev->hostdata; |
| const char *name; |
| |
| name = myrb_devstate_name(ldev_info->state); |
| if (name) |
| ret = snprintf(buf, 32, "%s\n", name); |
| else |
| ret = snprintf(buf, 32, "Invalid (%02X)\n", |
| ldev_info->state); |
| } else { |
| struct myrb_pdev_state *pdev_info = sdev->hostdata; |
| unsigned short status; |
| const char *name; |
| |
| status = myrb_exec_type3D(cb, MYRB_CMD_GET_DEVICE_STATE, |
| sdev, pdev_info); |
| if (status != MYRB_STATUS_SUCCESS) |
| sdev_printk(KERN_INFO, sdev, |
| "Failed to get device state, status %x\n", |
| status); |
| |
| if (!pdev_info->present) |
| name = "Removed"; |
| else |
| name = myrb_devstate_name(pdev_info->state); |
| if (name) |
| ret = snprintf(buf, 32, "%s\n", name); |
| else |
| ret = snprintf(buf, 32, "Invalid (%02X)\n", |
| pdev_info->state); |
| } |
| return ret; |
| } |
| |
| static ssize_t raid_state_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_pdev_state *pdev_info; |
| enum myrb_devstate new_state; |
| unsigned short status; |
| |
| if (!strncmp(buf, "kill", 4) || |
| !strncmp(buf, "offline", 7)) |
| new_state = MYRB_DEVICE_DEAD; |
| else if (!strncmp(buf, "online", 6)) |
| new_state = MYRB_DEVICE_ONLINE; |
| else if (!strncmp(buf, "standby", 7)) |
| new_state = MYRB_DEVICE_STANDBY; |
| else |
| return -EINVAL; |
| |
| pdev_info = sdev->hostdata; |
| if (!pdev_info) { |
| sdev_printk(KERN_INFO, sdev, |
| "Failed - no physical device information\n"); |
| return -ENXIO; |
| } |
| if (!pdev_info->present) { |
| sdev_printk(KERN_INFO, sdev, |
| "Failed - device not present\n"); |
| return -ENXIO; |
| } |
| |
| if (pdev_info->state == new_state) |
| return count; |
| |
| status = myrb_set_pdev_state(cb, sdev, new_state); |
| switch (status) { |
| case MYRB_STATUS_SUCCESS: |
| break; |
| case MYRB_STATUS_START_DEVICE_FAILED: |
| sdev_printk(KERN_INFO, sdev, |
| "Failed - Unable to Start Device\n"); |
| count = -EAGAIN; |
| break; |
| case MYRB_STATUS_NO_DEVICE: |
| sdev_printk(KERN_INFO, sdev, |
| "Failed - No Device at Address\n"); |
| count = -ENODEV; |
| break; |
| case MYRB_STATUS_INVALID_CHANNEL_OR_TARGET: |
| sdev_printk(KERN_INFO, sdev, |
| "Failed - Invalid Channel or Target or Modifier\n"); |
| count = -EINVAL; |
| break; |
| case MYRB_STATUS_CHANNEL_BUSY: |
| sdev_printk(KERN_INFO, sdev, |
| "Failed - Channel Busy\n"); |
| count = -EBUSY; |
| break; |
| default: |
| sdev_printk(KERN_INFO, sdev, |
| "Failed - Unexpected Status %04X\n", status); |
| count = -EIO; |
| break; |
| } |
| return count; |
| } |
| static DEVICE_ATTR_RW(raid_state); |
| |
| static ssize_t raid_level_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| |
| if (sdev->channel == myrb_logical_channel(sdev->host)) { |
| struct myrb_ldev_info *ldev_info = sdev->hostdata; |
| const char *name; |
| |
| if (!ldev_info) |
| return -ENXIO; |
| |
| name = myrb_raidlevel_name(ldev_info->raid_level); |
| if (!name) |
| return snprintf(buf, 32, "Invalid (%02X)\n", |
| ldev_info->state); |
| return snprintf(buf, 32, "%s\n", name); |
| } |
| return snprintf(buf, 32, "Physical Drive\n"); |
| } |
| static DEVICE_ATTR_RO(raid_level); |
| |
| static ssize_t rebuild_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_rbld_progress rbld_buf; |
| unsigned char status; |
| |
| if (sdev->channel < myrb_logical_channel(sdev->host)) |
| return snprintf(buf, 32, "physical device - not rebuilding\n"); |
| |
| status = myrb_get_rbld_progress(cb, &rbld_buf); |
| |
| if (rbld_buf.ldev_num != sdev->id || |
| status != MYRB_STATUS_SUCCESS) |
| return snprintf(buf, 32, "not rebuilding\n"); |
| |
| return snprintf(buf, 32, "rebuilding block %u of %u\n", |
| rbld_buf.ldev_size - rbld_buf.blocks_left, |
| rbld_buf.ldev_size); |
| } |
| |
| static ssize_t rebuild_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_cmdblk *cmd_blk; |
| union myrb_cmd_mbox *mbox; |
| unsigned short status; |
| int rc, start; |
| const char *msg; |
| |
| rc = kstrtoint(buf, 0, &start); |
| if (rc) |
| return rc; |
| |
| if (sdev->channel >= myrb_logical_channel(sdev->host)) |
| return -ENXIO; |
| |
| status = myrb_get_rbld_progress(cb, NULL); |
| if (start) { |
| if (status == MYRB_STATUS_SUCCESS) { |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Not Initiated; already in progress\n"); |
| return -EALREADY; |
| } |
| mutex_lock(&cb->dcmd_mutex); |
| cmd_blk = &cb->dcmd_blk; |
| myrb_reset_cmd(cmd_blk); |
| mbox = &cmd_blk->mbox; |
| mbox->type3D.opcode = MYRB_CMD_REBUILD_ASYNC; |
| mbox->type3D.id = MYRB_DCMD_TAG; |
| mbox->type3D.channel = sdev->channel; |
| mbox->type3D.target = sdev->id; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| mutex_unlock(&cb->dcmd_mutex); |
| } else { |
| struct pci_dev *pdev = cb->pdev; |
| unsigned char *rate; |
| dma_addr_t rate_addr; |
| |
| if (status != MYRB_STATUS_SUCCESS) { |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Not Cancelled; not in progress\n"); |
| return 0; |
| } |
| |
| rate = dma_alloc_coherent(&pdev->dev, sizeof(char), |
| &rate_addr, GFP_KERNEL); |
| if (rate == NULL) { |
| sdev_printk(KERN_INFO, sdev, |
| "Cancellation of Rebuild Failed - Out of Memory\n"); |
| return -ENOMEM; |
| } |
| mutex_lock(&cb->dcmd_mutex); |
| cmd_blk = &cb->dcmd_blk; |
| myrb_reset_cmd(cmd_blk); |
| mbox = &cmd_blk->mbox; |
| mbox->type3R.opcode = MYRB_CMD_REBUILD_CONTROL; |
| mbox->type3R.id = MYRB_DCMD_TAG; |
| mbox->type3R.rbld_rate = 0xFF; |
| mbox->type3R.addr = rate_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| dma_free_coherent(&pdev->dev, sizeof(char), rate, rate_addr); |
| mutex_unlock(&cb->dcmd_mutex); |
| } |
| if (status == MYRB_STATUS_SUCCESS) { |
| sdev_printk(KERN_INFO, sdev, "Rebuild %s\n", |
| start ? "Initiated" : "Cancelled"); |
| return count; |
| } |
| if (!start) { |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Not Cancelled, status 0x%x\n", |
| status); |
| return -EIO; |
| } |
| |
| switch (status) { |
| case MYRB_STATUS_ATTEMPT_TO_RBLD_ONLINE_DRIVE: |
| msg = "Attempt to Rebuild Online or Unresponsive Drive"; |
| break; |
| case MYRB_STATUS_RBLD_NEW_DISK_FAILED: |
| msg = "New Disk Failed During Rebuild"; |
| break; |
| case MYRB_STATUS_INVALID_ADDRESS: |
| msg = "Invalid Device Address"; |
| break; |
| case MYRB_STATUS_RBLD_OR_CHECK_INPROGRESS: |
| msg = "Already in Progress"; |
| break; |
| default: |
| msg = NULL; |
| break; |
| } |
| if (msg) |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Failed - %s\n", msg); |
| else |
| sdev_printk(KERN_INFO, sdev, |
| "Rebuild Failed, status 0x%x\n", status); |
| |
| return -EIO; |
| } |
| static DEVICE_ATTR_RW(rebuild); |
| |
| static ssize_t consistency_check_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_rbld_progress rbld_buf; |
| struct myrb_cmdblk *cmd_blk; |
| union myrb_cmd_mbox *mbox; |
| unsigned short ldev_num = 0xFFFF; |
| unsigned short status; |
| int rc, start; |
| const char *msg; |
| |
| rc = kstrtoint(buf, 0, &start); |
| if (rc) |
| return rc; |
| |
| if (sdev->channel < myrb_logical_channel(sdev->host)) |
| return -ENXIO; |
| |
| status = myrb_get_rbld_progress(cb, &rbld_buf); |
| if (start) { |
| if (status == MYRB_STATUS_SUCCESS) { |
| sdev_printk(KERN_INFO, sdev, |
| "Check Consistency Not Initiated; already in progress\n"); |
| return -EALREADY; |
| } |
| mutex_lock(&cb->dcmd_mutex); |
| cmd_blk = &cb->dcmd_blk; |
| myrb_reset_cmd(cmd_blk); |
| mbox = &cmd_blk->mbox; |
| mbox->type3C.opcode = MYRB_CMD_CHECK_CONSISTENCY_ASYNC; |
| mbox->type3C.id = MYRB_DCMD_TAG; |
| mbox->type3C.ldev_num = sdev->id; |
| mbox->type3C.auto_restore = true; |
| |
| status = myrb_exec_cmd(cb, cmd_blk); |
| mutex_unlock(&cb->dcmd_mutex); |
| } else { |
| struct pci_dev *pdev = cb->pdev; |
| unsigned char *rate; |
| dma_addr_t rate_addr; |
| |
| if (ldev_num != sdev->id) { |
| sdev_printk(KERN_INFO, sdev, |
| "Check Consistency Not Cancelled; not in progress\n"); |
| return 0; |
| } |
| rate = dma_alloc_coherent(&pdev->dev, sizeof(char), |
| &rate_addr, GFP_KERNEL); |
| if (rate == NULL) { |
| sdev_printk(KERN_INFO, sdev, |
| "Cancellation of Check Consistency Failed - Out of Memory\n"); |
| return -ENOMEM; |
| } |
| mutex_lock(&cb->dcmd_mutex); |
| cmd_blk = &cb->dcmd_blk; |
| myrb_reset_cmd(cmd_blk); |
| mbox = &cmd_blk->mbox; |
| mbox->type3R.opcode = MYRB_CMD_REBUILD_CONTROL; |
| mbox->type3R.id = MYRB_DCMD_TAG; |
| mbox->type3R.rbld_rate = 0xFF; |
| mbox->type3R.addr = rate_addr; |
| status = myrb_exec_cmd(cb, cmd_blk); |
| dma_free_coherent(&pdev->dev, sizeof(char), rate, rate_addr); |
| mutex_unlock(&cb->dcmd_mutex); |
| } |
| if (status == MYRB_STATUS_SUCCESS) { |
| sdev_printk(KERN_INFO, sdev, "Check Consistency %s\n", |
| start ? "Initiated" : "Cancelled"); |
| return count; |
| } |
| if (!start) { |
| sdev_printk(KERN_INFO, sdev, |
| "Check Consistency Not Cancelled, status 0x%x\n", |
| status); |
| return -EIO; |
| } |
| |
| switch (status) { |
| case MYRB_STATUS_ATTEMPT_TO_RBLD_ONLINE_DRIVE: |
| msg = "Dependent Physical Device is DEAD"; |
| break; |
| case MYRB_STATUS_RBLD_NEW_DISK_FAILED: |
| msg = "New Disk Failed During Rebuild"; |
| break; |
| case MYRB_STATUS_INVALID_ADDRESS: |
| msg = "Invalid or Nonredundant Logical Drive"; |
| break; |
| case MYRB_STATUS_RBLD_OR_CHECK_INPROGRESS: |
| msg = "Already in Progress"; |
| break; |
| default: |
| msg = NULL; |
| break; |
| } |
| if (msg) |
| sdev_printk(KERN_INFO, sdev, |
| "Check Consistency Failed - %s\n", msg); |
| else |
| sdev_printk(KERN_INFO, sdev, |
| "Check Consistency Failed, status 0x%x\n", status); |
| |
| return -EIO; |
| } |
| |
| static ssize_t consistency_check_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return rebuild_show(dev, attr, buf); |
| } |
| static DEVICE_ATTR_RW(consistency_check); |
| |
| static ssize_t ctlr_num_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *shost = class_to_shost(dev); |
| struct myrb_hba *cb = shost_priv(shost); |
| |
| return snprintf(buf, 20, "%d\n", cb->ctlr_num); |
| } |
| static DEVICE_ATTR_RO(ctlr_num); |
| |
| static ssize_t firmware_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *shost = class_to_shost(dev); |
| struct myrb_hba *cb = shost_priv(shost); |
| |
| return snprintf(buf, 16, "%s\n", cb->fw_version); |
| } |
| static DEVICE_ATTR_RO(firmware); |
| |
| static ssize_t model_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *shost = class_to_shost(dev); |
| struct myrb_hba *cb = shost_priv(shost); |
| |
| return snprintf(buf, 16, "%s\n", cb->model_name); |
| } |
| static DEVICE_ATTR_RO(model); |
| |
| static ssize_t flush_cache_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct Scsi_Host *shost = class_to_shost(dev); |
| struct myrb_hba *cb = shost_priv(shost); |
| unsigned short status; |
| |
| status = myrb_exec_type3(cb, MYRB_CMD_FLUSH, 0); |
| if (status == MYRB_STATUS_SUCCESS) { |
| shost_printk(KERN_INFO, shost, |
| "Cache Flush Completed\n"); |
| return count; |
| } |
| shost_printk(KERN_INFO, shost, |
| "Cache Flush Failed, status %x\n", status); |
| return -EIO; |
| } |
| static DEVICE_ATTR_WO(flush_cache); |
| |
| static struct device_attribute *myrb_sdev_attrs[] = { |
| &dev_attr_rebuild, |
| &dev_attr_consistency_check, |
| &dev_attr_raid_state, |
| &dev_attr_raid_level, |
| NULL, |
| }; |
| |
| static struct device_attribute *myrb_shost_attrs[] = { |
| &dev_attr_ctlr_num, |
| &dev_attr_model, |
| &dev_attr_firmware, |
| &dev_attr_flush_cache, |
| NULL, |
| }; |
| |
| struct scsi_host_template myrb_template = { |
| .module = THIS_MODULE, |
| .name = "DAC960", |
| .proc_name = "myrb", |
| .queuecommand = myrb_queuecommand, |
| .eh_host_reset_handler = myrb_host_reset, |
| .slave_alloc = myrb_slave_alloc, |
| .slave_configure = myrb_slave_configure, |
| .slave_destroy = myrb_slave_destroy, |
| .bios_param = myrb_biosparam, |
| .cmd_size = sizeof(struct myrb_cmdblk), |
| .shost_attrs = myrb_shost_attrs, |
| .sdev_attrs = myrb_sdev_attrs, |
| .this_id = -1, |
| }; |
| |
| /** |
| * myrb_is_raid - return boolean indicating device is raid volume |
| * @dev the device struct object |
| */ |
| static int myrb_is_raid(struct device *dev) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| |
| return sdev->channel == myrb_logical_channel(sdev->host); |
| } |
| |
| /** |
| * myrb_get_resync - get raid volume resync percent complete |
| * @dev the device struct object |
| */ |
| static void myrb_get_resync(struct device *dev) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_rbld_progress rbld_buf; |
| unsigned int percent_complete = 0; |
| unsigned short status; |
| unsigned int ldev_size = 0, remaining = 0; |
| |
| if (sdev->channel < myrb_logical_channel(sdev->host)) |
| return; |
| status = myrb_get_rbld_progress(cb, &rbld_buf); |
| if (status == MYRB_STATUS_SUCCESS) { |
| if (rbld_buf.ldev_num == sdev->id) { |
| ldev_size = rbld_buf.ldev_size; |
| remaining = rbld_buf.blocks_left; |
| } |
| } |
| if (remaining && ldev_size) |
| percent_complete = (ldev_size - remaining) * 100 / ldev_size; |
| raid_set_resync(myrb_raid_template, dev, percent_complete); |
| } |
| |
| /** |
| * myrb_get_state - get raid volume status |
| * @dev the device struct object |
| */ |
| static void myrb_get_state(struct device *dev) |
| { |
| struct scsi_device *sdev = to_scsi_device(dev); |
| struct myrb_hba *cb = shost_priv(sdev->host); |
| struct myrb_ldev_info *ldev_info = sdev->hostdata; |
| enum raid_state state = RAID_STATE_UNKNOWN; |
| unsigned short status; |
| |
| if (sdev->channel < myrb_logical_channel(sdev->host) || !ldev_info) |
| state = RAID_STATE_UNKNOWN; |
| else { |
| status = myrb_get_rbld_progress(cb, NULL); |
| if (status == MYRB_STATUS_SUCCESS) |
| state = RAID_STATE_RESYNCING; |
| else { |
| switch (ldev_info->state) { |
| case MYRB_DEVICE_ONLINE: |
| state = RAID_STATE_ACTIVE; |
| break; |
| case MYRB_DEVICE_WO: |
| case MYRB_DEVICE_CRITICAL: |
| state = RAID_STATE_DEGRADED; |
| break; |
| default: |
| state = RAID_STATE_OFFLINE; |
| } |
| } |
| } |
| raid_set_state(myrb_raid_template, dev, state); |
| } |
| |
| struct raid_function_template myrb_raid_functions = { |
| .cookie = &myrb_template, |
| .is_raid = myrb_is_raid, |
| .get_resync = myrb_get_resync, |
| .get_state = myrb_get_state, |
| }; |
| |
| static void myrb_handle_scsi(struct myrb_hba *cb, struct myrb_cmdblk *cmd_blk, |
| struct scsi_cmnd *scmd) |
| { |
| unsigned short status; |
| |
| if (!cmd_blk) |
| return; |
| |
| scsi_dma_unmap(scmd); |
| |
| if (cmd_blk->dcdb) { |
| memcpy(scmd->sense_buffer, &cmd_blk->dcdb->sense, 64); |
| dma_pool_free(cb->dcdb_pool, cmd_blk->dcdb, |
| cmd_blk->dcdb_addr); |
| cmd_blk->dcdb = NULL; |
| } |
| if (cmd_blk->sgl) { |
| dma_pool_free(cb->sg_pool, cmd_blk->sgl, cmd_blk->sgl_addr); |
| cmd_blk->sgl = NULL; |
| cmd_blk->sgl_addr = 0; |
| } |
| status = cmd_blk->status; |
| switch (status) { |
| case MYRB_STATUS_SUCCESS: |
| case MYRB_STATUS_DEVICE_BUSY: |
| scmd->result = (DID_OK << 16) | status; |
| break; |
| case MYRB_STATUS_BAD_DATA: |
| dev_dbg(&scmd->device->sdev_gendev, |
| "Bad Data Encountered\n"); |
| if (scmd->sc_data_direction == DMA_FROM_DEVICE) |
| /* Unrecovered read error */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| MEDIUM_ERROR, 0x11, 0); |
| else |
| /* Write error */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| MEDIUM_ERROR, 0x0C, 0); |
| scmd->result = (DID_OK << 16) | SAM_STAT_CHECK_CONDITION; |
| break; |
| case MYRB_STATUS_IRRECOVERABLE_DATA_ERROR: |
| scmd_printk(KERN_ERR, scmd, "Irrecoverable Data Error\n"); |
| if (scmd->sc_data_direction == DMA_FROM_DEVICE) |
| /* Unrecovered read error, auto-reallocation failed */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| MEDIUM_ERROR, 0x11, 0x04); |
| else |
| /* Write error, auto-reallocation failed */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| MEDIUM_ERROR, 0x0C, 0x02); |
| scmd->result = (DID_OK << 16) | SAM_STAT_CHECK_CONDITION; |
| break; |
| case MYRB_STATUS_LDRV_NONEXISTENT_OR_OFFLINE: |
| dev_dbg(&scmd->device->sdev_gendev, |
| "Logical Drive Nonexistent or Offline"); |
| scmd->result = (DID_BAD_TARGET << 16); |
| break; |
| case MYRB_STATUS_ACCESS_BEYOND_END_OF_LDRV: |
| dev_dbg(&scmd->device->sdev_gendev, |
| "Attempt to Access Beyond End of Logical Drive"); |
| /* Logical block address out of range */ |
| scsi_build_sense_buffer(0, scmd->sense_buffer, |
| NOT_READY, 0x21, 0); |
| break; |
| case MYRB_STATUS_DEVICE_NONRESPONSIVE: |
| dev_dbg(&scmd->device->sdev_gendev, "Device nonresponsive\n"); |
| scmd->result = (DID_BAD_TARGET << 16); |
| break; |
| default: |
| scmd_printk(KERN_ERR, scmd, |
| "Unexpected Error Status %04X", status); |
| scmd->result = (DID_ERROR << 16); |
| break; |
| } |
| scmd->scsi_done(scmd); |
| } |
| |
| static void myrb_handle_cmdblk(struct myrb_hba *cb, struct myrb_cmdblk *cmd_blk) |
| { |
| if (!cmd_blk) |
| return; |
| |
| if (cmd_blk->completion) { |
| complete(cmd_blk->completion); |
| cmd_blk->completion = NULL; |
| } |
| } |
| |
| static void myrb_monitor(struct work_struct *work) |
| { |
| struct myrb_hba *cb = container_of(work, |
| struct myrb_hba, monitor_work.work); |
| struct Scsi_Host *shost = cb->host; |
| unsigned long interval = MYRB_PRIMARY_MONITOR_INTERVAL; |
| |
| dev_dbg(&shost->shost_gendev, "monitor tick\n"); |
| |
| if (cb->new_ev_seq > cb->old_ev_seq) { |
| int event = cb->old_ev_seq; |
| |
| dev_dbg(&shost->shost_gendev, |
| "get event log no %d/%d\n", |
| cb->new_ev_seq, event); |
| myrb_get_event(cb, event); |
| cb->old_ev_seq = event + 1; |
| interval = 10; |
| } else if (cb->need_err_info) { |
| cb->need_err_info = false; |
| dev_dbg(&shost->shost_gendev, "get error table\n"); |
| myrb_get_errtable(cb); |
| interval = 10; |
| } else if (cb->need_rbld && cb->rbld_first) { |
| cb->need_rbld = false; |
| dev_dbg(&shost->shost_gendev, |
| "get rebuild progress\n"); |
| myrb_update_rbld_progress(cb); |
| interval = 10; |
| } else if (cb->need_ldev_info) { |
| cb->need_ldev_info = false; |
| dev_dbg(&shost->shost_gendev, |
| "get logical drive info\n"); |
| myrb_get_ldev_info(cb); |
| interval = 10; |
| } else if (cb->need_rbld) { |
| cb->need_rbld = false; |
| dev_dbg(&shost->shost_gendev, |
| "get rebuild progress\n"); |
| myrb_update_rbld_progress(cb); |
| interval = 10; |
| } else if (cb->need_cc_status) { |
| cb->need_cc_status = false; |
| dev_dbg(&shost->shost_gendev, |
| "get consistency check progress\n"); |
| myrb_get_cc_progress(cb); |
| interval = 10; |
| } else if (cb->need_bgi_status) { |
| cb->need_bgi_status = false; |
| dev_dbg(&shost->shost_gendev, "get background init status\n"); |
| myrb_bgi_control(cb); |
| interval = 10; |
| } else { |
| dev_dbg(&shost->shost_gendev, "new enquiry\n"); |
| mutex_lock(&cb->dma_mutex); |
| myrb_hba_enquiry(cb); |
| mutex_unlock(&cb->dma_mutex); |
| if ((cb->new_ev_seq - cb->old_ev_seq > 0) || |
| cb->need_err_info || cb->need_rbld || |
| cb->need_ldev_info || cb->need_cc_status || |
| cb->need_bgi_status) { |
| dev_dbg(&shost->shost_gendev, |
| "reschedule monitor\n"); |
| interval = 0; |
| } |
| } |
| if (interval > 1) |
| cb->primary_monitor_time = jiffies; |
| queue_delayed_work(cb->work_q, &cb->monitor_work, interval); |
| } |
| |
| /** |
| * myrb_err_status - reports controller BIOS messages |
| * |
| * Controller BIOS messages are passed through the Error Status Register |
| * when the driver performs the BIOS handshaking. |
| * |
| * Return: true for fatal errors and false otherwise. |
| */ |
| bool myrb_err_status(struct myrb_hba *cb, unsigned char error, |
| unsigned char parm0, unsigned char parm1) |
| { |
| struct pci_dev *pdev = cb->pdev; |
| |
| switch (error) { |
| case 0x00: |
| dev_info(&pdev->dev, |
| "Physical Device %d:%d Not Responding\n", |
| parm1, parm0); |
| break; |
| case 0x08: |
| dev_notice(&pdev->dev, "Spinning Up Drives\n"); |
| break; |
| case 0x30: |
| dev_notice(&pdev->dev, "Configuration Checksum Error\n"); |
| break; |
| case 0x60: |
| dev_notice(&pdev->dev, "Mirror Race Recovery Failed\n"); |
| break; |
| case 0x70: |
| dev_notice(&pdev->dev, "Mirror Race Recovery In Progress\n"); |
| break; |
| case 0x90: |
| dev_notice(&pdev->dev, "Physical Device %d:%d COD Mismatch\n", |
| parm1, parm0); |
| break; |
| case 0xA0: |
| dev_notice(&pdev->dev, "Logical Drive Installation Aborted\n"); |
| break; |
| case 0xB0: |
| dev_notice(&pdev->dev, "Mirror Race On A Critical Logical Drive\n"); |
| break; |
| case 0xD0: |
| dev_notice(&pdev->dev, "New Controller Configuration Found\n"); |
| break; |
| case 0xF0: |
| dev_err(&pdev->dev, "Fatal Memory Parity Error\n"); |
| return true; |
| default: |
| dev_err(&pdev->dev, "Unknown Initialization Error %02X\n", |
| error); |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * Hardware-specific functions |
| */ |
| |
| /* |
| * DAC960 LA Series Controllers |
| */ |
| |
| static inline void DAC960_LA_hw_mbox_new_cmd(void __iomem *base) |
| { |
| writeb(DAC960_LA_IDB_HWMBOX_NEW_CMD, base + DAC960_LA_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_LA_ack_hw_mbox_status(void __iomem *base) |
| { |
| writeb(DAC960_LA_IDB_HWMBOX_ACK_STS, base + DAC960_LA_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_LA_gen_intr(void __iomem *base) |
| { |
| writeb(DAC960_LA_IDB_GEN_IRQ, base + DAC960_LA_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_LA_reset_ctrl(void __iomem *base) |
| { |
| writeb(DAC960_LA_IDB_CTRL_RESET, base + DAC960_LA_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_LA_mem_mbox_new_cmd(void __iomem *base) |
| { |
| writeb(DAC960_LA_IDB_MMBOX_NEW_CMD, base + DAC960_LA_IDB_OFFSET); |
| } |
| |
| static inline bool DAC960_LA_hw_mbox_is_full(void __iomem *base) |
| { |
| unsigned char idb = readb(base + DAC960_LA_IDB_OFFSET); |
| |
| return !(idb & DAC960_LA_IDB_HWMBOX_EMPTY); |
| } |
| |
| static inline bool DAC960_LA_init_in_progress(void __iomem *base) |
| { |
| unsigned char idb = readb(base + DAC960_LA_IDB_OFFSET); |
| |
| return !(idb & DAC960_LA_IDB_INIT_DONE); |
| } |
| |
| static inline void DAC960_LA_ack_hw_mbox_intr(void __iomem *base) |
| { |
| writeb(DAC960_LA_ODB_HWMBOX_ACK_IRQ, base + DAC960_LA_ODB_OFFSET); |
| } |
| |
| static inline void DAC960_LA_ack_mem_mbox_intr(void __iomem *base) |
| { |
| writeb(DAC960_LA_ODB_MMBOX_ACK_IRQ, base + DAC960_LA_ODB_OFFSET); |
| } |
| |
| static inline void DAC960_LA_ack_intr(void __iomem *base) |
| { |
| writeb(DAC960_LA_ODB_HWMBOX_ACK_IRQ | DAC960_LA_ODB_MMBOX_ACK_IRQ, |
| base + DAC960_LA_ODB_OFFSET); |
| } |
| |
| static inline bool DAC960_LA_hw_mbox_status_available(void __iomem *base) |
| { |
| unsigned char odb = readb(base + DAC960_LA_ODB_OFFSET); |
| |
| return odb & DAC960_LA_ODB_HWMBOX_STS_AVAIL; |
| } |
| |
| static inline bool DAC960_LA_mem_mbox_status_available(void __iomem *base) |
| { |
| unsigned char odb = readb(base + DAC960_LA_ODB_OFFSET); |
| |
| return odb & DAC960_LA_ODB_MMBOX_STS_AVAIL; |
| } |
| |
| static inline void DAC960_LA_enable_intr(void __iomem *base) |
| { |
| unsigned char odb = 0xFF; |
| |
| odb &= ~DAC960_LA_IRQMASK_DISABLE_IRQ; |
| writeb(odb, base + DAC960_LA_IRQMASK_OFFSET); |
| } |
| |
| static inline void DAC960_LA_disable_intr(void __iomem *base) |
| { |
| unsigned char odb = 0xFF; |
| |
| odb |= DAC960_LA_IRQMASK_DISABLE_IRQ; |
| writeb(odb, base + DAC960_LA_IRQMASK_OFFSET); |
| } |
| |
| static inline bool DAC960_LA_intr_enabled(void __iomem *base) |
| { |
| unsigned char imask = readb(base + DAC960_LA_IRQMASK_OFFSET); |
| |
| return !(imask & DAC960_LA_IRQMASK_DISABLE_IRQ); |
| } |
| |
| static inline void DAC960_LA_write_cmd_mbox(union myrb_cmd_mbox *mem_mbox, |
| union myrb_cmd_mbox *mbox) |
| { |
| mem_mbox->words[1] = mbox->words[1]; |
| mem_mbox->words[2] = mbox->words[2]; |
| mem_mbox->words[3] = mbox->words[3]; |
| /* Memory barrier to prevent reordering */ |
| wmb(); |
| mem_mbox->words[0] = mbox->words[0]; |
| /* Memory barrier to force PCI access */ |
| mb(); |
| } |
| |
| static inline void DAC960_LA_write_hw_mbox(void __iomem *base, |
| union myrb_cmd_mbox *mbox) |
| { |
| writel(mbox->words[0], base + DAC960_LA_CMDOP_OFFSET); |
| writel(mbox->words[1], base + DAC960_LA_MBOX4_OFFSET); |
| writel(mbox->words[2], base + DAC960_LA_MBOX8_OFFSET); |
| writeb(mbox->bytes[12], base + DAC960_LA_MBOX12_OFFSET); |
| } |
| |
| static inline unsigned char DAC960_LA_read_status_cmd_ident(void __iomem *base) |
| { |
| return readb(base + DAC960_LA_STSID_OFFSET); |
| } |
| |
| static inline unsigned short DAC960_LA_read_status(void __iomem *base) |
| { |
| return readw(base + DAC960_LA_STS_OFFSET); |
| } |
| |
| static inline bool |
| DAC960_LA_read_error_status(void __iomem *base, unsigned char *error, |
| unsigned char *param0, unsigned char *param1) |
| { |
| unsigned char errsts = readb(base + DAC960_LA_ERRSTS_OFFSET); |
| |
| if (!(errsts & DAC960_LA_ERRSTS_PENDING)) |
| return false; |
| errsts &= ~DAC960_LA_ERRSTS_PENDING; |
| |
| *error = errsts; |
| *param0 = readb(base + DAC960_LA_CMDOP_OFFSET); |
| *param1 = readb(base + DAC960_LA_CMDID_OFFSET); |
| writeb(0xFF, base + DAC960_LA_ERRSTS_OFFSET); |
| return true; |
| } |
| |
| static inline unsigned short |
| DAC960_LA_mbox_init(struct pci_dev *pdev, void __iomem *base, |
| union myrb_cmd_mbox *mbox) |
| { |
| unsigned short status; |
| int timeout = 0; |
| |
| while (timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (!DAC960_LA_hw_mbox_is_full(base)) |
| break; |
| udelay(10); |
| timeout++; |
| } |
| if (DAC960_LA_hw_mbox_is_full(base)) { |
| dev_err(&pdev->dev, |
| "Timeout waiting for empty mailbox\n"); |
| return MYRB_STATUS_SUBSYS_TIMEOUT; |
| } |
| DAC960_LA_write_hw_mbox(base, mbox); |
| DAC960_LA_hw_mbox_new_cmd(base); |
| timeout = 0; |
| while (timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (DAC960_LA_hw_mbox_status_available(base)) |
| break; |
| udelay(10); |
| timeout++; |
| } |
| if (!DAC960_LA_hw_mbox_status_available(base)) { |
| dev_err(&pdev->dev, "Timeout waiting for mailbox status\n"); |
| return MYRB_STATUS_SUBSYS_TIMEOUT; |
| } |
| status = DAC960_LA_read_status(base); |
| DAC960_LA_ack_hw_mbox_intr(base); |
| DAC960_LA_ack_hw_mbox_status(base); |
| |
| return status; |
| } |
| |
| static int DAC960_LA_hw_init(struct pci_dev *pdev, |
| struct myrb_hba *cb, void __iomem *base) |
| { |
| int timeout = 0; |
| unsigned char error, parm0, parm1; |
| |
| DAC960_LA_disable_intr(base); |
| DAC960_LA_ack_hw_mbox_status(base); |
| udelay(1000); |
| timeout = 0; |
| while (DAC960_LA_init_in_progress(base) && |
| timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (DAC960_LA_read_error_status(base, &error, |
| &parm0, &parm1) && |
| myrb_err_status(cb, error, parm0, parm1)) |
| return -ENODEV; |
| udelay(10); |
| timeout++; |
| } |
| if (timeout == MYRB_MAILBOX_TIMEOUT) { |
| dev_err(&pdev->dev, |
| "Timeout waiting for Controller Initialisation\n"); |
| return -ETIMEDOUT; |
| } |
| if (!myrb_enable_mmio(cb, DAC960_LA_mbox_init)) { |
| dev_err(&pdev->dev, |
| "Unable to Enable Memory Mailbox Interface\n"); |
| DAC960_LA_reset_ctrl(base); |
| return -ENODEV; |
| } |
| DAC960_LA_enable_intr(base); |
| cb->qcmd = myrb_qcmd; |
| cb->write_cmd_mbox = DAC960_LA_write_cmd_mbox; |
| if (cb->dual_mode_interface) |
| cb->get_cmd_mbox = DAC960_LA_mem_mbox_new_cmd; |
| else |
| cb->get_cmd_mbox = DAC960_LA_hw_mbox_new_cmd; |
| cb->disable_intr = DAC960_LA_disable_intr; |
| cb->reset = DAC960_LA_reset_ctrl; |
| |
| return 0; |
| } |
| |
| static irqreturn_t DAC960_LA_intr_handler(int irq, void *arg) |
| { |
| struct myrb_hba *cb = arg; |
| void __iomem *base = cb->io_base; |
| struct myrb_stat_mbox *next_stat_mbox; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cb->queue_lock, flags); |
| DAC960_LA_ack_intr(base); |
| next_stat_mbox = cb->next_stat_mbox; |
| while (next_stat_mbox->valid) { |
| unsigned char id = next_stat_mbox->id; |
| struct scsi_cmnd *scmd = NULL; |
| struct myrb_cmdblk *cmd_blk = NULL; |
| |
| if (id == MYRB_DCMD_TAG) |
| cmd_blk = &cb->dcmd_blk; |
| else if (id == MYRB_MCMD_TAG) |
| cmd_blk = &cb->mcmd_blk; |
| else { |
| scmd = scsi_host_find_tag(cb->host, id - 3); |
| if (scmd) |
| cmd_blk = scsi_cmd_priv(scmd); |
| } |
| if (cmd_blk) |
| cmd_blk->status = next_stat_mbox->status; |
| else |
| dev_err(&cb->pdev->dev, |
| "Unhandled command completion %d\n", id); |
| |
| memset(next_stat_mbox, 0, sizeof(struct myrb_stat_mbox)); |
| if (++next_stat_mbox > cb->last_stat_mbox) |
| next_stat_mbox = cb->first_stat_mbox; |
| |
| if (cmd_blk) { |
| if (id < 3) |
| myrb_handle_cmdblk(cb, cmd_blk); |
| else |
| myrb_handle_scsi(cb, cmd_blk, scmd); |
| } |
| } |
| cb->next_stat_mbox = next_stat_mbox; |
| spin_unlock_irqrestore(&cb->queue_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| struct myrb_privdata DAC960_LA_privdata = { |
| .hw_init = DAC960_LA_hw_init, |
| .irq_handler = DAC960_LA_intr_handler, |
| .mmio_size = DAC960_LA_mmio_size, |
| }; |
| |
| /* |
| * DAC960 PG Series Controllers |
| */ |
| static inline void DAC960_PG_hw_mbox_new_cmd(void __iomem *base) |
| { |
| writel(DAC960_PG_IDB_HWMBOX_NEW_CMD, base + DAC960_PG_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_PG_ack_hw_mbox_status(void __iomem *base) |
| { |
| writel(DAC960_PG_IDB_HWMBOX_ACK_STS, base + DAC960_PG_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_PG_gen_intr(void __iomem *base) |
| { |
| writel(DAC960_PG_IDB_GEN_IRQ, base + DAC960_PG_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_PG_reset_ctrl(void __iomem *base) |
| { |
| writel(DAC960_PG_IDB_CTRL_RESET, base + DAC960_PG_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_PG_mem_mbox_new_cmd(void __iomem *base) |
| { |
| writel(DAC960_PG_IDB_MMBOX_NEW_CMD, base + DAC960_PG_IDB_OFFSET); |
| } |
| |
| static inline bool DAC960_PG_hw_mbox_is_full(void __iomem *base) |
| { |
| unsigned char idb = readl(base + DAC960_PG_IDB_OFFSET); |
| |
| return idb & DAC960_PG_IDB_HWMBOX_FULL; |
| } |
| |
| static inline bool DAC960_PG_init_in_progress(void __iomem *base) |
| { |
| unsigned char idb = readl(base + DAC960_PG_IDB_OFFSET); |
| |
| return idb & DAC960_PG_IDB_INIT_IN_PROGRESS; |
| } |
| |
| static inline void DAC960_PG_ack_hw_mbox_intr(void __iomem *base) |
| { |
| writel(DAC960_PG_ODB_HWMBOX_ACK_IRQ, base + DAC960_PG_ODB_OFFSET); |
| } |
| |
| static inline void DAC960_PG_ack_mem_mbox_intr(void __iomem *base) |
| { |
| writel(DAC960_PG_ODB_MMBOX_ACK_IRQ, base + DAC960_PG_ODB_OFFSET); |
| } |
| |
| static inline void DAC960_PG_ack_intr(void __iomem *base) |
| { |
| writel(DAC960_PG_ODB_HWMBOX_ACK_IRQ | DAC960_PG_ODB_MMBOX_ACK_IRQ, |
| base + DAC960_PG_ODB_OFFSET); |
| } |
| |
| static inline bool DAC960_PG_hw_mbox_status_available(void __iomem *base) |
| { |
| unsigned char odb = readl(base + DAC960_PG_ODB_OFFSET); |
| |
| return odb & DAC960_PG_ODB_HWMBOX_STS_AVAIL; |
| } |
| |
| static inline bool DAC960_PG_mem_mbox_status_available(void __iomem *base) |
| { |
| unsigned char odb = readl(base + DAC960_PG_ODB_OFFSET); |
| |
| return odb & DAC960_PG_ODB_MMBOX_STS_AVAIL; |
| } |
| |
| static inline void DAC960_PG_enable_intr(void __iomem *base) |
| { |
| unsigned int imask = (unsigned int)-1; |
| |
| imask &= ~DAC960_PG_IRQMASK_DISABLE_IRQ; |
| writel(imask, base + DAC960_PG_IRQMASK_OFFSET); |
| } |
| |
| static inline void DAC960_PG_disable_intr(void __iomem *base) |
| { |
| unsigned int imask = (unsigned int)-1; |
| |
| writel(imask, base + DAC960_PG_IRQMASK_OFFSET); |
| } |
| |
| static inline bool DAC960_PG_intr_enabled(void __iomem *base) |
| { |
| unsigned int imask = readl(base + DAC960_PG_IRQMASK_OFFSET); |
| |
| return !(imask & DAC960_PG_IRQMASK_DISABLE_IRQ); |
| } |
| |
| static inline void DAC960_PG_write_cmd_mbox(union myrb_cmd_mbox *mem_mbox, |
| union myrb_cmd_mbox *mbox) |
| { |
| mem_mbox->words[1] = mbox->words[1]; |
| mem_mbox->words[2] = mbox->words[2]; |
| mem_mbox->words[3] = mbox->words[3]; |
| /* Memory barrier to prevent reordering */ |
| wmb(); |
| mem_mbox->words[0] = mbox->words[0]; |
| /* Memory barrier to force PCI access */ |
| mb(); |
| } |
| |
| static inline void DAC960_PG_write_hw_mbox(void __iomem *base, |
| union myrb_cmd_mbox *mbox) |
| { |
| writel(mbox->words[0], base + DAC960_PG_CMDOP_OFFSET); |
| writel(mbox->words[1], base + DAC960_PG_MBOX4_OFFSET); |
| writel(mbox->words[2], base + DAC960_PG_MBOX8_OFFSET); |
| writeb(mbox->bytes[12], base + DAC960_PG_MBOX12_OFFSET); |
| } |
| |
| static inline unsigned char |
| DAC960_PG_read_status_cmd_ident(void __iomem *base) |
| { |
| return readb(base + DAC960_PG_STSID_OFFSET); |
| } |
| |
| static inline unsigned short |
| DAC960_PG_read_status(void __iomem *base) |
| { |
| return readw(base + DAC960_PG_STS_OFFSET); |
| } |
| |
| static inline bool |
| DAC960_PG_read_error_status(void __iomem *base, unsigned char *error, |
| unsigned char *param0, unsigned char *param1) |
| { |
| unsigned char errsts = readb(base + DAC960_PG_ERRSTS_OFFSET); |
| |
| if (!(errsts & DAC960_PG_ERRSTS_PENDING)) |
| return false; |
| errsts &= ~DAC960_PG_ERRSTS_PENDING; |
| *error = errsts; |
| *param0 = readb(base + DAC960_PG_CMDOP_OFFSET); |
| *param1 = readb(base + DAC960_PG_CMDID_OFFSET); |
| writeb(0, base + DAC960_PG_ERRSTS_OFFSET); |
| return true; |
| } |
| |
| static inline unsigned short |
| DAC960_PG_mbox_init(struct pci_dev *pdev, void __iomem *base, |
| union myrb_cmd_mbox *mbox) |
| { |
| unsigned short status; |
| int timeout = 0; |
| |
| while (timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (!DAC960_PG_hw_mbox_is_full(base)) |
| break; |
| udelay(10); |
| timeout++; |
| } |
| if (DAC960_PG_hw_mbox_is_full(base)) { |
| dev_err(&pdev->dev, |
| "Timeout waiting for empty mailbox\n"); |
| return MYRB_STATUS_SUBSYS_TIMEOUT; |
| } |
| DAC960_PG_write_hw_mbox(base, mbox); |
| DAC960_PG_hw_mbox_new_cmd(base); |
| |
| timeout = 0; |
| while (timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (DAC960_PG_hw_mbox_status_available(base)) |
| break; |
| udelay(10); |
| timeout++; |
| } |
| if (!DAC960_PG_hw_mbox_status_available(base)) { |
| dev_err(&pdev->dev, |
| "Timeout waiting for mailbox status\n"); |
| return MYRB_STATUS_SUBSYS_TIMEOUT; |
| } |
| status = DAC960_PG_read_status(base); |
| DAC960_PG_ack_hw_mbox_intr(base); |
| DAC960_PG_ack_hw_mbox_status(base); |
| |
| return status; |
| } |
| |
| static int DAC960_PG_hw_init(struct pci_dev *pdev, |
| struct myrb_hba *cb, void __iomem *base) |
| { |
| int timeout = 0; |
| unsigned char error, parm0, parm1; |
| |
| DAC960_PG_disable_intr(base); |
| DAC960_PG_ack_hw_mbox_status(base); |
| udelay(1000); |
| while (DAC960_PG_init_in_progress(base) && |
| timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (DAC960_PG_read_error_status(base, &error, |
| &parm0, &parm1) && |
| myrb_err_status(cb, error, parm0, parm1)) |
| return -EIO; |
| udelay(10); |
| timeout++; |
| } |
| if (timeout == MYRB_MAILBOX_TIMEOUT) { |
| dev_err(&pdev->dev, |
| "Timeout waiting for Controller Initialisation\n"); |
| return -ETIMEDOUT; |
| } |
| if (!myrb_enable_mmio(cb, DAC960_PG_mbox_init)) { |
| dev_err(&pdev->dev, |
| "Unable to Enable Memory Mailbox Interface\n"); |
| DAC960_PG_reset_ctrl(base); |
| return -ENODEV; |
| } |
| DAC960_PG_enable_intr(base); |
| cb->qcmd = myrb_qcmd; |
| cb->write_cmd_mbox = DAC960_PG_write_cmd_mbox; |
| if (cb->dual_mode_interface) |
| cb->get_cmd_mbox = DAC960_PG_mem_mbox_new_cmd; |
| else |
| cb->get_cmd_mbox = DAC960_PG_hw_mbox_new_cmd; |
| cb->disable_intr = DAC960_PG_disable_intr; |
| cb->reset = DAC960_PG_reset_ctrl; |
| |
| return 0; |
| } |
| |
| static irqreturn_t DAC960_PG_intr_handler(int irq, void *arg) |
| { |
| struct myrb_hba *cb = arg; |
| void __iomem *base = cb->io_base; |
| struct myrb_stat_mbox *next_stat_mbox; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cb->queue_lock, flags); |
| DAC960_PG_ack_intr(base); |
| next_stat_mbox = cb->next_stat_mbox; |
| while (next_stat_mbox->valid) { |
| unsigned char id = next_stat_mbox->id; |
| struct scsi_cmnd *scmd = NULL; |
| struct myrb_cmdblk *cmd_blk = NULL; |
| |
| if (id == MYRB_DCMD_TAG) |
| cmd_blk = &cb->dcmd_blk; |
| else if (id == MYRB_MCMD_TAG) |
| cmd_blk = &cb->mcmd_blk; |
| else { |
| scmd = scsi_host_find_tag(cb->host, id - 3); |
| if (scmd) |
| cmd_blk = scsi_cmd_priv(scmd); |
| } |
| if (cmd_blk) |
| cmd_blk->status = next_stat_mbox->status; |
| else |
| dev_err(&cb->pdev->dev, |
| "Unhandled command completion %d\n", id); |
| |
| memset(next_stat_mbox, 0, sizeof(struct myrb_stat_mbox)); |
| if (++next_stat_mbox > cb->last_stat_mbox) |
| next_stat_mbox = cb->first_stat_mbox; |
| |
| if (id < 3) |
| myrb_handle_cmdblk(cb, cmd_blk); |
| else |
| myrb_handle_scsi(cb, cmd_blk, scmd); |
| } |
| cb->next_stat_mbox = next_stat_mbox; |
| spin_unlock_irqrestore(&cb->queue_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| struct myrb_privdata DAC960_PG_privdata = { |
| .hw_init = DAC960_PG_hw_init, |
| .irq_handler = DAC960_PG_intr_handler, |
| .mmio_size = DAC960_PG_mmio_size, |
| }; |
| |
| |
| /* |
| * DAC960 PD Series Controllers |
| */ |
| |
| static inline void DAC960_PD_hw_mbox_new_cmd(void __iomem *base) |
| { |
| writeb(DAC960_PD_IDB_HWMBOX_NEW_CMD, base + DAC960_PD_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_PD_ack_hw_mbox_status(void __iomem *base) |
| { |
| writeb(DAC960_PD_IDB_HWMBOX_ACK_STS, base + DAC960_PD_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_PD_gen_intr(void __iomem *base) |
| { |
| writeb(DAC960_PD_IDB_GEN_IRQ, base + DAC960_PD_IDB_OFFSET); |
| } |
| |
| static inline void DAC960_PD_reset_ctrl(void __iomem *base) |
| { |
| writeb(DAC960_PD_IDB_CTRL_RESET, base + DAC960_PD_IDB_OFFSET); |
| } |
| |
| static inline bool DAC960_PD_hw_mbox_is_full(void __iomem *base) |
| { |
| unsigned char idb = readb(base + DAC960_PD_IDB_OFFSET); |
| |
| return idb & DAC960_PD_IDB_HWMBOX_FULL; |
| } |
| |
| static inline bool DAC960_PD_init_in_progress(void __iomem *base) |
| { |
| unsigned char idb = readb(base + DAC960_PD_IDB_OFFSET); |
| |
| return idb & DAC960_PD_IDB_INIT_IN_PROGRESS; |
| } |
| |
| static inline void DAC960_PD_ack_intr(void __iomem *base) |
| { |
| writeb(DAC960_PD_ODB_HWMBOX_ACK_IRQ, base + DAC960_PD_ODB_OFFSET); |
| } |
| |
| static inline bool DAC960_PD_hw_mbox_status_available(void __iomem *base) |
| { |
| unsigned char odb = readb(base + DAC960_PD_ODB_OFFSET); |
| |
| return odb & DAC960_PD_ODB_HWMBOX_STS_AVAIL; |
| } |
| |
| static inline void DAC960_PD_enable_intr(void __iomem *base) |
| { |
| writeb(DAC960_PD_IRQMASK_ENABLE_IRQ, base + DAC960_PD_IRQEN_OFFSET); |
| } |
| |
| static inline void DAC960_PD_disable_intr(void __iomem *base) |
| { |
| writeb(0, base + DAC960_PD_IRQEN_OFFSET); |
| } |
| |
| static inline bool DAC960_PD_intr_enabled(void __iomem *base) |
| { |
| unsigned char imask = readb(base + DAC960_PD_IRQEN_OFFSET); |
| |
| return imask & DAC960_PD_IRQMASK_ENABLE_IRQ; |
| } |
| |
| static inline void DAC960_PD_write_cmd_mbox(void __iomem *base, |
| union myrb_cmd_mbox *mbox) |
| { |
| writel(mbox->words[0], base + DAC960_PD_CMDOP_OFFSET); |
| writel(mbox->words[1], base + DAC960_PD_MBOX4_OFFSET); |
| writel(mbox->words[2], base + DAC960_PD_MBOX8_OFFSET); |
| writeb(mbox->bytes[12], base + DAC960_PD_MBOX12_OFFSET); |
| } |
| |
| static inline unsigned char |
| DAC960_PD_read_status_cmd_ident(void __iomem *base) |
| { |
| return readb(base + DAC960_PD_STSID_OFFSET); |
| } |
| |
| static inline unsigned short |
| DAC960_PD_read_status(void __iomem *base) |
| { |
| return readw(base + DAC960_PD_STS_OFFSET); |
| } |
| |
| static inline bool |
| DAC960_PD_read_error_status(void __iomem *base, unsigned char *error, |
| unsigned char *param0, unsigned char *param1) |
| { |
| unsigned char errsts = readb(base + DAC960_PD_ERRSTS_OFFSET); |
| |
| if (!(errsts & DAC960_PD_ERRSTS_PENDING)) |
| return false; |
| errsts &= ~DAC960_PD_ERRSTS_PENDING; |
| *error = errsts; |
| *param0 = readb(base + DAC960_PD_CMDOP_OFFSET); |
| *param1 = readb(base + DAC960_PD_CMDID_OFFSET); |
| writeb(0, base + DAC960_PD_ERRSTS_OFFSET); |
| return true; |
| } |
| |
| static void DAC960_PD_qcmd(struct myrb_hba *cb, struct myrb_cmdblk *cmd_blk) |
| { |
| void __iomem *base = cb->io_base; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| |
| while (DAC960_PD_hw_mbox_is_full(base)) |
| udelay(1); |
| DAC960_PD_write_cmd_mbox(base, mbox); |
| DAC960_PD_hw_mbox_new_cmd(base); |
| } |
| |
| static int DAC960_PD_hw_init(struct pci_dev *pdev, |
| struct myrb_hba *cb, void __iomem *base) |
| { |
| int timeout = 0; |
| unsigned char error, parm0, parm1; |
| |
| if (!request_region(cb->io_addr, 0x80, "myrb")) { |
| dev_err(&pdev->dev, "IO port 0x%lx busy\n", |
| (unsigned long)cb->io_addr); |
| return -EBUSY; |
| } |
| DAC960_PD_disable_intr(base); |
| DAC960_PD_ack_hw_mbox_status(base); |
| udelay(1000); |
| while (DAC960_PD_init_in_progress(base) && |
| timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (DAC960_PD_read_error_status(base, &error, |
| &parm0, &parm1) && |
| myrb_err_status(cb, error, parm0, parm1)) |
| return -EIO; |
| udelay(10); |
| timeout++; |
| } |
| if (timeout == MYRB_MAILBOX_TIMEOUT) { |
| dev_err(&pdev->dev, |
| "Timeout waiting for Controller Initialisation\n"); |
| return -ETIMEDOUT; |
| } |
| if (!myrb_enable_mmio(cb, NULL)) { |
| dev_err(&pdev->dev, |
| "Unable to Enable Memory Mailbox Interface\n"); |
| DAC960_PD_reset_ctrl(base); |
| return -ENODEV; |
| } |
| DAC960_PD_enable_intr(base); |
| cb->qcmd = DAC960_PD_qcmd; |
| cb->disable_intr = DAC960_PD_disable_intr; |
| cb->reset = DAC960_PD_reset_ctrl; |
| |
| return 0; |
| } |
| |
| static irqreturn_t DAC960_PD_intr_handler(int irq, void *arg) |
| { |
| struct myrb_hba *cb = arg; |
| void __iomem *base = cb->io_base; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cb->queue_lock, flags); |
| while (DAC960_PD_hw_mbox_status_available(base)) { |
| unsigned char id = DAC960_PD_read_status_cmd_ident(base); |
| struct scsi_cmnd *scmd = NULL; |
| struct myrb_cmdblk *cmd_blk = NULL; |
| |
| if (id == MYRB_DCMD_TAG) |
| cmd_blk = &cb->dcmd_blk; |
| else if (id == MYRB_MCMD_TAG) |
| cmd_blk = &cb->mcmd_blk; |
| else { |
| scmd = scsi_host_find_tag(cb->host, id - 3); |
| if (scmd) |
| cmd_blk = scsi_cmd_priv(scmd); |
| } |
| if (cmd_blk) |
| cmd_blk->status = DAC960_PD_read_status(base); |
| else |
| dev_err(&cb->pdev->dev, |
| "Unhandled command completion %d\n", id); |
| |
| DAC960_PD_ack_intr(base); |
| DAC960_PD_ack_hw_mbox_status(base); |
| |
| if (id < 3) |
| myrb_handle_cmdblk(cb, cmd_blk); |
| else |
| myrb_handle_scsi(cb, cmd_blk, scmd); |
| } |
| spin_unlock_irqrestore(&cb->queue_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| struct myrb_privdata DAC960_PD_privdata = { |
| .hw_init = DAC960_PD_hw_init, |
| .irq_handler = DAC960_PD_intr_handler, |
| .mmio_size = DAC960_PD_mmio_size, |
| }; |
| |
| |
| /* |
| * DAC960 P Series Controllers |
| * |
| * Similar to the DAC960 PD Series Controllers, but some commands have |
| * to be translated. |
| */ |
| |
| static inline void myrb_translate_enquiry(void *enq) |
| { |
| memcpy(enq + 132, enq + 36, 64); |
| memset(enq + 36, 0, 96); |
| } |
| |
| static inline void myrb_translate_devstate(void *state) |
| { |
| memcpy(state + 2, state + 3, 1); |
| memmove(state + 4, state + 5, 2); |
| memmove(state + 6, state + 8, 4); |
| } |
| |
| static inline void myrb_translate_to_rw_command(struct myrb_cmdblk *cmd_blk) |
| { |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| int ldev_num = mbox->type5.ld.ldev_num; |
| |
| mbox->bytes[3] &= 0x7; |
| mbox->bytes[3] |= mbox->bytes[7] << 6; |
| mbox->bytes[7] = ldev_num; |
| } |
| |
| static inline void myrb_translate_from_rw_command(struct myrb_cmdblk *cmd_blk) |
| { |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| int ldev_num = mbox->bytes[7]; |
| |
| mbox->bytes[7] = mbox->bytes[3] >> 6; |
| mbox->bytes[3] &= 0x7; |
| mbox->bytes[3] |= ldev_num << 3; |
| } |
| |
| static void DAC960_P_qcmd(struct myrb_hba *cb, struct myrb_cmdblk *cmd_blk) |
| { |
| void __iomem *base = cb->io_base; |
| union myrb_cmd_mbox *mbox = &cmd_blk->mbox; |
| |
| switch (mbox->common.opcode) { |
| case MYRB_CMD_ENQUIRY: |
| mbox->common.opcode = MYRB_CMD_ENQUIRY_OLD; |
| break; |
| case MYRB_CMD_GET_DEVICE_STATE: |
| mbox->common.opcode = MYRB_CMD_GET_DEVICE_STATE_OLD; |
| break; |
| case MYRB_CMD_READ: |
| mbox->common.opcode = MYRB_CMD_READ_OLD; |
| myrb_translate_to_rw_command(cmd_blk); |
| break; |
| case MYRB_CMD_WRITE: |
| mbox->common.opcode = MYRB_CMD_WRITE_OLD; |
| myrb_translate_to_rw_command(cmd_blk); |
| break; |
| case MYRB_CMD_READ_SG: |
| mbox->common.opcode = MYRB_CMD_READ_SG_OLD; |
| myrb_translate_to_rw_command(cmd_blk); |
| break; |
| case MYRB_CMD_WRITE_SG: |
| mbox->common.opcode = MYRB_CMD_WRITE_SG_OLD; |
| myrb_translate_to_rw_command(cmd_blk); |
| break; |
| default: |
| break; |
| } |
| while (DAC960_PD_hw_mbox_is_full(base)) |
| udelay(1); |
| DAC960_PD_write_cmd_mbox(base, mbox); |
| DAC960_PD_hw_mbox_new_cmd(base); |
| } |
| |
| |
| static int DAC960_P_hw_init(struct pci_dev *pdev, |
| struct myrb_hba *cb, void __iomem *base) |
| { |
| int timeout = 0; |
| unsigned char error, parm0, parm1; |
| |
| if (!request_region(cb->io_addr, 0x80, "myrb")) { |
| dev_err(&pdev->dev, "IO port 0x%lx busy\n", |
| (unsigned long)cb->io_addr); |
| return -EBUSY; |
| } |
| DAC960_PD_disable_intr(base); |
| DAC960_PD_ack_hw_mbox_status(base); |
| udelay(1000); |
| while (DAC960_PD_init_in_progress(base) && |
| timeout < MYRB_MAILBOX_TIMEOUT) { |
| if (DAC960_PD_read_error_status(base, &error, |
| &parm0, &parm1) && |
| myrb_err_status(cb, error, parm0, parm1)) |
| return -EAGAIN; |
| udelay(10); |
| timeout++; |
| } |
| if (timeout == MYRB_MAILBOX_TIMEOUT) { |
| dev_err(&pdev->dev, |
| "Timeout waiting for Controller Initialisation\n"); |
| return -ETIMEDOUT; |
| } |
| if (!myrb_enable_mmio(cb, NULL)) { |
| dev_err(&pdev->dev, |
| "Unable to allocate DMA mapped memory\n"); |
| DAC960_PD_reset_ctrl(base); |
| return -ETIMEDOUT; |
| } |
| DAC960_PD_enable_intr(base); |
| cb->qcmd = DAC960_P_qcmd; |
| cb->disable_intr = DAC960_PD_disable_intr; |
| cb->reset = DAC960_PD_reset_ctrl; |
| |
| return 0; |
| } |
| |
| static irqreturn_t DAC960_P_intr_handler(int irq, void *arg) |
| { |
| struct myrb_hba *cb = arg; |
| void __iomem *base = cb->io_base; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cb->queue_lock, flags); |
| while (DAC960_PD_hw_mbox_status_available(base)) { |
| unsigned char id = DAC960_PD_read_status_cmd_ident(base); |
| struct scsi_cmnd *scmd = NULL; |
| struct myrb_cmdblk *cmd_blk = NULL; |
| union myrb_cmd_mbox *mbox; |
| enum myrb_cmd_opcode op; |
| |
| |
| if (id == MYRB_DCMD_TAG) |
| cmd_blk = &cb->dcmd_blk; |
| else if (id == MYRB_MCMD_TAG) |
| cmd_blk = &cb->mcmd_blk; |
| else { |
| scmd = scsi_host_find_tag(cb->host, id - 3); |
| if (scmd) |
| cmd_blk = scsi_cmd_priv(scmd); |
| } |
| if (cmd_blk) |
| cmd_blk->status = DAC960_PD_read_status(base); |
| else |
| dev_err(&cb->pdev->dev, |
| "Unhandled command completion %d\n", id); |
| |
| DAC960_PD_ack_intr(base); |
| DAC960_PD_ack_hw_mbox_status(base); |
| |
| if (!cmd_blk) |
| continue; |
| |
| mbox = &cmd_blk->mbox; |
| op = mbox->common.opcode; |
| switch (op) { |
| case MYRB_CMD_ENQUIRY_OLD: |
| mbox->common.opcode = MYRB_CMD_ENQUIRY; |
| myrb_translate_enquiry(cb->enquiry); |
| break; |
| case MYRB_CMD_READ_OLD: |
| mbox->common.opcode = MYRB_CMD_READ; |
| myrb_translate_from_rw_command(cmd_blk); |
| break; |
| case MYRB_CMD_WRITE_OLD: |
| mbox->common.opcode = MYRB_CMD_WRITE; |
| myrb_translate_from_rw_command(cmd_blk); |
| break; |
| case MYRB_CMD_READ_SG_OLD: |
| mbox->common.opcode = MYRB_CMD_READ_SG; |
| myrb_translate_from_rw_command(cmd_blk); |
| break; |
| case MYRB_CMD_WRITE_SG_OLD: |
| mbox->common.opcode = MYRB_CMD_WRITE_SG; |
| myrb_translate_from_rw_command(cmd_blk); |
| break; |
| default: |
| break; |
| } |
| if (id < 3) |
| myrb_handle_cmdblk(cb, cmd_blk); |
| else |
| myrb_handle_scsi(cb, cmd_blk, scmd); |
| } |
| spin_unlock_irqrestore(&cb->queue_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| struct myrb_privdata DAC960_P_privdata = { |
| .hw_init = DAC960_P_hw_init, |
| .irq_handler = DAC960_P_intr_handler, |
| .mmio_size = DAC960_PD_mmio_size, |
| }; |
| |
| static struct myrb_hba *myrb_detect(struct pci_dev *pdev, |
| const struct pci_device_id *entry) |
| { |
| struct myrb_privdata *privdata = |
| (struct myrb_privdata *)entry->driver_data; |
| irq_handler_t irq_handler = privdata->irq_handler; |
| unsigned int mmio_size = privdata->mmio_size; |
| struct Scsi_Host *shost; |
| struct myrb_hba *cb = NULL; |
| |
| shost = scsi_host_alloc(&myrb_template, sizeof(struct myrb_hba)); |
| if (!shost) { |
| dev_err(&pdev->dev, "Unable to allocate Controller\n"); |
| return NULL; |
| } |
| shost->max_cmd_len = 12; |
| shost->max_lun = 256; |
| cb = shost_priv(shost); |
| mutex_init(&cb->dcmd_mutex); |
| mutex_init(&cb->dma_mutex); |
| cb->pdev = pdev; |
| |
| if (pci_enable_device(pdev)) |
| goto failure; |
| |
| if (privdata->hw_init == DAC960_PD_hw_init || |
| privdata->hw_init == DAC960_P_hw_init) { |
| cb->io_addr = pci_resource_start(pdev, 0); |
| cb->pci_addr = pci_resource_start(pdev, 1); |
| } else |
| cb->pci_addr = pci_resource_start(pdev, 0); |
| |
| pci_set_drvdata(pdev, cb); |
| spin_lock_init(&cb->queue_lock); |
| if (mmio_size < PAGE_SIZE) |
| mmio_size = PAGE_SIZE; |
| cb->mmio_base = ioremap(cb->pci_addr & PAGE_MASK, mmio_size); |
| if (cb->mmio_base == NULL) { |
| dev_err(&pdev->dev, |
| "Unable to map Controller Register Window\n"); |
| goto failure; |
| } |
| |
| cb->io_base = cb->mmio_base + (cb->pci_addr & ~PAGE_MASK); |
| if (privdata->hw_init(pdev, cb, cb->io_base)) |
| goto failure; |
| |
| if (request_irq(pdev->irq, irq_handler, IRQF_SHARED, "myrb", cb) < 0) { |
| dev_err(&pdev->dev, |
| "Unable to acquire IRQ Channel %d\n", pdev->irq); |
| goto failure; |
| } |
| cb->irq = pdev->irq; |
| return cb; |
| |
| failure: |
| dev_err(&pdev->dev, |
| "Failed to initialize Controller\n"); |
| myrb_cleanup(cb); |
| return NULL; |
| } |
| |
| static int myrb_probe(struct pci_dev *dev, const struct pci_device_id *entry) |
| { |
| struct myrb_hba *cb; |
| int ret; |
| |
| cb = myrb_detect(dev, entry); |
| if (!cb) |
| return -ENODEV; |
| |
| ret = myrb_get_hba_config(cb); |
| if (ret < 0) { |
| myrb_cleanup(cb); |
| return ret; |
| } |
| |
| if (!myrb_create_mempools(dev, cb)) { |
| ret = -ENOMEM; |
| goto failed; |
| } |
| |
| ret = scsi_add_host(cb->host, &dev->dev); |
| if (ret) { |
| dev_err(&dev->dev, "scsi_add_host failed with %d\n", ret); |
| myrb_destroy_mempools(cb); |
| goto failed; |
| } |
| scsi_scan_host(cb->host); |
| return 0; |
| failed: |
| myrb_cleanup(cb); |
| return ret; |
| } |
| |
| |
| static void myrb_remove(struct pci_dev *pdev) |
| { |
| struct myrb_hba *cb = pci_get_drvdata(pdev); |
| |
| shost_printk(KERN_NOTICE, cb->host, "Flushing Cache..."); |
| myrb_exec_type3(cb, MYRB_CMD_FLUSH, 0); |
| myrb_cleanup(cb); |
| myrb_destroy_mempools(cb); |
| } |
| |
| |
| static const struct pci_device_id myrb_id_table[] = { |
| { |
| PCI_DEVICE_SUB(PCI_VENDOR_ID_DEC, |
| PCI_DEVICE_ID_DEC_21285, |
| PCI_VENDOR_ID_MYLEX, |
| PCI_DEVICE_ID_MYLEX_DAC960_LA), |
| .driver_data = (unsigned long) &DAC960_LA_privdata, |
| }, |
| { |
| PCI_DEVICE_DATA(MYLEX, DAC960_PG, &DAC960_PG_privdata), |
| }, |
| { |
| PCI_DEVICE_DATA(MYLEX, DAC960_PD, &DAC960_PD_privdata), |
| }, |
| { |
| PCI_DEVICE_DATA(MYLEX, DAC960_P, &DAC960_P_privdata), |
| }, |
| {0, }, |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, myrb_id_table); |
| |
| static struct pci_driver myrb_pci_driver = { |
| .name = "myrb", |
| .id_table = myrb_id_table, |
| .probe = myrb_probe, |
| .remove = myrb_remove, |
| }; |
| |
| static int __init myrb_init_module(void) |
| { |
| int ret; |
| |
| myrb_raid_template = raid_class_attach(&myrb_raid_functions); |
| if (!myrb_raid_template) |
| return -ENODEV; |
| |
| ret = pci_register_driver(&myrb_pci_driver); |
| if (ret) |
| raid_class_release(myrb_raid_template); |
| |
| return ret; |
| } |
| |
| static void __exit myrb_cleanup_module(void) |
| { |
| pci_unregister_driver(&myrb_pci_driver); |
| raid_class_release(myrb_raid_template); |
| } |
| |
| module_init(myrb_init_module); |
| module_exit(myrb_cleanup_module); |
| |
| MODULE_DESCRIPTION("Mylex DAC960/AcceleRAID/eXtremeRAID driver (Block interface)"); |
| MODULE_AUTHOR("Hannes Reinecke <hare@suse.com>"); |
| MODULE_LICENSE("GPL"); |