nvme: stop aer posting if controller state not live
authorJames Smart <jsmart2021@gmail.com>
Thu, 14 Sep 2017 18:03:09 +0000 (11:03 -0700)
committerJens Axboe <axboe@kernel.dk>
Mon, 25 Sep 2017 14:56:05 +0000 (08:56 -0600)
If an nvme async_event command completes, in most cases, a new
async event is posted. However, if the controller enters a
resetting or reconnecting state, there is nothing to block the
scheduled work element from posting the async event again. Nor are
there calls from the transport to stop async events when an
association dies.

In the case of FC, where the association is torn down, the aer must
be aborted on the FC link and completes through the normal job
completion path. Thus the terminated async event ends up being
rescheduled even though the controller isn't in a valid state for
the aer, and the reposting gets the transport into a partially torn
down data structure.

It's possible to hit the scenario on rdma, although much less likely
due to an aer completing right as the association is terminated and
as the association teardown reclaims the blk requests via
nvme_cancel_request() so its immediate, not a link-related action
like on FC.

Fix by putting controller state checks in both the async event
completion routine where it schedules the async event and in the
async event work routine before it calls into the transport. It's
effectively a "stop_async_events()" behavior.  The transport, when
it creates a new association with the subsystem will transition
the state back to live and is already restarting the async event
posting.

Signed-off-by: James Smart <james.smart@broadcom.com>
[hch: remove taking a lock over reading the controller state]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
drivers/nvme/host/core.c

index acc816b67582f30524ad19f66843b071dfcef6ae..d470f031e27f52174700f50a81cd6600dc989d43 100644 (file)
@@ -2590,7 +2590,7 @@ static void nvme_async_event_work(struct work_struct *work)
                container_of(work, struct nvme_ctrl, async_event_work);
 
        spin_lock_irq(&ctrl->lock);
-       while (ctrl->event_limit > 0) {
+       while (ctrl->state == NVME_CTRL_LIVE && ctrl->event_limit > 0) {
                int aer_idx = --ctrl->event_limit;
 
                spin_unlock_irq(&ctrl->lock);
@@ -2677,7 +2677,8 @@ void nvme_complete_async_event(struct nvme_ctrl *ctrl, __le16 status,
                /*FALLTHRU*/
        case NVME_SC_ABORT_REQ:
                ++ctrl->event_limit;
-               queue_work(nvme_wq, &ctrl->async_event_work);
+               if (ctrl->state == NVME_CTRL_LIVE)
+                       schedule_work(&ctrl->async_event_work);
                break;
        default:
                break;