[PATCH 18/18] nvme: let controller deletion wait out a fencing window

From: Mohamed Khalfella

Date: Fri Sep 18 2026 - 14:25:43 EST


DELETING is not reachable from FENCING or FENCED, so during a fencing
window nvme_delete_ctrl() fails with -EBUSY. Its callers assume
deleting a live controller cannot fail: a sysfs disconnect is silently
dropped after the delete_controller node is already gone, rdma device
removal returns with the controller still holding resources on the
outgoing device, and module unload leaks live controllers.

Fencing is time-bounded and always ends in a state that allows
deletion. Add nvme_delete_ctrl_wait(), which waits on ctrl->state_wq
for the fencing window to end and retries, and use it in the tcp/rdma
module exit paths and rdma device removal. Make nvme_delete_ctrl_sync()
wait the same way so a sysfs disconnect during fencing is delayed
instead of dropped.

nvme-fc's remoteport unregister with dev_loss_tmo == 0 keeps the
non-waiting call since it runs under rport->lock; it does not need to
wait, as dev_loss_end has already expired and the reconnect path
deletes the controller once fencing completes.

Signed-off-by: Mohamed Khalfella <mkhalfella@xxxxxxxxxxxxxxx>
---
drivers/nvme/host/core.c | 41 ++++++++++++++++++++++++++++++++++++++--
drivers/nvme/host/nvme.h | 1 +
drivers/nvme/host/rdma.c | 4 ++--
drivers/nvme/host/tcp.c | 2 +-
4 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index dc44e3af5f14..ca7f083702f5 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -271,6 +271,38 @@ int nvme_delete_ctrl(struct nvme_ctrl *ctrl)
}
EXPORT_SYMBOL_GPL(nvme_delete_ctrl);

+static bool nvme_ctrl_fencing(struct nvme_ctrl *ctrl)
+{
+ enum nvme_ctrl_state state = nvme_ctrl_state(ctrl);
+
+ return state == NVME_CTRL_FENCING || state == NVME_CTRL_FENCED;
+}
+
+static void nvme_wait_fencing_done(struct nvme_ctrl *ctrl)
+{
+ wait_event(ctrl->state_wq, !nvme_ctrl_fencing(ctrl));
+}
+
+/*
+ * Like nvme_delete_ctrl(), but waits for a fencing window to end
+ * instead of failing with -EBUSY. May sleep; callers that cannot
+ * sleep must use nvme_delete_ctrl() and handle the failure.
+ */
+int nvme_delete_ctrl_wait(struct nvme_ctrl *ctrl)
+{
+ int ret;
+
+ might_sleep();
+
+ for (;;) {
+ ret = nvme_delete_ctrl(ctrl);
+ if (!ret || !nvme_ctrl_fencing(ctrl))
+ return ret;
+ nvme_wait_fencing_done(ctrl);
+ }
+}
+EXPORT_SYMBOL_GPL(nvme_delete_ctrl_wait);
+
void nvme_delete_ctrl_sync(struct nvme_ctrl *ctrl)
{
/*
@@ -278,8 +310,13 @@ void nvme_delete_ctrl_sync(struct nvme_ctrl *ctrl)
* since ->delete_ctrl can free the controller.
*/
nvme_get_ctrl(ctrl);
- if (nvme_change_ctrl_state(ctrl, NVME_CTRL_DELETING))
- nvme_do_delete_ctrl(ctrl);
+ while (!nvme_change_ctrl_state(ctrl, NVME_CTRL_DELETING)) {
+ if (!nvme_ctrl_fencing(ctrl))
+ goto out_put;
+ nvme_wait_fencing_done(ctrl);
+ }
+ nvme_do_delete_ctrl(ctrl);
+out_put:
nvme_put_ctrl(ctrl);
}

diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index bcecaaf06f7e..bf8c2bf91ad3 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -1026,6 +1026,7 @@ void nvme_stop_keep_alive(struct nvme_ctrl *ctrl);
int nvme_reset_ctrl(struct nvme_ctrl *ctrl);
int nvme_reset_ctrl_sync(struct nvme_ctrl *ctrl);
int nvme_delete_ctrl(struct nvme_ctrl *ctrl);
+int nvme_delete_ctrl_wait(struct nvme_ctrl *ctrl);
void nvme_queue_scan(struct nvme_ctrl *ctrl);
int nvme_get_log(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, u8 lsp, u8 csi,
void *log, size_t size, u64 offset);
diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c
index 8ff5aa312377..ce37e3eafeae 100644
--- a/drivers/nvme/host/rdma.c
+++ b/drivers/nvme/host/rdma.c
@@ -2483,7 +2483,7 @@ static void nvme_rdma_remove_one(struct ib_device *ib_device, void *client_data)
list_for_each_entry(ctrl, &nvme_rdma_ctrl_list, list) {
if (ctrl->device->dev != ib_device)
continue;
- nvme_delete_ctrl(&ctrl->ctrl);
+ nvme_delete_ctrl_wait(&ctrl->ctrl);
}
mutex_unlock(&nvme_rdma_ctrl_mutex);

@@ -2523,7 +2523,7 @@ static void __exit nvme_rdma_cleanup_module(void)

mutex_lock(&nvme_rdma_ctrl_mutex);
list_for_each_entry(ctrl, &nvme_rdma_ctrl_list, list)
- nvme_delete_ctrl(&ctrl->ctrl);
+ nvme_delete_ctrl_wait(&ctrl->ctrl);
mutex_unlock(&nvme_rdma_ctrl_mutex);
flush_workqueue(nvme_delete_wq);
}
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index 7b1d9e2cb00d..9331a3e0bd9a 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -3208,7 +3208,7 @@ static void __exit nvme_tcp_cleanup_module(void)

mutex_lock(&nvme_tcp_ctrl_mutex);
list_for_each_entry(ctrl, &nvme_tcp_ctrl_list, list)
- nvme_delete_ctrl(&ctrl->ctrl);
+ nvme_delete_ctrl_wait(&ctrl->ctrl);
mutex_unlock(&nvme_tcp_ctrl_mutex);
flush_workqueue(nvme_delete_wq);

--
2.55.0