Release 4.11 drivers/scsi/stex.c
/*
* SuperTrak EX Series Storage Controller driver for Linux
*
* Copyright (C) 2005-2015 Promise Technology Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Written By:
* Ed Lin <promise_linux@promise.com>
*
*/
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/pci.h>
#include <linux/blkdev.h>
#include <linux/interrupt.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/ktime.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/byteorder.h>
#include <scsi/scsi.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_tcq.h>
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_eh.h>
#define DRV_NAME "stex"
#define ST_DRIVER_VERSION "5.00.0000.01"
#define ST_VER_MAJOR 5
#define ST_VER_MINOR 00
#define ST_OEM 0000
#define ST_BUILD_VER 01
enum {
/* MU register offset */
IMR0 = 0x10, /* MU_INBOUND_MESSAGE_REG0 */
IMR1 = 0x14, /* MU_INBOUND_MESSAGE_REG1 */
OMR0 = 0x18, /* MU_OUTBOUND_MESSAGE_REG0 */
OMR1 = 0x1c, /* MU_OUTBOUND_MESSAGE_REG1 */
IDBL = 0x20, /* MU_INBOUND_DOORBELL */
IIS = 0x24, /* MU_INBOUND_INTERRUPT_STATUS */
IIM = 0x28, /* MU_INBOUND_INTERRUPT_MASK */
ODBL = 0x2c, /* MU_OUTBOUND_DOORBELL */
OIS = 0x30, /* MU_OUTBOUND_INTERRUPT_STATUS */
OIM = 0x3c, /* MU_OUTBOUND_INTERRUPT_MASK */
YIOA_STATUS = 0x00,
YH2I_INT = 0x20,
YINT_EN = 0x34,
YI2H_INT = 0x9c,
YI2H_INT_C = 0xa0,
YH2I_REQ = 0xc0,
YH2I_REQ_HI = 0xc4,
/* MU register value */
MU_INBOUND_DOORBELL_HANDSHAKE = (1 << 0),
MU_INBOUND_DOORBELL_REQHEADCHANGED = (1 << 1),
MU_INBOUND_DOORBELL_STATUSTAILCHANGED = (1 << 2),
MU_INBOUND_DOORBELL_HMUSTOPPED = (1 << 3),
MU_INBOUND_DOORBELL_RESET = (1 << 4),
MU_OUTBOUND_DOORBELL_HANDSHAKE = (1 << 0),
MU_OUTBOUND_DOORBELL_REQUESTTAILCHANGED = (1 << 1),
MU_OUTBOUND_DOORBELL_STATUSHEADCHANGED = (1 << 2),
MU_OUTBOUND_DOORBELL_BUSCHANGE = (1 << 3),
MU_OUTBOUND_DOORBELL_HASEVENT = (1 << 4),
MU_OUTBOUND_DOORBELL_REQUEST_RESET = (1 << 27),
/* MU status code */
MU_STATE_STARTING = 1,
MU_STATE_STARTED = 2,
MU_STATE_RESETTING = 3,
MU_STATE_FAILED = 4,
MU_STATE_STOP = 5,
MU_STATE_NOCONNECT = 6,
MU_MAX_DELAY = 120,
MU_HANDSHAKE_SIGNATURE = 0x55aaaa55,
MU_HANDSHAKE_SIGNATURE_HALF = 0x5a5a0000,
MU_HARD_RESET_WAIT = 30000,
HMU_PARTNER_TYPE = 2,
/* firmware returned values */
SRB_STATUS_SUCCESS = 0x01,
SRB_STATUS_ERROR = 0x04,
SRB_STATUS_BUSY = 0x05,
SRB_STATUS_INVALID_REQUEST = 0x06,
SRB_STATUS_SELECTION_TIMEOUT = 0x0A,
SRB_SEE_SENSE = 0x80,
/* task attribute */
TASK_ATTRIBUTE_SIMPLE = 0x0,
TASK_ATTRIBUTE_HEADOFQUEUE = 0x1,
TASK_ATTRIBUTE_ORDERED = 0x2,
TASK_ATTRIBUTE_ACA = 0x4,
SS_STS_NORMAL = 0x80000000,
SS_STS_DONE = 0x40000000,
SS_STS_HANDSHAKE = 0x20000000,
SS_HEAD_HANDSHAKE = 0x80,
SS_H2I_INT_RESET = 0x100,
SS_I2H_REQUEST_RESET = 0x2000,
SS_MU_OPERATIONAL = 0x80000000,
STEX_CDB_LENGTH = 16,
STATUS_VAR_LEN = 128,
/* sg flags */
SG_CF_EOT = 0x80, /* end of table */
SG_CF_64B = 0x40, /* 64 bit item */
SG_CF_HOST = 0x20, /* sg in host memory */
MSG_DATA_DIR_ND = 0,
MSG_DATA_DIR_IN = 1,
MSG_DATA_DIR_OUT = 2,
st_shasta = 0,
st_vsc = 1,
st_yosemite = 2,
st_seq = 3,
st_yel = 4,
PASSTHRU_REQ_TYPE = 0x00000001,
PASSTHRU_REQ_NO_WAKEUP = 0x00000100,
ST_INTERNAL_TIMEOUT = 180,
ST_TO_CMD = 0,
ST_FROM_CMD = 1,
/* vendor specific commands of Promise */
MGT_CMD = 0xd8,
SINBAND_MGT_CMD = 0xd9,
ARRAY_CMD = 0xe0,
CONTROLLER_CMD = 0xe1,
DEBUGGING_CMD = 0xe2,
PASSTHRU_CMD = 0xe3,
PASSTHRU_GET_ADAPTER = 0x05,
PASSTHRU_GET_DRVVER = 0x10,
CTLR_CONFIG_CMD = 0x03,
CTLR_SHUTDOWN = 0x0d,
CTLR_POWER_STATE_CHANGE = 0x0e,
CTLR_POWER_SAVING = 0x01,
PASSTHRU_SIGNATURE = 0x4e415041,
MGT_CMD_SIGNATURE = 0xba,
INQUIRY_EVPD = 0x01,
ST_ADDITIONAL_MEM = 0x200000,
ST_ADDITIONAL_MEM_MIN = 0x80000,
PMIC_SHUTDOWN = 0x0D,
PMIC_REUMSE = 0x10,
ST_IGNORED = -1,
ST_NOTHANDLED = 7,
ST_S3 = 3,
ST_S4 = 4,
ST_S5 = 5,
ST_S6 = 6,
};
struct st_sgitem {
u8 ctrl; /* SG_CF_xxx */
u8 reserved[3];
__le32 count;
__le64 addr;
};
struct st_ss_sgitem {
__le32 addr;
__le32 addr_hi;
__le32 count;
};
struct st_sgtable {
__le16 sg_count;
__le16 max_sg_count;
__le32 sz_in_byte;
};
struct st_msg_header {
__le64 handle;
u8 flag;
u8 channel;
__le16 timeout;
u32 reserved;
};
struct handshake_frame {
__le64 rb_phy; /* request payload queue physical address */
__le16 req_sz; /* size of each request payload */
__le16 req_cnt; /* count of reqs the buffer can hold */
__le16 status_sz; /* size of each status payload */
__le16 status_cnt; /* count of status the buffer can hold */
__le64 hosttime; /* seconds from Jan 1, 1970 (GMT) */
u8 partner_type; /* who sends this frame */
u8 reserved0[7];
__le32 partner_ver_major;
__le32 partner_ver_minor;
__le32 partner_ver_oem;
__le32 partner_ver_build;
__le32 extra_offset; /* NEW */
__le32 extra_size; /* NEW */
__le32 scratch_size;
u32 reserved1;
};
struct req_msg {
__le16 tag;
u8 lun;
u8 target;
u8 task_attr;
u8 task_manage;
u8 data_dir;
u8 payload_sz; /* payload size in 4-byte, not used */
u8 cdb[STEX_CDB_LENGTH];
u32 variable[0];
};
struct status_msg {
__le16 tag;
u8 lun;
u8 target;
u8 srb_status;
u8 scsi_status;
u8 reserved;
u8 payload_sz; /* payload size in 4-byte */
u8 variable[STATUS_VAR_LEN];
};
struct ver_info {
u32 major;
u32 minor;
u32 oem;
u32 build;
u32 reserved[2];
};
struct st_frame {
u32 base[6];
u32 rom_addr;
struct ver_info drv_ver;
struct ver_info bios_ver;
u32 bus;
u32 slot;
u32 irq_level;
u32 irq_vec;
u32 id;
u32 subid;
u32 dimm_size;
u8 dimm_type;
u8 reserved[3];
u32 channel;
u32 reserved1;
};
struct st_drvver {
u32 major;
u32 minor;
u32 oem;
u32 build;
u32 signature[2];
u8 console_id;
u8 host_no;
u8 reserved0[2];
u32 reserved[3];
};
struct st_ccb {
struct req_msg *req;
struct scsi_cmnd *cmd;
void *sense_buffer;
unsigned int sense_bufflen;
int sg_count;
u32 req_type;
u8 srb_status;
u8 scsi_status;
u8 reserved[2];
};
struct st_hba {
void __iomem *mmio_base; /* iomapped PCI memory space */
void *dma_mem;
dma_addr_t dma_handle;
size_t dma_size;
struct Scsi_Host *host;
struct pci_dev *pdev;
struct req_msg * (*alloc_rq) (struct st_hba *);
int (*map_sg)(struct st_hba *, struct req_msg *, struct st_ccb *);
void (*send) (struct st_hba *, struct req_msg *, u16);
u32 req_head;
u32 req_tail;
u32 status_head;
u32 status_tail;
struct status_msg *status_buffer;
void *copy_buffer; /* temp buffer for driver-handled commands */
struct st_ccb *ccb;
struct st_ccb *wait_ccb;
__le32 *scratch;
char work_q_name[20];
struct workqueue_struct *work_q;
struct work_struct reset_work;
wait_queue_head_t reset_waitq;
unsigned int mu_status;
unsigned int cardtype;
int msi_enabled;
int out_req_cnt;
u32 extra_offset;
u16 rq_count;
u16 rq_size;
u16 sts_count;
u8 supports_pm;
};
struct st_card_info {
struct req_msg * (*alloc_rq) (struct st_hba *);
int (*map_sg)(struct st_hba *, struct req_msg *, struct st_ccb *);
void (*send) (struct st_hba *, struct req_msg *, u16);
unsigned int max_id;
unsigned int max_lun;
unsigned int max_channel;
u16 rq_count;
u16 rq_size;
u16 sts_count;
};
static int msi;
module_param(msi, int, 0);
MODULE_PARM_DESC(msi, "Enable Message Signaled Interrupts(0=off, 1=on)");
static const char console_inq_page[] =
{
0x03,0x00,0x03,0x03,0xFA,0x00,0x00,0x30,
0x50,0x72,0x6F,0x6D,0x69,0x73,0x65,0x20, /* "Promise " */
0x52,0x41,0x49,0x44,0x20,0x43,0x6F,0x6E, /* "RAID Con" */
0x73,0x6F,0x6C,0x65,0x20,0x20,0x20,0x20, /* "sole " */
0x31,0x2E,0x30,0x30,0x20,0x20,0x20,0x20, /* "1.00 " */
0x53,0x58,0x2F,0x52,0x53,0x41,0x46,0x2D, /* "SX/RSAF-" */
0x54,0x45,0x31,0x2E,0x30,0x30,0x20,0x20, /* "TE1.00 " */
0x0C,0x20,0x20,0x20,0x20,0x20,0x20,0x20
};
MODULE_AUTHOR("Ed Lin");
MODULE_DESCRIPTION("Promise Technology SuperTrak EX Controllers");
MODULE_LICENSE("GPL");
MODULE_VERSION(ST_DRIVER_VERSION);
static struct status_msg *stex_get_status(struct st_hba *hba)
{
struct status_msg *status = hba->status_buffer + hba->status_tail;
++hba->status_tail;
hba->status_tail %= hba->sts_count+1;
return status;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Jeff Garzik | 39 | 88.64% | 1 | 50.00% |
Ed Lin | 5 | 11.36% | 1 | 50.00% |
Total | 44 | 100.00% | 2 | 100.00% |
static void stex_invalid_field(struct scsi_cmnd *cmd,
void (*done)(struct scsi_cmnd *))
{
cmd->result = (DRIVER_SENSE << 24) | SAM_STAT_CHECK_CONDITION;
/* "Invalid field in cdb" */
scsi_build_sense_buffer(0, cmd->sense_buffer, ILLEGAL_REQUEST, 0x24,
0x0);
done(cmd);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Jeff Garzik | 40 | 72.73% | 1 | 33.33% |
FUJITA Tomonori | 14 | 25.45% | 1 | 33.33% |
Ed Lin | 1 | 1.82% | 1 | 33.33% |
Total | 55 | 100.00% | 3 | 100.00% |
static struct req_msg *stex_alloc_req(struct st_hba *hba)
{
struct req_msg *req = hba->dma_mem + hba->req_head * hba->rq_size;
++hba->req_head;
hba->req_head %= hba->rq_count+1;
return req;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Jeff Garzik | 39 | 81.25% | 1 | 50.00% |
Ed Lin | 9 | 18.75% | 1 | 50.00% |
Total | 48 | 100.00% | 2 | 100.00% |
static struct req_msg *stex_ss_alloc_req(struct st_hba *hba)
{
return (struct req_msg *)(hba->dma_mem +
hba->req_head * hba->rq_size + sizeof(struct st_msg_header));
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Ed Lin | 39 | 100.00% | 1 | 100.00% |
Total | 39 | 100.00% | 1 | 100.00% |
static int stex_map_sg(struct st_hba *hba,
struct req_msg *req, struct st_ccb *ccb)
{
struct scsi_cmnd *cmd;
struct scatterlist *sg;
struct st_sgtable *dst;
struct st_sgitem *table;
int i, nseg;
cmd = ccb->cmd;
nseg = scsi_dma_map(cmd);
BUG_ON(nseg < 0);
if (nseg) {
dst = (struct st_sgtable *)req->variable;
ccb->sg_count = nseg;
dst->sg_count = cpu_to_le16((u16)nseg);
dst->max_sg_count = cpu_to_le16(hba->host->sg_tablesize);
dst->sz_in_byte = cpu_to_le32(scsi_bufflen(cmd));
table = (struct st_sgitem *)(dst + 1);
scsi_for_each_sg(cmd, sg, nseg, i) {
table[i].count = cpu_to_le32((u32)sg_dma_len(sg));
table[i].addr = cpu_to_le64(sg_dma_address(sg));
table[i].ctrl = SG_CF_64B | SG_CF_HOST;
}
table[--i].ctrl |= SG_CF_EOT;
}
return nseg;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Jeff Garzik | 132 | 63.46% | 1 | 33.33% |
Ed Lin | 58 | 27.88% | 1 | 33.33% |
FUJITA Tomonori | 18 | 8.65% | 1 | 33.33% |
Total | 208 | 100.00% | 3 | 100.00% |
static int stex_ss_map_sg(struct st_hba *hba,
struct req_msg *req, struct st_ccb *ccb)
{
struct scsi_cmnd *cmd;
struct scatterlist *sg;
struct st_sgtable *dst;
struct st_ss_sgitem *table;
int i, nseg;
cmd = ccb->cmd;
nseg = scsi_dma_map(cmd);
BUG_ON(nseg < 0);
if (nseg) {
dst = (struct st_sgtable *)req->variable;
ccb->sg_count = nseg;
dst->sg_count = cpu_to_le16((u16)nseg);
dst->max_sg_count = cpu_to_le16(hba->host->sg_tablesize);
dst->sz_in_byte = cpu_to_le32(scsi_bufflen(cmd));
table = (struct st_ss_sgitem *)(dst + 1);
scsi_for_each_sg(cmd, sg, nseg, i) {
table[i].count = cpu_to_le32((u32)sg_dma_len(sg));
table[i].addr =
cpu_to_le32(sg_dma_address(sg) & 0xffffffff);
table[i].addr_hi =
cpu_to_le32((sg_dma_address(sg) >> 16) >> 16);
}
}
return nseg;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Ed Lin | 189 | 90.00% | 2 | 66.67% |
Jeff Garzik | 21 | 10.00% | 1 | 33.33% |
Total | 210 | 100.00% | 3 | 100.00% |
static void stex_controller_info(struct st_hba *hba, struct st_ccb *ccb)
{
struct st_frame *p;
size_t count = sizeof(struct st_frame);
p = hba->copy_buffer;
scsi_sg_copy_to_buffer(ccb->cmd, p, count);
memset(p->base, 0, sizeof(u32)*6);
*(unsigned long *)(p->base) = pci_resource_start(hba->pdev, 0);
p->rom_addr = 0;
p->drv_ver.major = ST_VER_MAJOR;
p->drv_ver.minor = ST_VER_MINOR;
p->drv_ver.oem = ST_OEM;
p->drv_ver.build = ST_BUILD_VER;
p->bus = hba->pdev->bus->number;
p->slot = hba->pdev->devfn;
p->irq_level = 0;
p->irq_vec = hba->pdev->irq;
p->id = hba->pdev->vendor << 16 | hba->pdev->device;
p->subid =
hba->pdev->subsystem_vendor << 16 | hba->pdev->subsystem_device;
scsi_sg_copy_from_buffer(ccb->cmd, p, count);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Jeff Garzik | 156 | 75.36% | 1 | 33.33% |
Ed Lin | 50 | 24.15% | 1 | 33.33% |
FUJITA Tomonori | 1 | 0.48% | 1 | 33.33% |
Total | 207 | 100.00% | 3 | 100.00% |
static void
stex_send_cmd(struct st_hba *hba, struct req_msg *req, u16 tag)
{
req->tag = cpu_to_le16(tag);
hba->ccb[tag].req = req;
hba->out_req_cnt++;
writel(hba->req_head, hba->mmio_base + IMR0);
writel(MU_INBOUND_DOORBELL_REQHEADCHANGED, hba->mmio_base + IDBL);
readl(hba->mmio_base + IDBL); /* flush */
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Jeff Garzik | 78 | 100.00% | 1 | 100.00% |
Total | 78 | 100.00% | 1 | 100.00% |
static void
stex_ss_send_cmd(struct st_hba *hba, struct req_msg *req, u16 tag)
{
struct scsi_cmnd *cmd;
struct st_msg_header *msg_h;
dma_addr_t addr;
req->tag = cpu_to_le16(tag);
hba->ccb[tag].req = req;
hba->out_req_cnt++;
cmd = hba->ccb[tag].cmd;
msg_h = (struct st_msg_header *)req - 1;
if (likely(cmd)) {
msg_h->channel = (u8)cmd->device->channel;
msg_h->timeout = cpu_to_le16(cmd->request->timeout/HZ);
}
addr = hba->dma_handle + hba->req_head * hba->rq_size;
addr += (hba->ccb[tag].sg_count+4)/11;
msg_h->handle = cpu_to_le64(addr);
++hba->req_head;
hba->req_head %= hba->rq_count+1;
writel((addr >> 16) >> 16, hba->mmio_base + YH2I_REQ_HI);
readl(hba->mmio_base + YH2I_REQ_HI); /* flush */
writel(addr, hba->mmio_base + YH2I_REQ);
readl(hba->mmio_base + YH2I_REQ); /* flush */
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Ed Lin | 218 | 99.54% | 2 | 66.67% |
Jeff Garzik | 1 | 0.46% | 1 | 33.33% |
Total | 219 | 100.00% | 3 | 100.00% |
static void return_abnormal_state(struct st_hba *hba, int status)
{
struct st_ccb *ccb;
unsigned long flags;
u16 tag;
spin_lock_irqsave(hba->host->host_lock, flags);
for (tag = 0; tag < hba->host->can_queue; tag++) {
ccb = &hba->ccb[tag];
if (ccb->req == NULL)
continue;
ccb->req = NULL;
if (ccb->cmd) {
scsi_dma_unmap(ccb->cmd);
ccb->cmd->result = status << 16;
ccb->cmd->scsi_done(ccb->cmd);
ccb->cmd = NULL;
}
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Charles | 134 | 100.00% | 1 | 100.00% |
Total | 134 | 100.00% | 1 | 100.00% |
static int
stex_slave_config(struct scsi_device *sdev)
{
sdev->use_10_for_rw = 1;
sdev->use_10_for_ms = 1;
blk_queue_rq_timeout(sdev->request_queue, 60 * HZ);
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Jeff Garzik | 28 | 75.68% | 1 | 33.33% |
James Bottomley | 5 | 13.51% | 1 | 33.33% |
Ed Lin | 4 | 10.81% | 1 | 33.33% |
Total | 37 | 100.00% | 3 | 100.00% |
static int
stex_queuecommand_lck(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
{
struct st_hba *hba;
struct Scsi_Host *host;
unsigned int id, lun;
struct req_msg *req;
u16 tag;
host = cmd->device->host;
id = cmd->device->id;
lun = cmd->device->lun;
hba = (struct st_hba *) &host->hostdata[0];
if (hba->mu_status == MU_STATE_NOCONNECT) {
cmd->result = DID_NO_CONNECT;
done(cmd);
return 0;
}
if (unlikely(hba->mu_status != MU_STATE_STARTED))
return SCSI_MLQUEUE_HOST_BUSY;
switch (cmd->cmnd[0]) {
case MODE_SENSE_10:
{
static char ms10_caching_page[12] =
{ 0, 0x12, 0, 0, 0, 0, 0, 0, 0x8, 0xa, 0x4, 0 };
unsigned char page;
page = cmd->cmnd[2] & 0x3f;
if (page == 0x8 || page == 0x3f) {
scsi_sg_copy_from_buffer(cmd, ms10_caching_page,
sizeof(ms10_caching_page));
cmd->result = DID_OK << 16 | COMMAND_COMPLETE << 8;
done(cmd);
} else
stex_invalid_field(cmd, done);
return 0;
}
case REPORT_LUNS:
/*
* The shasta firmware does not report actual luns in the
* target, so fail the command to force sequential lun scan.
* Also, the console device does not support this command.
*/
if (hba->cardtype == st_shasta || id == host->max_id - 1) {
stex_invalid_field(cmd, done);
return 0;
}
break;
case TEST_UNIT_READY:
if (id == host->max_id - 1) {
cmd->result = DID_OK << 16 | COMMAND_COMPLETE << 8;
done(cmd);
return 0;
}
break;
case INQUIRY:
if (lun >= host->max_lun) {
cmd->result = DID_NO_CONNECT << 16;
done(cmd);
return 0;
}
if (id != host->max_id - 1)
break;
if (!lun && !cmd->device->channel &&
(cmd->cmnd[1] & INQUIRY_EVPD) == 0) {
scsi_sg_copy_from_buffer(cmd, (