NVMe: Allow fatal signals to interrupt I/O
authorMatthew Wilcox <matthew.r.wilcox@intel.com>
Fri, 4 Feb 2011 21:03:56 +0000 (16:03 -0500)
committerMatthew Wilcox <matthew.r.wilcox@intel.com>
Fri, 4 Nov 2011 19:52:55 +0000 (15:52 -0400)
If the user sends a fatal signal, sleeping in the TASK_KILLABLE state
permits the task to be aborted.  The only wrinkle is making sure that
if/when the command completes later that it doesn't upset anything.
Handle this by setting the data pointer to 0, and checking the value
isn't NULL in the sync completion path.  Eventually, bios can be cancelled
through this path too.  Note that the cmdid isn't freed to prevent reuse.

We should also abort the command in the future, but this is a good start.

Signed-off-by: Matthew Wilcox <matthew.r.wilcox@intel.com>
drivers/block/nvme.c

index 06a6aeaa827a6b499e05dbb9ee42b5cc7bd58234..4bfed59f3629263ecf3cbd924f47e9fede212812 100644 (file)
@@ -155,7 +155,9 @@ static int alloc_cmdid_killable(struct nvme_queue *nvmeq, void *ctx,
 }
 
 /* If you need more than four handlers, you'll need to change how
- * alloc_cmdid and nvme_process_cq work
+ * alloc_cmdid and nvme_process_cq work.  Also, aborted commands take
+ * the sync_completion path (if they complete), so don't put anything
+ * else in slot zero.
  */
 enum {
        sync_completion_id = 0,
@@ -172,6 +174,11 @@ static unsigned long free_cmdid(struct nvme_queue *nvmeq, int cmdid)
        return data;
 }
 
+static void clear_cmdid_data(struct nvme_queue *nvmeq, int cmdid)
+{
+       nvmeq->cmdid_data[cmdid + BITS_TO_LONGS(nvmeq->q_depth)] = 0;
+}
+
 static struct nvme_queue *get_nvmeq(struct nvme_ns *ns)
 {
        int qid, cpu = get_cpu();
@@ -386,6 +393,8 @@ static void sync_completion(struct nvme_queue *nvmeq, void *ctx,
                                                struct nvme_completion *cqe)
 {
        struct sync_cmd_info *cmdinfo = ctx;
+       if (!cmdinfo)
+               return; /* Command aborted */
        cmdinfo->result = le32_to_cpup(&cqe->result);
        cmdinfo->status = le16_to_cpup(&cqe->status) >> 1;
        wake_up_process(cmdinfo->task);
@@ -446,12 +455,19 @@ static irqreturn_t nvme_irq(int irq, void *data)
        return nvme_process_cq(data);
 }
 
+static void nvme_abort_command(struct nvme_queue *nvmeq, int cmdid)
+{
+       spin_lock_irq(&nvmeq->q_lock);
+       clear_cmdid_data(nvmeq, cmdid);
+       spin_unlock_irq(&nvmeq->q_lock);
+}
+
 /*
  * Returns 0 on success.  If the result is negative, it's a Linux error code;
  * if the result is positive, it's an NVM Express status code
  */
-static int nvme_submit_sync_cmd(struct nvme_queue *q, struct nvme_command *cmd,
-                                                               u32 *result)
+static int nvme_submit_sync_cmd(struct nvme_queue *nvmeq,
+                                       struct nvme_command *cmd, u32 *result)
 {
        int cmdid;
        struct sync_cmd_info cmdinfo;
@@ -459,15 +475,20 @@ static int nvme_submit_sync_cmd(struct nvme_queue *q, struct nvme_command *cmd,
        cmdinfo.task = current;
        cmdinfo.status = -EINTR;
 
-       cmdid = alloc_cmdid_killable(q, &cmdinfo, sync_completion_id);
+       cmdid = alloc_cmdid_killable(nvmeq, &cmdinfo, sync_completion_id);
        if (cmdid < 0)
                return cmdid;
        cmd->common.command_id = cmdid;
 
-       set_current_state(TASK_UNINTERRUPTIBLE);
-       nvme_submit_cmd(q, cmd);
+       set_current_state(TASK_KILLABLE);
+       nvme_submit_cmd(nvmeq, cmd);
        schedule();
 
+       if (cmdinfo.status == -EINTR) {
+               nvme_abort_command(nvmeq, cmdid);
+               return -EINTR;
+       }
+
        if (result)
                *result = cmdinfo.result;