diff options
author | Hannes Reinecke <hare@suse.com> | 2018-10-17 17:25:11 +0200 |
---|---|---|
committer | Martin K. Petersen <martin.petersen@oracle.com> | 2018-10-17 21:06:49 -0400 |
commit | 081ff398c56cc1052091e299aae6f7f7de680853 (patch) | |
tree | 83cdbc69b089554cb1b4a1dd46ae12cb1c71f517 | |
parent | e6760cc43e689ea29dce046c9fa1b10982e6b2b5 (diff) | |
download | linux-081ff398c56cc1052091e299aae6f7f7de680853.tar.gz linux-081ff398c56cc1052091e299aae6f7f7de680853.tar.bz2 linux-081ff398c56cc1052091e299aae6f7f7de680853.zip |
scsi: myrb: Add Mylex RAID controller (block interface)
This patch adds support for the Mylex DAC960 RAID controller,
supporting the older, block-based interface only. The driver is a
re-implementation of the original DAC960 driver.
Signed-off-by: Hannes Reinecke <hare@suse.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/scsi/Kconfig | 15 | ||||
-rw-r--r-- | drivers/scsi/Makefile | 1 | ||||
-rw-r--r-- | drivers/scsi/myrb.c | 3656 | ||||
-rw-r--r-- | drivers/scsi/myrb.h | 958 |
5 files changed, 4636 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 3594292ac295..99c7e6791519 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9892,6 +9892,12 @@ S: Supported F: drivers/gpu/drm/mxsfb/ F: Documentation/devicetree/bindings/display/mxsfb.txt +MYLEX DAC960 PCI RAID Controller +M: Hannes Reinecke <hare@kernel.org> +L: linux-scsi@vger.kernel.org +S: Supported +F: drivers/scsi/myrb.* + MYRICOM MYRI-10G 10GbE DRIVER (MYRI10GE) M: Chris Lee <christopher.lee@cspi.com> L: netdev@vger.kernel.org diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 8fc851a9e116..f2a71bb74f48 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -557,6 +557,21 @@ config SCSI_FLASHPOINT substantial, so users of MultiMaster Host Adapters may not wish to include it. +config SCSI_MYRB + tristate "Mylex DAC960/DAC1100 PCI RAID Controller (Block Interface)" + depends on PCI + select RAID_ATTRS + help + This driver adds support for the Mylex DAC960, AcceleRAID, and + eXtremeRAID PCI RAID controllers. This driver supports the + older, block based interface. + This driver is a reimplementation of the original DAC960 + driver. If you have used the DAC960 driver you should enable + this module. + + To compile this driver as a module, choose M here: the + module will be called myrb. + config VMWARE_PVSCSI tristate "VMware PVSCSI driver support" depends on PCI && SCSI && X86 diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 6d71b2a9592b..cfd58ffc0b61 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -106,6 +106,7 @@ obj-$(CONFIG_SCSI_INIA100) += a100u2w.o obj-$(CONFIG_SCSI_QLOGICPTI) += qlogicpti.o obj-$(CONFIG_SCSI_MESH) += mesh.o obj-$(CONFIG_SCSI_MAC53C94) += mac53c94.o +obj-$(CONFIG_SCSI_MYRB) += myrb.o obj-$(CONFIG_BLK_DEV_3W_XXXX_RAID) += 3w-xxxx.o obj-$(CONFIG_SCSI_3W_9XXX) += 3w-9xxx.o obj-$(CONFIG_SCSI_3W_SAS) += 3w-sas.o diff --git a/drivers/scsi/myrb.c b/drivers/scsi/myrb.c new file mode 100644 index 000000000000..aeb282f617c5 --- /dev/null +++ b/drivers/scsi/myrb.c @@ -0,0 +1,3656 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Linux Driver for Mylex DAC960/AcceleRAID/eXtremeRAID PCI RAID Controllers + * + * Copyright 2017 Hannes Reinecke, SUSE Linux GmbH <hare@suse.com> + * + * Based on the original DAC960 driver, + * Copyright 1998-2001 by Leonard N. Zubkoff <lnz@dandelion.com> + * Portions Copyright 2002 by Mylex (An IBM Business Unit) + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/raid_class.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_tcq.h> +#include "myrb.h" + +static struct raid_template *myrb_raid_template; + +static void myrb_monitor(struct work_struct *work); +static inline void myrb_translate_devstate(void *DeviceState); + +static inline int myrb_logical_channel(struct Scsi_Host *shost) +{ + return shost->max_channel - 1; +} + +static struct myrb_devstate_name_entry { + enum myrb_devstate state; + const char *name; +} myrb_devstate_name_list[] = { + { MYRB_DEVICE_DEAD, "Dead" }, + { MYRB_DEVICE_WO, "WriteOnly" }, + { MYRB_DEVICE_ONLINE, "Online" }, + { MYRB_DEVICE_CRITICAL, "Critical" }, + { MYRB_DEVICE_STANDBY, "Standby" }, + { MYRB_DEVICE_OFFLINE, "Offline" }, +}; + +static const char *myrb_devstate_name(enum myrb_devstate state) +{ + struct myrb_devstate_name_entry *entry = myrb_devstate_name_list; + int i; + + for (i = 0; i < ARRAY_SIZE(myrb_devstate_name_list); i++) { + if (entry[i].state == state) + return entry[i].name; + } + return "Unknown"; +} + +static struct myrb_raidlevel_name_entry { + enum myrb_raidlevel level; + const char *name; +} myrb_raidlevel_name_list[] = { + { MYRB_RAID_LEVEL0, "RAID0" }, + { MYRB_RAID_LEVEL1, "RAID1" }, + { MYRB_RAID_LEVEL3, "RAID3" }, + { MYRB_RAID_LEVEL5, "RAID5" }, + { MYRB_RAID_LEVEL6, "RAID6" }, + { MYRB_RAID_JBOD, "JBOD" }, +}; + +static const char *myrb_raidlevel_name(enum myrb_raidlevel level) +{ + struct myrb_raidlevel_name_entry *entry = myrb_raidlevel_name_list; + int i; + + for (i = 0; i < ARRAY_SIZE(myrb_raidlevel_name_list); i++) { + if (entry[i].level == level) + return entry[i].name; + } + return NULL; +} + +/** + * myrb_create_mempools - allocates auxiliary data structures + * + * Return: true on success, false otherwise. + */ +static bool myrb_create_mempools(struct pci_dev *pdev, struct myrb_hba *cb) +{ + size_t elem_size, elem_align; + + elem_align = sizeof(struct myrb_sge); + elem_size = cb->host->sg_tablesize * elem_align; + cb->sg_pool = dma_pool_create("myrb_sg", &pdev->dev, + elem_size, elem_align, 0); + if (cb->sg_pool == NULL) { + shost_printk(KERN_ERR, cb->host, + "Failed to allocate SG pool\n"); + return false; + } + + cb->dcdb_pool = dma_pool_create("myrb_dcdb", &pdev->dev, + sizeof(struct myrb_dcdb), + sizeof(unsigned int), 0); + if (!cb->dcdb_pool) { + dma_pool_destroy(cb->sg_pool); + cb->sg_pool = NULL; + shost_printk(KERN_ERR, cb->host, + "Failed to allocate DCDB pool\n"); + return false; + } + + snprintf(cb->work_q_name, sizeof(cb->work_q_name), + "myrb_wq_%d", cb->host->host_no); + cb->work_q = create_singlethread_workqueue(cb->work_q_name); + if (!cb->work_q) { + dma_pool_destroy(cb->dcdb_pool); + cb->dcdb_pool = NULL; + dma_pool_destroy(cb->sg_pool); + cb->sg_pool = NULL; + shost_printk(KERN_ERR, cb->host, + "Failed to create workqueue\n"); + return false; + } + + /* + * Initialize the Monitoring Timer. + */ + INIT_DELAYED_WORK(&cb->monitor_work, myrb_monitor); + queue_delayed_work(cb->work_q, &cb->monitor_work, 1); + + return true; +} + +/** + * myrb_destroy_mempools - tears down the memory pools for the controller + */ +static void myrb_destroy_mempools(struct myrb_hba *cb) +{ + cancel_delayed_work_sync(&cb->monitor_work); + destroy_workqueue(cb->work_q); + + dma_pool_destroy(cb->sg_pool); + dma_pool_destroy(cb->dcdb_pool); +} + +/** + * myrb_reset_cmd - reset command block + */ +static inline void myrb_reset_cmd(struct myrb_cmdblk *cmd_blk) +{ + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + + memset(mbox, 0, sizeof(union myrb_cmd_mbox)); + cmd_blk->status = 0; +} + +/** + * myrb_qcmd - queues command block for execution + */ +static void myrb_qcmd(struct myrb_hba *cb, struct myrb_cmdblk *cmd_blk) +{ + void __iomem *base = cb->io_base; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + union myrb_cmd_mbox *next_mbox = cb->next_cmd_mbox; + + cb->write_cmd_mbox(next_mbox, mbox); + if (cb->prev_cmd_mbox1->words[0] == 0 || + cb->prev_cmd_mbox2->words[0] == 0) + cb->get_cmd_mbox(base); + cb->prev_cmd_mbox2 = cb->prev_cmd_mbox1; + cb->prev_cmd_mbox1 = next_mbox; + if (++next_mbox > cb->last_cmd_mbox) + next_mbox = cb->first_cmd_mbox; + cb->next_cmd_mbox = next_mbox; +} + +/** + * myrb_exec_cmd - executes command block and waits for completion. + * + * Return: command status + */ +static unsigned short myrb_exec_cmd(struct myrb_hba *cb, + struct myrb_cmdblk *cmd_blk) +{ + DECLARE_COMPLETION_ONSTACK(cmpl); + unsigned long flags; + + cmd_blk->completion = &cmpl; + + spin_lock_irqsave(&cb->queue_lock, flags); + cb->qcmd(cb, cmd_blk); + spin_unlock_irqrestore(&cb->queue_lock, flags); + + WARN_ON(in_interrupt()); + wait_for_completion(&cmpl); + return cmd_blk->status; +} + +/** + * myrb_exec_type3 - executes a type 3 command and waits for completion. + * + * Return: command status + */ +static unsigned short myrb_exec_type3(struct myrb_hba *cb, + enum myrb_cmd_opcode op, dma_addr_t addr) +{ + struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + unsigned short status; + + mutex_lock(&cb->dcmd_mutex); + myrb_reset_cmd(cmd_blk); + mbox->type3.id = MYRB_DCMD_TAG; + mbox->type3.opcode = op; + mbox->type3.addr = addr; + status = myrb_exec_cmd(cb, cmd_blk); + mutex_unlock(&cb->dcmd_mutex); + return status; +} + +/** + * myrb_exec_type3D - executes a type 3D command and waits for completion. + * + * Return: command status + */ +static unsigned short myrb_exec_type3D(struct myrb_hba *cb, + enum myrb_cmd_opcode op, struct scsi_device *sdev, + struct myrb_pdev_state *pdev_info) +{ + struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + unsigned short status; + dma_addr_t pdev_info_addr; + + pdev_info_addr = dma_map_single(&cb->pdev->dev, pdev_info, + sizeof(struct myrb_pdev_state), + DMA_FROM_DEVICE); + if (dma_mapping_error(&cb->pdev->dev, pdev_info_addr)) + return MYRB_STATUS_SUBSYS_FAILED; + + mutex_lock(&cb->dcmd_mutex); + myrb_reset_cmd(cmd_blk); + mbox->type3D.id = MYRB_DCMD_TAG; + mbox->type3D.opcode = op; + mbox->type3D.channel = sdev->channel; + mbox->type3D.target = sdev->id; + mbox->type3D.addr = pdev_info_addr; + status = myrb_exec_cmd(cb, cmd_blk); + mutex_unlock(&cb->dcmd_mutex); + dma_unmap_single(&cb->pdev->dev, pdev_info_addr, + sizeof(struct myrb_pdev_state), DMA_FROM_DEVICE); + if (status == MYRB_STATUS_SUCCESS && + mbox->type3D.opcode == MYRB_CMD_GET_DEVICE_STATE_OLD) + myrb_translate_devstate(pdev_info); + + return status; +} + +static char *myrb_event_msg[] = { + "killed because write recovery failed", + "killed because of SCSI bus reset failure", + "killed because of double check condition", + "killed because it was removed", + "killed because of gross error on SCSI chip", + "killed because of bad tag returned from drive", + "killed because of timeout on SCSI command", + "killed because of reset SCSI command issued from system", + "killed because busy or parity error count exceeded limit", + "killed because of 'kill drive' command from system", + "killed because of selection timeout", + "killed due to SCSI phase sequence error", + "killed due to unknown status", +}; + +/** + * myrb_get_event - get event log from HBA + * @cb: pointer to the hba structure + * @event: number of the event + * + * Execute a type 3E command and logs the event message + */ +static void myrb_get_event(struct myrb_hba *cb, unsigned int event) +{ + struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + struct myrb_log_entry *ev_buf; + dma_addr_t ev_addr; + unsigned short status; + + ev_buf = dma_alloc_coherent(&cb->pdev->dev, + sizeof(struct myrb_log_entry), + &ev_addr, GFP_KERNEL); + if (!ev_buf) + return; + + myrb_reset_cmd(cmd_blk); + mbox->type3E.id = MYRB_MCMD_TAG; + mbox->type3E.opcode = MYRB_CMD_EVENT_LOG_OPERATION; + mbox->type3E.optype = DAC960_V1_GetEventLogEntry; + mbox->type3E.opqual = 1; + mbox->type3E.ev_seq = event; + mbox->type3E.addr = ev_addr; + status = myrb_exec_cmd(cb, cmd_blk); + if (status != MYRB_STATUS_SUCCESS) + shost_printk(KERN_INFO, cb->host, + "Failed to get event log %d, status %04x\n", + event, status); + + else if (ev_buf->seq_num == event) { + struct scsi_sense_hdr sshdr; + + memset(&sshdr, 0, sizeof(sshdr)); + scsi_normalize_sense(ev_buf->sense, 32, &sshdr); + + if (sshdr.sense_key == VENDOR_SPECIFIC && + sshdr.asc == 0x80 && + sshdr.ascq < ARRAY_SIZE(myrb_event_msg)) + shost_printk(KERN_CRIT, cb->host, + "Physical drive %d:%d: %s\n", + ev_buf->channel, ev_buf->target, + myrb_event_msg[sshdr.ascq]); + else + shost_printk(KERN_CRIT, cb->host, + "Physical drive %d:%d: Sense: %X/%02X/%02X\n", + ev_buf->channel, ev_buf->target, + sshdr.sense_key, sshdr.asc, sshdr.ascq); + } + + dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_log_entry), + ev_buf, ev_addr); +} + +/** + * myrb_get_errtable - retrieves the error table from the controller + * + * Executes a type 3 command and logs the error table from the controller. + */ +static void myrb_get_errtable(struct myrb_hba *cb) +{ + struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + unsigned short status; + struct myrb_error_entry old_table[MYRB_MAX_CHANNELS * MYRB_MAX_TARGETS]; + + memcpy(&old_table, cb->err_table, sizeof(old_table)); + + myrb_reset_cmd(cmd_blk); + mbox->type3.id = MYRB_MCMD_TAG; + mbox->type3.opcode = MYRB_CMD_GET_ERROR_TABLE; + mbox->type3.addr = cb->err_table_addr; + status = myrb_exec_cmd(cb, cmd_blk); + if (status == MYRB_STATUS_SUCCESS) { + struct myrb_error_entry *table = cb->err_table; + struct myrb_error_entry *new, *old; + size_t err_table_offset; + struct scsi_device *sdev; + + shost_for_each_device(sdev, cb->host) { + if (sdev->channel >= myrb_logical_channel(cb->host)) + continue; + err_table_offset = sdev->channel * MYRB_MAX_TARGETS + + sdev->id; + new = table + err_table_offset; + old = &old_table[err_table_offset]; + if (new->parity_err == old->parity_err && + new->soft_err == old->soft_err && + new->hard_err == old->hard_err && + new->misc_err == old->misc_err) + continue; + sdev_printk(KERN_CRIT, sdev, + "Errors: Parity = %d, Soft = %d, Hard = %d, Misc = %d\n", + new->parity_err, new->soft_err, + new->hard_err, new->misc_err); + } + } +} + +/** + * myrb_get_ldev_info - retrieves the logical device table from the controller + * + * Executes a type 3 command and updates the logical device table. + * + * Return: command status + */ +static unsigned short myrb_get_ldev_info(struct myrb_hba *cb) +{ + unsigned short status; + int ldev_num, ldev_cnt = cb->enquiry->ldev_count; + struct Scsi_Host *shost = cb->host; + + status = myrb_exec_type3(cb, MYRB_CMD_GET_LDEV_INFO, + cb->ldev_info_addr); + if (status != MYRB_STATUS_SUCCESS) + return status; + + for (ldev_num = 0; ldev_num < ldev_cnt; ldev_num++) { + struct myrb_ldev_info *old = NULL; + struct myrb_ldev_info *new = cb->ldev_info_buf + ldev_num; + struct scsi_device *sdev; + + sdev = scsi_device_lookup(shost, myrb_logical_channel(shost), + ldev_num, 0); + if (!sdev) { + if (new->state == MYRB_DEVICE_OFFLINE) + continue; + shost_printk(KERN_INFO, shost, + "Adding Logical Drive %d in state %s\n", + ldev_num, myrb_devstate_name(new->state)); + scsi_add_device(shost, myrb_logical_channel(shost), + ldev_num, 0); + continue; + } + old = sdev->hostdata; + if (new->state != old->state) + shost_printk(KERN_INFO, shost, + "Logical Drive %d is now %s\n", + ldev_num, myrb_devstate_name(new->state)); + if (new->wb_enabled != old->wb_enabled) + sdev_printk(KERN_INFO, sdev, + "Logical Drive is now WRITE %s\n", + (new->wb_enabled ? "BACK" : "THRU")); + memcpy(old, new, sizeof(*new)); + scsi_device_put(sdev); + } + return status; +} + +/** + * myrb_get_rbld_progress - get rebuild progress information + * + * Executes a type 3 command and returns the rebuild progress + * information. + * + * Return: command status + */ +static unsigned short myrb_get_rbld_progress(struct myrb_hba *cb, + struct myrb_rbld_progress *rbld) +{ + struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + struct myrb_rbld_progress *rbld_buf; + dma_addr_t rbld_addr; + unsigned short status; + + rbld_buf = dma_alloc_coherent(&cb->pdev->dev, + sizeof(struct myrb_rbld_progress), + &rbld_addr, GFP_KERNEL); + if (!rbld_buf) + return MYRB_STATUS_RBLD_NOT_CHECKED; + + myrb_reset_cmd(cmd_blk); + mbox->type3.id = MYRB_MCMD_TAG; + mbox->type3.opcode = MYRB_CMD_GET_REBUILD_PROGRESS; + mbox->type3.addr = rbld_addr; + status = myrb_exec_cmd(cb, cmd_blk); + if (rbld) + memcpy(rbld, rbld_buf, sizeof(struct myrb_rbld_progress)); + dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_rbld_progress), + rbld_buf, rbld_addr); + return status; +} + +/** + * myrb_update_rbld_progress - updates the rebuild status + * + * Updates the rebuild status for the attached logical devices. + * + */ +static void myrb_update_rbld_progress(struct myrb_hba *cb) +{ + struct myrb_rbld_progress rbld_buf; + unsigned short status; + + status = myrb_get_rbld_progress(cb, &rbld_buf); + if (status == MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS && + cb->last_rbld_status == MYRB_STATUS_SUCCESS) + status = MYRB_STATUS_RBLD_SUCCESS; + if (status != MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS) { + unsigned int blocks_done = + rbld_buf.ldev_size - rbld_buf.blocks_left; + struct scsi_device *sdev; + + sdev = scsi_device_lookup(cb->host, + myrb_logical_channel(cb->host), + rbld_buf.ldev_num, 0); + if (!sdev) + return; + + switch (status) { + case MYRB_STATUS_SUCCESS: + sdev_printk(KERN_INFO, sdev, + "Rebuild in Progress, %d%% completed\n", + (100 * (blocks_done >> 7)) + / (rbld_buf.ldev_size >> 7)); + break; + case MYRB_STATUS_RBLD_FAILED_LDEV_FAILURE: + sdev_printk(KERN_INFO, sdev, + "Rebuild Failed due to Logical Drive Failure\n"); + break; + case MYRB_STATUS_RBLD_FAILED_BADBLOCKS: + sdev_printk(KERN_INFO, sdev, + "Rebuild Failed due to Bad Blocks on Other Drives\n"); + break; + case MYRB_STATUS_RBLD_FAILED_NEW_DRIVE_FAILED: + sdev_printk(KERN_INFO, sdev, + "Rebuild Failed due to Failure of Drive Being Rebuilt\n"); + break; + case MYRB_STATUS_RBLD_SUCCESS: + sdev_printk(KERN_INFO, sdev, + "Rebuild Completed Successfully\n"); + break; + case MYRB_STATUS_RBLD_SUCCESS_TERMINATED: + sdev_printk(KERN_INFO, sdev, + "Rebuild Successfully Terminated\n"); + break; + default: + break; + } + scsi_device_put(sdev); + } + cb->last_rbld_status = status; +} + +/** + * myrb_get_cc_progress - retrieve the rebuild status + * + * Execute a type 3 Command and fetch the rebuild / consistency check + * status. + */ +static void myrb_get_cc_progress(struct myrb_hba *cb) +{ + struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + struct myrb_rbld_progress *rbld_buf; + dma_addr_t rbld_addr; + unsigned short status; + + rbld_buf = dma_alloc_coherent(&cb->pdev->dev, + sizeof(struct myrb_rbld_progress), + &rbld_addr, GFP_KERNEL); + if (!rbld_buf) { + cb->need_cc_status = true; + return; + } + myrb_reset_cmd(cmd_blk); + mbox->type3.id = MYRB_MCMD_TAG; + mbox->type3.opcode = MYRB_CMD_REBUILD_STAT; + mbox->type3.addr = rbld_addr; + status = myrb_exec_cmd(cb, cmd_blk); + if (status == MYRB_STATUS_SUCCESS) { + unsigned int ldev_num = rbld_buf->ldev_num; + unsigned int ldev_size = rbld_buf->ldev_size; + unsigned int blocks_done = + ldev_size - rbld_buf->blocks_left; + struct scsi_device *sdev; + + sdev = scsi_device_lookup(cb->host, + myrb_logical_channel(cb->host), + ldev_num, 0); + if (sdev) { + sdev_printk(KERN_INFO, sdev, + "Consistency Check in Progress: %d%% completed\n", + (100 * (blocks_done >> 7)) + / (ldev_size >> 7)); + scsi_device_put(sdev); + } + } + dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_rbld_progress), + rbld_buf, rbld_addr); +} + +/** + * myrb_bgi_control - updates background initialisation status + * + * Executes a type 3B command and updates the background initialisation status + */ +static void myrb_bgi_control(struct myrb_hba *cb) +{ + struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + struct myrb_bgi_status *bgi, *last_bgi; + dma_addr_t bgi_addr; + struct scsi_device *sdev = NULL; + unsigned short status; + + bgi = dma_alloc_coherent(&cb->pdev->dev, sizeof(struct myrb_bgi_status), + &bgi_addr, GFP_KERNEL); + if (!bgi) { + shost_printk(KERN_ERR, cb->host, + "Failed to allocate bgi memory\n"); + return; + } + myrb_reset_cmd(cmd_blk); + mbox->type3B.id = MYRB_DCMD_TAG; + mbox->type3B.opcode = MYRB_CMD_BGI_CONTROL; + mbox->type3B.optype = 0x20; + mbox->type3B.addr = bgi_addr; + status = myrb_exec_cmd(cb, cmd_blk); + last_bgi = &cb->bgi_status; + sdev = scsi_device_lookup(cb->host, + myrb_logical_channel(cb->host), + bgi->ldev_num, 0); + switch (status) { + case MYRB_STATUS_SUCCESS: + switch (bgi->status) { + case MYRB_BGI_INVALID: + break; + case MYRB_BGI_STARTED: + if (!sdev) + break; + sdev_printk(KERN_INFO, sdev, + "Background Initialization Started\n"); + break; + case MYRB_BGI_INPROGRESS: + if (!sdev) + break; + if (bgi->blocks_done == last_bgi->blocks_done && + bgi->ldev_num == last_bgi->ldev_num) + break; + sdev_printk(KERN_INFO, sdev, + "Background Initialization in Progress: %d%% completed\n", + (100 * (bgi->blocks_done >> 7)) + / (bgi->ldev_size >> 7)); + break; + case MYRB_BGI_SUSPENDED: + if (!sdev) + break; + sdev_printk(KERN_INFO, sdev, + "Background Initialization Suspended\n"); + break; + case MYRB_BGI_CANCELLED: + if (!sdev) + break; + sdev_printk(KERN_INFO, sdev, + "Background Initialization Cancelled\n"); + break; + } + memcpy(&cb->bgi_status, bgi, sizeof(struct myrb_bgi_status)); + break; + case MYRB_STATUS_BGI_SUCCESS: + if (sdev && cb->bgi_status.status == MYRB_BGI_INPROGRESS) + sdev_printk(KERN_INFO, sdev, + "Background Initialization Completed Successfully\n"); + cb->bgi_status.status = MYRB_BGI_INVALID; + break; + case MYRB_STATUS_BGI_ABORTED: + if (sdev && cb->bgi_status.status == MYRB_BGI_INPROGRESS) + sdev_printk(KERN_INFO, sdev, + "Background Initialization Aborted\n"); + /* Fallthrough */ + case MYRB_STATUS_NO_BGI_INPROGRESS: + cb->bgi_status.status = MYRB_BGI_INVALID; + break; + } + if (sdev) + scsi_device_put(sdev); + dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_bgi_status), + bgi, bgi_addr); +} + +/** + * myrb_hba_enquiry - updates the controller status + * + * Executes a DAC_V1_Enquiry command and updates the controller status. + * + * Return: command status + */ +static unsigned short myrb_hba_enquiry(struct myrb_hba *cb) +{ + struct myrb_enquiry old, *new; + unsigned short status; + + memcpy(&old, cb->enquiry, sizeof(struct myrb_enquiry)); + + status = myrb_exec_type3(cb, MYRB_CMD_ENQUIRY, cb->enquiry_addr); + if (status != MYRB_STATUS_SUCCESS) + return status; + + new = cb->enquiry; + if (new->ldev_count > old.ldev_count) { + int ldev_num = old.ldev_count - 1; + + while (++ldev_num < new->ldev_count) + shost_printk(KERN_CRIT, cb->host, + "Logical Drive %d Now Exists\n", + ldev_num); + } + if (new->ldev_count < old.ldev_count) { + int ldev_num = new->ldev_count - 1; + + while (++ldev_num < old.ldev_count) + shost_printk(KERN_CRIT, cb->host, + "Logical Drive %d No Longer Exists\n", + ldev_num); + } + if (new->status.deferred != old.status.deferred) + shost_printk(KERN_CRIT, cb->host, + "Deferred Write Error Flag is now %s\n", + (new->status.deferred ? "TRUE" : "FALSE")); + if (new->ev_seq != old.ev_seq) { + cb->new_ev_seq = new->ev_seq; + cb->need_err_info = true; + shost_printk(KERN_INFO, cb->host, + "Event log %d/%d (%d/%d) available\n", + cb->old_ev_seq, cb->new_ev_seq, + old.ev_seq, new->ev_seq); + } + if ((new->ldev_critical > 0 && + new->ldev_critical != old.ldev_critical) || + (new->ldev_offline > 0 && + new->ldev_offline != old.ldev_offline) || + (new->ldev_count != old.ldev_count)) { + shost_printk(KERN_INFO, cb->host, + "Logical drive count changed (%d/%d/%d)\n", + new->ldev_critical, + new->ldev_offline, + new->ldev_count); + cb->need_ldev_info = true; + } + if (new->pdev_dead > 0 || + new->pdev_dead != old.pdev_dead || + time_after_eq(jiffies, cb->secondary_monitor_time + + MYRB_SECONDARY_MONITOR_INTERVAL)) { + cb->need_bgi_status = cb->bgi_status_supported; + cb->secondary_monitor_time = jiffies; + } + if (new->rbld == MYRB_STDBY_RBLD_IN_PROGRESS || + new->rbld == MYRB_BG_RBLD_IN_PROGRESS || + old.rbld == MYRB_STDBY_RBLD_IN_PROGRESS || + old.rbld == MYRB_BG_RBLD_IN_PROGRESS) { + cb->need_rbld = true; + cb->rbld_first = (new->ldev_critical < old.ldev_critical); + } + if (old.rbld == MYRB_BG_CHECK_IN_PROGRESS) + switch (new->rbld) { + case MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS: + shost_printk(KERN_INFO, cb->host, + "Consistency Check Completed Successfully\n"); + break; + case MYRB_STDBY_RBLD_IN_PROGRESS: + case MYRB_BG_RBLD_IN_PROGRESS: + break; + case MYRB_BG_CHECK_IN_PROGRESS: + cb->need_cc_status = true; + break; + case MYRB_STDBY_RBLD_COMPLETED_WITH_ERROR: + shost_printk(KERN_INFO, cb->host, + "Consistency Check Completed with Error\n"); + break; + case MYRB_BG_RBLD_OR_CHECK_FAILED_DRIVE_FAILED: + shost_printk(KERN_INFO, cb->host, + "Consistency Check Failed - Physical Device Failed\n"); + break; + case MYRB_BG_RBLD_OR_CHECK_FAILED_LDEV_FAILED: + shost_printk(KERN_INFO, cb->host, + "Consistency Check Failed - Logical Drive Failed\n"); + break; + case MYRB_BG_RBLD_OR_CHECK_FAILED_OTHER: + shost_printk(KERN_INFO, cb->host, + "Consistency Check Failed - Other Causes\n"); + break; + case MYRB_BG_RBLD_OR_CHECK_SUCCESS_TERMINATED: + shost_printk(KERN_INFO, cb->host, + "Consistency Check Successfully Terminated\n"); + break; + } + else if (new->rbld == MYRB_BG_CHECK_IN_PROGRESS) + cb->need_cc_status = true; + + return MYRB_STATUS_SUCCESS; +} + +/** + * myrb_set_pdev_state - sets the device state for a physical device + * + * Return: command status + */ +static unsigned short myrb_set_pdev_state(struct myrb_hba *cb, + struct scsi_device *sdev, enum myrb_devstate state) +{ + struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk; + union myrb_cmd_mbox *mbox = &cmd_blk->mbox; + unsigned short status; + + mutex_lock(&cb->dcmd_mutex); + mbox->type3D.opcode = MYRB_CMD_START_DEVICE; + mbox->type3D.id = MYRB_DCMD_TAG; + mbox->type3D.channel = sdev->channel; + mbox->type3D.target = sdev->id; + mbox->type3D.state = state & 0x1F; + status = myrb_exec_cmd(cb, cmd_blk); + mutex_unlock(&cb->dcmd_mutex); + + return status; +} + +/** + * myrb_enable_mmio - enables the Memory Mailbox Interface + * + * PD and P controller types have no memory mailbox, but still need the + * other dma mapped memory. + * + * Return: true on success, false otherwise. + */ +static bool myrb_enable_mmio(struct myrb_hba *cb, mbox_mmio_init_t mmio_init_fn) +{ + void __iomem *base = cb->io_base; + struct pci_dev *pdev = cb->pdev; + size_t err_table_size; + size_t ldev_info_size; + union myrb_cmd_mbox *cmd_mbox_mem; + struct myrb_stat_mbox *stat_mbox_mem; + union myrb_cmd_mbox mbox; + unsigned short status; + + memset(&mbox, 0, sizeof(union myrb_cmd_mbox)); + + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) { + dev_err(&pdev->dev, "DMA mask out of range\n"); + return false; + } + + cb->enquiry = dma_alloc_coherent(&pdev->dev, + sizeof(struct myrb_enquiry), + &cb->enquiry_addr, GFP_KERNEL); + if (!cb->enquiry) + return false; + + err_table_size = sizeof(struct myrb_error_entry) * + MYRB_MAX_CHANNELS * MYRB_MAX_TARGETS; + cb->err_table = dma_alloc_coherent(&pdev->dev, err_table_size, + &cb->err_table_addr, GFP_KERNEL); + if (!cb->err_table) + return false; + + ldev_info_size = sizeof(struct myrb_ldev_info) * MYRB_MAX_LDEVS; + cb->ldev_info_buf = dma_alloc_coherent(&pdev->dev, ldev_info_size, + &cb->ldev_info_addr, GFP_KERNEL); + if (!cb->ldev_info_buf) + return false; + + /* + * Skip mailbox initialisation for PD and P Controllers + */ + if (!mmio_init_fn) + return true; + + /* These are the base addresses for the command memory mailbox array */ + cb->cmd_mbox_size = MYRB_CMD_MBOX_COUNT * sizeof(union myrb_cmd_mbox); + cb->first_cmd_mbox = dma_alloc_coherent(&pdev->dev, + cb->cmd_mbox_size, + &cb->cmd_mbox_addr, + GFP_KERNEL); + if (!cb->first_cmd_mbox) + return false; + + cmd_mbox_mem = cb->first_cmd_mbox; + cmd_mbox_mem += MYRB_CMD_MBOX_COUNT - 1; + cb->last_cmd_mbox = cmd_mbox_mem; + cb->next_cmd_mbox = cb->first_cmd_mbox; + cb->prev_cmd_mbox1 = cb->last_cmd_mbox; + cb->prev_cmd_mbox2 = cb->last_cmd_mbox - 1; + + /* These are the base addresses for the status memory mailbox array */ + cb->stat_mbox_size = MYRB_STAT_MBOX_COUNT * + sizeof(struct myrb_stat_mbox); + cb->first_stat_mbox = dma_alloc_coherent(&pdev->dev, + cb->stat_mbox_size, + &cb->stat_mbox_addr, + GFP_KERNEL); + if (!cb->first_stat_mbox) + return false; + + stat_mbox_mem = cb->first_stat_mbox; + stat_mbox_mem += MYRB_STAT_MBOX_COUNT - 1; + cb->last_stat_mbox = stat_mbox_mem; + cb->next_stat_mbox = cb->first_stat_mbox; + + /* Enable the Memory Mailbox Interface. */ + cb->dual_mode_interface = true; + mbox.typeX.opcode = 0x2B; + mbox.typeX.id = 0; + mbox.typeX.opcode2 = 0x14; + mbox.typeX.cmd_mbox_addr = cb->cmd_mbox_addr; + mbox.typeX.stat_mbox_addr = cb->stat_mbox_addr; + + status = mmio_init_fn(pdev, base, &mbox); + if (status != MYRB_STATUS_SUCCESS) { + cb->dual_mode_interface = false; + mbox.typeX.opcode2 = 0x10; + status = mmio_init_fn(pdev, base, &mbox); + if (status != MYRB_STATUS_SUCCESS) { + dev_err(&pdev->dev, + "Failed to enable mailbox, statux %02X\n", + status); + return false; + } + } + return true; +} + +/** + * myrb_get_hba_config - reads the configuration information + * + * Reads the configuration information from the controller and + * initializes the controller structure. + * + * Return: 0 on success, errno otherwise + */ +static int myrb_get_hba_config(struct myrb_hba *cb) +{ + struct myrb_enquiry2 *enquiry2; + dma_addr_t enquiry2_addr; + struct myrb_config2 *config2; + dma_addr_t config2_addr; + struct Scsi_Host *shost = cb->host; + struct pci_dev *pdev = cb->pdev; + int pchan_max = 0, pchan_cur = 0; + unsigned short status; + int ret = -ENODEV, memsize = 0; + + enquiry2 = dma_alloc_coherent(&pdev->dev, sizeof(struct myrb_enquiry2), + &enquiry2_addr, GFP_KERNEL); + if (!enquiry2) { + shost_printk(KERN_ERR, cb->host, + "Failed to allocate V1 enquiry2 memory\n"); + return -ENOMEM; + } + config2 = dma_alloc_coherent(&pdev->dev, sizeof(struct myrb_config2), + &config2_addr, GFP_KERNEL); + if (!config2) { + shost_printk(KERN_ERR, cb->host, + "Failed to allocate V1 config2 memory\n"); + dma_free_coherent(&pdev->dev, sizeof(struct myrb_enquiry2), + enquiry2, enquiry2_addr); + return -ENOMEM; + } + mutex_lock(&cb->dma_mutex); + status = myrb_hba_enquiry(cb); + mutex_unlock(&cb->dma_mutex); + if (status != MYRB_STATUS_SUCCESS) { + shost_printk(KERN_WARNING, cb->host, + "Failed it issue V1 Enquiry\n"); + goto out_free; + } + + status = myrb_exec_type3(cb, MYRB_CMD_ENQUIRY2, enquiry2_addr); + if (status != MYRB_STATUS_SUCCESS) { + shost_printk(KERN_WARNING, cb->host, + "Failed to issue V1 Enquiry2\n"); + goto out_free; + } + + status = myrb_exec_type3(cb, MYRB_CMD_READ_CONFIG2, config2_addr); + if (status != MYRB_STATUS_SUCCESS) { + shost_printk(KERN_WARNING, cb->host, + "Failed to issue ReadConfig2\n"); + goto out_free; + } + + status = myrb_get_ldev_info(cb); + if (status != MYRB_STATUS_SUCCESS) { + shost_printk(KERN_WARNING, cb->host, + "Failed to get logical drive information\n"); + goto out_free; + } + + /* + * Initialize the Controller Model Name and Full Model Name fields. + */ + switch (enquiry2->hw.sub_model) { + case DAC960_V1_P_PD_PU: + if (enquiry2->scsi_cap.bus_speed == MYRB_SCSI_SPEED_ULTRA) + strcpy(cb->model_name, "DAC960PU"); + else + strcpy(cb->model_name, "DAC960PD"); + break; + case DAC960_V1_PL: + strcpy(cb->model_name, "DAC960PL"); + break; + case DAC960_V1_PG: |