[SCSI] stex: add support for reset request from firmware

Add support for reset request from firmware for controllers
of st_shasta and st_yel type. Code adjustments necessary
for this change are also included.

Signed-off-by: Ed Lin <ed.lin@promise.com>
Signed-off-by: James Bottomley <James.Bottomley@suse.de>
This commit is contained in:
Ed Lin 2009-09-28 22:58:33 -08:00 committed by James Bottomley
parent cbacfb5fd9
commit 9eb46d2a08
1 changed files with 168 additions and 85 deletions

View File

@ -64,24 +64,24 @@ enum {
YH2I_REQ_HI = 0xc4,
/* MU register value */
MU_INBOUND_DOORBELL_HANDSHAKE = 1,
MU_INBOUND_DOORBELL_REQHEADCHANGED = 2,
MU_INBOUND_DOORBELL_STATUSTAILCHANGED = 4,
MU_INBOUND_DOORBELL_HMUSTOPPED = 8,
MU_INBOUND_DOORBELL_RESET = 16,
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,
MU_OUTBOUND_DOORBELL_REQUESTTAILCHANGED = 2,
MU_OUTBOUND_DOORBELL_STATUSHEADCHANGED = 4,
MU_OUTBOUND_DOORBELL_BUSCHANGE = 8,
MU_OUTBOUND_DOORBELL_HASEVENT = 16,
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_FMU_READY_FOR_HANDSHAKE = 2,
MU_STATE_SEND_HANDSHAKE_FRAME = 3,
MU_STATE_STARTED = 4,
MU_STATE_RESETTING = 5,
MU_STATE_STARTED = 2,
MU_STATE_RESETTING = 3,
MU_STATE_FAILED = 4,
MU_MAX_DELAY = 120,
MU_HANDSHAKE_SIGNATURE = 0x55aaaa55,
@ -111,6 +111,8 @@ enum {
SS_H2I_INT_RESET = 0x100,
SS_I2H_REQUEST_RESET = 0x2000,
SS_MU_OPERATIONAL = 0x80000000,
STEX_CDB_LENGTH = 16,
@ -312,6 +314,10 @@ struct st_hba {
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;
@ -578,6 +584,9 @@ stex_queuecommand(struct scsi_cmnd *cmd, void (* done)(struct scsi_cmnd *))
lun = cmd->device->lun;
hba = (struct st_hba *) &host->hostdata[0];
if (unlikely(hba->mu_status == MU_STATE_RESETTING))
return SCSI_MLQUEUE_HOST_BUSY;
switch (cmd->cmnd[0]) {
case MODE_SENSE_10:
{
@ -842,7 +851,6 @@ static irqreturn_t stex_intr(int irq, void *__hba)
void __iomem *base = hba->mmio_base;
u32 data;
unsigned long flags;
int handled = 0;
spin_lock_irqsave(hba->host->host_lock, flags);
@ -853,12 +861,16 @@ static irqreturn_t stex_intr(int irq, void *__hba)
writel(data, base + ODBL);
readl(base + ODBL); /* flush */
stex_mu_intr(hba, data);
handled = 1;
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (unlikely(data & MU_OUTBOUND_DOORBELL_REQUEST_RESET &&
hba->cardtype == st_shasta))
queue_work(hba->work_q, &hba->reset_work);
return IRQ_HANDLED;
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
return IRQ_RETVAL(handled);
return IRQ_NONE;
}
static void stex_ss_mu_intr(struct st_hba *hba)
@ -940,7 +952,6 @@ static irqreturn_t stex_ss_intr(int irq, void *__hba)
void __iomem *base = hba->mmio_base;
u32 data;
unsigned long flags;
int handled = 0;
spin_lock_irqsave(hba->host->host_lock, flags);
@ -949,12 +960,15 @@ static irqreturn_t stex_ss_intr(int irq, void *__hba)
/* clear the interrupt */
writel(data, base + YI2H_INT_C);
stex_ss_mu_intr(hba);
handled = 1;
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (unlikely(data & SS_I2H_REQUEST_RESET))
queue_work(hba->work_q, &hba->reset_work);
return IRQ_HANDLED;
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
return IRQ_RETVAL(handled);
return IRQ_NONE;
}
static int stex_common_handshake(struct st_hba *hba)
@ -1047,7 +1061,7 @@ static int stex_ss_handshake(struct st_hba *hba)
struct st_msg_header *msg_h;
struct handshake_frame *h;
__le32 *scratch;
u32 data;
u32 data, scratch_size;
unsigned long before;
int ret = 0;
@ -1075,13 +1089,16 @@ static int stex_ss_handshake(struct st_hba *hba)
stex_gettime(&h->hosttime);
h->partner_type = HMU_PARTNER_TYPE;
h->extra_offset = h->extra_size = 0;
h->scratch_size = cpu_to_le32((hba->sts_count+1)*sizeof(u32));
scratch_size = (hba->sts_count+1)*sizeof(u32);
h->scratch_size = cpu_to_le32(scratch_size);
data = readl(base + YINT_EN);
data &= ~4;
writel(data, base + YINT_EN);
writel((hba->dma_handle >> 16) >> 16, base + YH2I_REQ_HI);
readl(base + YH2I_REQ_HI);
writel(hba->dma_handle, base + YH2I_REQ);
readl(base + YH2I_REQ); /* flush */
scratch = hba->scratch;
before = jiffies;
@ -1097,7 +1114,7 @@ static int stex_ss_handshake(struct st_hba *hba)
msleep(1);
}
*scratch = 0;
memset(scratch, 0, scratch_size);
msg_h->flag = 0;
return ret;
}
@ -1106,19 +1123,24 @@ static int stex_handshake(struct st_hba *hba)
{
int err;
unsigned long flags;
unsigned int mu_status;
err = (hba->cardtype == st_yel) ?
stex_ss_handshake(hba) : stex_common_handshake(hba);
spin_lock_irqsave(hba->host->host_lock, flags);
mu_status = hba->mu_status;
if (err == 0) {
spin_lock_irqsave(hba->host->host_lock, flags);
hba->req_head = 0;
hba->req_tail = 0;
hba->status_head = 0;
hba->status_tail = 0;
hba->out_req_cnt = 0;
hba->mu_status = MU_STATE_STARTED;
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
} else
hba->mu_status = MU_STATE_FAILED;
if (mu_status == MU_STATE_RESETTING)
wake_up_all(&hba->reset_waitq);
spin_unlock_irqrestore(hba->host->host_lock, flags);
return err;
}
@ -1138,17 +1160,11 @@ static int stex_abort(struct scsi_cmnd *cmd)
base = hba->mmio_base;
spin_lock_irqsave(host->host_lock, flags);
if (tag < host->can_queue && hba->ccb[tag].cmd == cmd)
if (tag < host->can_queue &&
hba->ccb[tag].req && hba->ccb[tag].cmd == cmd)
hba->wait_ccb = &hba->ccb[tag];
else {
for (tag = 0; tag < host->can_queue; tag++)
if (hba->ccb[tag].cmd == cmd) {
hba->wait_ccb = &hba->ccb[tag];
break;
}
if (tag >= host->can_queue)
goto out;
}
else
goto out;
if (hba->cardtype == st_yel) {
data = readl(base + YI2H_INT);
@ -1222,6 +1238,37 @@ static void stex_hard_reset(struct st_hba *hba)
hba->pdev->saved_config_space[i]);
}
static int stex_yos_reset(struct st_hba *hba)
{
void __iomem *base;
unsigned long flags, before;
int ret = 0;
base = hba->mmio_base;
writel(MU_INBOUND_DOORBELL_RESET, base + IDBL);
readl(base + IDBL); /* flush */
before = jiffies;
while (hba->out_req_cnt > 0) {
if (time_after(jiffies, before + ST_INTERNAL_TIMEOUT * HZ)) {
printk(KERN_WARNING DRV_NAME
"(%s): reset timeout\n", pci_name(hba->pdev));
ret = -1;
break;
}
msleep(1);
}
spin_lock_irqsave(hba->host->host_lock, flags);
if (ret == -1)
hba->mu_status = MU_STATE_FAILED;
else
hba->mu_status = MU_STATE_STARTED;
wake_up_all(&hba->reset_waitq);
spin_unlock_irqrestore(hba->host->host_lock, flags);
return ret;
}
static void stex_ss_reset(struct st_hba *hba)
{
writel(SS_H2I_INT_RESET, hba->mmio_base + YH2I_INT);
@ -1229,11 +1276,71 @@ static void stex_ss_reset(struct st_hba *hba)
ssleep(5);
}
static int stex_do_reset(struct st_hba *hba)
{
struct st_ccb *ccb;
unsigned long flags;
unsigned int mu_status = MU_STATE_RESETTING;
u16 tag;
spin_lock_irqsave(hba->host->host_lock, flags);
if (hba->mu_status == MU_STATE_STARTING) {
spin_unlock_irqrestore(hba->host->host_lock, flags);
printk(KERN_INFO DRV_NAME "(%s): request reset during init\n",
pci_name(hba->pdev));
return 0;
}
while (hba->mu_status == MU_STATE_RESETTING) {
spin_unlock_irqrestore(hba->host->host_lock, flags);
wait_event_timeout(hba->reset_waitq,
hba->mu_status != MU_STATE_RESETTING,
MU_MAX_DELAY * HZ);
spin_lock_irqsave(hba->host->host_lock, flags);
mu_status = hba->mu_status;
}
if (mu_status != MU_STATE_RESETTING) {
spin_unlock_irqrestore(hba->host->host_lock, flags);
return (mu_status == MU_STATE_STARTED) ? 0 : -1;
}
hba->mu_status = MU_STATE_RESETTING;
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (hba->cardtype == st_yosemite)
return stex_yos_reset(hba);
if (hba->cardtype == st_shasta)
stex_hard_reset(hba);
else if (hba->cardtype == st_yel)
stex_ss_reset(hba);
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 = DID_RESET << 16;
ccb->cmd->scsi_done(ccb->cmd);
ccb->cmd = NULL;
}
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (stex_handshake(hba) == 0)
return 0;
printk(KERN_WARNING DRV_NAME "(%s): resetting: handshake failed\n",
pci_name(hba->pdev));
return -1;
}
static int stex_reset(struct scsi_cmnd *cmd)
{
struct st_hba *hba;
void __iomem *base;
unsigned long flags, before;
hba = (struct st_hba *) &cmd->device->host->hostdata[0];
@ -1241,54 +1348,14 @@ static int stex_reset(struct scsi_cmnd *cmd)
"(%s): resetting host\n", pci_name(hba->pdev));
scsi_print_command(cmd);
hba->mu_status = MU_STATE_RESETTING;
return stex_do_reset(hba) ? FAILED : SUCCESS;
}
if (hba->cardtype == st_shasta)
stex_hard_reset(hba);
else if (hba->cardtype == st_yel)
stex_ss_reset(hba);
static void stex_reset_work(struct work_struct *work)
{
struct st_hba *hba = container_of(work, struct st_hba, reset_work);
if (hba->cardtype != st_yosemite) {
if (stex_handshake(hba)) {
printk(KERN_WARNING DRV_NAME
"(%s): resetting: handshake failed\n",
pci_name(hba->pdev));
return FAILED;
}
return SUCCESS;
}
/* st_yosemite */
writel(MU_INBOUND_DOORBELL_RESET, hba->mmio_base + IDBL);
readl(hba->mmio_base + IDBL); /* flush */
before = jiffies;
while (hba->out_req_cnt > 0) {
if (time_after(jiffies, before + ST_INTERNAL_TIMEOUT * HZ)) {
printk(KERN_WARNING DRV_NAME
"(%s): reset timeout\n", pci_name(hba->pdev));
return FAILED;
}
msleep(1);
}
base = hba->mmio_base;
writel(0, base + IMR0);
readl(base + IMR0);
writel(0, base + OMR0);
readl(base + OMR0);
writel(0, base + IMR1);
readl(base + IMR1);
writel(0, base + OMR1);
readl(base + OMR1); /* flush */
spin_lock_irqsave(hba->host->host_lock, flags);
hba->req_head = 0;
hba->req_tail = 0;
hba->status_head = 0;
hba->status_tail = 0;
hba->out_req_cnt = 0;
hba->mu_status = MU_STATE_STARTED;
spin_unlock_irqrestore(hba->host->host_lock, flags);
return SUCCESS;
stex_do_reset(hba);
}
static int stex_biosparam(struct scsi_device *sdev,
@ -1583,12 +1650,24 @@ stex_probe(struct pci_dev *pdev, const struct pci_device_id *id)
hba->host = host;
hba->pdev = pdev;
init_waitqueue_head(&hba->reset_waitq);
snprintf(hba->work_q_name, sizeof(hba->work_q_name),
"stex_wq_%d", host->host_no);
hba->work_q = create_singlethread_workqueue(hba->work_q_name);
if (!hba->work_q) {
printk(KERN_ERR DRV_NAME "(%s): create workqueue failed\n",
pci_name(pdev));
err = -ENOMEM;
goto out_ccb_free;
}
INIT_WORK(&hba->reset_work, stex_reset_work);
err = stex_request_irq(hba);
if (err) {
printk(KERN_ERR DRV_NAME "(%s): request irq failed\n",
pci_name(pdev));
goto out_ccb_free;
goto out_free_wq;
}
err = stex_handshake(hba);
@ -1617,6 +1696,8 @@ stex_probe(struct pci_dev *pdev, const struct pci_device_id *id)
out_free_irq:
stex_free_irq(hba);
out_free_wq:
destroy_workqueue(hba->work_q);
out_ccb_free:
kfree(hba->ccb);
out_pci_free:
@ -1684,6 +1765,8 @@ static void stex_hba_free(struct st_hba *hba)
{
stex_free_irq(hba);
destroy_workqueue(hba->work_q);
iounmap(hba->mmio_base);
pci_release_regions(hba->pdev);