Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2ca60ee
blk-integrity: take const pointer in blk_integrity_rq()
calebsander Jan 8, 2026
bb8d04f
ublk: move ublk flag check functions earlier
calebsander Jan 8, 2026
6b7d68c
ublk: support UBLK_PARAM_TYPE_INTEGRITY in device creation
Jan 8, 2026
e80cbfe
ublk: set UBLK_IO_F_INTEGRITY in ublksrv_io_desc
calebsander Jan 8, 2026
95f9514
ublk: split out ublk_copy_user_bvec() helper
calebsander Jan 8, 2026
ea4b258
ublk: split out ublk_user_copy() helper
calebsander Jan 8, 2026
73b62cf
ublk: inline ublk_check_and_get_req() into ublk_user_copy()
calebsander Jan 8, 2026
ec4b618
ublk: move offset check out of __ublk_check_and_get_req()
calebsander Jan 8, 2026
23827b7
ublk: implement integrity user copy
Jan 8, 2026
2d8b460
ublk: support UBLK_F_INTEGRITY
Jan 8, 2026
1b2f07c
ublk: optimize ublk_user_copy() on daemon task
calebsander Jan 8, 2026
83480a6
selftests: ublk: display UBLK_F_INTEGRITY support
calebsander Jan 8, 2026
7fa7457
selftests: ublk: add utility to get block device metadata size
calebsander Jan 8, 2026
a26b159
selftests: ublk: add kublk support for integrity params
calebsander Jan 8, 2026
661afba
selftests: ublk: implement integrity user copy in kublk
calebsander Jan 8, 2026
b95ac3b
selftests: ublk: support non-O_DIRECT backing files
calebsander Jan 8, 2026
3fd28cf
selftests: ublk: add integrity data support to loop target
calebsander Jan 8, 2026
ad42c07
selftests: ublk: add integrity params test
calebsander Jan 8, 2026
c873e00
selftests: ublk: add end-to-end integrity test
calebsander Jan 8, 2026
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
358 changes: 256 additions & 102 deletions drivers/block/ublk_drv.c

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions include/linux/blk-integrity.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ static inline unsigned int bio_integrity_bytes(struct blk_integrity *bi,
return bio_integrity_intervals(bi, sectors) * bi->metadata_size;
}

static inline bool blk_integrity_rq(struct request *rq)
static inline bool blk_integrity_rq(const struct request *rq)
{
return rq->cmd_flags & REQ_INTEGRITY;
}
Expand Down Expand Up @@ -168,9 +168,9 @@ static inline unsigned int bio_integrity_bytes(struct blk_integrity *bi,
{
return 0;
}
static inline int blk_integrity_rq(struct request *rq)
static inline bool blk_integrity_rq(const struct request *rq)
{
return 0;
return false;
}

static inline struct bio_vec rq_integrity_vec(struct request *rq)
Expand Down
25 changes: 25 additions & 0 deletions include/uapi/linux/ublk_cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@
#define UBLKSRV_IO_BUF_TOTAL_BITS (UBLK_QID_OFF + UBLK_QID_BITS)
#define UBLKSRV_IO_BUF_TOTAL_SIZE (1ULL << UBLKSRV_IO_BUF_TOTAL_BITS)

/* Copy to/from request integrity buffer instead of data buffer */
#define UBLK_INTEGRITY_FLAG_OFF 62
#define UBLKSRV_IO_INTEGRITY_FLAG (1ULL << UBLK_INTEGRITY_FLAG_OFF)

/*
* ublk server can register data buffers for incoming I/O requests with a sparse
* io_uring buffer table. The request buffer can then be used as the data buffer
Expand Down Expand Up @@ -311,6 +315,12 @@
*/
#define UBLK_F_BUF_REG_OFF_DAEMON (1ULL << 14)

/*
* ublk device supports requests with integrity/metadata buffer.
* Requires UBLK_F_USER_COPY.
*/
#define UBLK_F_INTEGRITY (1ULL << 16)

/* device state */
#define UBLK_S_DEV_DEAD 0
#define UBLK_S_DEV_LIVE 1
Expand Down Expand Up @@ -408,6 +418,8 @@ struct ublksrv_ctrl_dev_info {
* passed in.
*/
#define UBLK_IO_F_NEED_REG_BUF (1U << 17)
/* Request has an integrity data buffer */
#define UBLK_IO_F_INTEGRITY (1UL << 18)

/*
* io cmd is described by this structure, and stored in share memory, indexed
Expand Down Expand Up @@ -600,6 +612,17 @@ struct ublk_param_segment {
__u8 pad[2];
};

struct ublk_param_integrity {
__u32 flags; /* LBMD_PI_CAP_* from linux/fs.h */
__u16 max_integrity_segments; /* 0 means no limit */
__u8 interval_exp;
__u8 metadata_size; /* UBLK_PARAM_TYPE_INTEGRITY requires nonzero */
__u8 pi_offset;
__u8 csum_type; /* LBMD_PI_CSUM_* from linux/fs.h */
__u8 tag_size;
__u8 pad[5];
};

struct ublk_params {
/*
* Total length of parameters, userspace has to set 'len' for both
Expand All @@ -614,6 +637,7 @@ struct ublk_params {
#define UBLK_PARAM_TYPE_ZONED (1 << 3)
#define UBLK_PARAM_TYPE_DMA_ALIGN (1 << 4)
#define UBLK_PARAM_TYPE_SEGMENT (1 << 5)
#define UBLK_PARAM_TYPE_INTEGRITY (1 << 6) /* requires UBLK_F_INTEGRITY */
__u32 types; /* types of parameter included */

struct ublk_param_basic basic;
Expand All @@ -622,6 +646,7 @@ struct ublk_params {
struct ublk_param_zoned zoned;
struct ublk_param_dma_align dma;
struct ublk_param_segment seg;
struct ublk_param_integrity integrity;
};

#endif
7 changes: 5 additions & 2 deletions tools/testing/selftests/ublk/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ TEST_PROGS += test_generic_15.sh
TEST_PROGS += test_null_01.sh
TEST_PROGS += test_null_02.sh
TEST_PROGS += test_null_03.sh
TEST_PROGS += test_null_04.sh
TEST_PROGS += test_loop_01.sh
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_stripe_01.sh
TEST_PROGS += test_stripe_02.sh
TEST_PROGS += test_stripe_03.sh
Expand All @@ -49,12 +51,13 @@ TEST_PROGS += test_stress_05.sh
TEST_PROGS += test_stress_06.sh
TEST_PROGS += test_stress_07.sh

TEST_GEN_PROGS_EXTENDED = kublk
TEST_GEN_PROGS_EXTENDED = kublk metadata_size
STANDALONE_UTILS := metadata_size.c

LOCAL_HDRS += $(wildcard *.h)
include ../lib.mk

$(TEST_GEN_PROGS_EXTENDED): $(wildcard *.c)
$(OUTPUT)/kublk: $(filter-out $(STANDALONE_UTILS),$(wildcard *.c))

check:
shellcheck -x -f gcc *.sh
4 changes: 2 additions & 2 deletions tools/testing/selftests/ublk/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ void backing_file_tgt_deinit(struct ublk_dev *dev)
}
}

int backing_file_tgt_init(struct ublk_dev *dev)
int backing_file_tgt_init(struct ublk_dev *dev, unsigned int nr_direct)
{
int fd, i;

Expand All @@ -25,7 +25,7 @@ int backing_file_tgt_init(struct ublk_dev *dev)

ublk_dbg(UBLK_DBG_DEV, "%s: file %d: %s\n", __func__, i, file);

fd = open(file, O_RDWR | O_DIRECT);
fd = open(file, O_RDWR | (i < nr_direct ? O_DIRECT : 0));
if (fd < 0) {
ublk_err("%s: backing file %s can't be opened: %s\n",
__func__, file, strerror(errno));
Expand Down
1 change: 1 addition & 0 deletions tools/testing/selftests/ublk/fault_inject.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ static int ublk_fault_inject_tgt_init(const struct dev_ctx *ctx,
.dev_sectors = dev_size >> 9,
},
};
ublk_set_integrity_params(ctx, &dev->tgt.params);

dev->private_data = (void *)(unsigned long)(ctx->fault_inject.delay_us * 1000);
return 0;
Expand Down
90 changes: 75 additions & 15 deletions tools/testing/selftests/ublk/file_backed.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,38 @@ static int loop_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *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);
__u64 offset = iod->start_sector << 9;
__u32 len = iod->nr_sectors << 9;
struct io_uring_sqe *sqe[3];
void *addr = io->buf_addr;

if (iod->op_flags & UBLK_IO_F_INTEGRITY) {
ublk_io_alloc_sqes(t, sqe, 1);
/* Use second backing file for integrity data */
io_uring_prep_rw(op, sqe[0], ublk_get_registered_fd(q, 2),
io->integrity_buf,
ublk_integrity_len(q, len),
ublk_integrity_len(q, offset));
sqe[0]->flags = IOSQE_FIXED_FILE;
/* tgt_data = 1 indicates integrity I/O */
sqe[0]->user_data = build_user_data(tag, ublk_op, 1, q->q_id, 1);
}

if (!zc || auto_zc) {
ublk_io_alloc_sqes(t, sqe, 1);
if (!sqe[0])
return -ENOMEM;

io_uring_prep_rw(op, sqe[0], ublk_get_registered_fd(q, 1) /*fds[1]*/,
addr,
iod->nr_sectors << 9,
iod->start_sector << 9);
len,
offset);
if (auto_zc)
sqe[0]->buf_index = tag;
io_uring_sqe_set_flags(sqe[0], IOSQE_FIXED_FILE);
/* bit63 marks us as tgt io */
sqe[0]->user_data = build_user_data(tag, ublk_op, 0, q->q_id, 1);
return 1;
return !!(iod->op_flags & UBLK_IO_F_INTEGRITY) + 1;
}

ublk_io_alloc_sqes(t, sqe, 3);
Expand All @@ -63,16 +77,16 @@ static int loop_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q,
ublk_cmd_op_nr(sqe[0]->cmd_op), 0, q->q_id, 1);

io_uring_prep_rw(op, sqe[1], ublk_get_registered_fd(q, 1) /*fds[1]*/, 0,
iod->nr_sectors << 9,
iod->start_sector << 9);
len,
offset);
sqe[1]->buf_index = tag;
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, 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;
return !!(iod->op_flags & UBLK_IO_F_INTEGRITY) + 2;
}

static int loop_queue_tgt_io(struct ublk_thread *t, struct ublk_queue *q, int tag)
Expand Down Expand Up @@ -119,12 +133,17 @@ static void ublk_loop_io_done(struct ublk_thread *t, struct ublk_queue *q,
unsigned op = user_data_to_op(cqe->user_data);
struct ublk_io *io = ublk_get_io(q, tag);

if (cqe->res < 0 || op != ublk_cmd_op_nr(UBLK_U_IO_UNREGISTER_IO_BUF)) {
if (!io->result)
io->result = cqe->res;
if (cqe->res < 0)
ublk_err("%s: io failed op %x user_data %lx\n",
__func__, op, cqe->user_data);
if (cqe->res < 0) {
io->result = cqe->res;
ublk_err("%s: io failed op %x user_data %lx\n",
__func__, op, cqe->user_data);
} else if (op != ublk_cmd_op_nr(UBLK_U_IO_UNREGISTER_IO_BUF)) {
__s32 data_len = user_data_to_tgt_data(cqe->user_data)
? ublk_integrity_data_len(q, cqe->res)
: cqe->res;

if (!io->result || data_len < io->result)
io->result = data_len;
}

/* buffer register op is IOSQE_CQE_SKIP_SUCCESS */
Expand All @@ -135,9 +154,30 @@ static void ublk_loop_io_done(struct ublk_thread *t, struct ublk_queue *q,
ublk_complete_io(t, q, tag, io->result);
}

static int ublk_loop_memset_file(int fd, __u8 byte, size_t len)
{
off_t offset = 0;
__u8 buf[4096];

memset(buf, byte, sizeof(buf));
while (len) {
int ret = pwrite(fd, buf, min(len, sizeof(buf)), offset);

if (ret < 0)
return -errno;
if (!ret)
return -EIO;

len -= ret;
offset += ret;
}
return 0;
}

static int ublk_loop_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev)
{
unsigned long long bytes;
unsigned long blocks;
int ret;
struct ublk_params p = {
.types = UBLK_PARAM_TYPE_BASIC | UBLK_PARAM_TYPE_DMA_ALIGN,
Expand All @@ -154,19 +194,39 @@ static int ublk_loop_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev)
},
};

ublk_set_integrity_params(ctx, &p);
if (ctx->auto_zc_fallback) {
ublk_err("%s: not support auto_zc_fallback\n", __func__);
return -EINVAL;
}

ret = backing_file_tgt_init(dev);
/* Use O_DIRECT only for data file */
ret = backing_file_tgt_init(dev, 1);
if (ret)
return ret;

if (dev->tgt.nr_backing_files != 1)
/* Expect a second file for integrity data */
if (dev->tgt.nr_backing_files != 1 + !!ctx->metadata_size)
return -EINVAL;

bytes = dev->tgt.backing_file_size[0];
blocks = dev->tgt.backing_file_size[0] >> p.basic.logical_bs_shift;
if (ctx->metadata_size) {
unsigned long metadata_blocks =
dev->tgt.backing_file_size[1] / ctx->metadata_size;
unsigned long integrity_len;

/* Ensure both data and integrity data fit in backing files */
blocks = min(blocks, metadata_blocks);
integrity_len = blocks * ctx->metadata_size;
/*
* Initialize PI app tag and ref tag to 0xFF
* to disable bio-integrity-auto checks
*/
ret = ublk_loop_memset_file(dev->fds[2], 0xFF, integrity_len);
if (ret)
return ret;
}
bytes = blocks << p.basic.logical_bs_shift;
dev->tgt.dev_size = bytes;
p.basic.dev_sectors = bytes >> 9;
dev->tgt.params = p;
Expand Down
Loading