[PATCH v2 6/6] scsi: bsg: release io_uring requests in end-I/O
From: Yingjie Wang
Date: Mon Oct 26 2026 - 04:32:24 EST
From: Yingjie Wang <x-professor@xxxxxxxxxxx>
The SCSI BSG end-I/O callback returns RQ_END_IO_NONE and leaves the
completed request for task work to release. The request therefore
retains its tag before task work runs as well as during user copyback.
With deferred task work, completing the device command alone does not
make that request available to other I/O.
Save the status, residual count and bounded sense data in end-I/O, then
return RQ_END_IO_FREE. Task work can unmap the saved BIO, copy the saved
sense data and publish the CQE without accessing the request or scmd.
Allocate sense storage at submission only when a nonempty response
buffer was requested. Charge this user-triggered allocation to memcg.
Do this before acquiring the request, respect nonblocking allocation,
and return -EAGAIN for an allocation failure that can be retried in
blocking context. Free the storage on submission failure or after
task-work copyback. Reuse the snapshot-capacity field for the packed
completion result after the end-I/O snapshot, keeping the PDU within
32 bytes.
Preallocation avoids relying on a non-sleeping allocation in end-I/O
to preserve sense data after a command has already completed.
Fixes: 7b6d3255e7f8 ("scsi: bsg: add io_uring passthrough handler")
Link: https://lore.kernel.org/r/20261022.scsi-copyback-review.31b8d6a70f4c@xxxxxxxxxxx
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yingjie Wang <x-professor@xxxxxxxxxxx>
---
drivers/scsi/scsi_bsg.c | 78 ++++++++++++++++++++++++++---------------
1 file changed, 49 insertions(+), 29 deletions(-)
diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c
index 98c9db1cae653..fafa5c2cdac0e 100644
--- a/drivers/scsi/scsi_bsg.c
+++ b/drivers/scsi/scsi_bsg.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bsg.h>
#include <linux/io_uring/cmd.h>
+#include <linux/slab.h>
#include <scsi/scsi.h>
#include <scsi/scsi_ioctl.h>
#include <scsi/scsi_cmnd.h>
@@ -16,9 +17,13 @@
*/
struct scsi_bsg_uring_cmd_pdu {
struct bio *bio; /* mapped user buffer, unmap in task work */
- struct request *req; /* block request, freed in task work */
+ u8 *sense_buffer; /* allocated at submission, freed in task work */
u64 response_addr; /* user space response buffer address */
- u32 max_response_len; /* user response buffer size */
+ /* max_response_len is replaced by res2 after the end-I/O snapshot. */
+ union {
+ u32 max_response_len; /* maximum sense snapshot size */
+ u64 res2; /* saved completion result */
+ };
};
static_assert(sizeof(struct scsi_bsg_uring_cmd_pdu) <= sizeof_field(struct io_uring_cmd, pdu));
@@ -28,41 +33,30 @@ static inline struct scsi_bsg_uring_cmd_pdu *scsi_bsg_uring_cmd_pdu(
return io_uring_cmd_to_pdu(ioucmd, struct scsi_bsg_uring_cmd_pdu);
}
-/* Task work: build res2 (layout in uapi/linux/bsg.h) and copy sense to user. */
+/* Task work: unmap the saved BIO and copy sense to userspace. */
static void scsi_bsg_uring_task_cb(struct io_tw_req tw_req, io_tw_token_t tw)
{
struct io_uring_cmd *ioucmd = io_uring_cmd_from_tw(tw_req);
struct scsi_bsg_uring_cmd_pdu *pdu = scsi_bsg_uring_cmd_pdu(ioucmd);
- struct request *rq = pdu->req;
- struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(rq);
- u64 res2;
+ u64 res2 = pdu->res2;
int ret = 0;
- u8 driver_status = 0;
- u8 sense_len_wr = 0;
+ u8 sense_len_wr = bsg_scsi_res2_sense_len(res2);
if (pdu->bio)
ret = blk_rq_unmap_user(pdu->bio);
- if (scsi_status_is_check_condition(scmd->result)) {
- driver_status = DRIVER_SENSE;
- if (pdu->response_addr)
- sense_len_wr = min_t(unsigned int, pdu->max_response_len,
- scmd->sense_len);
- }
-
if (sense_len_wr) {
- if (copy_to_user(uptr64(pdu->response_addr), scmd->sense_buffer,
+ if (copy_to_user(uptr64(pdu->response_addr), pdu->sense_buffer,
sense_len_wr)) {
ret = -EFAULT;
- sense_len_wr = 0;
+ res2 = bsg_scsi_res2_build(bsg_scsi_res2_device_status(res2),
+ bsg_scsi_res2_driver_status(res2),
+ bsg_scsi_res2_host_status(res2), 0,
+ bsg_scsi_res2_resid_len(res2));
}
}
- res2 = bsg_scsi_res2_build(status_byte(scmd->result), driver_status,
- host_byte(scmd->result), sense_len_wr,
- scmd->resid_len);
-
- blk_mq_free_request(rq);
+ kfree(pdu->sense_buffer);
io_uring_cmd_done32(ioucmd, ret, res2,
IO_URING_CMD_TASK_WORK_ISSUE_FLAGS);
}
@@ -72,9 +66,24 @@ static enum rq_end_io_ret scsi_bsg_uring_cmd_done(struct request *req,
const struct io_comp_batch *iocb)
{
struct io_uring_cmd *ioucmd = req->end_io_data;
+ struct scsi_bsg_uring_cmd_pdu *pdu = scsi_bsg_uring_cmd_pdu(ioucmd);
+ struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
+ u8 driver_status = 0;
+ u8 sense_len = 0;
+
+ if (scsi_status_is_check_condition(scmd->result)) {
+ driver_status = DRIVER_SENSE;
+ if (pdu->sense_buffer) {
+ sense_len = min(pdu->max_response_len, scmd->sense_len);
+ memcpy(pdu->sense_buffer, scmd->sense_buffer, sense_len);
+ }
+ }
+ pdu->res2 = bsg_scsi_res2_build(status_byte(scmd->result), driver_status,
+ host_byte(scmd->result), sense_len,
+ scmd->resid_len);
io_uring_cmd_do_in_task_lazy(ioucmd, scsi_bsg_uring_task_cb);
- return RQ_END_IO_NONE;
+ return RQ_END_IO_FREE;
}
static int scsi_bsg_map_user_buffer(struct request *req,
@@ -139,10 +148,23 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
gfp_mask = GFP_NOWAIT;
}
+ pdu->response_addr = READ_ONCE(cmd->response);
+ pdu->max_response_len = min(READ_ONCE(cmd->max_response_len),
+ SCSI_SENSE_BUFFERSIZE);
+ pdu->sense_buffer = NULL;
+ if (pdu->response_addr && pdu->max_response_len) {
+ pdu->sense_buffer = kmalloc(pdu->max_response_len,
+ gfp_mask | __GFP_ACCOUNT);
+ if (!pdu->sense_buffer)
+ return (issue_flags & IO_URING_F_NONBLOCK) ? -EAGAIN : -ENOMEM;
+ }
+
req = scsi_alloc_request(q, dout_xfer_len ?
REQ_OP_DRV_OUT : REQ_OP_DRV_IN, blk_flags);
- if (IS_ERR(req))
- return PTR_ERR(req);
+ if (IS_ERR(req)) {
+ ret = PTR_ERR(req);
+ goto out_free_sense;
+ }
scmd = blk_mq_rq_to_pdu(req);
if (request_len > sizeof(scmd->cmnd)) {
@@ -162,9 +184,6 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
goto out_free_req;
}
- pdu->response_addr = cmd->response;
- pdu->max_response_len = cmd->max_response_len;
-
if (dout_xfer_len || din_xfer_len) {
bool is_write = dout_xfer_len > 0;
u64 buf_addr = is_write ? dout_xferp : din_xferp;
@@ -185,13 +204,14 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
req->end_io = scsi_bsg_uring_cmd_done;
req->end_io_data = ioucmd;
- pdu->req = req;
blk_execute_rq_nowait(req, false);
return -EIOCBQUEUED;
out_free_req:
blk_mq_free_request(req);
+out_free_sense:
+ kfree(pdu->sense_buffer);
return ret;
}
--
2.43.0