Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: ublk: support UBLK_F_AUTO_BUF_REG

Enable UBLK_F_AUTO_BUF_REG support for ublk utility by argument `--auto_zc`,
meantime support this feature in null, loop and stripe target code.

Add function test generic_08 for covering basic UBLK_F_AUTO_BUF_REG feature.

Also cover UBLK_F_AUTO_BUF_REG in stress_03, stress_04 and stress_05 test too.

'fio/t/io_uring -p0 /dev/ublkb0' shows that F_AUTO_BUF_REG can improve
IOPS by 50% compared with F_SUPPORT_ZERO_COPY in my test VM.

Signed-off-by: Ming Lei <ming.lei@redhat.com>
Link: https://lore.kernel.org/r/20250520045455.515691-6-ming.lei@redhat.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>

authored by

Ming Lei and committed by
Jens Axboe
8ccebc19 53f427e7

+138 -29
+2
tools/testing/selftests/ublk/Makefile
··· 15 15 TEST_PROGS += test_generic_06.sh 16 16 TEST_PROGS += test_generic_07.sh 17 17 18 + TEST_PROGS += test_generic_08.sh 19 + 18 20 TEST_PROGS += test_null_01.sh 19 21 TEST_PROGS += test_null_02.sh 20 22 TEST_PROGS += test_loop_01.sh
+8 -4
tools/testing/selftests/ublk/file_backed.c
··· 29 29 static int loop_queue_tgt_rw_io(struct ublk_queue *q, const struct ublksrv_io_desc *iod, int tag) 30 30 { 31 31 unsigned ublk_op = ublksrv_get_op(iod); 32 - int zc = ublk_queue_use_zc(q); 33 - enum io_uring_op op = ublk_to_uring_op(iod, zc); 32 + unsigned zc = ublk_queue_use_zc(q); 33 + unsigned auto_zc = ublk_queue_use_auto_zc(q); 34 + enum io_uring_op op = ublk_to_uring_op(iod, zc | auto_zc); 34 35 struct io_uring_sqe *sqe[3]; 36 + void *addr = (zc | auto_zc) ? NULL : (void *)iod->addr; 35 37 36 - if (!zc) { 38 + if (!zc || auto_zc) { 37 39 ublk_queue_alloc_sqes(q, sqe, 1); 38 40 if (!sqe[0]) 39 41 return -ENOMEM; 40 42 41 43 io_uring_prep_rw(op, sqe[0], 1 /*fds[1]*/, 42 - (void *)iod->addr, 44 + addr, 43 45 iod->nr_sectors << 9, 44 46 iod->start_sector << 9); 47 + if (auto_zc) 48 + sqe[0]->buf_index = tag; 45 49 io_uring_sqe_set_flags(sqe[0], IOSQE_FIXED_FILE); 46 50 /* bit63 marks us as tgt io */ 47 51 sqe[0]->user_data = build_user_data(tag, ublk_op, 0, 1);
+26 -4
tools/testing/selftests/ublk/kublk.c
··· 420 420 q->cmd_inflight = 0; 421 421 q->tid = gettid(); 422 422 423 - if (dev->dev_info.flags & UBLK_F_SUPPORT_ZERO_COPY) { 423 + if (dev->dev_info.flags & (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_AUTO_BUF_REG)) { 424 424 q->state |= UBLKSRV_NO_BUF; 425 - q->state |= UBLKSRV_ZC; 425 + if (dev->dev_info.flags & UBLK_F_SUPPORT_ZERO_COPY) 426 + q->state |= UBLKSRV_ZC; 427 + if (dev->dev_info.flags & UBLK_F_AUTO_BUF_REG) 428 + q->state |= UBLKSRV_AUTO_BUF_REG; 426 429 } 427 430 428 431 cmd_buf_size = ublk_queue_cmd_buf_sz(q); ··· 464 461 goto fail; 465 462 } 466 463 467 - if (dev->dev_info.flags & UBLK_F_SUPPORT_ZERO_COPY) { 464 + if (dev->dev_info.flags & (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_AUTO_BUF_REG)) { 468 465 ret = io_uring_register_buffers_sparse(&q->ring, q->q_depth); 469 466 if (ret) { 470 467 ublk_err("ublk dev %d queue %d register spare buffers failed %d", ··· 528 525 close(dev->fds[0]); 529 526 } 530 527 528 + static void ublk_set_auto_buf_reg(struct io_uring_sqe *sqe, 529 + unsigned short buf_idx, 530 + unsigned char flags) 531 + { 532 + struct ublk_auto_buf_reg buf = { 533 + .index = buf_idx, 534 + .flags = flags, 535 + }; 536 + 537 + sqe->addr = ublk_auto_buf_reg_to_sqe_addr(&buf); 538 + } 539 + 531 540 int ublk_queue_io_cmd(struct ublk_queue *q, struct ublk_io *io, unsigned tag) 532 541 { 533 542 struct ublksrv_io_cmd *cmd; ··· 593 578 cmd->addr = (__u64) (uintptr_t) io->buf_addr; 594 579 else 595 580 cmd->addr = 0; 581 + 582 + if (q->state & UBLKSRV_AUTO_BUF_REG) 583 + ublk_set_auto_buf_reg(sqe[0], tag, 0); 596 584 597 585 user_data = build_user_data(tag, _IOC_NR(cmd_op), 0, 0); 598 586 io_uring_sqe_set_data64(sqe[0], user_data); ··· 1224 1206 [const_ilog2(UBLK_F_USER_COPY)] = "USER_COPY", 1225 1207 [const_ilog2(UBLK_F_ZONED)] = "ZONED", 1226 1208 [const_ilog2(UBLK_F_USER_RECOVERY_FAIL_IO)] = "RECOVERY_FAIL_IO", 1209 + [const_ilog2(UBLK_F_AUTO_BUF_REG)] = "AUTO_BUF_REG", 1227 1210 }; 1228 1211 struct ublk_dev *dev; 1229 1212 __u64 features = 0; ··· 1264 1245 1265 1246 printf("%s %s -t [null|loop|stripe|fault_inject] [-q nr_queues] [-d depth] [-n dev_id]\n", 1266 1247 exe, recovery ? "recover" : "add"); 1267 - printf("\t[--foreground] [--quiet] [-z] [--debug_mask mask] [-r 0|1 ] [-g]\n"); 1248 + printf("\t[--foreground] [--quiet] [-z] [--auto_zc] [--debug_mask mask] [-r 0|1 ] [-g]\n"); 1268 1249 printf("\t[-e 0|1 ] [-i 0|1]\n"); 1269 1250 printf("\t[target options] [backfile1] [backfile2] ...\n"); 1270 1251 printf("\tdefault: nr_queues=2(max 32), depth=128(max 1024), dev_id=-1(auto allocation)\n"); ··· 1319 1300 { "recovery_fail_io", 1, NULL, 'e'}, 1320 1301 { "recovery_reissue", 1, NULL, 'i'}, 1321 1302 { "get_data", 1, NULL, 'g'}, 1303 + { "auto_zc", 0, NULL, 0}, 1322 1304 { 0, 0, 0, 0 } 1323 1305 }; 1324 1306 const struct ublk_tgt_ops *ops = NULL; ··· 1388 1368 ublk_dbg_mask = 0; 1389 1369 if (!strcmp(longopts[option_idx].name, "foreground")) 1390 1370 ctx.fg = 1; 1371 + if (!strcmp(longopts[option_idx].name, "auto_zc")) 1372 + ctx.flags |= UBLK_F_AUTO_BUF_REG; 1391 1373 break; 1392 1374 case '?': 1393 1375 /*
+7
tools/testing/selftests/ublk/kublk.h
··· 115 115 #define UBLKSRV_NEED_COMMIT_RQ_COMP (1UL << 1) 116 116 #define UBLKSRV_IO_FREE (1UL << 2) 117 117 #define UBLKSRV_NEED_GET_DATA (1UL << 3) 118 + #define UBLKSRV_NEED_REG_BUF (1UL << 4) 118 119 unsigned short flags; 119 120 unsigned short refs; /* used by target code only */ 120 121 ··· 169 168 #define UBLKSRV_QUEUE_IDLE (1U << 1) 170 169 #define UBLKSRV_NO_BUF (1U << 2) 171 170 #define UBLKSRV_ZC (1U << 3) 171 + #define UBLKSRV_AUTO_BUF_REG (1U << 4) 172 172 unsigned state; 173 173 pid_t tid; 174 174 pthread_t thread; ··· 387 385 static inline int ublk_queue_use_zc(const struct ublk_queue *q) 388 386 { 389 387 return q->state & UBLKSRV_ZC; 388 + } 389 + 390 + static inline int ublk_queue_use_auto_zc(const struct ublk_queue *q) 391 + { 392 + return q->state & UBLKSRV_AUTO_BUF_REG; 390 393 } 391 394 392 395 extern const struct ublk_tgt_ops null_tgt_ops;
+32 -11
tools/testing/selftests/ublk/null.c
··· 42 42 return 0; 43 43 } 44 44 45 + static void __setup_nop_io(int tag, const struct ublksrv_io_desc *iod, 46 + struct io_uring_sqe *sqe) 47 + { 48 + unsigned ublk_op = ublksrv_get_op(iod); 49 + 50 + io_uring_prep_nop(sqe); 51 + sqe->buf_index = tag; 52 + sqe->flags |= IOSQE_FIXED_FILE; 53 + sqe->rw_flags = IORING_NOP_FIXED_BUFFER | IORING_NOP_INJECT_RESULT; 54 + sqe->len = iod->nr_sectors << 9; /* injected result */ 55 + sqe->user_data = build_user_data(tag, ublk_op, 0, 1); 56 + } 57 + 45 58 static int null_queue_zc_io(struct ublk_queue *q, int tag) 46 59 { 47 60 const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag); 48 - unsigned ublk_op = ublksrv_get_op(iod); 49 61 struct io_uring_sqe *sqe[3]; 50 62 51 63 ublk_queue_alloc_sqes(q, sqe, 3); ··· 67 55 ublk_cmd_op_nr(sqe[0]->cmd_op), 0, 1); 68 56 sqe[0]->flags |= IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_HARDLINK; 69 57 70 - io_uring_prep_nop(sqe[1]); 71 - sqe[1]->buf_index = tag; 72 - sqe[1]->flags |= IOSQE_FIXED_FILE | IOSQE_IO_HARDLINK; 73 - sqe[1]->rw_flags = IORING_NOP_FIXED_BUFFER | IORING_NOP_INJECT_RESULT; 74 - sqe[1]->len = iod->nr_sectors << 9; /* injected result */ 75 - sqe[1]->user_data = build_user_data(tag, ublk_op, 0, 1); 58 + __setup_nop_io(tag, iod, sqe[1]); 59 + sqe[1]->flags |= IOSQE_IO_HARDLINK; 76 60 77 61 io_uring_prep_buf_unregister(sqe[2], 0, tag, q->q_id, tag); 78 62 sqe[2]->user_data = build_user_data(tag, ublk_cmd_op_nr(sqe[2]->cmd_op), 0, 1); 79 63 80 64 // buf register is marked as IOSQE_CQE_SKIP_SUCCESS 81 65 return 2; 66 + } 67 + 68 + static int null_queue_auto_zc_io(struct ublk_queue *q, int tag) 69 + { 70 + const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag); 71 + struct io_uring_sqe *sqe[1]; 72 + 73 + ublk_queue_alloc_sqes(q, sqe, 1); 74 + __setup_nop_io(tag, iod, sqe[0]); 75 + return 1; 82 76 } 83 77 84 78 static void ublk_null_io_done(struct ublk_queue *q, int tag, ··· 112 94 static int ublk_null_queue_io(struct ublk_queue *q, int tag) 113 95 { 114 96 const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag); 115 - int zc = ublk_queue_use_zc(q); 97 + unsigned auto_zc = ublk_queue_use_auto_zc(q); 98 + unsigned zc = ublk_queue_use_zc(q); 116 99 int queued; 117 100 118 - if (!zc) { 101 + if (auto_zc) 102 + queued = null_queue_auto_zc_io(q, tag); 103 + else if (zc) 104 + queued = null_queue_zc_io(q, tag); 105 + else { 119 106 ublk_complete_io(q, tag, iod->nr_sectors << 9); 120 107 return 0; 121 108 } 122 - 123 - queued = null_queue_zc_io(q, tag); 124 109 ublk_queued_tgt_io(q, tag, queued); 125 110 return 0; 126 111 }
+11 -10
tools/testing/selftests/ublk/stripe.c
··· 70 70 } 71 71 72 72 static void calculate_stripe_array(const struct stripe_conf *conf, 73 - const struct ublksrv_io_desc *iod, struct stripe_array *s) 73 + const struct ublksrv_io_desc *iod, struct stripe_array *s, void *base) 74 74 { 75 75 const unsigned shift = conf->shift - 9; 76 76 const unsigned chunk_sects = 1 << shift; ··· 102 102 } 103 103 104 104 assert(this->nr_vec < this->cap); 105 - this->vec[this->nr_vec].iov_base = (void *)(iod->addr + done); 105 + this->vec[this->nr_vec].iov_base = (void *)(base + done); 106 106 this->vec[this->nr_vec++].iov_len = nr_sects << 9; 107 107 108 108 start += nr_sects; ··· 126 126 static int stripe_queue_tgt_rw_io(struct ublk_queue *q, const struct ublksrv_io_desc *iod, int tag) 127 127 { 128 128 const struct stripe_conf *conf = get_chunk_shift(q); 129 - int zc = !!(ublk_queue_use_zc(q) != 0); 130 - enum io_uring_op op = stripe_to_uring_op(iod, zc); 129 + unsigned auto_zc = (ublk_queue_use_auto_zc(q) != 0); 130 + unsigned zc = (ublk_queue_use_zc(q) != 0); 131 + enum io_uring_op op = stripe_to_uring_op(iod, zc | auto_zc); 131 132 struct io_uring_sqe *sqe[NR_STRIPE]; 132 133 struct stripe_array *s = alloc_stripe_array(conf, iod); 133 134 struct ublk_io *io = ublk_get_io(q, tag); 134 135 int i, extra = zc ? 2 : 0; 136 + void *base = (zc | auto_zc) ? NULL : (void *)iod->addr; 135 137 136 138 io->private_data = s; 137 - calculate_stripe_array(conf, iod, s); 139 + calculate_stripe_array(conf, iod, s, base); 138 140 139 141 ublk_queue_alloc_sqes(q, sqe, s->nr + extra); 140 142 ··· 155 153 (void *)t->vec, 156 154 t->nr_vec, 157 155 t->start << 9); 158 - if (zc) { 156 + io_uring_sqe_set_flags(sqe[i], IOSQE_FIXED_FILE); 157 + if (auto_zc || zc) { 159 158 sqe[i]->buf_index = tag; 160 - io_uring_sqe_set_flags(sqe[i], 161 - IOSQE_FIXED_FILE | IOSQE_IO_HARDLINK); 162 - } else { 163 - io_uring_sqe_set_flags(sqe[i], IOSQE_FIXED_FILE); 159 + if (zc) 160 + sqe[i]->flags |= IOSQE_IO_HARDLINK; 164 161 } 165 162 /* bit63 marks us as tgt io */ 166 163 sqe[i]->user_data = build_user_data(tag, ublksrv_get_op(iod), i - zc, 1);
+32
tools/testing/selftests/ublk/test_generic_08.sh
··· 1 + #!/bin/bash 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + . "$(cd "$(dirname "$0")" && pwd)"/test_common.sh 5 + 6 + TID="generic_08" 7 + ERR_CODE=0 8 + 9 + if ! _have_feature "AUTO_BUF_REG"; then 10 + exit "$UBLK_SKIP_CODE" 11 + fi 12 + 13 + _prep_test "generic" "test UBLK_F_AUTO_BUF_REG" 14 + 15 + _create_backfile 0 256M 16 + _create_backfile 1 256M 17 + 18 + dev_id=$(_add_ublk_dev -t loop -q 2 --auto_zc "${UBLK_BACKFILES[0]}") 19 + _check_add_dev $TID $? 20 + 21 + if ! _mkfs_mount_test /dev/ublkb"${dev_id}"; then 22 + _cleanup_test "generic" 23 + _show_result $TID 255 24 + fi 25 + 26 + dev_id=$(_add_ublk_dev -t stripe --auto_zc "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}") 27 + _check_add_dev $TID $? 28 + _mkfs_mount_test /dev/ublkb"${dev_id}" 29 + ERR_CODE=$? 30 + 31 + _cleanup_test "generic" 32 + _show_result $TID $ERR_CODE
+6
tools/testing/selftests/ublk/test_stress_03.sh
··· 32 32 ublk_io_and_remove 8G -t null -q 4 -z & 33 33 ublk_io_and_remove 256M -t loop -q 4 -z "${UBLK_BACKFILES[0]}" & 34 34 ublk_io_and_remove 256M -t stripe -q 4 -z "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & 35 + 36 + if _have_feature "AUTO_BUF_REG"; then 37 + ublk_io_and_remove 8G -t null -q 4 --auto_zc & 38 + ublk_io_and_remove 256M -t loop -q 4 --auto_zc "${UBLK_BACKFILES[0]}" & 39 + ublk_io_and_remove 256M -t stripe -q 4 --auto_zc "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & 40 + fi 35 41 wait 36 42 37 43 _cleanup_test "stress"
+6
tools/testing/selftests/ublk/test_stress_04.sh
··· 31 31 ublk_io_and_kill_daemon 8G -t null -q 4 -z & 32 32 ublk_io_and_kill_daemon 256M -t loop -q 4 -z "${UBLK_BACKFILES[0]}" & 33 33 ublk_io_and_kill_daemon 256M -t stripe -q 4 -z "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & 34 + 35 + if _have_feature "AUTO_BUF_REG"; then 36 + ublk_io_and_kill_daemon 8G -t null -q 4 --auto_zc & 37 + ublk_io_and_kill_daemon 256M -t loop -q 4 --auto_zc "${UBLK_BACKFILES[0]}" & 38 + ublk_io_and_kill_daemon 256M -t stripe -q 4 --auto_zc "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & 39 + fi 34 40 wait 35 41 36 42 _cleanup_test "stress"
+8
tools/testing/selftests/ublk/test_stress_05.sh
··· 60 60 done 61 61 fi 62 62 63 + if _have_feature "AUTO_BUF_REG"; then 64 + for reissue in $(seq 0 1); do 65 + ublk_io_and_remove 8G -t null -q 4 -g --auto_zc -r 1 -i "$reissue" & 66 + ublk_io_and_remove 256M -t loop -q 4 -g --auto_zc -r 1 -i "$reissue" "${UBLK_BACKFILES[1]}" & 67 + wait 68 + done 69 + fi 70 + 63 71 _cleanup_test "stress" 64 72 _show_result $TID $ERR_CODE