Commit 5d95bfb5 authored by Ming Lei's avatar Ming Lei Committed by Jens Axboe
Browse files

selftests: ublk: add file backed ublk



Add file backed ublk target code, meantime add one fio test for
covering write verify, another test for mkfs/mount/umount.

Signed-off-by: default avatarMing Lei <ming.lei@redhat.com>
Link: https://lore.kernel.org/r/20250228161919.2869102-3-ming.lei@redhat.com


Signed-off-by: default avatarJens Axboe <axboe@kernel.dk>
parent 6aecda00
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -4,9 +4,11 @@ CFLAGS += -O3 -Wl,-no-as-needed -Wall -I $(top_srcdir)
LDLIBS += -lpthread -lm -luring

TEST_PROGS := test_null_01.sh
TEST_PROGS += test_loop_01.sh
TEST_PROGS += test_loop_02.sh

TEST_GEN_PROGS_EXTENDED = kublk

include ../lib.mk

$(TEST_GEN_PROGS_EXTENDED): kublk.c null.c
$(TEST_GEN_PROGS_EXTENDED): kublk.c null.c file_backed.c
+158 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#include "kublk.h"

static void backing_file_tgt_deinit(struct ublk_dev *dev)
{
	int i;

	for (i = 1; i < dev->nr_fds; i++) {
		fsync(dev->fds[i]);
		close(dev->fds[i]);
	}
}

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

	assert(dev->nr_fds == 1);

	for (i = 0; i < dev->tgt.nr_backing_files; i++) {
		char *file = dev->tgt.backing_file[i];
		unsigned long bytes;
		struct stat st;

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

		fd = open(file, O_RDWR | O_DIRECT);
		if (fd < 0) {
			ublk_err("%s: backing file %s can't be opened: %s\n",
					__func__, file, strerror(errno));
			return -EBADF;
		}

		if (fstat(fd, &st) < 0) {
			close(fd);
			return -EBADF;
		}

		if (S_ISREG(st.st_mode))
			bytes = st.st_size;
		else if (S_ISBLK(st.st_mode)) {
			if (ioctl(fd, BLKGETSIZE64, &bytes) != 0)
				return -1;
		} else {
			return -EINVAL;
		}

		dev->tgt.backing_file_size[i] = bytes;
		dev->fds[dev->nr_fds] = fd;
		dev->nr_fds += 1;
	}

	return 0;
}

static int loop_queue_tgt_io(struct ublk_queue *q, int tag)
{
	const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
	struct io_uring_sqe *sqe = ublk_queue_alloc_sqe(q);
	unsigned ublk_op = ublksrv_get_op(iod);

	if (!sqe)
		return -ENOMEM;

	switch (ublk_op) {
	case UBLK_IO_OP_FLUSH:
		io_uring_prep_sync_file_range(sqe, 1 /*fds[1]*/,
				iod->nr_sectors << 9,
				iod->start_sector << 9,
				IORING_FSYNC_DATASYNC);
		io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE);
		break;
	case UBLK_IO_OP_WRITE_ZEROES:
	case UBLK_IO_OP_DISCARD:
		return -ENOTSUP;
	case UBLK_IO_OP_READ:
		io_uring_prep_read(sqe, 1 /*fds[1]*/,
				(void *)iod->addr,
				iod->nr_sectors << 9,
				iod->start_sector << 9);
		io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE);
		break;
	case UBLK_IO_OP_WRITE:
		io_uring_prep_write(sqe, 1 /*fds[1]*/,
				(void *)iod->addr,
				iod->nr_sectors << 9,
				iod->start_sector << 9);
		io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE);
		break;
	default:
		return -EINVAL;
	}

	q->io_inflight++;
	/* bit63 marks us as tgt io */
	sqe->user_data = build_user_data(tag, ublk_op, 0, 1);

	ublk_dbg(UBLK_DBG_IO, "%s: tag %d ublk io %x %llx %u\n", __func__, tag,
			iod->op_flags, iod->start_sector, iod->nr_sectors << 9);
	return 1;
}

static int ublk_loop_queue_io(struct ublk_queue *q, int tag)
{
	int queued = loop_queue_tgt_io(q, tag);

	if (queued < 0)
		ublk_complete_io(q, tag, queued);

	return 0;
}

static void ublk_loop_io_done(struct ublk_queue *q, int tag,
		const struct io_uring_cqe *cqe)
{
	int cqe_tag = user_data_to_tag(cqe->user_data);

	assert(tag == cqe_tag);
	ublk_complete_io(q, tag, cqe->res);
	q->io_inflight--;
}

static int ublk_loop_tgt_init(struct ublk_dev *dev)
{
	unsigned long long bytes;
	int ret;
	struct ublk_params p = {
		.types = UBLK_PARAM_TYPE_BASIC,
		.basic = {
			.logical_bs_shift	= 9,
			.physical_bs_shift	= 12,
			.io_opt_shift	= 12,
			.io_min_shift	= 9,
			.max_sectors = dev->dev_info.max_io_buf_bytes >> 9,
		},
	};

	assert(dev->tgt.nr_backing_files == 1);
	ret = backing_file_tgt_init(dev);
	if (ret)
		return ret;

	bytes = dev->tgt.backing_file_size[0];
	dev->tgt.dev_size = bytes;
	p.basic.dev_sectors = bytes >> 9;
	dev->tgt.params = p;

	return 0;
}

const struct ublk_tgt_ops loop_tgt_ops = {
	.name = "loop",
	.init_tgt = ublk_loop_tgt_init,
	.deinit_tgt = backing_file_tgt_deinit,
	.queue_io = ublk_loop_queue_io,
	.tgt_io_done = ublk_loop_io_done,
};
+10 −2
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
unsigned int ublk_dbg_mask = UBLK_LOG;
static const struct ublk_tgt_ops *tgt_ops_list[] = {
	&null_tgt_ops,
	&loop_tgt_ops,
};

static const struct ublk_tgt_ops *ublk_find_tgt(const char *name)
@@ -774,7 +775,7 @@ static int __cmd_dev_add(const struct dev_ctx *ctx)
	struct ublksrv_ctrl_dev_info *info;
	struct ublk_dev *dev;
	int dev_id = ctx->dev_id;
	int ret;
	int ret, i;

	ops = ublk_find_tgt(tgt_type);
	if (!ops) {
@@ -813,6 +814,13 @@ static int __cmd_dev_add(const struct dev_ctx *ctx)
	dev->tgt.sq_depth = depth;
	dev->tgt.cq_depth = depth;

	for (i = 0; i < MAX_BACK_FILES; i++) {
		if (ctx->files[i]) {
			strcpy(dev->tgt.backing_file[i], ctx->files[i]);
			dev->tgt.nr_backing_files++;
		}
	}

	ret = ublk_ctrl_add_dev(dev);
	if (ret < 0) {
		ublk_err("%s: can't add dev id %d, type %s ret %d\n",
@@ -994,7 +1002,7 @@ static int cmd_dev_get_features(void)

static int cmd_dev_help(char *exe)
{
	printf("%s add -t [null] [-q nr_queues] [-d depth] [-n dev_id]\n", exe);
	printf("%s add -t [null|loop] [-q nr_queues] [-d depth] [-n dev_id] [backfile1] [backfile2] ...\n", exe);
	printf("\t default: nr_queues=2(max 4), depth=128(max 128), dev_id=-1(auto allocation)\n");
	printf("%s del [-n dev_id] -a \n", exe);
	printf("\t -a delete all devices -n delete specified device\n");
+6 −2
Original line number Diff line number Diff line
@@ -105,7 +105,10 @@ struct ublk_tgt {
	unsigned int  cq_depth;
	const struct ublk_tgt_ops *ops;
	struct ublk_params params;
	char backing_file[1024 - 8 - sizeof(struct ublk_params)];

	int nr_backing_files;
	unsigned long backing_file_size[MAX_BACK_FILES];
	char backing_file[MAX_BACK_FILES][PATH_MAX];
};

struct ublk_queue {
@@ -131,7 +134,7 @@ struct ublk_dev {
	struct ublksrv_ctrl_dev_info  dev_info;
	struct ublk_queue q[UBLK_MAX_QUEUES];

	int fds[2];	/* fds[0] points to /dev/ublkcN */
	int fds[MAX_BACK_FILES + 1];	/* fds[0] points to /dev/ublkcN */
	int nr_fds;
	int ctrl_fd;
	struct io_uring ring;
@@ -248,5 +251,6 @@ static inline int ublk_complete_io(struct ublk_queue *q, unsigned tag, int res)
}

extern const struct ublk_tgt_ops null_tgt_ops;
extern const struct ublk_tgt_ops loop_tgt_ops;

#endif
+47 −0
Original line number Diff line number Diff line
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0

_create_backfile() {
	local my_size=$1
	local my_file=`mktemp ublk_bpf_${my_size}_XXXXX`

	truncate -s ${my_size} ${my_file}
	echo $my_file
}

_remove_backfile() {
	local file=$1

	[ -f "$file" ] && rm -f $file
}

_create_tmp_dir() {
	local my_file=`mktemp -d ublk_bpf_dir_XXXXX`

	echo $my_file
}

_remove_tmp_dir() {
	local dir=$1

	[ -d "$dir" ] && rmdir $dir
}

_mkfs_mount_test()
{
	local dev=$1
	local err_code=0
	local mnt_dir=`_create_tmp_dir`

	mkfs.ext4 -F $dev > /dev/null 2>&1
	err_code=$?
	if [ $err_code -ne 0 ]; then
		return $err_code
	fi

	mount -t ext4 $dev $mnt_dir > /dev/null 2>&1
	umount $dev
	err_code=$?
	_remove_tmp_dir $mnt_dir
	if [ $err_code -ne 0 ]; then
		return $err_code
	fi
}

_check_root() {
	local ksft_skip=4

Loading