| // 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 <linux/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; | |
| } | |
| cb->work_q = alloc_ordered_workqueue("myrb_wq_%d", WQ_MEM_RECLAIM, | |
| cb->host->host_no); | |
| 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); | |
| 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, status %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), | |
| "%u.%02u-%c-%02u", | |
| 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) { | |
| if (cb->disable_intr) | |
| 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 enum scsi_qc_status myrb_pthru_queuecommand(struct Scsi_Host *shost, | |
| struct scsi_cmnd *scmd) | |
| { | |
| struct request *rq = scsi_cmd_to_rq(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); | |
| scsi_done(scmd); | |
| return 0; | |
| } | |
| mbox->type3.opcode = MYRB_CMD_DCDB; | |
| mbox->type3.id = rq->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 (rq->timeout <= 10) | |
| dcdb->timeout = MYRB_DCDB_TMO_10_SECS; | |
| else if (rq->timeout <= 60) | |
| dcdb->timeout = MYRB_DCDB_TMO_60_SECS; | |
| else if (rq->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(scmd, 0, 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 enum scsi_qc_status 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); | |
| scsi_done(scmd); | |
| return 0; | |
| } | |
| switch (scmd->cmnd[0]) { | |
| case TEST_UNIT_READY: | |
| scmd->result = (DID_OK << 16); | |
| scsi_done(scmd); | |
| return 0; | |
| case INQUIRY: | |
| if (scmd->cmnd[1] & 1) { | |
| /* Illegal request, invalid field in CDB */ | |
| scsi_build_sense(scmd, 0, ILLEGAL_REQUEST, 0x24, 0); | |
| } else { | |
| myrb_inquiry(cb, scmd); | |
| scmd->result = (DID_OK << 16); | |
| } | |
| scsi_done(scmd); | |
| return 0; | |
| case SYNCHRONIZE_CACHE: | |
| scmd->result = (DID_OK << 16); | |
| 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(scmd, 0, ILLEGAL_REQUEST, 0x24, 0); | |
| } else { | |
| myrb_mode_sense(cb, scmd, ldev_info); | |
| scmd->result = (DID_OK << 16); | |
| } | |
| 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(scmd, 0, ILLEGAL_REQUEST, 0x24, 0); | |
| scsi_done(scmd); | |
| return 0; | |
| } | |
| lba = get_unaligned_be32(&scmd->cmnd[2]); | |
| if (lba) { | |
| /* Illegal request, invalid field in CDB */ | |
| scsi_build_sense(scmd, 0, ILLEGAL_REQUEST, 0x24, 0); | |
| scsi_done(scmd); | |
| return 0; | |
| } | |
| myrb_read_capacity(cb, scmd, ldev_info); | |
| 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(scmd, 0, ILLEGAL_REQUEST, 0x24, 0); | |
| } else { | |
| /* Assume good status */ | |
| scmd->result = (DID_OK << 16); | |
| } | |
| 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(scmd, 0, DATA_PROTECT, 0x21, 0x06); | |
| 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(scmd, 0, DATA_PROTECT, 0x21, 0x06); | |
| 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(scmd, 0, DATA_PROTECT, 0x21, 0x06); | |
| 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(scmd, 0, ILLEGAL_REQUEST, 0x20, 0); | |
| scsi_done(scmd); | |
| return 0; | |
| } | |
| myrb_reset_cmd(cmd_blk); | |
| mbox->type5.id = scsi_cmd_to_rq(scmd)->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 enum scsi_qc_status 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); | |
| 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_sdev_init(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; | |
| sdev->hostdata = kzalloc_obj(*ldev_info); | |
| 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_sdev_init(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_obj(*pdev_info); | |
| 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_sdev_init(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_sdev_init(sdev); | |
| return myrb_pdev_sdev_init(sdev); | |
| } | |
| static int myrb_sdev_configure(struct scsi_device *sdev, | |
| struct queue_limits *lim) | |
| { | |
| 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_sdev_destroy(struct scsi_device *sdev) | |
| { | |
| kfree(sdev->hostdata); | |
| } | |
| static int myrb_biosparam(struct scsi_device *sdev, struct gendisk *unused, | |
| 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, 64, "%s\n", name); | |
| else | |
| ret = snprintf(buf, 64, "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, 64, "%s\n", name); | |
| else | |
| ret = snprintf(buf, 64, "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, 64, "Invalid (%02X)\n", | |
| ldev_info->state); | |
| return snprintf(buf, 64, "%s\n", name); | |
| } | |
| return snprintf(buf, 64, "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, 64, "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, 64, "not rebuilding\n"); | |
| return snprintf(buf, 64, "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, "%u\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 attribute *myrb_sdev_attrs[] = { | |
| &dev_attr_rebuild.attr, | |
| &dev_attr_consistency_check.attr, | |
| &dev_attr_raid_state.attr, | |
| &dev_attr_raid_level.attr, | |
| NULL, | |
| }; | |
| ATTRIBUTE_GROUPS(myrb_sdev); | |
| static struct attribute *myrb_shost_attrs[] = { | |
| &dev_attr_ctlr_num.attr, | |
| &dev_attr_model.attr, | |
| &dev_attr_firmware.attr, | |
| &dev_attr_flush_cache.attr, | |
| NULL, | |
| }; | |
| ATTRIBUTE_GROUPS(myrb_shost); | |
| static const struct scsi_host_template myrb_template = { | |
| .module = THIS_MODULE, | |
| .name = "DAC960", | |
| .proc_name = "myrb", | |
| .queuecommand = myrb_queuecommand, | |
| .eh_host_reset_handler = myrb_host_reset, | |
| .sdev_init = myrb_sdev_init, | |
| .sdev_configure = myrb_sdev_configure, | |
| .sdev_destroy = myrb_sdev_destroy, | |
| .bios_param = myrb_biosparam, | |
| .cmd_size = sizeof(struct myrb_cmdblk), | |
| .shost_groups = myrb_shost_groups, | |
| .sdev_groups = myrb_sdev_groups, | |
| .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); | |
| } | |
| static 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(scmd, 0, MEDIUM_ERROR, 0x11, 0); | |
| else | |
| /* Write error */ | |
| scsi_build_sense(scmd, 0, MEDIUM_ERROR, 0x0C, 0); | |
| 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(scmd, 0, MEDIUM_ERROR, 0x11, 0x04); | |
| else | |
| /* Write error, auto-reallocation failed */ | |
| scsi_build_sense(scmd, 0, MEDIUM_ERROR, 0x0C, 0x02); | |
| 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(scmd, 0, 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; | |
| } | |
| 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. | |
| */ | |
| static 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_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_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 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 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 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); | |
| 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; | |
| } | |
| static 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_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_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 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 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 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; | |
| } | |
| static 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_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 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; | |
| } | |
| static 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; | |
| } | |
| static 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; | |
| cb->host = shost; | |
| if (pci_enable_device(pdev)) { | |
| dev_err(&pdev->dev, "Failed to enable PCI device\n"); | |
| scsi_host_put(shost); | |
| return NULL; | |
| } | |
| 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"); |