cregit-Linux how code gets into the kernel

Release 4.11 drivers/scsi/stex.c

Directory: drivers/scsi
/*
 * 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

PersonTokensPropCommitsCommitProp
Jeff Garzik3988.64%150.00%
Ed Lin511.36%150.00%
Total44100.00%2100.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

PersonTokensPropCommitsCommitProp
Jeff Garzik4072.73%133.33%
FUJITA Tomonori1425.45%133.33%
Ed Lin11.82%133.33%
Total55100.00%3100.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

PersonTokensPropCommitsCommitProp
Jeff Garzik3981.25%150.00%
Ed Lin918.75%150.00%
Total48100.00%2100.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

PersonTokensPropCommitsCommitProp
Ed Lin39100.00%1100.00%
Total39100.00%1100.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

PersonTokensPropCommitsCommitProp
Jeff Garzik13263.46%133.33%
Ed Lin5827.88%133.33%
FUJITA Tomonori188.65%133.33%
Total208100.00%3100.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

PersonTokensPropCommitsCommitProp
Ed Lin18990.00%266.67%
Jeff Garzik2110.00%133.33%
Total210100.00%3100.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

PersonTokensPropCommitsCommitProp
Jeff Garzik15675.36%133.33%
Ed Lin5024.15%133.33%
FUJITA Tomonori10.48%133.33%
Total207100.00%3100.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

PersonTokensPropCommitsCommitProp
Jeff Garzik78100.00%1100.00%
Total78100.00%1100.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

PersonTokensPropCommitsCommitProp
Ed Lin21899.54%266.67%
Jeff Garzik10.46%133.33%
Total219100.00%3100.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

PersonTokensPropCommitsCommitProp
Charles134100.00%1100.00%
Total134100.00%1100.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

PersonTokensPropCommitsCommitProp
Jeff Garzik2875.68%133.33%
James Bottomley513.51%133.33%
Ed Lin410.81%133.33%
Total37100.00%3100.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, (