From 5ac560183c525fe61be86831360ab27fd8c8e0ee Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Fri, 16 Jan 2026 12:25:14 +0800 Subject: [PATCH 1/3] bsg: add bsg_uring_cmd uapi structure Add the bsg_uring_cmd structure to the BSG UAPI header to support io_uring-based SCSI passthrough operations via IORING_OP_URING_CMD. Signed-off-by: Yang Xiuwei --- include/uapi/linux/bsg.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/include/uapi/linux/bsg.h b/include/uapi/linux/bsg.h index cd6302def5ed7..d9514b99b7a96 100644 --- a/include/uapi/linux/bsg.h +++ b/include/uapi/linux/bsg.h @@ -63,5 +63,29 @@ struct sg_io_v4 { __u32 padding; }; +struct bsg_uring_cmd { + /* Command request related */ + __u64 request; /* [i], [*i] command descriptor address */ + __u32 request_len; /* [i] command descriptor length in bytes */ + __u32 protocol; /* [i] protocol type (BSG_PROTOCOL_*) */ + __u32 subprotocol; /* [i] subprotocol type (BSG_SUB_PROTOCOL_*) */ + __u32 max_response_len; /* [i] response buffer size in bytes */ + /* Response data related */ + __u64 response; /* [i], [*o] response data address */ + /* Data transfer related - dout */ + __u64 dout_xferp; /* [i], [*i] */ + __u32 dout_xfer_len; /* [i] bytes to be transferred to device */ + __u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else + * dout_xferp points to array of iovec + */ + /* Data transfer related - din */ + __u64 din_xferp; /* [i], [*o] */ + __u32 din_xfer_len; /* [i] bytes to be transferred from device */ + __u32 din_iovec_count; /* [i] 0 -> "flat" din transfer */ + /* Control related */ + __u32 timeout_ms; /* [i] timeout in milliseconds */ + __u32 flags; /* [i] bit mask */ + __u8 reserved[8]; /* reserved for future extension */ +}; #endif /* _UAPIBSG_H */ From 593caf73eaf10737e3009a57ea994301c4fc4107 Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Fri, 16 Jan 2026 12:25:15 +0800 Subject: [PATCH 2/3] bsg: add uring_cmd support to BSG generic layer Add io_uring command handler to the generic BSG layer. This handler validates that SQE128 and CQE32 flags are set (required for the command structure and status information), then delegates to the SCSI-specific handler. Signed-off-by: Yang Xiuwei --- block/bsg.c | 66 +++++++++++++++++++++++++++++++++++++++++ drivers/scsi/scsi_bsg.c | 7 +++++ include/linux/bsg.h | 4 +++ 3 files changed, 77 insertions(+) diff --git a/block/bsg.c b/block/bsg.c index 72157a59b7881..51f9f3cc57c4c 100644 --- a/block/bsg.c +++ b/block/bsg.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -158,11 +159,76 @@ static long bsg_ioctl(struct file *file, unsigned int cmd, unsigned long arg) } } +static int bsg_check_uring_features(unsigned int issue_flags) +{ + /* BSG passthrough requires big SQE/CQE support */ + if ((issue_flags & (IO_URING_F_SQE128|IO_URING_F_CQE32)) != + (IO_URING_F_SQE128|IO_URING_F_CQE32)) + return -EOPNOTSUPP; + return 0; +} + +static int bsg_validate_command(const struct bsg_uring_cmd *cmd) +{ + if (cmd->protocol != BSG_PROTOCOL_SCSI) + return -EINVAL; + + if (cmd->subprotocol == BSG_SUB_PROTOCOL_SCSI_CMD) { + if (!cmd->request || cmd->request_len == 0) + return -EINVAL; + + if (cmd->dout_xfer_len && cmd->din_xfer_len) { + pr_warn_once("BIDI support in bsg has been removed.\n"); + return -EOPNOTSUPP; + } + + if (cmd->dout_iovec_count > 0 || cmd->din_iovec_count > 0) + return -EOPNOTSUPP; + + return 0; + } else if (cmd->subprotocol == BSG_SUB_PROTOCOL_SCSI_TRANSPORT) { + return 0; + } + + return -EINVAL; +} + +static int bsg_uring_cmd(struct io_uring_cmd *ioucmd, unsigned int issue_flags) +{ + struct bsg_device *bd = to_bsg_device(file_inode(ioucmd->file)); + struct request_queue *q = bd->queue; + bool open_for_write = ioucmd->file->f_mode & FMODE_WRITE; + const struct bsg_uring_cmd *cmd = io_uring_sqe_cmd(ioucmd->sqe); + int ret; + + if (!q) + return -EINVAL; + + ret = bsg_check_uring_features(issue_flags); + if (ret) + return ret; + + ret = bsg_validate_command(cmd); + if (ret) + return ret; + + if (cmd->protocol == BSG_PROTOCOL_SCSI) { + if (cmd->subprotocol == BSG_SUB_PROTOCOL_SCSI_CMD) + return scsi_bsg_uring_cmd(q, ioucmd, issue_flags, open_for_write); + else if (cmd->subprotocol == BSG_SUB_PROTOCOL_SCSI_TRANSPORT) + return -EOPNOTSUPP; + return -EINVAL; + } + + return -EINVAL; +} + static const struct file_operations bsg_fops = { .open = bsg_open, .release = bsg_release, .unlocked_ioctl = bsg_ioctl, .compat_ioctl = compat_ptr_ioctl, + .uring_cmd = bsg_uring_cmd, .owner = THIS_MODULE, .llseek = default_llseek, }; diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c index a9a9ec086a7e3..4399a25990fc1 100644 --- a/drivers/scsi/scsi_bsg.c +++ b/drivers/scsi/scsi_bsg.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include @@ -9,6 +10,12 @@ #define uptr64(val) ((void __user *)(uintptr_t)(val)) +int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *ioucmd, + unsigned int issue_flags, bool open_for_write) +{ + return -EOPNOTSUPP; +} + static int scsi_bsg_sg_io_fn(struct request_queue *q, struct sg_io_v4 *hdr, bool open_for_write, unsigned int timeout) { diff --git a/include/linux/bsg.h b/include/linux/bsg.h index ee2df73edf83f..68ec50b5e97c0 100644 --- a/include/linux/bsg.h +++ b/include/linux/bsg.h @@ -7,6 +7,7 @@ struct bsg_device; struct device; struct request_queue; +struct io_uring_cmd; typedef int (bsg_sg_io_fn)(struct request_queue *, struct sg_io_v4 *hdr, bool open_for_write, unsigned int timeout); @@ -16,4 +17,7 @@ struct bsg_device *bsg_register_queue(struct request_queue *q, bsg_sg_io_fn *sg_io_fn); void bsg_unregister_queue(struct bsg_device *bcd); +int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *ioucmd, + unsigned int issue_flags, bool open_for_write); + #endif /* _LINUX_BSG_H */ From 954be0a20bc5cdbd5a85480d4118b9492c3f740c Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Fri, 16 Jan 2026 12:25:16 +0800 Subject: [PATCH 3/3] bsg: implement SCSI BSG uring_cmd handler Implement the SCSI-specific io_uring command handler for BSG. This handler processes SCSI passthrough commands asynchronously via io_uring, supporting both traditional user buffers and zero-copy fixed buffers. Key features: - Async command execution with proper completion handling - Zero-copy support via io_uring fixed buffers - Status information returned in CQE res2 field - Non-blocking I/O support via IO_URING_F_NONBLOCK - Proper error handling and validation The implementation uses a PDU structure overlaying io_uring_cmd.pdu[32] to store temporary state during command execution. Completion is handled via task work to safely access user space. This patch replaces the stub implementation from patch 2/3 with the full implementation. Signed-off-by: Yang Xiuwei --- drivers/scsi/scsi_bsg.c | 187 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c index 4399a25990fc1..3a45a597855af 100644 --- a/drivers/scsi/scsi_bsg.c +++ b/drivers/scsi/scsi_bsg.c @@ -10,10 +10,195 @@ #define uptr64(val) ((void __user *)(uintptr_t)(val)) +/* + * BSG io_uring PDU structure overlaying io_uring_cmd.pdu[32]. + * Stores temporary data needed during command execution. + */ +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 */ + u64 response_addr; /* user space response buffer address */ + u32 resid_len; /* residual transfer length */ + /* Protocol-specific status fields using union for extensibility */ + union { + struct { + u8 device_status; /* SCSI device status (low 8 bits of result) */ + u8 driver_status; /* SCSI driver status (DRIVER_SENSE if check) */ + u8 host_status; /* SCSI host status (host_byte of result) */ + u8 sense_len_wr; /* actual sense data length written */ + } scsi; + /* Future protocols can add their own status layouts here */ + }; +}; + +static inline struct scsi_bsg_uring_cmd_pdu *scsi_bsg_uring_cmd_pdu( + struct io_uring_cmd *ioucmd) +{ + return io_uring_cmd_to_pdu(ioucmd, struct scsi_bsg_uring_cmd_pdu); +} + +/* + * Task work callback executed in process context. + * Builds res2 with status information and copies sense data to user space. + * res2 layout (64-bit): + * 0-7: device_status + * 8-15: driver_status + * 16-23: host_status + * 24-31: sense_len_wr + * 32-63: resid_len + */ +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 scsi_cmnd *scmd; + struct request *rq = pdu->req; + int ret = 0; + u64 res2; + + scmd = blk_mq_rq_to_pdu(rq); + + if (pdu->bio) + blk_rq_unmap_user(pdu->bio); + + /* Build res2 with status information */ + res2 = ((u64)pdu->resid_len << 32) | + ((u64)(pdu->scsi.sense_len_wr & 0xff) << 24) | + ((u64)(pdu->scsi.host_status & 0xff) << 16) | + ((u64)(pdu->scsi.driver_status & 0xff) << 8) | + (pdu->scsi.device_status & 0xff); + + if (pdu->scsi.sense_len_wr && pdu->response_addr) { + if (copy_to_user(uptr64(pdu->response_addr), scmd->sense_buffer, + pdu->scsi.sense_len_wr)) + ret = -EFAULT; + } + + blk_mq_free_request(rq); + io_uring_cmd_done32(ioucmd, ret, res2, + IO_URING_CMD_TASK_WORK_ISSUE_FLAGS); +} + +/* + * Async completion callback executed in interrupt/atomic context. + * Saves SCSI status information and schedules task work for final completion. + */ +static enum rq_end_io_ret scsi_bsg_uring_cmd_done(struct request *req, + blk_status_t status) +{ + 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); + + /* Pack SCSI status fields into union */ + pdu->scsi.device_status = scmd->result & 0xff; + pdu->scsi.host_status = host_byte(scmd->result); + pdu->scsi.driver_status = 0; + pdu->scsi.sense_len_wr = 0; + + if (scsi_status_is_check_condition(scmd->result)) { + pdu->scsi.driver_status = DRIVER_SENSE; + if (pdu->response_addr) + pdu->scsi.sense_len_wr = min_t(u8, scmd->sense_len, SCSI_SENSE_BUFFERSIZE); + } + + pdu->resid_len = scmd->resid_len; + + io_uring_cmd_do_in_task_lazy(ioucmd, scsi_bsg_uring_task_cb); + return RQ_END_IO_NONE; +} + +static int scsi_bsg_map_user_buffer(struct request *req, + struct io_uring_cmd *ioucmd, + unsigned int issue_flags, gfp_t gfp_mask) +{ + const struct bsg_uring_cmd *cmd = io_uring_sqe_cmd(ioucmd->sqe); + struct iov_iter iter; + bool is_write = cmd->dout_xfer_len > 0; + u64 buf_addr = is_write ? cmd->dout_xferp : cmd->din_xferp; + unsigned long buf_len = is_write ? cmd->dout_xfer_len : cmd->din_xfer_len; + int ret; + + if (ioucmd->flags & IORING_URING_CMD_FIXED) { + ret = io_uring_cmd_import_fixed(buf_addr, buf_len, + is_write ? WRITE : READ, + &iter, ioucmd, issue_flags); + if (ret < 0) + return ret; + ret = blk_rq_map_user_iov(req->q, req, NULL, &iter, gfp_mask); + } else { + ret = blk_rq_map_user(req->q, req, NULL, uptr64(buf_addr), + buf_len, gfp_mask); + } + + return ret; +} + int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *ioucmd, unsigned int issue_flags, bool open_for_write) { - return -EOPNOTSUPP; + struct scsi_bsg_uring_cmd_pdu *pdu = scsi_bsg_uring_cmd_pdu(ioucmd); + const struct bsg_uring_cmd *cmd = io_uring_sqe_cmd(ioucmd->sqe); + struct scsi_cmnd *scmd; + struct request *req; + blk_mq_req_flags_t blk_flags = 0; + gfp_t gfp_mask = GFP_KERNEL; + int ret = 0; + + if (issue_flags & IO_URING_F_NONBLOCK) { + blk_flags = BLK_MQ_REQ_NOWAIT; + gfp_mask = GFP_NOWAIT; + } + + req = scsi_alloc_request(q, cmd->dout_xfer_len ? + REQ_OP_DRV_OUT : REQ_OP_DRV_IN, blk_flags); + if (IS_ERR(req)) + return PTR_ERR(req); + + scmd = blk_mq_rq_to_pdu(req); + scmd->cmd_len = cmd->request_len; + if (scmd->cmd_len > sizeof(scmd->cmnd)) { + ret = -EINVAL; + goto out_free_req; + } + scmd->allowed = SG_DEFAULT_RETRIES; + + if (copy_from_user(scmd->cmnd, uptr64(cmd->request), cmd->request_len)) { + ret = -EFAULT; + goto out_free_req; + } + + if (!scsi_cmd_allowed(scmd->cmnd, open_for_write)) { + ret = -EPERM; + goto out_free_req; + } + + pdu->response_addr = cmd->response; + scmd->sense_len = cmd->max_response_len ? + min(cmd->max_response_len, SCSI_SENSE_BUFFERSIZE) : SCSI_SENSE_BUFFERSIZE; + + if (cmd->dout_xfer_len || cmd->din_xfer_len) { + ret = scsi_bsg_map_user_buffer(req, ioucmd, issue_flags, gfp_mask); + if (ret) + goto out_free_req; + pdu->bio = req->bio; + } else { + pdu->bio = NULL; + } + + req->timeout = cmd->timeout_ms ? + msecs_to_jiffies(cmd->timeout_ms) : BLK_DEFAULT_SG_TIMEOUT; + + 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); + return ret; } static int scsi_bsg_sg_io_fn(struct request_queue *q, struct sg_io_v4 *hdr,