Commit 281a7fd0 authored by Webb Scales's avatar Webb Scales Committed by James Bottomley

hpsa: fix race between abort handler and main i/o path

This means changing the allocator to reference count commands.
The reference count is now the authoritative indicator of whether a
command is allocated or not.  The h->cmd_pool_bits bitmap is now
only a heuristic hint to speed up the allocation process, it is no
longer the authoritative record of allocated commands.

Since we changed the command allocator to use reference counting
as the authoritative indicator of whether a command is allocated,
fail_all_outstanding_cmds needs to use the reference count not
h->cmd_pool_bits for this purpose.

Fix hpsa_drain_accel_commands to use the reference count as the
authoritative indicator of whether a command is allocated instead of
the h->cmd_pool_bits bitmap.
Reviewed-by: default avatarScott Teel <scott.teel@pmcs.com>
Signed-off-by: default avatarDon Brace <don.brace@pmcs.com>
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
parent 03383736
...@@ -4552,6 +4552,7 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc) ...@@ -4552,6 +4552,7 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc)
char msg[256]; /* For debug messaging. */ char msg[256]; /* For debug messaging. */
int ml = 0; int ml = 0;
__le32 tagupper, taglower; __le32 tagupper, taglower;
int refcount;
/* Find the controller of the command to be aborted */ /* Find the controller of the command to be aborted */
h = sdev_to_hba(sc->device); h = sdev_to_hba(sc->device);
...@@ -4580,9 +4581,13 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc) ...@@ -4580,9 +4581,13 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc)
/* Get SCSI command to be aborted */ /* Get SCSI command to be aborted */
abort = (struct CommandList *) sc->host_scribble; abort = (struct CommandList *) sc->host_scribble;
if (abort == NULL) { if (abort == NULL) {
dev_err(&h->pdev->dev, "%s FAILED, Command to abort is NULL.\n", /* This can happen if the command already completed. */
msg); return SUCCESS;
return FAILED; }
refcount = atomic_inc_return(&abort->refcount);
if (refcount == 1) { /* Command is done already. */
cmd_free(h, abort);
return SUCCESS;
} }
hpsa_get_tag(h, abort, &taglower, &tagupper); hpsa_get_tag(h, abort, &taglower, &tagupper);
ml += sprintf(msg+ml, "Tag:0x%08x:%08x ", tagupper, taglower); ml += sprintf(msg+ml, "Tag:0x%08x:%08x ", tagupper, taglower);
...@@ -4604,6 +4609,7 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc) ...@@ -4604,6 +4609,7 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc)
dev_warn(&h->pdev->dev, "FAILED abort on device C%d:B%d:T%d:L%d\n", dev_warn(&h->pdev->dev, "FAILED abort on device C%d:B%d:T%d:L%d\n",
h->scsi_host->host_no, h->scsi_host->host_no,
dev->bus, dev->target, dev->lun); dev->bus, dev->target, dev->lun);
cmd_free(h, abort);
return FAILED; return FAILED;
} }
dev_info(&h->pdev->dev, "%s REQUEST SUCCEEDED.\n", msg); dev_info(&h->pdev->dev, "%s REQUEST SUCCEEDED.\n", msg);
...@@ -4615,32 +4621,35 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc) ...@@ -4615,32 +4621,35 @@ static int hpsa_eh_abort_handler(struct scsi_cmnd *sc)
*/ */
#define ABORT_COMPLETE_WAIT_SECS 30 #define ABORT_COMPLETE_WAIT_SECS 30
for (i = 0; i < ABORT_COMPLETE_WAIT_SECS * 10; i++) { for (i = 0; i < ABORT_COMPLETE_WAIT_SECS * 10; i++) {
if (test_bit(abort->cmdindex & (BITS_PER_LONG - 1), refcount = atomic_read(&abort->refcount);
h->cmd_pool_bits + if (refcount < 2) {
(abort->cmdindex / BITS_PER_LONG))) cmd_free(h, abort);
msleep(100);
else
return SUCCESS; return SUCCESS;
} else {
msleep(100);
}
} }
dev_warn(&h->pdev->dev, "%s FAILED. Aborted command has not completed after %d seconds.\n", dev_warn(&h->pdev->dev, "%s FAILED. Aborted command has not completed after %d seconds.\n",
msg, ABORT_COMPLETE_WAIT_SECS); msg, ABORT_COMPLETE_WAIT_SECS);
cmd_free(h, abort);
return FAILED; return FAILED;
} }
/* /*
* For operations that cannot sleep, a command block is allocated at init, * For operations that cannot sleep, a command block is allocated at init,
* and managed by cmd_alloc() and cmd_free() using a simple bitmap to track * and managed by cmd_alloc() and cmd_free() using a simple bitmap to track
* which ones are free or in use. Lock must be held when calling this. * which ones are free or in use. Lock must be held when calling this.
* cmd_free() is the complement. * cmd_free() is the complement.
*/ */
static struct CommandList *cmd_alloc(struct ctlr_info *h) static struct CommandList *cmd_alloc(struct ctlr_info *h)
{ {
struct CommandList *c; struct CommandList *c;
int i; int i;
union u64bit temp64; union u64bit temp64;
dma_addr_t cmd_dma_handle, err_dma_handle; dma_addr_t cmd_dma_handle, err_dma_handle;
int loopcount; int refcount;
unsigned long offset = 0;
/* There is some *extremely* small but non-zero chance that that /* There is some *extremely* small but non-zero chance that that
* multiple threads could get in here, and one thread could * multiple threads could get in here, and one thread could
...@@ -4653,23 +4662,27 @@ static struct CommandList *cmd_alloc(struct ctlr_info *h) ...@@ -4653,23 +4662,27 @@ static struct CommandList *cmd_alloc(struct ctlr_info *h)
* infrequently as to be indistinguishable from never. * infrequently as to be indistinguishable from never.
*/ */
loopcount = 0; for (;;) {
do { i = find_next_zero_bit(h->cmd_pool_bits, h->nr_cmds, offset);
i = find_first_zero_bit(h->cmd_pool_bits, h->nr_cmds); if (unlikely(i == h->nr_cmds)) {
if (i == h->nr_cmds) offset = 0;
i = 0; continue;
loopcount++; }
} while (test_and_set_bit(i & (BITS_PER_LONG - 1), c = h->cmd_pool + i;
h->cmd_pool_bits + (i / BITS_PER_LONG)) != 0 && refcount = atomic_inc_return(&c->refcount);
loopcount < 10); if (unlikely(refcount > 1)) {
cmd_free(h, c); /* already in use */
/* Thread got starved? We do not expect this to ever happen. */ offset = (i + 1) % h->nr_cmds;
if (loopcount >= 10) continue;
return NULL; }
set_bit(i & (BITS_PER_LONG - 1),
c = h->cmd_pool + i; h->cmd_pool_bits + (i / BITS_PER_LONG));
memset(c, 0, sizeof(*c)); break; /* it's ours now. */
c->Header.tag = cpu_to_le64((u64) i << DIRECT_LOOKUP_SHIFT); }
/* Zero out all of commandlist except the last field, refcount */
memset(c, 0, offsetof(struct CommandList, refcount));
c->Header.tag = cpu_to_le64((u64) (i << DIRECT_LOOKUP_SHIFT));
cmd_dma_handle = h->cmd_pool_dhandle + i * sizeof(*c); cmd_dma_handle = h->cmd_pool_dhandle + i * sizeof(*c);
c->err_info = h->errinfo_pool + i; c->err_info = h->errinfo_pool + i;
memset(c->err_info, 0, sizeof(*c->err_info)); memset(c->err_info, 0, sizeof(*c->err_info));
...@@ -4680,8 +4693,8 @@ static struct CommandList *cmd_alloc(struct ctlr_info *h) ...@@ -4680,8 +4693,8 @@ static struct CommandList *cmd_alloc(struct ctlr_info *h)
c->busaddr = (u32) cmd_dma_handle; c->busaddr = (u32) cmd_dma_handle;
temp64.val = (u64) err_dma_handle; temp64.val = (u64) err_dma_handle;
c->ErrDesc.Addr = cpu_to_le64(err_dma_handle); c->ErrDesc.Addr = cpu_to_le64((u64) err_dma_handle);
c->ErrDesc.Len = cpu_to_le32(sizeof(*c->err_info)); c->ErrDesc.Len = cpu_to_le32((u32) sizeof(*c->err_info));
c->h = h; c->h = h;
return c; return c;
...@@ -4689,11 +4702,13 @@ static struct CommandList *cmd_alloc(struct ctlr_info *h) ...@@ -4689,11 +4702,13 @@ static struct CommandList *cmd_alloc(struct ctlr_info *h)
static void cmd_free(struct ctlr_info *h, struct CommandList *c) static void cmd_free(struct ctlr_info *h, struct CommandList *c)
{ {
int i; if (atomic_dec_and_test(&c->refcount)) {
int i;
i = c - h->cmd_pool; i = c - h->cmd_pool;
clear_bit(i & (BITS_PER_LONG - 1), clear_bit(i & (BITS_PER_LONG - 1),
h->cmd_pool_bits + (i / BITS_PER_LONG)); h->cmd_pool_bits + (i / BITS_PER_LONG));
}
} }
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
...@@ -6598,17 +6613,18 @@ static void hpsa_undo_allocations_after_kdump_soft_reset(struct ctlr_info *h) ...@@ -6598,17 +6613,18 @@ static void hpsa_undo_allocations_after_kdump_soft_reset(struct ctlr_info *h)
/* Called when controller lockup detected. */ /* Called when controller lockup detected. */
static void fail_all_outstanding_cmds(struct ctlr_info *h) static void fail_all_outstanding_cmds(struct ctlr_info *h)
{ {
int i; int i, refcount;
struct CommandList *c = NULL; struct CommandList *c;
flush_workqueue(h->resubmit_wq); /* ensure all cmds are fully built */ flush_workqueue(h->resubmit_wq); /* ensure all cmds are fully built */
for (i = 0; i < h->nr_cmds; i++) { for (i = 0; i < h->nr_cmds; i++) {
if (!test_bit(i & (BITS_PER_LONG - 1),
h->cmd_pool_bits + (i / BITS_PER_LONG)))
continue;
c = h->cmd_pool + i; c = h->cmd_pool + i;
c->err_info->CommandStatus = CMD_HARDWARE_ERR; refcount = atomic_inc_return(&c->refcount);
finish_cmd(c); if (refcount > 1) {
c->err_info->CommandStatus = CMD_HARDWARE_ERR;
finish_cmd(c);
}
cmd_free(h, c);
} }
} }
...@@ -6645,9 +6661,7 @@ static void controller_lockup_detected(struct ctlr_info *h) ...@@ -6645,9 +6661,7 @@ static void controller_lockup_detected(struct ctlr_info *h)
dev_warn(&h->pdev->dev, "Controller lockup detected: 0x%08x\n", dev_warn(&h->pdev->dev, "Controller lockup detected: 0x%08x\n",
lockup_detected); lockup_detected);
pci_disable_device(h->pdev); pci_disable_device(h->pdev);
spin_lock_irqsave(&h->lock, flags);
fail_all_outstanding_cmds(h); fail_all_outstanding_cmds(h);
spin_unlock_irqrestore(&h->lock, flags);
} }
static void detect_controller_lockup(struct ctlr_info *h) static void detect_controller_lockup(struct ctlr_info *h)
...@@ -7449,18 +7463,19 @@ static void hpsa_drain_accel_commands(struct ctlr_info *h) ...@@ -7449,18 +7463,19 @@ static void hpsa_drain_accel_commands(struct ctlr_info *h)
{ {
struct CommandList *c = NULL; struct CommandList *c = NULL;
int i, accel_cmds_out; int i, accel_cmds_out;
int refcount;
do { /* wait for all outstanding ioaccel commands to drain out */ do { /* wait for all outstanding ioaccel commands to drain out */
accel_cmds_out = 0; accel_cmds_out = 0;
for (i = 0; i < h->nr_cmds; i++) { for (i = 0; i < h->nr_cmds; i++) {
if (!test_bit(i & (BITS_PER_LONG - 1),
h->cmd_pool_bits + (i / BITS_PER_LONG)))
continue;
c = h->cmd_pool + i; c = h->cmd_pool + i;
accel_cmds_out += is_accelerated_cmd(c); refcount = atomic_inc_return(&c->refcount);
if (refcount > 1) /* Command is allocated */
accel_cmds_out += is_accelerated_cmd(c);
cmd_free(h, c);
} }
if (accel_cmds_out <= 0) if (accel_cmds_out <= 0)
break; break;
msleep(100); msleep(100);
} while (1); } while (1);
} }
......
...@@ -309,6 +309,8 @@ struct offline_device_entry { ...@@ -309,6 +309,8 @@ struct offline_device_entry {
*/ */
#define SA5_DOORBELL 0x20 #define SA5_DOORBELL 0x20
#define SA5_REQUEST_PORT_OFFSET 0x40 #define SA5_REQUEST_PORT_OFFSET 0x40
#define SA5_REQUEST_PORT64_LO_OFFSET 0xC0
#define SA5_REQUEST_PORT64_HI_OFFSET 0xC4
#define SA5_REPLY_INTR_MASK_OFFSET 0x34 #define SA5_REPLY_INTR_MASK_OFFSET 0x34
#define SA5_REPLY_PORT_OFFSET 0x44 #define SA5_REPLY_PORT_OFFSET 0x44
#define SA5_INTR_STATUS 0x30 #define SA5_INTR_STATUS 0x30
......
...@@ -421,6 +421,7 @@ struct CommandList { ...@@ -421,6 +421,7 @@ struct CommandList {
* not used. * not used.
*/ */
struct hpsa_scsi_dev_t *phys_disk; struct hpsa_scsi_dev_t *phys_disk;
atomic_t refcount; /* Must be last to avoid memset in cmd_alloc */
} __aligned(COMMANDLIST_ALIGNMENT); } __aligned(COMMANDLIST_ALIGNMENT);
/* Max S/G elements in I/O accelerator command */ /* Max S/G elements in I/O accelerator command */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment