* [PATCH v2 1/8] ublk: consistently use u16 for queue and tag numbers
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 2/8] ublk: remove struct ublk_zoned_report_desc's operation field Caleb Sander Mateos
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos
The u16 nr_hw_queues and queue_depth fields of the ublk UAPI struct
ublksrv_ctrl_dev_info constrain the number of queues and queue depth of
each ublk device. However, the ublk driver is a bit inconsistent with
the type it uses to represent these values, mixing u16 with int and
unsigned int. Change all queue number, queue depth, q_id, and tag
variables/fields to u16 to save some space.
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
Reviewed-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/block/ublk_drv.c | 80 ++++++++++++++++++++--------------------
1 file changed, 41 insertions(+), 39 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index dd5d336eba29..da88c8042db6 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -235,22 +235,22 @@ struct ublk_io {
void *buf_ctx_handle;
spinlock_t lock;
} ____cacheline_aligned_in_smp;
struct ublk_queue {
- int q_id;
- int q_depth;
+ u16 q_id;
+ u16 q_depth;
unsigned long flags;
struct ublksrv_io_desc *io_cmd_buf;
bool force_abort;
bool canceling;
bool fail_io; /* copy of dev->state == UBLK_S_DEV_FAIL_IO */
spinlock_t cancel_lock;
struct ublk_device *dev;
- u32 nr_io_ready;
+ u16 nr_io_ready;
/*
* For supporting UBLK_F_BATCH_IO only.
*
* Inflight ublk request tag is saved in this fifo
@@ -325,11 +325,11 @@ struct ublk_device {
spinlock_t lock;
struct mm_struct *mm;
struct ublk_params params;
- u32 nr_queue_ready;
+ u16 nr_queue_ready;
bool unprivileged_daemons;
struct mutex cancel_mutex;
bool canceling;
pid_t ublksrv_tgid;
struct delayed_work exit_work;
@@ -401,11 +401,11 @@ static inline void ublk_io_evts_deinit(struct ublk_queue *q)
WARN_ON_ONCE(!kfifo_is_empty(&q->evts_fifo));
kfifo_free(&q->evts_fifo);
}
static inline struct ublksrv_io_desc *
-ublk_get_iod(const struct ublk_queue *ubq, unsigned tag)
+ublk_get_iod(const struct ublk_queue *ubq, u16 tag)
{
return &ubq->io_cmd_buf[tag];
}
static inline bool ublk_support_zero_copy(const struct ublk_queue *ubq)
@@ -421,12 +421,11 @@ static inline bool ublk_dev_support_zero_copy(const struct ublk_device *ub)
static inline bool ublk_support_shmem_zc(const struct ublk_queue *ubq)
{
return ubq->flags & UBLK_F_SHMEM_ZC;
}
-static inline bool ublk_iod_is_shmem_zc(const struct ublk_queue *ubq,
- unsigned int tag)
+static inline bool ublk_iod_is_shmem_zc(const struct ublk_queue *ubq, u16 tag)
{
return ublk_get_iod(ubq, tag)->op_flags & UBLK_IO_F_SHMEM_ZC;
}
static inline bool ublk_dev_support_shmem_zc(const struct ublk_device *ub)
@@ -862,22 +861,22 @@ static ssize_t ublk_batch_copy_io_tags(struct ublk_batch_fetch_cmd *fcmd,
static unsigned int unprivileged_ublks_max = 64;
static unsigned int unprivileged_ublks_added; /* protected by ublk_ctl_mutex */
static struct miscdevice ublk_misc;
-static inline unsigned ublk_pos_to_hwq(loff_t pos)
+static inline u16 ublk_pos_to_hwq(loff_t pos)
{
return ((pos - UBLKSRV_IO_BUF_OFFSET) >> UBLK_QID_OFF) &
UBLK_QID_BITS_MASK;
}
static inline unsigned ublk_pos_to_buf_off(loff_t pos)
{
return (pos - UBLKSRV_IO_BUF_OFFSET) & UBLK_IO_BUF_BITS_MASK;
}
-static inline unsigned ublk_pos_to_tag(loff_t pos)
+static inline u16 ublk_pos_to_tag(loff_t pos)
{
return ((pos - UBLKSRV_IO_BUF_OFFSET) >> UBLK_TAG_OFF) &
UBLK_TAG_BITS_MASK;
}
@@ -1229,22 +1228,22 @@ static noinline void ublk_put_device(struct ublk_device *ub)
{
put_device(&ub->cdev_dev);
}
static inline struct ublk_queue *ublk_get_queue(struct ublk_device *dev,
- int qid)
+ u16 qid)
{
return dev->queues[qid];
}
static inline struct ublksrv_io_desc *
-ublk_queue_cmd_buf(struct ublk_device *ub, int q_id)
+ublk_queue_cmd_buf(struct ublk_device *ub, u16 q_id)
{
return ublk_get_queue(ub, q_id)->io_cmd_buf;
}
-static inline int __ublk_queue_cmd_buf_size(int depth)
+static inline int __ublk_queue_cmd_buf_size(u16 depth)
{
return round_up(depth * sizeof(struct ublksrv_io_desc), PAGE_SIZE);
}
static inline int ublk_queue_cmd_buf_size(struct ublk_device *ub)
@@ -1665,11 +1664,11 @@ static inline void __ublk_abort_rq(struct ublk_queue *ubq,
else
ublk_end_request(rq, BLK_STS_IOERR);
}
static void
-ublk_auto_buf_reg_fallback(const struct ublk_queue *ubq, unsigned tag)
+ublk_auto_buf_reg_fallback(const struct ublk_queue *ubq, u16 tag)
{
struct ublksrv_io_desc *iod = ublk_get_iod(ubq, tag);
iod->op_flags |= UBLK_IO_F_NEED_REG_BUF;
}
@@ -1776,11 +1775,11 @@ static bool ublk_start_io(const struct ublk_queue *ubq, struct request *req,
}
static void ublk_dispatch_req(struct ublk_queue *ubq, struct request *req)
{
unsigned int issue_flags = IO_URING_CMD_TASK_WORK_ISSUE_FLAGS;
- int tag = req->tag;
+ u16 tag = req->tag;
struct ublk_io *io = &ubq->ios[tag];
pr_devel("%s: complete: qid %d tag %d io_flags %x addr %llx\n",
__func__, ubq->q_id, req->tag, io->flags,
ublk_get_iod(ubq, req->tag)->addr);
@@ -2366,11 +2365,11 @@ static const struct blk_mq_ops ublk_batch_mq_ops = {
.timeout = ublk_timeout,
};
static void ublk_queue_reinit(struct ublk_device *ub, struct ublk_queue *ubq)
{
- int i;
+ u16 i;
ubq->nr_io_ready = 0;
for (i = 0; i < ubq->q_depth; i++) {
struct ublk_io *io = &ubq->ios[i];
@@ -2411,11 +2410,11 @@ static int ublk_ch_open(struct inode *inode, struct file *filp)
return 0;
}
static void ublk_reset_ch_dev(struct ublk_device *ub)
{
- int i;
+ u16 i;
for (i = 0; i < ub->dev_info.nr_hw_queues; i++) {
struct ublk_queue *ubq = ublk_get_queue(ub, i);
/* Sync with ublk_cancel_cmd() */
@@ -2483,20 +2482,20 @@ static void ublk_partition_scan_work(struct work_struct *work)
* means.
*/
static void ublk_set_canceling(struct ublk_device *ub, bool canceling)
__must_hold(&ub->cancel_mutex)
{
- int i;
+ u16 i;
ub->canceling = canceling;
for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
ublk_get_queue(ub, i)->canceling = canceling;
}
static bool ublk_check_and_reset_active_ref(struct ublk_device *ub)
{
- int i, j;
+ u16 i, j;
if (!ublk_dev_need_req_ref(ub))
return false;
for (i = 0; i < ub->dev_info.nr_hw_queues; i++) {
@@ -2525,11 +2524,11 @@ static bool ublk_check_and_reset_active_ref(struct ublk_device *ub)
static void ublk_ch_release_work_fn(struct work_struct *work)
{
struct ublk_device *ub =
container_of(work, struct ublk_device, exit_work.work);
struct gendisk *disk;
- int i;
+ u16 i;
/*
* For zero-copy and auto buffer register modes, I/O references
* might not be dropped naturally when the daemon is killed, but
* io_uring guarantees that registered bvec kernel buffers are
@@ -2644,11 +2643,12 @@ static int ublk_ch_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct ublk_device *ub = filp->private_data;
size_t sz = vma->vm_end - vma->vm_start;
unsigned max_sz = ublk_max_cmd_buf_size();
unsigned long pfn, end, phys_off = vma->vm_pgoff << PAGE_SHIFT;
- int q_id, ret = 0;
+ int ret = 0;
+ u16 q_id;
spin_lock(&ub->lock);
if (!ub->mm)
ub->mm = current->mm;
if (current->mm != ub->mm)
@@ -2717,11 +2717,11 @@ static void ublk_abort_batch_queue(struct ublk_device *ub,
* So no one can hold our request IO reference any more, simply ignore the
* reference, and complete the request immediately
*/
static void ublk_abort_queue(struct ublk_device *ub, struct ublk_queue *ubq)
{
- int i;
+ u16 i;
for (i = 0; i < ubq->q_depth; i++) {
struct ublk_io *io = &ubq->ios[i];
if (io->flags & UBLK_IO_FLAG_OWNED_BY_SRV)
@@ -2760,11 +2760,11 @@ static void ublk_start_cancel(struct ublk_device *ub)
out:
mutex_unlock(&ub->cancel_mutex);
ublk_put_disk(disk);
}
-static void ublk_cancel_cmd(struct ublk_queue *ubq, unsigned tag,
+static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
unsigned int issue_flags)
{
struct ublk_io *io = &ubq->ios[tag];
struct ublk_device *ub = ubq->dev;
struct io_uring_cmd *cmd = NULL;
@@ -2911,11 +2911,11 @@ static inline bool ublk_dev_ready(const struct ublk_device *ub)
return ub->nr_queue_ready == ub->dev_info.nr_hw_queues;
}
static void ublk_cancel_queue(struct ublk_queue *ubq)
{
- int i;
+ u16 i;
if (ublk_support_batch_io(ubq)) {
ublk_batch_cancel_queue(ubq);
return;
}
@@ -2925,11 +2925,11 @@ static void ublk_cancel_queue(struct ublk_queue *ubq)
}
/* Cancel all pending commands, must be called after del_gendisk() returns */
static void ublk_cancel_dev(struct ublk_device *ub)
{
- int i;
+ u16 i;
for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
ublk_cancel_queue(ublk_get_queue(ub, i));
}
@@ -2959,11 +2959,11 @@ static void ublk_wait_tagset_rqs_idle(struct ublk_device *ub)
}
}
static void ublk_force_abort_dev(struct ublk_device *ub)
{
- int i;
+ u16 i;
pr_devel("%s: force abort ub: dev_id %d state %s\n",
__func__, ub->dev_info.dev_id,
ub->dev_info.state == UBLK_S_DEV_LIVE ?
"LIVE" : "QUIESCED");
@@ -3156,11 +3156,11 @@ ublk_config_io_buf(const struct ublk_device *ub, struct ublk_io *io,
return 0;
}
static inline void ublk_prep_cancel(struct io_uring_cmd *cmd,
unsigned int issue_flags,
- struct ublk_queue *ubq, unsigned int tag)
+ struct ublk_queue *ubq, u16 tag)
{
struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
/*
* Safe to refer to @ubq since ublk_queue won't be died until its
@@ -3960,12 +3960,12 @@ static int ublk_handle_non_batch_cmd(struct io_uring_cmd *cmd,
unsigned int issue_flags)
{
const struct ublksrv_io_cmd *ub_cmd = io_uring_sqe_cmd(cmd->sqe,
struct ublksrv_io_cmd);
struct ublk_device *ub = cmd->file->private_data;
- unsigned tag = READ_ONCE(ub_cmd->tag);
- unsigned q_id = READ_ONCE(ub_cmd->q_id);
+ u16 tag = READ_ONCE(ub_cmd->tag);
+ u16 q_id = READ_ONCE(ub_cmd->q_id);
unsigned index = READ_ONCE(ub_cmd->addr);
struct ublk_queue *ubq;
struct ublk_io *io;
if (cmd->cmd_op == UBLK_U_IO_UNREGISTER_IO_BUF)
@@ -4167,11 +4167,12 @@ static const struct file_operations ublk_ch_batch_io_fops = {
.mmap = ublk_ch_mmap,
};
static void __ublk_deinit_queue(struct ublk_device *ub, struct ublk_queue *ubq)
{
- int size, i;
+ int size;
+ u16 i;
size = ublk_queue_cmd_buf_size(ub);
for (i = 0; i < ubq->q_depth; i++) {
struct ublk_io *io = &ubq->ios[i];
@@ -4188,22 +4189,22 @@ static void __ublk_deinit_queue(struct ublk_device *ub, struct ublk_queue *ubq)
ublk_io_evts_deinit(ubq);
kvfree(ubq);
}
-static void ublk_deinit_queue(struct ublk_device *ub, int q_id)
+static void ublk_deinit_queue(struct ublk_device *ub, u16 q_id)
{
struct ublk_queue *ubq = ub->queues[q_id];
if (!ubq)
return;
__ublk_deinit_queue(ub, ubq);
ub->queues[q_id] = NULL;
}
-static int ublk_get_queue_numa_node(struct ublk_device *ub, int q_id)
+static int ublk_get_queue_numa_node(struct ublk_device *ub, u16 q_id)
{
unsigned int cpu;
/* Find first CPU mapped to this queue */
for_each_possible_cpu(cpu) {
@@ -4212,18 +4213,19 @@ static int ublk_get_queue_numa_node(struct ublk_device *ub, int q_id)
}
return NUMA_NO_NODE;
}
-static int ublk_init_queue(struct ublk_device *ub, int q_id)
+static int ublk_init_queue(struct ublk_device *ub, u16 q_id)
{
- int depth = ub->dev_info.queue_depth;
+ u16 depth = ub->dev_info.queue_depth;
gfp_t gfp_flags = GFP_KERNEL | __GFP_ZERO;
struct ublk_queue *ubq;
struct page *page;
int numa_node;
- int size, i, ret;
+ int size, ret;
+ u16 i;
/* Determine NUMA node based on queue's CPU affinity */
numa_node = ublk_get_queue_numa_node(ub, q_id);
/* Allocate queue structure on local NUMA node */
@@ -4264,19 +4266,20 @@ static int ublk_init_queue(struct ublk_device *ub, int q_id)
return ret;
}
static void ublk_deinit_queues(struct ublk_device *ub)
{
- int i;
+ u16 i;
for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
ublk_deinit_queue(ub, i);
}
static int ublk_init_queues(struct ublk_device *ub)
{
- int i, ret;
+ int ret;
+ u16 i;
for (i = 0; i < ub->dev_info.nr_hw_queues; i++) {
ret = ublk_init_queue(ub, i);
if (ret)
goto fail;
@@ -5179,11 +5182,11 @@ static int ublk_ctrl_set_size(struct ublk_device *ub, const struct ublksrv_ctrl_
return ret;
}
struct count_busy {
const struct ublk_queue *ubq;
- unsigned int nr_busy;
+ u16 nr_busy;
};
static bool ublk_count_busy_req(struct request *rq, void *data)
{
struct count_busy *idle = data;
@@ -5217,12 +5220,11 @@ static int ublk_wait_for_idle_io(struct ublk_device *ub,
*/
if (ublk_dev_support_batch_io(ub))
return 0;
while (elapsed < timeout_ms && !signal_pending(current)) {
- unsigned int queues_cancelable = 0;
- int i;
+ u16 i, queues_cancelable = 0;
for (i = 0; i < ub->dev_info.nr_hw_queues; i++) {
struct ublk_queue *ubq = ublk_get_queue(ub, i);
queues_cancelable += !!ubq_has_idle_io(ubq);
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 2/8] ublk: remove struct ublk_zoned_report_desc's operation field
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 1/8] ublk: consistently use u16 for queue and tag numbers Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 3/8] ublk: split request validation from io_desc init Caleb Sander Mateos
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos
struct ublk_zoned_report_desc's operation field is only ever set to
UBLK_IO_OP_REPORT_ZONES, so remove it. Replace its one load with the
constant.
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
Reviewed-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/block/ublk_drv.c | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index da88c8042db6..d364a5d0ebc2 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -526,11 +526,10 @@ static void ublk_init_iod(struct ublk_queue *ubq, struct request *req,
#ifdef CONFIG_BLK_DEV_ZONED
struct ublk_zoned_report_desc {
__u64 sector;
- __u32 operation;
__u32 nr_zones;
};
static DEFINE_XARRAY(ublk_zoned_report_descs);
@@ -656,11 +655,10 @@ static int ublk_report_zones(struct gendisk *disk, sector_t sector,
if (IS_ERR(req)) {
ret = PTR_ERR(req);
goto out;
}
- desc.operation = UBLK_IO_OP_REPORT_ZONES;
desc.sector = sector;
desc.nr_zones = zones_in_request;
ret = ublk_zoned_insert_report_desc(req, &desc);
if (ret)
goto free_req;
@@ -729,19 +727,13 @@ static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq,
break;
case REQ_OP_DRV_IN:
desc = ublk_zoned_get_report_desc(req);
if (!desc)
return BLK_STS_IOERR;
- ublk_op = desc->operation;
- switch (ublk_op) {
- case UBLK_IO_OP_REPORT_ZONES:
- ublk_init_iod(ubq, req, ublk_op, desc->nr_zones,
- desc->sector);
- return BLK_STS_OK;
- default:
- return BLK_STS_IOERR;
- }
+ ublk_init_iod(ubq, req, UBLK_IO_OP_REPORT_ZONES, desc->nr_zones,
+ desc->sector);
+ return BLK_STS_OK;
case REQ_OP_DRV_OUT:
/* We do not support drv_out */
return BLK_STS_NOTSUPP;
default:
return BLK_STS_IOERR;
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 3/8] ublk: split request validation from io_desc init
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 1/8] ublk: consistently use u16 for queue and tag numbers Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 2/8] ublk: remove struct ublk_zoned_report_desc's operation field Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 4/8] ublk: initialize io_desc on daemon task Caleb Sander Mateos
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos
In preparation for moving the struct ublksrv_io_desc initialization from
the thread submitting ublk requests to the daemon thread receiving them,
split the fallible part of ublk_setup_iod{,_zoned}() into new helper
ublk_validate_req{,_zoned}(). Only ublk_setup_iod{,_zoned}() accesses
the io_desc and cannot error out.
Return a bool value from ublk_validate_req{,_zoned}() as the existing
error code ublk_setup_iod{,_zoned}() returns is only checked against
BLK_STS_OK.
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
Reviewed-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/block/ublk_drv.c | 70 +++++++++++++++++++++++++++-------------
1 file changed, 48 insertions(+), 22 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index d364a5d0ebc2..7f0549238d76 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -698,12 +698,28 @@ static int ublk_report_zones(struct gendisk *disk, sector_t sector,
out:
kvfree(buffer);
return ret;
}
-static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq,
- struct request *req)
+static bool ublk_validate_req_zoned(const struct request *req)
+{
+ switch (req_op(req)) {
+ case REQ_OP_ZONE_OPEN:
+ case REQ_OP_ZONE_CLOSE:
+ case REQ_OP_ZONE_FINISH:
+ case REQ_OP_ZONE_RESET:
+ case REQ_OP_ZONE_APPEND:
+ case REQ_OP_ZONE_RESET_ALL:
+ return true;
+ case REQ_OP_DRV_IN:
+ return !!ublk_zoned_get_report_desc(req);
+ default:
+ return false;
+ }
+}
+
+static void ublk_setup_iod_zoned(struct ublk_queue *ubq, struct request *req)
{
struct ublk_zoned_report_desc *desc;
u32 ublk_op;
switch (req_op(req)) {
@@ -725,24 +741,19 @@ static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq,
case REQ_OP_ZONE_RESET_ALL:
ublk_op = UBLK_IO_OP_ZONE_RESET_ALL;
break;
case REQ_OP_DRV_IN:
desc = ublk_zoned_get_report_desc(req);
- if (!desc)
- return BLK_STS_IOERR;
ublk_init_iod(ubq, req, UBLK_IO_OP_REPORT_ZONES, desc->nr_zones,
desc->sector);
- return BLK_STS_OK;
- case REQ_OP_DRV_OUT:
- /* We do not support drv_out */
- return BLK_STS_NOTSUPP;
+ return;
default:
- return BLK_STS_IOERR;
+ WARN_ON_ONCE(1);
+ return;
}
ublk_init_iod(ubq, req, ublk_op, blk_rq_sectors(req), blk_rq_pos(req));
- return BLK_STS_OK;
}
#else
#define ublk_report_zones (NULL)
@@ -759,14 +770,18 @@ static void ublk_dev_param_zoned_apply(struct ublk_device *ub)
static int ublk_revalidate_disk_zones(struct ublk_device *ub)
{
return 0;
}
-static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq,
- struct request *req)
+static bool ublk_validate_req_zoned(const struct request *req)
{
- return BLK_STS_NOTSUPP;
+ return false;
+}
+
+static void ublk_setup_iod_zoned(struct ublk_queue *ubq, struct request *req)
+{
+ WARN_ON_ONCE(1);
}
#endif
static inline void __ublk_complete_rq(struct request *req, struct ublk_io *io,
@@ -1495,11 +1510,26 @@ static unsigned int ublk_unmap_io(bool need_map,
return ublk_copy_user_pages(req, 0, &iter, dir);
}
return rq_bytes;
}
-static blk_status_t ublk_setup_iod(struct ublk_queue *ubq, struct request *req)
+static bool ublk_validate_req(const struct ublk_queue *ubq,
+ const struct request *req)
+{
+ switch (req_op(req)) {
+ case REQ_OP_READ:
+ case REQ_OP_WRITE:
+ case REQ_OP_FLUSH:
+ case REQ_OP_DISCARD:
+ case REQ_OP_WRITE_ZEROES:
+ return true;
+ default:
+ return ublk_queue_is_zoned(ubq) && ublk_validate_req_zoned(req);
+ }
+}
+
+static void ublk_setup_iod(struct ublk_queue *ubq, struct request *req)
{
u32 ublk_op;
switch (req_op(req)) {
case REQ_OP_READ:
@@ -1516,17 +1546,15 @@ static blk_status_t ublk_setup_iod(struct ublk_queue *ubq, struct request *req)
break;
case REQ_OP_WRITE_ZEROES:
ublk_op = UBLK_IO_OP_WRITE_ZEROES;
break;
default:
- if (ublk_queue_is_zoned(ubq))
- return ublk_setup_iod_zoned(ubq, req);
- return BLK_STS_IOERR;
+ ublk_setup_iod_zoned(ubq, req);
+ return;
}
ublk_init_iod(ubq, req, ublk_op, blk_rq_sectors(req), blk_rq_pos(req));
- return BLK_STS_OK;
}
static inline struct ublk_uring_cmd_pdu *ublk_get_uring_cmd_pdu(
struct io_uring_cmd *ioucmd)
{
@@ -2136,12 +2164,10 @@ static enum blk_eh_timer_return ublk_timeout(struct request *rq)
}
static blk_status_t ublk_prep_req(struct ublk_queue *ubq, struct request *rq,
bool check_cancel)
{
- blk_status_t res;
-
if (unlikely(READ_ONCE(ubq->fail_io)))
return BLK_STS_TARGET;
/* With recovery feature enabled, force_abort is set in
* ublk_stop_dev() before calling del_gendisk(). We have to
@@ -2158,14 +2184,14 @@ static blk_status_t ublk_prep_req(struct ublk_queue *ubq, struct request *rq,
if (check_cancel && unlikely(ubq->canceling))
return BLK_STS_IOERR;
/* fill iod to slot in io cmd buffer */
- res = ublk_setup_iod(ubq, rq);
- if (unlikely(res != BLK_STS_OK))
+ if (unlikely(!ublk_validate_req(ubq, rq)))
return BLK_STS_IOERR;
+ ublk_setup_iod(ubq, rq);
blk_mq_start_request(rq);
return BLK_STS_OK;
}
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 4/8] ublk: initialize io_desc on daemon task
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
` (2 preceding siblings ...)
2026-08-03 21:14 ` [PATCH v2 3/8] ublk: split request validation from io_desc init Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 5/8] ublk: add UBLK_F_IO_DESC_SIZE Caleb Sander Mateos
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos
ublk_setup_iod() is currently called to populate struct ublksrv_io_desc
on the thread submitting I/O to a ublk device. However, only the ublk
server threads read the io_descs. This basically guarantees a cache miss
on both threads for each ublk I/O. There's really no need to initialize
the io_descs on the submitting thread. Move the ublk_setup_iod() call to
ublk_dispatch_req() (for non-UBLK_F_BATCH_IO) and
__ublk_batch_prep_dispatch() (for UBLK_F_BATCH_IO), which runs on the
ublk server daemon thread before dispatching the I/O to userspace.
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
Reviewed-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/block/ublk_drv.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 7f0549238d76..291461e1b236 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -1798,10 +1798,11 @@ static void ublk_dispatch_req(struct ublk_queue *ubq, struct request *req)
{
unsigned int issue_flags = IO_URING_CMD_TASK_WORK_ISSUE_FLAGS;
u16 tag = req->tag;
struct ublk_io *io = &ubq->ios[tag];
+ ublk_setup_iod(ubq, req);
pr_devel("%s: complete: qid %d tag %d io_flags %x addr %llx\n",
__func__, ubq->q_id, req->tag, io->flags,
ublk_get_iod(ubq, req->tag)->addr);
/*
@@ -1851,10 +1852,11 @@ static bool __ublk_batch_prep_dispatch(struct ublk_queue *ubq,
struct ublk_io *io = &ubq->ios[tag];
struct request *req = blk_mq_tag_to_rq(ub->tag_set.tags[ubq->q_id], tag);
enum auto_buf_reg_res res = AUTO_BUF_REG_FALLBACK;
struct io_uring_cmd *cmd = data->cmd;
+ ublk_setup_iod(ubq, req);
if (!ublk_start_io(ubq, req, io))
return false;
if (ublk_support_auto_buf_reg(ubq) && blk_rq_has_data(req)) {
res = ublk_auto_buf_register(ubq, req, io, cmd,
@@ -2187,11 +2189,10 @@ static blk_status_t ublk_prep_req(struct ublk_queue *ubq, struct request *rq,
/* fill iod to slot in io cmd buffer */
if (unlikely(!ublk_validate_req(ubq, rq)))
return BLK_STS_IOERR;
- ublk_setup_iod(ubq, rq);
blk_mq_start_request(rq);
return BLK_STS_OK;
}
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 5/8] ublk: add UBLK_F_IO_DESC_SIZE
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
` (3 preceding siblings ...)
2026-08-03 21:14 ` [PATCH v2 4/8] ublk: initialize io_desc on daemon task Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 6/8] selftests: ublk: add support for --io_desc_size Caleb Sander Mateos
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos,
Ming Lei
ublk passes the parameters of incoming I/O in memory shared between the
kernel ublk driver and userspace ublk server in struct ublksrv_io_desc.
The size of this struct is currently fixed to 24 bytes, which has been
an obstacle to extending it with additional fields [1]. Additionally,
with multiple ublk server threads handling I/Os from the same ublk queue
(possible with UBLK_F_PER_IO_DAEMON or UBLK_F_BATCH_IO), false sharing
results from adjacent io_descs sharing the same cache line.
Add a ublk feature UBLK_F_IO_DESC_SIZE to allow a ublk server to
override the size of each io_desc. The size must be at least 24 and a
multiple of 8 to store a properly-aligned struct ublksrv_io_desc. It's
also limited to a maximum of 256, though this bound could be lifted in
the future.
The struct ublksrv_io_desc is located at the beginning of each io_desc
and the remainder is padding. The mmap() performed for each queue must
have a length of queue_depth * io_desc_size rounded up to the page size.
The mmap() offset must be q_id * UBLK_MAX_QUEUE_DEPTH * io_desc_size,
also rounded up to the page size.
[1]: https://lore.kernel.org/linux-block/aV8QfvaNO5P6vOs6@fedora/
Suggested-by: Ming Lei <ming.lei@redhat.com>
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
---
drivers/block/ublk_drv.c | 38 ++++++++++++++++++++--------
include/uapi/linux/ublk_cmd.h | 5 +++-
tools/testing/selftests/ublk/kublk.c | 1 +
3 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 291461e1b236..2f5de735b2d2 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -87,11 +87,12 @@
| UBLK_F_BUF_REG_OFF_DAEMON \
| (IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY) ? UBLK_F_INTEGRITY : 0) \
| UBLK_F_SAFE_STOP_DEV \
| UBLK_F_BATCH_IO \
| UBLK_F_NO_AUTO_PART_SCAN \
- | UBLK_F_SHMEM_ZC)
+ | UBLK_F_SHMEM_ZC \
+ | UBLK_F_IO_DESC_SIZE)
#define UBLK_F_ALL_RECOVERY_FLAGS (UBLK_F_USER_RECOVERY \
| UBLK_F_USER_RECOVERY_REISSUE \
| UBLK_F_USER_RECOVERY_FAIL_IO)
@@ -105,10 +106,12 @@
#define UBLK_BATCH_F_ALL \
(UBLK_BATCH_F_HAS_ZONE_LBA | \
UBLK_BATCH_F_HAS_BUF_ADDR | \
UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK)
+#define UBLK_MAX_IO_DESC_SIZE 256
+
/* ublk batch fetch uring_cmd */
struct ublk_batch_fetch_cmd {
struct list_head node;
struct io_uring_cmd *cmd;
unsigned short buf_group;
@@ -237,10 +240,11 @@ struct ublk_io {
} ____cacheline_aligned_in_smp;
struct ublk_queue {
u16 q_id;
u16 q_depth;
+ u16 io_desc_size;
unsigned long flags;
struct ublksrv_io_desc *io_cmd_buf;
bool force_abort;
@@ -403,11 +407,11 @@ static inline void ublk_io_evts_deinit(struct ublk_queue *q)
}
static inline struct ublksrv_io_desc *
ublk_get_iod(const struct ublk_queue *ubq, u16 tag)
{
- return &ubq->io_cmd_buf[tag];
+ return (void *)ubq->io_cmd_buf + tag * (size_t)ubq->io_desc_size;
}
static inline bool ublk_support_zero_copy(const struct ublk_queue *ubq)
{
return ubq->flags & UBLK_F_SUPPORT_ZERO_COPY;
@@ -1246,23 +1250,24 @@ static inline struct ublksrv_io_desc *
ublk_queue_cmd_buf(struct ublk_device *ub, u16 q_id)
{
return ublk_get_queue(ub, q_id)->io_cmd_buf;
}
-static inline int __ublk_queue_cmd_buf_size(u16 depth)
+static inline size_t __ublk_queue_cmd_buf_size(const struct ublk_device *ub,
+ u16 depth)
{
- return round_up(depth * sizeof(struct ublksrv_io_desc), PAGE_SIZE);
+ return round_up(depth * (size_t)ub->dev_info.io_desc_size, PAGE_SIZE);
}
-static inline int ublk_queue_cmd_buf_size(struct ublk_device *ub)
+static inline size_t ublk_queue_cmd_buf_size(const struct ublk_device *ub)
{
- return __ublk_queue_cmd_buf_size(ub->dev_info.queue_depth);
+ return __ublk_queue_cmd_buf_size(ub, ub->dev_info.queue_depth);
}
-static int ublk_max_cmd_buf_size(void)
+static size_t ublk_max_cmd_buf_size(const struct ublk_device *ub)
{
- return __ublk_queue_cmd_buf_size(UBLK_MAX_QUEUE_DEPTH);
+ return __ublk_queue_cmd_buf_size(ub, UBLK_MAX_QUEUE_DEPTH);
}
/*
* Should I/O outstanding to the ublk server when it exits be reissued?
* If not, outstanding I/O will get errors.
@@ -2660,11 +2665,11 @@ static int ublk_ch_release(struct inode *inode, struct file *filp)
/* map pre-allocated per-queue cmd buffer to ublksrv daemon */
static int ublk_ch_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct ublk_device *ub = filp->private_data;
size_t sz = vma->vm_end - vma->vm_start;
- unsigned max_sz = ublk_max_cmd_buf_size();
+ size_t max_sz = ublk_max_cmd_buf_size(ub);
unsigned long pfn, end, phys_off = vma->vm_pgoff << PAGE_SHIFT;
int ret = 0;
u16 q_id;
spin_lock(&ub->lock);
@@ -4186,11 +4191,11 @@ static const struct file_operations ublk_ch_batch_io_fops = {
.mmap = ublk_ch_mmap,
};
static void __ublk_deinit_queue(struct ublk_device *ub, struct ublk_queue *ubq)
{
- int size;
+ size_t size;
u16 i;
size = ublk_queue_cmd_buf_size(ub);
for (i = 0; i < ubq->q_depth; i++) {
@@ -4239,11 +4244,12 @@ static int ublk_init_queue(struct ublk_device *ub, u16 q_id)
u16 depth = ub->dev_info.queue_depth;
gfp_t gfp_flags = GFP_KERNEL | __GFP_ZERO;
struct ublk_queue *ubq;
struct page *page;
int numa_node;
- int size, ret;
+ size_t size;
+ int ret;
u16 i;
/* Determine NUMA node based on queue's CPU affinity */
numa_node = ublk_get_queue_numa_node(ub, q_id);
@@ -4264,10 +4270,11 @@ static int ublk_init_queue(struct ublk_device *ub, u16 q_id)
if (!page) {
kvfree(ubq);
return -ENOMEM;
}
ubq->io_cmd_buf = page_address(page);
+ ubq->io_desc_size = ub->dev_info.io_desc_size;
for (i = 0; i < ubq->q_depth; i++)
spin_lock_init(&ubq->ios[i].lock);
if (ublk_dev_support_batch_io(ub)) {
@@ -4748,10 +4755,19 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header)
/* User copy is required to access integrity buffer */
if (info.flags & UBLK_F_INTEGRITY && !(info.flags & UBLK_F_USER_COPY))
return -EINVAL;
+ if (info.flags & UBLK_F_IO_DESC_SIZE) {
+ if (info.io_desc_size < sizeof(struct ublksrv_io_desc) ||
+ info.io_desc_size % _Alignof(struct ublksrv_io_desc) ||
+ info.io_desc_size > UBLK_MAX_IO_DESC_SIZE)
+ return -EINVAL;
+ } else {
+ info.io_desc_size = sizeof(struct ublksrv_io_desc);
+ }
+
/* the created device is always owned by current user */
ublk_store_owner_uid_gid(&info.owner_uid, &info.owner_gid);
if (header->dev_id != info.dev_id) {
pr_warn("%s: dev id not match %u %u\n",
diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h
index 6991370a72ce..33b25dd13965 100644
--- a/include/uapi/linux/ublk_cmd.h
+++ b/include/uapi/linux/ublk_cmd.h
@@ -415,10 +415,13 @@ struct ublk_shmem_buf_reg {
* pages match a registered buffer, UBLK_IO_F_SHMEM_ZC is set and addr
* encodes the buffer index + offset instead of a userspace buffer address.
*/
#define UBLK_F_SHMEM_ZC (1ULL << 19)
+/* ublksrv_io_desc size is specified by ublksrv_ctrl_dev_info's io_desc_size */
+#define UBLK_F_IO_DESC_SIZE (1ULL << 20)
+
/* device state */
#define UBLK_S_DEV_DEAD 0
#define UBLK_S_DEV_LIVE 1
#define UBLK_S_DEV_QUIESCED 2
#define UBLK_S_DEV_FAIL_IO 3
@@ -450,11 +453,11 @@ struct ublksrv_ctrl_cmd {
struct ublksrv_ctrl_dev_info {
__u16 nr_hw_queues;
__u16 queue_depth;
__u16 state;
- __u16 pad0;
+ __u16 io_desc_size;
__u32 max_io_buf_bytes;
__u32 dev_id;
__s32 ublksrv_pid;
diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c
index 0b23c09daea5..5c4a1f18d0a3 100644
--- a/tools/testing/selftests/ublk/kublk.c
+++ b/tools/testing/selftests/ublk/kublk.c
@@ -1968,10 +1968,11 @@ static int cmd_dev_get_features(void)
FEAT_NAME(UBLK_F_INTEGRITY),
FEAT_NAME(UBLK_F_SAFE_STOP_DEV),
FEAT_NAME(UBLK_F_BATCH_IO),
FEAT_NAME(UBLK_F_NO_AUTO_PART_SCAN),
FEAT_NAME(UBLK_F_SHMEM_ZC),
+ FEAT_NAME(UBLK_F_IO_DESC_SIZE),
};
struct ublk_dev *dev;
__u64 features = 0;
int ret;
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 6/8] selftests: ublk: add support for --io_desc_size
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
` (4 preceding siblings ...)
2026-08-03 21:14 ` [PATCH v2 5/8] ublk: add UBLK_F_IO_DESC_SIZE Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 7/8] selftests: ublk: add UBLK_F_IO_DESC_SIZE test Caleb Sander Mateos
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos
Add an optional --io_desc_size argument to the kublk add/recover
commands to enable UBLK_F_IO_DESC on the ublk device. The mmap()
arguments and ublk_get_iod() computation are adjusted accordingly.
Display the configured io_desc_size in the kublk list output for ublk
devices with UBLK_F_IO_DESC.
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
---
tools/testing/selftests/ublk/kublk.c | 29 +++++++++++++++++++---------
tools/testing/selftests/ublk/kublk.h | 6 ++++--
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c
index 5c4a1f18d0a3..0e3e2d74cc4d 100644
--- a/tools/testing/selftests/ublk/kublk.c
+++ b/tools/testing/selftests/ublk/kublk.c
@@ -350,10 +350,12 @@ static void ublk_ctrl_dump(struct ublk_dev *dev)
info->dev_id, info->nr_hw_queues, info->queue_depth,
1 << p.basic.logical_bs_shift, p.basic.dev_sectors);
ublk_log("\tmax rq size %d daemon pid %d flags 0x%llx state %s\n",
info->max_io_buf_bytes, info->ublksrv_pid, info->flags,
ublk_dev_state_desc(dev));
+ if (info->flags & UBLK_F_IO_DESC_SIZE)
+ ublk_log("\tio_desc_size %u\n", info->io_desc_size);
if (affinity) {
char buf[512];
int i;
@@ -398,26 +400,26 @@ static struct ublk_dev *ublk_ctrl_init(void)
dev->nr_fds = 1;
return dev;
}
-static int __ublk_queue_cmd_buf_sz(unsigned depth)
+static size_t __ublk_queue_cmd_buf_sz(const struct ublk_queue *q, __u16 depth)
{
- int size = depth * sizeof(struct ublksrv_io_desc);
- unsigned int page_sz = getpagesize();
+ size_t size = depth * (size_t)q->io_desc_size;
+ size_t page_sz = getpagesize();
return round_up(size, page_sz);
}
-static int ublk_queue_max_cmd_buf_sz(void)
+static size_t ublk_queue_max_cmd_buf_sz(const struct ublk_queue *q)
{
- return __ublk_queue_cmd_buf_sz(UBLK_MAX_QUEUE_DEPTH);
+ return __ublk_queue_cmd_buf_sz(q, UBLK_MAX_QUEUE_DEPTH);
}
-static int ublk_queue_cmd_buf_sz(struct ublk_queue *q)
+static size_t ublk_queue_cmd_buf_sz(const struct ublk_queue *q)
{
- return __ublk_queue_cmd_buf_sz(q->q_depth);
+ return __ublk_queue_cmd_buf_sz(q, q->q_depth);
}
static void ublk_queue_deinit(struct ublk_queue *q)
{
int i;
@@ -451,26 +453,27 @@ static int ublk_queue_init(struct ublk_queue *q, unsigned long long extra_flags,
__u8 metadata_size)
{
struct ublk_dev *dev = q->dev;
int depth = dev->dev_info.queue_depth;
int i;
- int cmd_buf_size, io_buf_size, integrity_size;
+ size_t cmd_buf_size, io_buf_size, integrity_size;
unsigned long off;
pthread_spin_init(&q->lock, PTHREAD_PROCESS_PRIVATE);
q->tgt_ops = dev->tgt.ops;
q->flags = 0;
q->q_depth = depth;
q->flags = dev->dev_info.flags;
q->flags |= extra_flags;
q->metadata_size = metadata_size;
+ q->io_desc_size = dev->dev_info.io_desc_size;
/* Cache fd in queue for fast path access */
q->ublk_fd = dev->fds[0];
cmd_buf_size = ublk_queue_cmd_buf_sz(q);
- off = UBLKSRV_CMD_BUF_OFFSET + q->q_id * ublk_queue_max_cmd_buf_sz();
+ off = UBLKSRV_CMD_BUF_OFFSET + q->q_id * ublk_queue_max_cmd_buf_sz(q);
q->io_cmd_buf = mmap(0, cmd_buf_size, PROT_READ,
MAP_SHARED | MAP_POPULATE, dev->fds[0], off);
if (q->io_cmd_buf == MAP_FAILED) {
ublk_err("ublk dev %d queue %d map io_cmd_buf failed %m\n",
q->dev->dev_info.dev_id, q->q_id);
@@ -1706,10 +1709,11 @@ static int __cmd_dev_add(const struct dev_ctx *ctx)
info = &dev->dev_info;
info->dev_id = ctx->dev_id;
info->nr_hw_queues = nr_queues;
info->queue_depth = depth;
+ info->io_desc_size = ctx->io_desc_size;
info->flags = ctx->flags;
if ((features & UBLK_F_QUIESCE) &&
(info->flags & UBLK_F_USER_RECOVERY))
info->flags |= UBLK_F_QUIESCE;
dev->nthreads = nthreads;
@@ -2067,10 +2071,11 @@ static void __cmd_create_help(char *exe, bool recovery)
printf("\t[-e 0|1 ] [-i 0|1] [--no_ublk_fixed_fd]\n");
printf("\t[--nthreads threads] [--per_io_tasks]\n");
printf("\t[--integrity_capable] [--integrity_reftag] [--metadata_size SIZE] "
"[--pi_offset OFFSET] [--csum_type ip|t10dif|nvme] [--tag_size SIZE]\n");
printf("\t[--batch|-b] [--no_auto_part_scan]\n");
+ printf("\t[--io_desc_size SIZE]\n");
printf("\t[target options] [backfile1] [backfile2] ...\n");
printf("\tdefault: nr_queues=2(max 32), depth=128(max 1024), dev_id=-1(auto allocation)\n");
printf("\tdefault: nthreads=nr_queues");
for (i = 0; i < ARRAY_SIZE(tgt_ops_list); i++) {
@@ -2144,10 +2149,11 @@ int main(int argc, char *argv[])
{ "batch", 0, NULL, 'b'},
{ "no_auto_part_scan", 0, NULL, 0 },
{ "shmem_zc", 0, NULL, 0 },
{ "htlb", 1, NULL, 0 },
{ "rdonly_shmem_buf", 0, NULL, 0 },
+ { "io_desc_size", 1, NULL, 0 },
{ 0, 0, 0, 0 }
};
const struct ublk_tgt_ops *ops = NULL;
int option_idx, opt;
const char *cmd = argv[1];
@@ -2156,10 +2162,11 @@ int main(int argc, char *argv[])
.queue_depth = 128,
.nr_hw_queues = 2,
.dev_id = -1,
.tgt_type = "unknown",
.csum_type = LBMD_PI_CSUM_NONE,
+ .io_desc_size = sizeof(struct ublksrv_io_desc),
};
int ret = -EINVAL, i;
int tgt_argc = 1;
char *tgt_argv[MAX_NR_TGT_ARG] = { NULL };
int value;
@@ -2265,10 +2272,14 @@ int main(int argc, char *argv[])
ctx.flags |= UBLK_F_SHMEM_ZC;
if (!strcmp(longopts[option_idx].name, "htlb"))
ctx.htlb_path = strdup(optarg);
if (!strcmp(longopts[option_idx].name, "rdonly_shmem_buf"))
ctx.rdonly_shmem_buf = 1;
+ if (!strcmp(longopts[option_idx].name, "io_desc_size")) {
+ ctx.flags |= UBLK_F_IO_DESC_SIZE;
+ ctx.io_desc_size = strtoul(optarg, NULL, 0);
+ }
break;
case '?':
/*
* target requires every option must have argument
*/
diff --git a/tools/testing/selftests/ublk/kublk.h b/tools/testing/selftests/ublk/kublk.h
index 742c41d77df1..15b56ff45bb6 100644
--- a/tools/testing/selftests/ublk/kublk.h
+++ b/tools/testing/selftests/ublk/kublk.h
@@ -85,10 +85,11 @@ struct dev_ctx {
__u32 integrity_flags;
__u8 metadata_size;
__u8 pi_offset;
__u8 csum_type;
__u8 tag_size;
+ __u16 io_desc_size;
int _evtfd;
int _shmid;
/* built from shmem, only for ublk_dump_dev() */
@@ -185,10 +186,11 @@ struct ublk_queue {
#define UBLKS_Q_NO_UBLK_FIXED_FD (1ULL << 62)
#define UBLKS_Q_PREPARED (1ULL << 61)
__u64 flags;
int ublk_fd; /* cached ublk char device fd */
__u8 metadata_size;
+ __u16 io_desc_size;
struct ublk_io ios[UBLK_QUEUE_DEPTH];
/* used for prep io commands */
pthread_spinlock_t lock;
};
@@ -459,13 +461,13 @@ static inline void ublk_mark_io_done(struct ublk_io *io, int res)
{
io->flags |= (UBLKS_IO_NEED_COMMIT_RQ_COMP | UBLKS_IO_FREE);
io->result = res;
}
-static inline const struct ublksrv_io_desc *ublk_get_iod(const struct ublk_queue *q, int tag)
+static inline const struct ublksrv_io_desc *ublk_get_iod(const struct ublk_queue *q, __u16 tag)
{
- return &q->io_cmd_buf[tag];
+ return (void *)q->io_cmd_buf + tag * (size_t)q->io_desc_size;
}
static inline void ublk_set_sqe_cmd_op(struct io_uring_sqe *sqe, __u32 cmd_op)
{
__u32 *addr = (__u32 *)&sqe->off;
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 7/8] selftests: ublk: add UBLK_F_IO_DESC_SIZE test
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
` (5 preceding siblings ...)
2026-08-03 21:14 ` [PATCH v2 6/8] selftests: ublk: add support for --io_desc_size Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-03 21:14 ` [PATCH v2 8/8] ublk: lift checks out of ublk_{,un}map_io() Caleb Sander Mateos
2026-08-04 2:32 ` [PATCH v2 0/8] ublk: io_desc optimizations Jens Axboe
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos
Add test loop_08, which creates a ublk device with UBLK_F_IO_DESC_SIZE
enabled and io_desc_size set to 64. The test issues verified I/O to the
device using fio.
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
---
tools/testing/selftests/ublk/Makefile | 1 +
tools/testing/selftests/ublk/test_loop_08.sh | 25 ++++++++++++++++++++
2 files changed, 26 insertions(+)
create mode 100755 tools/testing/selftests/ublk/test_loop_08.sh
diff --git a/tools/testing/selftests/ublk/Makefile b/tools/testing/selftests/ublk/Makefile
index 6e4fe8d1fed1..b00ef238a038 100644
--- a/tools/testing/selftests/ublk/Makefile
+++ b/tools/testing/selftests/ublk/Makefile
@@ -32,10 +32,11 @@ TEST_PROGS += test_loop_02.sh
TEST_PROGS += test_loop_03.sh
TEST_PROGS += test_loop_04.sh
TEST_PROGS += test_loop_05.sh
TEST_PROGS += test_loop_06.sh
TEST_PROGS += test_loop_07.sh
+TEST_PROGS += test_loop_08.sh
TEST_PROGS += test_integrity_01.sh
TEST_PROGS += test_integrity_02.sh
TEST_PROGS += test_integrity_03.sh
diff --git a/tools/testing/selftests/ublk/test_loop_08.sh b/tools/testing/selftests/ublk/test_loop_08.sh
new file mode 100755
index 000000000000..f7af2587482d
--- /dev/null
+++ b/tools/testing/selftests/ublk/test_loop_08.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
+
+ERR_CODE=0
+
+if ! _have_program fio; then
+ exit "$UBLK_SKIP_CODE"
+fi
+
+_prep_test "loop" "write and verify with io_desc_size"
+
+_create_backfile 0 256M
+
+dev_id=$(_add_ublk_dev -t loop --io_desc_size 64 "${UBLK_BACKFILES[0]}")
+_check_add_dev $TID $?
+
+# run fio over the ublk disk
+_run_fio_verify_io --filename=/dev/ublkb"${dev_id}" --size=256M
+ERR_CODE=$?
+
+_cleanup_test
+
+_show_result $TID $ERR_CODE
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 8/8] ublk: lift checks out of ublk_{,un}map_io()
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
` (6 preceding siblings ...)
2026-08-03 21:14 ` [PATCH v2 7/8] selftests: ublk: add UBLK_F_IO_DESC_SIZE test Caleb Sander Mateos
@ 2026-08-03 21:14 ` Caleb Sander Mateos
2026-08-04 2:32 ` [PATCH v2 0/8] ublk: io_desc optimizations Jens Axboe
8 siblings, 0 replies; 10+ messages in thread
From: Caleb Sander Mateos @ 2026-08-03 21:14 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Shuah Khan
Cc: linux-block, linux-kselftest, linux-kernel, Caleb Sander Mateos
ublk_map_io() and ublk_unmap_io() are no-ops for ublk devices that
enable user copy or zero copy, as well as for requests without data to
copy in the given direction. However, the implementation is a bit
convoluted, returning the full request data length and relying on the
caller to check the return value against the request length.
UBLK_F_SHMEM_ZC recently added branches to skip the ublk_{,un}map_io()
call for I/Os using a shared-memory buffer. This is a more logical place
for the device need_map and the ublk_need_{,un}map_req() checks, so move
them there from ublk_{,un}map_io().
Checking these conditions early also skips the expensive pointer-chasing
for the ublk_iod_is_shmem_zc() check in __ublk_complete_rq() for the
common case of a ublk device using user copy or zero copy.
Drop the req_op() filter in __ublk_complete_rq(), as it's redundant with
the ublk_need_unmap_req() check.
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
Reviewed-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/block/ublk_drv.c | 70 +++++++++++-----------------------------
1 file changed, 19 insertions(+), 51 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 2f5de735b2d2..9249d25dceed 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -1466,57 +1466,33 @@ static inline bool ublk_need_unmap_req(const struct request *req)
{
return blk_rq_has_data(req) &&
(req_op(req) == REQ_OP_READ || req_op(req) == REQ_OP_DRV_IN);
}
-static unsigned int ublk_map_io(const struct ublk_queue *ubq,
- const struct request *req,
+static unsigned int ublk_map_io(const struct request *req,
const struct ublk_io *io)
{
- const unsigned int rq_bytes = blk_rq_bytes(req);
+ struct iov_iter iter;
+ const int dir = ITER_DEST;
- if (!ublk_need_map_io(ubq))
- return rq_bytes;
-
- /*
- * no zero copy, we delay copy WRITE request data into ublksrv
- * context and the big benefit is that pinning pages in current
- * context is pretty fast, see ublk_pin_user_pages
- */
- if (ublk_need_map_req(req)) {
- struct iov_iter iter;
- const int dir = ITER_DEST;
-
- if (import_ubuf(dir, u64_to_user_ptr(io->buf.addr), rq_bytes,
- &iter) < 0)
- return 0;
+ if (import_ubuf(dir, u64_to_user_ptr(io->buf.addr), blk_rq_bytes(req),
+ &iter) < 0)
+ return 0;
- return ublk_copy_user_pages(req, 0, &iter, dir);
- }
- return rq_bytes;
+ return ublk_copy_user_pages(req, 0, &iter, dir);
}
-static unsigned int ublk_unmap_io(bool need_map,
- const struct request *req,
+static unsigned int ublk_unmap_io(const struct request *req,
const struct ublk_io *io)
{
- const unsigned int rq_bytes = blk_rq_bytes(req);
-
- if (!need_map)
- return rq_bytes;
-
- if (ublk_need_unmap_req(req)) {
- struct iov_iter iter;
- const int dir = ITER_SOURCE;
+ struct iov_iter iter;
+ const int dir = ITER_SOURCE;
- if (import_ubuf(dir, u64_to_user_ptr(io->buf.addr), io->res,
- &iter) < 0)
- return 0;
+ if (import_ubuf(dir, u64_to_user_ptr(io->buf.addr), io->res, &iter) < 0)
+ return 0;
- return ublk_copy_user_pages(req, 0, &iter, dir);
- }
- return rq_bytes;
+ return ublk_copy_user_pages(req, 0, &iter, dir);
}
static bool ublk_validate_req(const struct ublk_queue *ubq,
const struct request *req)
{
@@ -1588,26 +1564,17 @@ static inline void __ublk_complete_rq(struct request *req, struct ublk_io *io,
if (io->res < 0) {
res = errno_to_blk_status(io->res);
goto exit;
}
- /*
- * FLUSH, DISCARD or WRITE_ZEROES usually won't return bytes returned, so end them
- * directly.
- *
- * Both the two needn't unmap.
- */
- if (req_op(req) != REQ_OP_READ && req_op(req) != REQ_OP_WRITE &&
- req_op(req) != REQ_OP_DRV_IN)
- goto exit;
-
/* shmem zero copy: no data to unmap, pages already shared */
- if (ublk_iod_is_shmem_zc(req->mq_hctx->driver_data, req->tag))
+ if (!need_map || !ublk_need_unmap_req(req) ||
+ ublk_iod_is_shmem_zc(req->mq_hctx->driver_data, req->tag))
goto exit;
/* for READ request, writing data in iod->addr to rq buffers */
- unmapped_bytes = ublk_unmap_io(need_map, req, io);
+ unmapped_bytes = ublk_unmap_io(req, io);
/*
* Extremely impossible since we got data filled in just before
*
* Re-read simply for this unlikely case.
@@ -1769,14 +1736,15 @@ static bool ublk_start_io(const struct ublk_queue *ubq, struct request *req,
struct ublk_io *io)
{
unsigned mapped_bytes;
/* shmem zero copy: skip data copy, pages already shared */
- if (ublk_iod_is_shmem_zc(ubq, req->tag))
+ if (!ublk_need_map_io(ubq) || !ublk_need_map_req(req) ||
+ ublk_iod_is_shmem_zc(ubq, req->tag))
return true;
- mapped_bytes = ublk_map_io(ubq, req, io);
+ mapped_bytes = ublk_map_io(req, io);
/* partially mapped, update io descriptor */
if (unlikely(mapped_bytes != blk_rq_bytes(req))) {
/*
* Nothing mapped, retry until we succeed.
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v2 0/8] ublk: io_desc optimizations
2026-08-03 21:14 [PATCH v2 0/8] ublk: io_desc optimizations Caleb Sander Mateos
` (7 preceding siblings ...)
2026-08-03 21:14 ` [PATCH v2 8/8] ublk: lift checks out of ublk_{,un}map_io() Caleb Sander Mateos
@ 2026-08-04 2:32 ` Jens Axboe
8 siblings, 0 replies; 10+ messages in thread
From: Jens Axboe @ 2026-08-04 2:32 UTC (permalink / raw)
To: Ming Lei, Shuah Khan, Caleb Sander Mateos
Cc: linux-block, linux-kselftest, linux-kernel
On Mon, 03 Aug 2026 15:14:32 -0600, Caleb Sander Mateos wrote:
> This series is based on the "ublk: harden user buffer handling" series.
>
> ublk passes the parameters of incoming I/O in memory shared between the
> kernel ublk driver and userspace ublk server in struct ublksrv_io_desc.
>
> The thread submitting the ublk I/O writes to the io_desc, while the ublk
> server thread handling the I/O reads the io_desc. This basically
> guarantees a cache miss on both threads for each ublk I/O. Avoid the
> cache misses by writing the io_desc on the server thread in the kernel
> before dispatching the I/O to userspace.
>
> [...]
Applied, thanks!
[1/8] ublk: consistently use u16 for queue and tag numbers
commit: f510198855b6ddac0ffe62684dc407c56d3e0f24
[2/8] ublk: remove struct ublk_zoned_report_desc's operation field
commit: 3a00b782a7b63b1876261ab883e1d8e1aa0e09fb
[3/8] ublk: split request validation from io_desc init
commit: 8c76625ff9360cbe9ce0c47a624877aea14b3499
[4/8] ublk: initialize io_desc on daemon task
commit: 735409f58b3da45094f2dfd7c18fb9e1937431f1
[5/8] ublk: add UBLK_F_IO_DESC_SIZE
commit: 5c0958d80190822c4614ca00478d9acd9671bba6
[6/8] selftests: ublk: add support for --io_desc_size
commit: fc01b96d74b3a9eec2b558d49ec9f5176473766a
[7/8] selftests: ublk: add UBLK_F_IO_DESC_SIZE test
commit: d61d0f95e686be015cfaf193c0ae15156d1a0cc4
[8/8] ublk: lift checks out of ublk_{,un}map_io()
commit: a8a79eba22dc4c11f2877bcf9e8557f6d95541ac
Best regards,
--
Jens Axboe
^ permalink raw reply [flat|nested] 10+ messages in thread