]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests: ublk: add support for user copy to kublk
authorCaleb Sander Mateos <csander@purestorage.com>
Fri, 12 Dec 2025 17:17:06 +0000 (10:17 -0700)
committerJens Axboe <axboe@kernel.dk>
Fri, 12 Dec 2025 19:50:41 +0000 (12:50 -0700)
The ublk selftests mock ublk server kublk supports every data copy mode
except user copy. Add support for user copy to kublk, enabled via the
--user_copy (-u) command line argument. On writes, issue pread() calls
to copy the write data into the ublk_io's buffer before dispatching the
write to the target implementation. On reads, issue pwrite() calls to
copy read data from the ublk_io's buffer before committing the request.
Copy in 2 KB chunks to provide some coverage of the offseting logic.

Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
Reviewed-by: Ming Lei <ming.lei@redhat.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
tools/testing/selftests/ublk/file_backed.c
tools/testing/selftests/ublk/kublk.c
tools/testing/selftests/ublk/kublk.h
tools/testing/selftests/ublk/stripe.c

index cd9fe69ecce2016062657dd9d27593dd94c9ceb3..269d5f124e06a0387b522404d54a501be90f1529 100644 (file)
@@ -34,8 +34,9 @@ static int loop_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q,
        unsigned zc = ublk_queue_use_zc(q);
        unsigned auto_zc = ublk_queue_use_auto_zc(q);
        enum io_uring_op op = ublk_to_uring_op(iod, zc | auto_zc);
+       struct ublk_io *io = ublk_get_io(q, tag);
        struct io_uring_sqe *sqe[3];
-       void *addr = (zc | auto_zc) ? NULL : (void *)iod->addr;
+       void *addr = io->buf_addr;
 
        if (!zc || auto_zc) {
                ublk_io_alloc_sqes(t, sqe, 1);
@@ -56,7 +57,7 @@ static int loop_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q,
 
        ublk_io_alloc_sqes(t, sqe, 3);
 
-       io_uring_prep_buf_register(sqe[0], q, tag, q->q_id, ublk_get_io(q, tag)->buf_index);
+       io_uring_prep_buf_register(sqe[0], q, tag, q->q_id, io->buf_index);
        sqe[0]->flags |= IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_HARDLINK;
        sqe[0]->user_data = build_user_data(tag,
                        ublk_cmd_op_nr(sqe[0]->cmd_op), 0, q->q_id, 1);
@@ -68,7 +69,7 @@ static int loop_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q,
        sqe[1]->flags |= IOSQE_FIXED_FILE | IOSQE_IO_HARDLINK;
        sqe[1]->user_data = build_user_data(tag, ublk_op, 0, q->q_id, 1);
 
-       io_uring_prep_buf_unregister(sqe[2], q, tag, q->q_id, ublk_get_io(q, tag)->buf_index);
+       io_uring_prep_buf_unregister(sqe[2], q, tag, q->q_id, io->buf_index);
        sqe[2]->user_data = build_user_data(tag, ublk_cmd_op_nr(sqe[2]->cmd_op), 0, q->q_id, 1);
 
        return 2;
index 4dd02cb083baafcf9d74ad31fa0f1e47d14c685f..185ba553686abea11e90919da7518fb07227915e 100644 (file)
@@ -596,6 +596,38 @@ static void ublk_set_auto_buf_reg(const struct ublk_queue *q,
        sqe->addr = ublk_auto_buf_reg_to_sqe_addr(&buf);
 }
 
+/* Copy in pieces to test the buffer offset logic */
+#define UBLK_USER_COPY_LEN 2048
+
+static void ublk_user_copy(const struct ublk_io *io, __u8 match_ublk_op)
+{
+       const struct ublk_queue *q = ublk_io_to_queue(io);
+       const struct ublksrv_io_desc *iod = ublk_get_iod(q, io->tag);
+       __u64 off = ublk_user_copy_offset(q->q_id, io->tag);
+       __u8 ublk_op = ublksrv_get_op(iod);
+       __u32 len = iod->nr_sectors << 9;
+       void *addr = io->buf_addr;
+
+       if (ublk_op != match_ublk_op)
+               return;
+
+       while (len) {
+               __u32 copy_len = min(len, UBLK_USER_COPY_LEN);
+               ssize_t copied;
+
+               if (ublk_op == UBLK_IO_OP_WRITE)
+                       copied = pread(q->ublk_fd, addr, copy_len, off);
+               else if (ublk_op == UBLK_IO_OP_READ)
+                       copied = pwrite(q->ublk_fd, addr, copy_len, off);
+               else
+                       assert(0);
+               assert(copied == (ssize_t)copy_len);
+               addr += copy_len;
+               off += copy_len;
+               len -= copy_len;
+       }
+}
+
 int ublk_queue_io_cmd(struct ublk_thread *t, struct ublk_io *io)
 {
        struct ublk_queue *q = ublk_io_to_queue(io);
@@ -618,9 +650,12 @@ int ublk_queue_io_cmd(struct ublk_thread *t, struct ublk_io *io)
 
        if (io->flags & UBLKS_IO_NEED_GET_DATA)
                cmd_op = UBLK_U_IO_NEED_GET_DATA;
-       else if (io->flags & UBLKS_IO_NEED_COMMIT_RQ_COMP)
+       else if (io->flags & UBLKS_IO_NEED_COMMIT_RQ_COMP) {
+               if (ublk_queue_use_user_copy(q))
+                       ublk_user_copy(io, UBLK_IO_OP_READ);
+
                cmd_op = UBLK_U_IO_COMMIT_AND_FETCH_REQ;
-       else if (io->flags & UBLKS_IO_NEED_FETCH_RQ)
+       else if (io->flags & UBLKS_IO_NEED_FETCH_RQ)
                cmd_op = UBLK_U_IO_FETCH_REQ;
 
        if (io_uring_sq_space_left(&t->ring) < 1)
@@ -649,7 +684,7 @@ int ublk_queue_io_cmd(struct ublk_thread *t, struct ublk_io *io)
        sqe[0]->rw_flags        = 0;
        cmd->tag        = io->tag;
        cmd->q_id       = q->q_id;
-       if (!ublk_queue_no_buf(q))
+       if (!ublk_queue_no_buf(q) && !ublk_queue_use_user_copy(q))
                cmd->addr       = (__u64) (uintptr_t) io->buf_addr;
        else
                cmd->addr       = 0;
@@ -751,6 +786,10 @@ static void ublk_handle_uring_cmd(struct ublk_thread *t,
 
        if (cqe->res == UBLK_IO_RES_OK) {
                assert(tag < q->q_depth);
+
+               if (ublk_queue_use_user_copy(q))
+                       ublk_user_copy(io, UBLK_IO_OP_WRITE);
+
                if (q->tgt_ops->queue_io)
                        q->tgt_ops->queue_io(t, q, tag);
        } else if (cqe->res == UBLK_IO_RES_NEED_GET_DATA) {
@@ -1507,7 +1546,7 @@ static void __cmd_create_help(char *exe, bool recovery)
 
        printf("%s %s -t [null|loop|stripe|fault_inject] [-q nr_queues] [-d depth] [-n dev_id]\n",
                        exe, recovery ? "recover" : "add");
-       printf("\t[--foreground] [--quiet] [-z] [--auto_zc] [--auto_zc_fallback] [--debug_mask mask] [-r 0|1 ] [-g]\n");
+       printf("\t[--foreground] [--quiet] [-z] [--auto_zc] [--auto_zc_fallback] [--debug_mask mask] [-r 0|1] [-g] [-u]\n");
        printf("\t[-e 0|1 ] [-i 0|1] [--no_ublk_fixed_fd]\n");
        printf("\t[--nthreads threads] [--per_io_tasks]\n");
        printf("\t[target options] [backfile1] [backfile2] ...\n");
@@ -1568,6 +1607,7 @@ int main(int argc, char *argv[])
                { "get_data",           1,      NULL, 'g'},
                { "auto_zc",            0,      NULL,  0 },
                { "auto_zc_fallback",   0,      NULL,  0 },
+               { "user_copy",          0,      NULL, 'u'},
                { "size",               1,      NULL, 's'},
                { "nthreads",           1,      NULL,  0 },
                { "per_io_tasks",       0,      NULL,  0 },
@@ -1593,7 +1633,7 @@ int main(int argc, char *argv[])
 
        opterr = 0;
        optind = 2;
-       while ((opt = getopt_long(argc, argv, "t:n:d:q:r:e:i:s:gaz",
+       while ((opt = getopt_long(argc, argv, "t:n:d:q:r:e:i:s:gazu",
                                  longopts, &option_idx)) != -1) {
                switch (opt) {
                case 'a':
@@ -1633,6 +1673,9 @@ int main(int argc, char *argv[])
                case 'g':
                        ctx.flags |= UBLK_F_NEED_GET_DATA;
                        break;
+               case 'u':
+                       ctx.flags |= UBLK_F_USER_COPY;
+                       break;
                case 's':
                        ctx.size = strtoull(optarg, NULL, 10);
                        break;
index 6e8f381f34810d50c51f43638ece538a1e5dc3d1..8a83b90ec603ad00735a9393143b588a17a5c12f 100644 (file)
@@ -208,6 +208,12 @@ static inline int ublk_io_auto_zc_fallback(const struct ublksrv_io_desc *iod)
        return !!(iod->op_flags & UBLK_IO_F_NEED_REG_BUF);
 }
 
+static inline __u64 ublk_user_copy_offset(unsigned q_id, unsigned tag)
+{
+       return UBLKSRV_IO_BUF_OFFSET +
+              ((__u64)q_id << UBLK_QID_OFF | (__u64)tag << UBLK_TAG_OFF);
+}
+
 static inline int is_target_io(__u64 user_data)
 {
        return (user_data & (1ULL << 63)) != 0;
@@ -405,6 +411,11 @@ static inline bool ublk_queue_auto_zc_fallback(const struct ublk_queue *q)
        return !!(q->flags & UBLKS_Q_AUTO_BUF_REG_FALLBACK);
 }
 
+static inline bool ublk_queue_use_user_copy(const struct ublk_queue *q)
+{
+       return !!(q->flags & UBLK_F_USER_COPY);
+}
+
 static inline int ublk_queue_no_buf(const struct ublk_queue *q)
 {
        return ublk_queue_use_zc(q) || ublk_queue_use_auto_zc(q);
index 791fa8dc165109e810795406a1c71f582450e99a..fd412e1f01c0ee398343392662b78a8f9d4c65a2 100644 (file)
@@ -134,7 +134,7 @@ static int stripe_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q,
        struct stripe_array *s = alloc_stripe_array(conf, iod);
        struct ublk_io *io = ublk_get_io(q, tag);
        int i, extra = zc ? 2 : 0;
-       void *base = (zc | auto_zc) ? NULL : (void *)iod->addr;
+       void *base = io->buf_addr;
 
        io->private_data = s;
        calculate_stripe_array(conf, iod, s, base);