mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-27 22:24:11 +08:00
1197c5b209
The myrb and myrs drivers use an odd way of implementing their sysfs files, calling snprintf() with a fixed length of 32 bytes to print into a page sized buffer. One of the strings is actually longer than 32 bytes, which clang can warn about: drivers/scsi/myrb.c:1906:10: error: 'snprintf' will always be truncated; specified size is 32, but format string expands to at least 34 [-Werror,-Wformat-truncation] drivers/scsi/myrs.c:1089:10: error: 'snprintf' will always be truncated; specified size is 32, but format string expands to at least 34 [-Werror,-Wformat-truncation] These could all be plain sprintf() without a length as the buffer is always long enough. On the other hand, sysfs files should not be overly long either, so just double the length to make sure the longest strings don't get truncated here. Fixes:7726618639
("scsi: myrs: Add Mylex RAID controller (SCSI interface)") Fixes:081ff398c5
("scsi: myrb: Add Mylex RAID controller (block interface)") Signed-off-by: Arnd Bergmann <arnd@arndb.de> Link: https://lore.kernel.org/r/20240326223825.4084412-8-arnd@kernel.org Reviewed-by: Hannes Reinecke <hare@suse.de> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
3563 lines
94 KiB
C
3563 lines
94 KiB
C
// 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);
|
|
|
|
wait_for_completion(&cmpl);
|
|
return cmd_blk->status;
|
|
}
|
|
|
|
/*
|
|
* myrb_exec_type3 - executes a type 3 command and waits for completion.
|
|
*
|
|
* Return: command status
|
|
*/
|
|
static unsigned short myrb_exec_type3(struct myrb_hba *cb,
|
|
enum myrb_cmd_opcode op, dma_addr_t addr)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
unsigned short status;
|
|
|
|
mutex_lock(&cb->dcmd_mutex);
|
|
myrb_reset_cmd(cmd_blk);
|
|
mbox->type3.id = MYRB_DCMD_TAG;
|
|
mbox->type3.opcode = op;
|
|
mbox->type3.addr = addr;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
mutex_unlock(&cb->dcmd_mutex);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* myrb_exec_type3D - executes a type 3D command and waits for completion.
|
|
*
|
|
* Return: command status
|
|
*/
|
|
static unsigned short myrb_exec_type3D(struct myrb_hba *cb,
|
|
enum myrb_cmd_opcode op, struct scsi_device *sdev,
|
|
struct myrb_pdev_state *pdev_info)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
unsigned short status;
|
|
dma_addr_t pdev_info_addr;
|
|
|
|
pdev_info_addr = dma_map_single(&cb->pdev->dev, pdev_info,
|
|
sizeof(struct myrb_pdev_state),
|
|
DMA_FROM_DEVICE);
|
|
if (dma_mapping_error(&cb->pdev->dev, pdev_info_addr))
|
|
return MYRB_STATUS_SUBSYS_FAILED;
|
|
|
|
mutex_lock(&cb->dcmd_mutex);
|
|
myrb_reset_cmd(cmd_blk);
|
|
mbox->type3D.id = MYRB_DCMD_TAG;
|
|
mbox->type3D.opcode = op;
|
|
mbox->type3D.channel = sdev->channel;
|
|
mbox->type3D.target = sdev->id;
|
|
mbox->type3D.addr = pdev_info_addr;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
mutex_unlock(&cb->dcmd_mutex);
|
|
dma_unmap_single(&cb->pdev->dev, pdev_info_addr,
|
|
sizeof(struct myrb_pdev_state), DMA_FROM_DEVICE);
|
|
if (status == MYRB_STATUS_SUCCESS &&
|
|
mbox->type3D.opcode == MYRB_CMD_GET_DEVICE_STATE_OLD)
|
|
myrb_translate_devstate(pdev_info);
|
|
|
|
return status;
|
|
}
|
|
|
|
static char *myrb_event_msg[] = {
|
|
"killed because write recovery failed",
|
|
"killed because of SCSI bus reset failure",
|
|
"killed because of double check condition",
|
|
"killed because it was removed",
|
|
"killed because of gross error on SCSI chip",
|
|
"killed because of bad tag returned from drive",
|
|
"killed because of timeout on SCSI command",
|
|
"killed because of reset SCSI command issued from system",
|
|
"killed because busy or parity error count exceeded limit",
|
|
"killed because of 'kill drive' command from system",
|
|
"killed because of selection timeout",
|
|
"killed due to SCSI phase sequence error",
|
|
"killed due to unknown status",
|
|
};
|
|
|
|
/**
|
|
* myrb_get_event - get event log from HBA
|
|
* @cb: pointer to the hba structure
|
|
* @event: number of the event
|
|
*
|
|
* Execute a type 3E command and logs the event message
|
|
*/
|
|
static void myrb_get_event(struct myrb_hba *cb, unsigned int event)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
struct myrb_log_entry *ev_buf;
|
|
dma_addr_t ev_addr;
|
|
unsigned short status;
|
|
|
|
ev_buf = dma_alloc_coherent(&cb->pdev->dev,
|
|
sizeof(struct myrb_log_entry),
|
|
&ev_addr, GFP_KERNEL);
|
|
if (!ev_buf)
|
|
return;
|
|
|
|
myrb_reset_cmd(cmd_blk);
|
|
mbox->type3E.id = MYRB_MCMD_TAG;
|
|
mbox->type3E.opcode = MYRB_CMD_EVENT_LOG_OPERATION;
|
|
mbox->type3E.optype = DAC960_V1_GetEventLogEntry;
|
|
mbox->type3E.opqual = 1;
|
|
mbox->type3E.ev_seq = event;
|
|
mbox->type3E.addr = ev_addr;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
if (status != MYRB_STATUS_SUCCESS)
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Failed to get event log %d, status %04x\n",
|
|
event, status);
|
|
|
|
else if (ev_buf->seq_num == event) {
|
|
struct scsi_sense_hdr sshdr;
|
|
|
|
memset(&sshdr, 0, sizeof(sshdr));
|
|
scsi_normalize_sense(ev_buf->sense, 32, &sshdr);
|
|
|
|
if (sshdr.sense_key == VENDOR_SPECIFIC &&
|
|
sshdr.asc == 0x80 &&
|
|
sshdr.ascq < ARRAY_SIZE(myrb_event_msg))
|
|
shost_printk(KERN_CRIT, cb->host,
|
|
"Physical drive %d:%d: %s\n",
|
|
ev_buf->channel, ev_buf->target,
|
|
myrb_event_msg[sshdr.ascq]);
|
|
else
|
|
shost_printk(KERN_CRIT, cb->host,
|
|
"Physical drive %d:%d: Sense: %X/%02X/%02X\n",
|
|
ev_buf->channel, ev_buf->target,
|
|
sshdr.sense_key, sshdr.asc, sshdr.ascq);
|
|
}
|
|
|
|
dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_log_entry),
|
|
ev_buf, ev_addr);
|
|
}
|
|
|
|
/*
|
|
* myrb_get_errtable - retrieves the error table from the controller
|
|
*
|
|
* Executes a type 3 command and logs the error table from the controller.
|
|
*/
|
|
static void myrb_get_errtable(struct myrb_hba *cb)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
unsigned short status;
|
|
struct myrb_error_entry old_table[MYRB_MAX_CHANNELS * MYRB_MAX_TARGETS];
|
|
|
|
memcpy(&old_table, cb->err_table, sizeof(old_table));
|
|
|
|
myrb_reset_cmd(cmd_blk);
|
|
mbox->type3.id = MYRB_MCMD_TAG;
|
|
mbox->type3.opcode = MYRB_CMD_GET_ERROR_TABLE;
|
|
mbox->type3.addr = cb->err_table_addr;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
if (status == MYRB_STATUS_SUCCESS) {
|
|
struct myrb_error_entry *table = cb->err_table;
|
|
struct myrb_error_entry *new, *old;
|
|
size_t err_table_offset;
|
|
struct scsi_device *sdev;
|
|
|
|
shost_for_each_device(sdev, cb->host) {
|
|
if (sdev->channel >= myrb_logical_channel(cb->host))
|
|
continue;
|
|
err_table_offset = sdev->channel * MYRB_MAX_TARGETS
|
|
+ sdev->id;
|
|
new = table + err_table_offset;
|
|
old = &old_table[err_table_offset];
|
|
if (new->parity_err == old->parity_err &&
|
|
new->soft_err == old->soft_err &&
|
|
new->hard_err == old->hard_err &&
|
|
new->misc_err == old->misc_err)
|
|
continue;
|
|
sdev_printk(KERN_CRIT, sdev,
|
|
"Errors: Parity = %d, Soft = %d, Hard = %d, Misc = %d\n",
|
|
new->parity_err, new->soft_err,
|
|
new->hard_err, new->misc_err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* myrb_get_ldev_info - retrieves the logical device table from the controller
|
|
*
|
|
* Executes a type 3 command and updates the logical device table.
|
|
*
|
|
* Return: command status
|
|
*/
|
|
static unsigned short myrb_get_ldev_info(struct myrb_hba *cb)
|
|
{
|
|
unsigned short status;
|
|
int ldev_num, ldev_cnt = cb->enquiry->ldev_count;
|
|
struct Scsi_Host *shost = cb->host;
|
|
|
|
status = myrb_exec_type3(cb, MYRB_CMD_GET_LDEV_INFO,
|
|
cb->ldev_info_addr);
|
|
if (status != MYRB_STATUS_SUCCESS)
|
|
return status;
|
|
|
|
for (ldev_num = 0; ldev_num < ldev_cnt; ldev_num++) {
|
|
struct myrb_ldev_info *old = NULL;
|
|
struct myrb_ldev_info *new = cb->ldev_info_buf + ldev_num;
|
|
struct scsi_device *sdev;
|
|
|
|
sdev = scsi_device_lookup(shost, myrb_logical_channel(shost),
|
|
ldev_num, 0);
|
|
if (!sdev) {
|
|
if (new->state == MYRB_DEVICE_OFFLINE)
|
|
continue;
|
|
shost_printk(KERN_INFO, shost,
|
|
"Adding Logical Drive %d in state %s\n",
|
|
ldev_num, myrb_devstate_name(new->state));
|
|
scsi_add_device(shost, myrb_logical_channel(shost),
|
|
ldev_num, 0);
|
|
continue;
|
|
}
|
|
old = sdev->hostdata;
|
|
if (new->state != old->state)
|
|
shost_printk(KERN_INFO, shost,
|
|
"Logical Drive %d is now %s\n",
|
|
ldev_num, myrb_devstate_name(new->state));
|
|
if (new->wb_enabled != old->wb_enabled)
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Logical Drive is now WRITE %s\n",
|
|
(new->wb_enabled ? "BACK" : "THRU"));
|
|
memcpy(old, new, sizeof(*new));
|
|
scsi_device_put(sdev);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* myrb_get_rbld_progress - get rebuild progress information
|
|
*
|
|
* Executes a type 3 command and returns the rebuild progress
|
|
* information.
|
|
*
|
|
* Return: command status
|
|
*/
|
|
static unsigned short myrb_get_rbld_progress(struct myrb_hba *cb,
|
|
struct myrb_rbld_progress *rbld)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
struct myrb_rbld_progress *rbld_buf;
|
|
dma_addr_t rbld_addr;
|
|
unsigned short status;
|
|
|
|
rbld_buf = dma_alloc_coherent(&cb->pdev->dev,
|
|
sizeof(struct myrb_rbld_progress),
|
|
&rbld_addr, GFP_KERNEL);
|
|
if (!rbld_buf)
|
|
return MYRB_STATUS_RBLD_NOT_CHECKED;
|
|
|
|
myrb_reset_cmd(cmd_blk);
|
|
mbox->type3.id = MYRB_MCMD_TAG;
|
|
mbox->type3.opcode = MYRB_CMD_GET_REBUILD_PROGRESS;
|
|
mbox->type3.addr = rbld_addr;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
if (rbld)
|
|
memcpy(rbld, rbld_buf, sizeof(struct myrb_rbld_progress));
|
|
dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_rbld_progress),
|
|
rbld_buf, rbld_addr);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* myrb_update_rbld_progress - updates the rebuild status
|
|
*
|
|
* Updates the rebuild status for the attached logical devices.
|
|
*/
|
|
static void myrb_update_rbld_progress(struct myrb_hba *cb)
|
|
{
|
|
struct myrb_rbld_progress rbld_buf;
|
|
unsigned short status;
|
|
|
|
status = myrb_get_rbld_progress(cb, &rbld_buf);
|
|
if (status == MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS &&
|
|
cb->last_rbld_status == MYRB_STATUS_SUCCESS)
|
|
status = MYRB_STATUS_RBLD_SUCCESS;
|
|
if (status != MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS) {
|
|
unsigned int blocks_done =
|
|
rbld_buf.ldev_size - rbld_buf.blocks_left;
|
|
struct scsi_device *sdev;
|
|
|
|
sdev = scsi_device_lookup(cb->host,
|
|
myrb_logical_channel(cb->host),
|
|
rbld_buf.ldev_num, 0);
|
|
if (!sdev)
|
|
return;
|
|
|
|
switch (status) {
|
|
case MYRB_STATUS_SUCCESS:
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Rebuild in Progress, %d%% completed\n",
|
|
(100 * (blocks_done >> 7))
|
|
/ (rbld_buf.ldev_size >> 7));
|
|
break;
|
|
case MYRB_STATUS_RBLD_FAILED_LDEV_FAILURE:
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Rebuild Failed due to Logical Drive Failure\n");
|
|
break;
|
|
case MYRB_STATUS_RBLD_FAILED_BADBLOCKS:
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Rebuild Failed due to Bad Blocks on Other Drives\n");
|
|
break;
|
|
case MYRB_STATUS_RBLD_FAILED_NEW_DRIVE_FAILED:
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Rebuild Failed due to Failure of Drive Being Rebuilt\n");
|
|
break;
|
|
case MYRB_STATUS_RBLD_SUCCESS:
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Rebuild Completed Successfully\n");
|
|
break;
|
|
case MYRB_STATUS_RBLD_SUCCESS_TERMINATED:
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Rebuild Successfully Terminated\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
scsi_device_put(sdev);
|
|
}
|
|
cb->last_rbld_status = status;
|
|
}
|
|
|
|
/*
|
|
* myrb_get_cc_progress - retrieve the rebuild status
|
|
*
|
|
* Execute a type 3 Command and fetch the rebuild / consistency check
|
|
* status.
|
|
*/
|
|
static void myrb_get_cc_progress(struct myrb_hba *cb)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
struct myrb_rbld_progress *rbld_buf;
|
|
dma_addr_t rbld_addr;
|
|
unsigned short status;
|
|
|
|
rbld_buf = dma_alloc_coherent(&cb->pdev->dev,
|
|
sizeof(struct myrb_rbld_progress),
|
|
&rbld_addr, GFP_KERNEL);
|
|
if (!rbld_buf) {
|
|
cb->need_cc_status = true;
|
|
return;
|
|
}
|
|
myrb_reset_cmd(cmd_blk);
|
|
mbox->type3.id = MYRB_MCMD_TAG;
|
|
mbox->type3.opcode = MYRB_CMD_REBUILD_STAT;
|
|
mbox->type3.addr = rbld_addr;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
if (status == MYRB_STATUS_SUCCESS) {
|
|
unsigned int ldev_num = rbld_buf->ldev_num;
|
|
unsigned int ldev_size = rbld_buf->ldev_size;
|
|
unsigned int blocks_done =
|
|
ldev_size - rbld_buf->blocks_left;
|
|
struct scsi_device *sdev;
|
|
|
|
sdev = scsi_device_lookup(cb->host,
|
|
myrb_logical_channel(cb->host),
|
|
ldev_num, 0);
|
|
if (sdev) {
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Consistency Check in Progress: %d%% completed\n",
|
|
(100 * (blocks_done >> 7))
|
|
/ (ldev_size >> 7));
|
|
scsi_device_put(sdev);
|
|
}
|
|
}
|
|
dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_rbld_progress),
|
|
rbld_buf, rbld_addr);
|
|
}
|
|
|
|
/*
|
|
* myrb_bgi_control - updates background initialisation status
|
|
*
|
|
* Executes a type 3B command and updates the background initialisation status
|
|
*/
|
|
static void myrb_bgi_control(struct myrb_hba *cb)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->mcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
struct myrb_bgi_status *bgi, *last_bgi;
|
|
dma_addr_t bgi_addr;
|
|
struct scsi_device *sdev = NULL;
|
|
unsigned short status;
|
|
|
|
bgi = dma_alloc_coherent(&cb->pdev->dev, sizeof(struct myrb_bgi_status),
|
|
&bgi_addr, GFP_KERNEL);
|
|
if (!bgi) {
|
|
shost_printk(KERN_ERR, cb->host,
|
|
"Failed to allocate bgi memory\n");
|
|
return;
|
|
}
|
|
myrb_reset_cmd(cmd_blk);
|
|
mbox->type3B.id = MYRB_DCMD_TAG;
|
|
mbox->type3B.opcode = MYRB_CMD_BGI_CONTROL;
|
|
mbox->type3B.optype = 0x20;
|
|
mbox->type3B.addr = bgi_addr;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
last_bgi = &cb->bgi_status;
|
|
sdev = scsi_device_lookup(cb->host,
|
|
myrb_logical_channel(cb->host),
|
|
bgi->ldev_num, 0);
|
|
switch (status) {
|
|
case MYRB_STATUS_SUCCESS:
|
|
switch (bgi->status) {
|
|
case MYRB_BGI_INVALID:
|
|
break;
|
|
case MYRB_BGI_STARTED:
|
|
if (!sdev)
|
|
break;
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Background Initialization Started\n");
|
|
break;
|
|
case MYRB_BGI_INPROGRESS:
|
|
if (!sdev)
|
|
break;
|
|
if (bgi->blocks_done == last_bgi->blocks_done &&
|
|
bgi->ldev_num == last_bgi->ldev_num)
|
|
break;
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Background Initialization in Progress: %d%% completed\n",
|
|
(100 * (bgi->blocks_done >> 7))
|
|
/ (bgi->ldev_size >> 7));
|
|
break;
|
|
case MYRB_BGI_SUSPENDED:
|
|
if (!sdev)
|
|
break;
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Background Initialization Suspended\n");
|
|
break;
|
|
case MYRB_BGI_CANCELLED:
|
|
if (!sdev)
|
|
break;
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Background Initialization Cancelled\n");
|
|
break;
|
|
}
|
|
memcpy(&cb->bgi_status, bgi, sizeof(struct myrb_bgi_status));
|
|
break;
|
|
case MYRB_STATUS_BGI_SUCCESS:
|
|
if (sdev && cb->bgi_status.status == MYRB_BGI_INPROGRESS)
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Background Initialization Completed Successfully\n");
|
|
cb->bgi_status.status = MYRB_BGI_INVALID;
|
|
break;
|
|
case MYRB_STATUS_BGI_ABORTED:
|
|
if (sdev && cb->bgi_status.status == MYRB_BGI_INPROGRESS)
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Background Initialization Aborted\n");
|
|
fallthrough;
|
|
case MYRB_STATUS_NO_BGI_INPROGRESS:
|
|
cb->bgi_status.status = MYRB_BGI_INVALID;
|
|
break;
|
|
}
|
|
if (sdev)
|
|
scsi_device_put(sdev);
|
|
dma_free_coherent(&cb->pdev->dev, sizeof(struct myrb_bgi_status),
|
|
bgi, bgi_addr);
|
|
}
|
|
|
|
/*
|
|
* myrb_hba_enquiry - updates the controller status
|
|
*
|
|
* Executes a DAC_V1_Enquiry command and updates the controller status.
|
|
*
|
|
* Return: command status
|
|
*/
|
|
static unsigned short myrb_hba_enquiry(struct myrb_hba *cb)
|
|
{
|
|
struct myrb_enquiry old, *new;
|
|
unsigned short status;
|
|
|
|
memcpy(&old, cb->enquiry, sizeof(struct myrb_enquiry));
|
|
|
|
status = myrb_exec_type3(cb, MYRB_CMD_ENQUIRY, cb->enquiry_addr);
|
|
if (status != MYRB_STATUS_SUCCESS)
|
|
return status;
|
|
|
|
new = cb->enquiry;
|
|
if (new->ldev_count > old.ldev_count) {
|
|
int ldev_num = old.ldev_count - 1;
|
|
|
|
while (++ldev_num < new->ldev_count)
|
|
shost_printk(KERN_CRIT, cb->host,
|
|
"Logical Drive %d Now Exists\n",
|
|
ldev_num);
|
|
}
|
|
if (new->ldev_count < old.ldev_count) {
|
|
int ldev_num = new->ldev_count - 1;
|
|
|
|
while (++ldev_num < old.ldev_count)
|
|
shost_printk(KERN_CRIT, cb->host,
|
|
"Logical Drive %d No Longer Exists\n",
|
|
ldev_num);
|
|
}
|
|
if (new->status.deferred != old.status.deferred)
|
|
shost_printk(KERN_CRIT, cb->host,
|
|
"Deferred Write Error Flag is now %s\n",
|
|
(new->status.deferred ? "TRUE" : "FALSE"));
|
|
if (new->ev_seq != old.ev_seq) {
|
|
cb->new_ev_seq = new->ev_seq;
|
|
cb->need_err_info = true;
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Event log %d/%d (%d/%d) available\n",
|
|
cb->old_ev_seq, cb->new_ev_seq,
|
|
old.ev_seq, new->ev_seq);
|
|
}
|
|
if ((new->ldev_critical > 0 &&
|
|
new->ldev_critical != old.ldev_critical) ||
|
|
(new->ldev_offline > 0 &&
|
|
new->ldev_offline != old.ldev_offline) ||
|
|
(new->ldev_count != old.ldev_count)) {
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Logical drive count changed (%d/%d/%d)\n",
|
|
new->ldev_critical,
|
|
new->ldev_offline,
|
|
new->ldev_count);
|
|
cb->need_ldev_info = true;
|
|
}
|
|
if (new->pdev_dead > 0 ||
|
|
new->pdev_dead != old.pdev_dead ||
|
|
time_after_eq(jiffies, cb->secondary_monitor_time
|
|
+ MYRB_SECONDARY_MONITOR_INTERVAL)) {
|
|
cb->need_bgi_status = cb->bgi_status_supported;
|
|
cb->secondary_monitor_time = jiffies;
|
|
}
|
|
if (new->rbld == MYRB_STDBY_RBLD_IN_PROGRESS ||
|
|
new->rbld == MYRB_BG_RBLD_IN_PROGRESS ||
|
|
old.rbld == MYRB_STDBY_RBLD_IN_PROGRESS ||
|
|
old.rbld == MYRB_BG_RBLD_IN_PROGRESS) {
|
|
cb->need_rbld = true;
|
|
cb->rbld_first = (new->ldev_critical < old.ldev_critical);
|
|
}
|
|
if (old.rbld == MYRB_BG_CHECK_IN_PROGRESS)
|
|
switch (new->rbld) {
|
|
case MYRB_NO_STDBY_RBLD_OR_CHECK_IN_PROGRESS:
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Consistency Check Completed Successfully\n");
|
|
break;
|
|
case MYRB_STDBY_RBLD_IN_PROGRESS:
|
|
case MYRB_BG_RBLD_IN_PROGRESS:
|
|
break;
|
|
case MYRB_BG_CHECK_IN_PROGRESS:
|
|
cb->need_cc_status = true;
|
|
break;
|
|
case MYRB_STDBY_RBLD_COMPLETED_WITH_ERROR:
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Consistency Check Completed with Error\n");
|
|
break;
|
|
case MYRB_BG_RBLD_OR_CHECK_FAILED_DRIVE_FAILED:
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Consistency Check Failed - Physical Device Failed\n");
|
|
break;
|
|
case MYRB_BG_RBLD_OR_CHECK_FAILED_LDEV_FAILED:
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Consistency Check Failed - Logical Drive Failed\n");
|
|
break;
|
|
case MYRB_BG_RBLD_OR_CHECK_FAILED_OTHER:
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Consistency Check Failed - Other Causes\n");
|
|
break;
|
|
case MYRB_BG_RBLD_OR_CHECK_SUCCESS_TERMINATED:
|
|
shost_printk(KERN_INFO, cb->host,
|
|
"Consistency Check Successfully Terminated\n");
|
|
break;
|
|
}
|
|
else if (new->rbld == MYRB_BG_CHECK_IN_PROGRESS)
|
|
cb->need_cc_status = true;
|
|
|
|
return MYRB_STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* myrb_set_pdev_state - sets the device state for a physical device
|
|
*
|
|
* Return: command status
|
|
*/
|
|
static unsigned short myrb_set_pdev_state(struct myrb_hba *cb,
|
|
struct scsi_device *sdev, enum myrb_devstate state)
|
|
{
|
|
struct myrb_cmdblk *cmd_blk = &cb->dcmd_blk;
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
unsigned short status;
|
|
|
|
mutex_lock(&cb->dcmd_mutex);
|
|
mbox->type3D.opcode = MYRB_CMD_START_DEVICE;
|
|
mbox->type3D.id = MYRB_DCMD_TAG;
|
|
mbox->type3D.channel = sdev->channel;
|
|
mbox->type3D.target = sdev->id;
|
|
mbox->type3D.state = state & 0x1F;
|
|
status = myrb_exec_cmd(cb, cmd_blk);
|
|
mutex_unlock(&cb->dcmd_mutex);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* myrb_enable_mmio - enables the Memory Mailbox Interface
|
|
*
|
|
* PD and P controller types have no memory mailbox, but still need the
|
|
* other dma mapped memory.
|
|
*
|
|
* Return: true on success, false otherwise.
|
|
*/
|
|
static bool myrb_enable_mmio(struct myrb_hba *cb, mbox_mmio_init_t mmio_init_fn)
|
|
{
|
|
void __iomem *base = cb->io_base;
|
|
struct pci_dev *pdev = cb->pdev;
|
|
size_t err_table_size;
|
|
size_t ldev_info_size;
|
|
union myrb_cmd_mbox *cmd_mbox_mem;
|
|
struct myrb_stat_mbox *stat_mbox_mem;
|
|
union myrb_cmd_mbox mbox;
|
|
unsigned short status;
|
|
|
|
memset(&mbox, 0, sizeof(union myrb_cmd_mbox));
|
|
|
|
if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) {
|
|
dev_err(&pdev->dev, "DMA mask out of range\n");
|
|
return false;
|
|
}
|
|
|
|
cb->enquiry = dma_alloc_coherent(&pdev->dev,
|
|
sizeof(struct myrb_enquiry),
|
|
&cb->enquiry_addr, GFP_KERNEL);
|
|
if (!cb->enquiry)
|
|
return false;
|
|
|
|
err_table_size = sizeof(struct myrb_error_entry) *
|
|
MYRB_MAX_CHANNELS * MYRB_MAX_TARGETS;
|
|
cb->err_table = dma_alloc_coherent(&pdev->dev, err_table_size,
|
|
&cb->err_table_addr, GFP_KERNEL);
|
|
if (!cb->err_table)
|
|
return false;
|
|
|
|
ldev_info_size = sizeof(struct myrb_ldev_info) * MYRB_MAX_LDEVS;
|
|
cb->ldev_info_buf = dma_alloc_coherent(&pdev->dev, ldev_info_size,
|
|
&cb->ldev_info_addr, GFP_KERNEL);
|
|
if (!cb->ldev_info_buf)
|
|
return false;
|
|
|
|
/*
|
|
* Skip mailbox initialisation for PD and P Controllers
|
|
*/
|
|
if (!mmio_init_fn)
|
|
return true;
|
|
|
|
/* These are the base addresses for the command memory mailbox array */
|
|
cb->cmd_mbox_size = MYRB_CMD_MBOX_COUNT * sizeof(union myrb_cmd_mbox);
|
|
cb->first_cmd_mbox = dma_alloc_coherent(&pdev->dev,
|
|
cb->cmd_mbox_size,
|
|
&cb->cmd_mbox_addr,
|
|
GFP_KERNEL);
|
|
if (!cb->first_cmd_mbox)
|
|
return false;
|
|
|
|
cmd_mbox_mem = cb->first_cmd_mbox;
|
|
cmd_mbox_mem += MYRB_CMD_MBOX_COUNT - 1;
|
|
cb->last_cmd_mbox = cmd_mbox_mem;
|
|
cb->next_cmd_mbox = cb->first_cmd_mbox;
|
|
cb->prev_cmd_mbox1 = cb->last_cmd_mbox;
|
|
cb->prev_cmd_mbox2 = cb->last_cmd_mbox - 1;
|
|
|
|
/* These are the base addresses for the status memory mailbox array */
|
|
cb->stat_mbox_size = MYRB_STAT_MBOX_COUNT *
|
|
sizeof(struct myrb_stat_mbox);
|
|
cb->first_stat_mbox = dma_alloc_coherent(&pdev->dev,
|
|
cb->stat_mbox_size,
|
|
&cb->stat_mbox_addr,
|
|
GFP_KERNEL);
|
|
if (!cb->first_stat_mbox)
|
|
return false;
|
|
|
|
stat_mbox_mem = cb->first_stat_mbox;
|
|
stat_mbox_mem += MYRB_STAT_MBOX_COUNT - 1;
|
|
cb->last_stat_mbox = stat_mbox_mem;
|
|
cb->next_stat_mbox = cb->first_stat_mbox;
|
|
|
|
/* Enable the Memory Mailbox Interface. */
|
|
cb->dual_mode_interface = true;
|
|
mbox.typeX.opcode = 0x2B;
|
|
mbox.typeX.id = 0;
|
|
mbox.typeX.opcode2 = 0x14;
|
|
mbox.typeX.cmd_mbox_addr = cb->cmd_mbox_addr;
|
|
mbox.typeX.stat_mbox_addr = cb->stat_mbox_addr;
|
|
|
|
status = mmio_init_fn(pdev, base, &mbox);
|
|
if (status != MYRB_STATUS_SUCCESS) {
|
|
cb->dual_mode_interface = false;
|
|
mbox.typeX.opcode2 = 0x10;
|
|
status = mmio_init_fn(pdev, base, &mbox);
|
|
if (status != MYRB_STATUS_SUCCESS) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to enable mailbox, statux %02X\n",
|
|
status);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* myrb_get_hba_config - reads the configuration information
|
|
*
|
|
* Reads the configuration information from the controller and
|
|
* initializes the controller structure.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
static int myrb_get_hba_config(struct myrb_hba *cb)
|
|
{
|
|
struct myrb_enquiry2 *enquiry2;
|
|
dma_addr_t enquiry2_addr;
|
|
struct myrb_config2 *config2;
|
|
dma_addr_t config2_addr;
|
|
struct Scsi_Host *shost = cb->host;
|
|
struct pci_dev *pdev = cb->pdev;
|
|
int pchan_max = 0, pchan_cur = 0;
|
|
unsigned short status;
|
|
int ret = -ENODEV, memsize = 0;
|
|
|
|
enquiry2 = dma_alloc_coherent(&pdev->dev, sizeof(struct myrb_enquiry2),
|
|
&enquiry2_addr, GFP_KERNEL);
|
|
if (!enquiry2) {
|
|
shost_printk(KERN_ERR, cb->host,
|
|
"Failed to allocate V1 enquiry2 memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
config2 = dma_alloc_coherent(&pdev->dev, sizeof(struct myrb_config2),
|
|
&config2_addr, GFP_KERNEL);
|
|
if (!config2) {
|
|
shost_printk(KERN_ERR, cb->host,
|
|
"Failed to allocate V1 config2 memory\n");
|
|
dma_free_coherent(&pdev->dev, sizeof(struct myrb_enquiry2),
|
|
enquiry2, enquiry2_addr);
|
|
return -ENOMEM;
|
|
}
|
|
mutex_lock(&cb->dma_mutex);
|
|
status = myrb_hba_enquiry(cb);
|
|
mutex_unlock(&cb->dma_mutex);
|
|
if (status != MYRB_STATUS_SUCCESS) {
|
|
shost_printk(KERN_WARNING, cb->host,
|
|
"Failed it issue V1 Enquiry\n");
|
|
goto out_free;
|
|
}
|
|
|
|
status = myrb_exec_type3(cb, MYRB_CMD_ENQUIRY2, enquiry2_addr);
|
|
if (status != MYRB_STATUS_SUCCESS) {
|
|
shost_printk(KERN_WARNING, cb->host,
|
|
"Failed to issue V1 Enquiry2\n");
|
|
goto out_free;
|
|
}
|
|
|
|
status = myrb_exec_type3(cb, MYRB_CMD_READ_CONFIG2, config2_addr);
|
|
if (status != MYRB_STATUS_SUCCESS) {
|
|
shost_printk(KERN_WARNING, cb->host,
|
|
"Failed to issue ReadConfig2\n");
|
|
goto out_free;
|
|
}
|
|
|
|
status = myrb_get_ldev_info(cb);
|
|
if (status != MYRB_STATUS_SUCCESS) {
|
|
shost_printk(KERN_WARNING, cb->host,
|
|
"Failed to get logical drive information\n");
|
|
goto out_free;
|
|
}
|
|
|
|
/*
|
|
* Initialize the Controller Model Name and Full Model Name fields.
|
|
*/
|
|
switch (enquiry2->hw.sub_model) {
|
|
case DAC960_V1_P_PD_PU:
|
|
if (enquiry2->scsi_cap.bus_speed == MYRB_SCSI_SPEED_ULTRA)
|
|
strcpy(cb->model_name, "DAC960PU");
|
|
else
|
|
strcpy(cb->model_name, "DAC960PD");
|
|
break;
|
|
case DAC960_V1_PL:
|
|
strcpy(cb->model_name, "DAC960PL");
|
|
break;
|
|
case DAC960_V1_PG:
|
|
strcpy(cb->model_name, "DAC960PG");
|
|
break;
|
|
case DAC960_V1_PJ:
|
|
strcpy(cb->model_name, "DAC960PJ");
|
|
break;
|
|
case DAC960_V1_PR:
|
|
strcpy(cb->model_name, "DAC960PR");
|
|
break;
|
|
case DAC960_V1_PT:
|
|
strcpy(cb->model_name, "DAC960PT");
|
|
break;
|
|
case DAC960_V1_PTL0:
|
|
strcpy(cb->model_name, "DAC960PTL0");
|
|
break;
|
|
case DAC960_V1_PRL:
|
|
strcpy(cb->model_name, "DAC960PRL");
|
|
break;
|
|
case DAC960_V1_PTL1:
|
|
strcpy(cb->model_name, "DAC960PTL1");
|
|
break;
|
|
case DAC960_V1_1164P:
|
|
strcpy(cb->model_name, "eXtremeRAID 1100");
|
|
break;
|
|
default:
|
|
shost_printk(KERN_WARNING, cb->host,
|
|
"Unknown Model %X\n",
|
|
enquiry2->hw.sub_model);
|
|
goto out;
|
|
}
|
|
/*
|
|
* Initialize the Controller Firmware Version field and verify that it
|
|
* is a supported firmware version.
|
|
* The supported firmware versions are:
|
|
*
|
|
* DAC1164P 5.06 and above
|
|
* DAC960PTL/PRL/PJ/PG 4.06 and above
|
|
* DAC960PU/PD/PL 3.51 and above
|
|
* DAC960PU/PD/PL/P 2.73 and above
|
|
*/
|
|
#if defined(CONFIG_ALPHA)
|
|
/*
|
|
* DEC Alpha machines were often equipped with DAC960 cards that were
|
|
* OEMed from Mylex, and had their own custom firmware. Version 2.70,
|
|
* the last custom FW revision to be released by DEC for these older
|
|
* controllers, appears to work quite well with this driver.
|
|
*
|
|
* Cards tested successfully were several versions each of the PD and
|
|
* PU, called by DEC the KZPSC and KZPAC, respectively, and having
|
|
* the Manufacturer Numbers (from Mylex), usually on a sticker on the
|
|
* back of the board, of:
|
|
*
|
|
* KZPSC: D040347 (1-channel) or D040348 (2-channel)
|
|
* or D040349 (3-channel)
|
|
* KZPAC: D040395 (1-channel) or D040396 (2-channel)
|
|
* or D040397 (3-channel)
|
|
*/
|
|
# define FIRMWARE_27X "2.70"
|
|
#else
|
|
# define FIRMWARE_27X "2.73"
|
|
#endif
|
|
|
|
if (enquiry2->fw.major_version == 0) {
|
|
enquiry2->fw.major_version = cb->enquiry->fw_major_version;
|
|
enquiry2->fw.minor_version = cb->enquiry->fw_minor_version;
|
|
enquiry2->fw.firmware_type = '0';
|
|
enquiry2->fw.turn_id = 0;
|
|
}
|
|
snprintf(cb->fw_version, sizeof(cb->fw_version),
|
|
"%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 int 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 int myrb_ldev_queuecommand(struct Scsi_Host *shost,
|
|
struct scsi_cmnd *scmd)
|
|
{
|
|
struct myrb_hba *cb = shost_priv(shost);
|
|
struct myrb_cmdblk *cmd_blk = scsi_cmd_priv(scmd);
|
|
union myrb_cmd_mbox *mbox = &cmd_blk->mbox;
|
|
struct myrb_ldev_info *ldev_info;
|
|
struct scsi_device *sdev = scmd->device;
|
|
struct scatterlist *sgl;
|
|
unsigned long flags;
|
|
u64 lba;
|
|
u32 block_cnt;
|
|
int nsge;
|
|
|
|
ldev_info = sdev->hostdata;
|
|
if (ldev_info->state != MYRB_DEVICE_ONLINE &&
|
|
ldev_info->state != MYRB_DEVICE_WO) {
|
|
dev_dbg(&shost->shost_gendev, "ldev %u in state %x, skip\n",
|
|
sdev->id, ldev_info ? ldev_info->state : 0xff);
|
|
scmd->result = (DID_BAD_TARGET << 16);
|
|
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 int myrb_queuecommand(struct Scsi_Host *shost,
|
|
struct scsi_cmnd *scmd)
|
|
{
|
|
struct scsi_device *sdev = scmd->device;
|
|
|
|
if (sdev->channel > myrb_logical_channel(shost)) {
|
|
scmd->result = (DID_BAD_TARGET << 16);
|
|
scsi_done(scmd);
|
|
return 0;
|
|
}
|
|
if (sdev->channel == myrb_logical_channel(shost))
|
|
return myrb_ldev_queuecommand(shost, scmd);
|
|
|
|
return myrb_pthru_queuecommand(shost, scmd);
|
|
}
|
|
|
|
static int myrb_ldev_slave_alloc(struct scsi_device *sdev)
|
|
{
|
|
struct myrb_hba *cb = shost_priv(sdev->host);
|
|
struct myrb_ldev_info *ldev_info;
|
|
unsigned short ldev_num = sdev->id;
|
|
enum raid_level level;
|
|
|
|
ldev_info = cb->ldev_info_buf + ldev_num;
|
|
if (!ldev_info)
|
|
return -ENXIO;
|
|
|
|
sdev->hostdata = kzalloc(sizeof(*ldev_info), GFP_KERNEL);
|
|
if (!sdev->hostdata)
|
|
return -ENOMEM;
|
|
dev_dbg(&sdev->sdev_gendev,
|
|
"slave alloc ldev %d state %x\n",
|
|
ldev_num, ldev_info->state);
|
|
memcpy(sdev->hostdata, ldev_info,
|
|
sizeof(*ldev_info));
|
|
switch (ldev_info->raid_level) {
|
|
case MYRB_RAID_LEVEL0:
|
|
level = RAID_LEVEL_LINEAR;
|
|
break;
|
|
case MYRB_RAID_LEVEL1:
|
|
level = RAID_LEVEL_1;
|
|
break;
|
|
case MYRB_RAID_LEVEL3:
|
|
level = RAID_LEVEL_3;
|
|
break;
|
|
case MYRB_RAID_LEVEL5:
|
|
level = RAID_LEVEL_5;
|
|
break;
|
|
case MYRB_RAID_LEVEL6:
|
|
level = RAID_LEVEL_6;
|
|
break;
|
|
case MYRB_RAID_JBOD:
|
|
level = RAID_LEVEL_JBOD;
|
|
break;
|
|
default:
|
|
level = RAID_LEVEL_UNKNOWN;
|
|
break;
|
|
}
|
|
raid_set_level(myrb_raid_template, &sdev->sdev_gendev, level);
|
|
return 0;
|
|
}
|
|
|
|
static int myrb_pdev_slave_alloc(struct scsi_device *sdev)
|
|
{
|
|
struct myrb_hba *cb = shost_priv(sdev->host);
|
|
struct myrb_pdev_state *pdev_info;
|
|
unsigned short status;
|
|
|
|
if (sdev->id > MYRB_MAX_TARGETS)
|
|
return -ENXIO;
|
|
|
|
pdev_info = kzalloc(sizeof(*pdev_info), GFP_KERNEL);
|
|
if (!pdev_info)
|
|
return -ENOMEM;
|
|
|
|
status = myrb_exec_type3D(cb, MYRB_CMD_GET_DEVICE_STATE,
|
|
sdev, pdev_info);
|
|
if (status != MYRB_STATUS_SUCCESS) {
|
|
dev_dbg(&sdev->sdev_gendev,
|
|
"Failed to get device state, status %x\n",
|
|
status);
|
|
kfree(pdev_info);
|
|
return -ENXIO;
|
|
}
|
|
if (!pdev_info->present) {
|
|
dev_dbg(&sdev->sdev_gendev,
|
|
"device not present, skip\n");
|
|
kfree(pdev_info);
|
|
return -ENXIO;
|
|
}
|
|
dev_dbg(&sdev->sdev_gendev,
|
|
"slave alloc pdev %d:%d state %x\n",
|
|
sdev->channel, sdev->id, pdev_info->state);
|
|
sdev->hostdata = pdev_info;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int myrb_slave_alloc(struct scsi_device *sdev)
|
|
{
|
|
if (sdev->channel > myrb_logical_channel(sdev->host))
|
|
return -ENXIO;
|
|
|
|
if (sdev->lun > 0)
|
|
return -ENXIO;
|
|
|
|
if (sdev->channel == myrb_logical_channel(sdev->host))
|
|
return myrb_ldev_slave_alloc(sdev);
|
|
|
|
return myrb_pdev_slave_alloc(sdev);
|
|
}
|
|
|
|
static int myrb_slave_configure(struct scsi_device *sdev)
|
|
{
|
|
struct myrb_ldev_info *ldev_info;
|
|
|
|
if (sdev->channel > myrb_logical_channel(sdev->host))
|
|
return -ENXIO;
|
|
|
|
if (sdev->channel < myrb_logical_channel(sdev->host)) {
|
|
sdev->no_uld_attach = 1;
|
|
return 0;
|
|
}
|
|
if (sdev->lun != 0)
|
|
return -ENXIO;
|
|
|
|
ldev_info = sdev->hostdata;
|
|
if (!ldev_info)
|
|
return -ENXIO;
|
|
if (ldev_info->state != MYRB_DEVICE_ONLINE)
|
|
sdev_printk(KERN_INFO, sdev,
|
|
"Logical drive is %s\n",
|
|
myrb_devstate_name(ldev_info->state));
|
|
|
|
sdev->tagged_supported = 1;
|
|
return 0;
|
|
}
|
|
|
|
static void myrb_slave_destroy(struct scsi_device *sdev)
|
|
{
|
|
kfree(sdev->hostdata);
|
|
}
|
|
|
|
static int myrb_biosparam(struct scsi_device *sdev, struct block_device *bdev,
|
|
sector_t capacity, int geom[])
|
|
{
|
|
struct myrb_hba *cb = shost_priv(sdev->host);
|
|
|
|
geom[0] = cb->ldev_geom_heads;
|
|
geom[1] = cb->ldev_geom_sectors;
|
|
geom[2] = sector_div(capacity, geom[0] * geom[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t raid_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct scsi_device *sdev = to_scsi_device(dev);
|
|
struct myrb_hba *cb = shost_priv(sdev->host);
|
|
int ret;
|
|
|
|
if (!sdev->hostdata)
|
|
return snprintf(buf, 16, "Unknown\n");
|
|
|
|
if (sdev->channel == myrb_logical_channel(sdev->host)) {
|
|
struct myrb_ldev_info *ldev_info = sdev->hostdata;
|
|
const char *name;
|
|
|
|
name = myrb_devstate_name(ldev_info->state);
|
|
if (name)
|
|
ret = snprintf(buf, 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,
|
|
.slave_alloc = myrb_slave_alloc,
|
|
.slave_configure = myrb_slave_configure,
|
|
.slave_destroy = myrb_slave_destroy,
|
|
.bios_param = myrb_biosparam,
|
|
.cmd_size = sizeof(struct myrb_cmdblk),
|
|
.shost_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");
|