[PATCH 19/19] nvmet: wait only for executing I/O in preempt and abort

Christoph Hellwig hch at lst.de
Mon Nov 2 22:15:44 PST 2026


Adding Guixin for the nvmet PR code.  It would have been useful
to group things a bit more..

On Thu, Oct 29, 2026 at 08:52:52AM -0700, Keith Busch wrote:
> From: Chris Mason <mason at kernel.org>
> 
> nvmet_parse_io_cmd() takes the pc_ref for an I/O command as soon as it's
> parsed, and "preempt and abort" waits for all pc_refs of the preempted
> controllers before it releases pr_sem. But a write that still needs its
> data can't complete without the host sending it and without the
> transport context receiving it, and that same context is where Register,
> Acquire and Release sleep on pr_sem. So a host that sends a write and
> holds the data back, then preempt and abort with its own key, then
> Register on the same queue, deadlocks io_work, the abort work and the
> write with each other. Two well-behaved hosts get there by accident
> when one fences the other while it re-registers under I/O. The
> connection can't be torn down, the namespace can't be disabled, and
> with nvmet_config_sem held across that, nobody can connect.
> 
> Take the pc_ref when the transport calls ->execute(), once all the data
> is there: nvmet_parse_io_cmd() stashes the real execute method and
> installs nvmet_pr_execute_io(), which gets the reference, rechecks the
> reservation access since the host may have lost it since parse time,
> and calls through. Preempt and abort then only waits for commands that
> have started executing, which need neither the host nor the transport
> to finish. A write whose data arrives after the abort now fails with
> Reservation Conflict where it was previously executed, that's the
> input the hang needs.
> 
> Fixes: 5a47c2080a73 ("nvmet: support reservation feature")
> Cc: Christoph Hellwig <hch at lst.de>
> Cc: Sagi Grimberg <sagi at grimberg.me>
> Cc: Chaitanya Kulkarni <kch at nvidia.com>
> Signed-off-by: Chris Mason <mason at kernel.org>
> Signed-off-by: Jens Axboe <axboe at kernel.dk>
> Signed-off-by: Keith Busch <kbusch at kernel.org>
> ---
>  drivers/nvme/target/core.c  |  2 +-
>  drivers/nvme/target/nvmet.h |  4 +++-
>  drivers/nvme/target/pr.c    | 42 ++++++++++++++++++++++++++++++++++++-
>  3 files changed, 45 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
> index 1ed250cd4b56e..4f6fb7c4181ab 100644
> --- a/drivers/nvme/target/core.c
> +++ b/drivers/nvme/target/core.c
> @@ -1176,7 +1176,7 @@ static u16 nvmet_parse_io_cmd(struct nvmet_req *req)
>  		if (ret)
>  			return ret;
>  
> -		ret = nvmet_pr_get_ns_pc_ref(req);
> +		nvmet_pr_set_execute(req);
>  	}
>  	return ret;
>  }
> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
> index 7b429cc16d491..99b3bf5cbd3b6 100644
> --- a/drivers/nvme/target/nvmet.h
> +++ b/drivers/nvme/target/nvmet.h
> @@ -513,6 +513,8 @@ struct nvmet_req {
>  	struct nvmet_port	*port;
>  
>  	void (*execute)(struct nvmet_req *req);
> +	/* what nvmet_pr_execute_io() calls once it has the pc_ref */
> +	void (*pr_execute)(struct nvmet_req *req);
>  	const struct nvmet_fabrics_ops *ops;
>  
>  	struct pci_dev		*p2p_dev;
> @@ -1041,7 +1043,7 @@ void nvmet_pr_exit_ns(struct nvmet_ns *ns);
>  void nvmet_execute_get_log_page_resv(struct nvmet_req *req);
>  u16 nvmet_set_feat_resv_notif_mask(struct nvmet_req *req, u32 mask);
>  u16 nvmet_get_feat_resv_notif_mask(struct nvmet_req *req);
> -u16 nvmet_pr_get_ns_pc_ref(struct nvmet_req *req);
> +void nvmet_pr_set_execute(struct nvmet_req *req);
>  static inline void nvmet_pr_put_ns_pc_ref(struct nvmet_pr_per_ctrl_ref *pc_ref)
>  {
>  	percpu_ref_put(&pc_ref->ref);
> diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c
> index 1a03f74c2e14f..798f5ce9011f8 100644
> --- a/drivers/nvme/target/pr.c
> +++ b/drivers/nvme/target/pr.c
> @@ -1060,7 +1060,7 @@ u16 nvmet_pr_check_cmd_access(struct nvmet_req *req)
>  	return status;
>  }
>  
> -u16 nvmet_pr_get_ns_pc_ref(struct nvmet_req *req)
> +static u16 nvmet_pr_get_ns_pc_ref(struct nvmet_req *req)
>  {
>  	struct nvmet_pr_per_ctrl_ref *pc_ref;
>  
> @@ -1072,6 +1072,46 @@ u16 nvmet_pr_get_ns_pc_ref(struct nvmet_req *req)
>  	return NVME_SC_SUCCESS;
>  }
>  
> +static void nvmet_pr_execute_io(struct nvmet_req *req)
> +{
> +	u16 status;
> +
> +	status = nvmet_pr_get_ns_pc_ref(req);
> +	if (!status) {
> +		/*
> +		 * The host can have lost its registration since the command was
> +		 * parsed. A get that succeeds after "preempt and abort" has
> +		 * resurrected the pc_ref has to see that. In percpu mode the
> +		 * get orders no later load, so this pairs with the
> +		 * smp_store_release() in __percpu_ref_switch_to_percpu(). While
> +		 * the resurrect is under way the get is a fully ordered
> +		 * atomic_long_inc_not_zero(). queue_work() in
> +		 * nvmet_execute_pr_acquire() orders the unregistration before
> +		 * the resurrect.
> +		 */
> +		smp_rmb();
> +		status = nvmet_pr_check_cmd_access(req);
> +	}
> +	if (status) {
> +		nvmet_req_complete(req, status);
> +		return;
> +	}
> +
> +	req->pr_execute(req);
> +}
> +
> +/*
> + * "Preempt and abort" waits for the commands that hold a pc_ref. Until the
> + * transport calls ->execute(), the end of a command can depend on the host,
> + * which may still have to send the data, and on the context and the resources
> + * of the transport. So a command gets its pc_ref only then.
> + */
> +void nvmet_pr_set_execute(struct nvmet_req *req)
> +{
> +	req->pr_execute = req->execute;
> +	req->execute = nvmet_pr_execute_io;
> +}
> +
>  static void nvmet_pr_ctrl_ns_all_cmds_done(struct percpu_ref *ref)
>  {
>  	struct nvmet_pr_per_ctrl_ref *pc_ref =
> -- 
> 2.52.0
---end quoted text---



More information about the Linux-nvme mailing list