Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions block/bsg.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <linux/idr.h>
#include <linux/bsg.h>
#include <linux/slab.h>
#include <linux/io_uring/cmd.h>

#include <scsi/scsi.h>
#include <scsi/scsi_ioctl.h>
Expand Down Expand Up @@ -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,
};
Expand Down
192 changes: 192 additions & 0 deletions drivers/scsi/scsi_bsg.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bsg.h>
#include <linux/io_uring/cmd.h>
#include <scsi/scsi.h>
#include <scsi/scsi_ioctl.h>
#include <scsi/scsi_cmnd.h>
Expand All @@ -9,6 +10,197 @@

#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)
{
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,
bool open_for_write, unsigned int timeout)
{
Expand Down
4 changes: 4 additions & 0 deletions include/linux/bsg.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 */
24 changes: 24 additions & 0 deletions include/uapi/linux/bsg.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */