Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
*-verify.state
/tools
kublk
metadata_size
@@ -0,0 +1,119 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS += -O3 -Wl,-no-as-needed -Wall -I $(top_srcdir)/usr/include
ifneq ($(WERROR),0)
CFLAGS += -Werror
endif
LDLIBS += -lpthread -lm -luring
TEST_PROGS := test_generic_02.sh
TEST_PROGS += test_generic_03.sh
TEST_PROGS += test_generic_06.sh
TEST_PROGS += test_generic_07.sh
TEST_PROGS += test_generic_08.sh
TEST_PROGS += test_generic_09.sh
TEST_PROGS += test_generic_10.sh
TEST_PROGS += test_generic_12.sh
TEST_PROGS += test_generic_13.sh
TEST_PROGS += test_generic_16.sh
TEST_PROGS += test_generic_17.sh
TEST_PROGS += test_batch_01.sh
TEST_PROGS += test_batch_02.sh
TEST_PROGS += test_batch_03.sh
TEST_PROGS += test_null_01.sh
TEST_PROGS += test_null_02.sh
TEST_PROGS += test_null_03.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_integrity_01.sh
TEST_PROGS += test_integrity_02.sh
TEST_PROGS += test_integrity_03.sh
TEST_PROGS += test_recover_01.sh
TEST_PROGS += test_recover_02.sh
TEST_PROGS += test_recover_03.sh
TEST_PROGS += test_recover_04.sh
TEST_PROGS += test_stripe_01.sh
TEST_PROGS += test_stripe_02.sh
TEST_PROGS += test_stripe_03.sh
TEST_PROGS += test_stripe_04.sh
TEST_PROGS += test_stripe_05.sh
TEST_PROGS += test_stripe_06.sh
TEST_PROGS += test_part_01.sh
TEST_PROGS += test_part_02.sh
TEST_PROGS += test_shmemzc_01.sh
TEST_PROGS += test_shmemzc_02.sh
TEST_PROGS += test_shmemzc_03.sh
TEST_PROGS += test_shmemzc_04.sh
TEST_PROGS += test_stress_01.sh
TEST_PROGS += test_stress_02.sh
TEST_PROGS += test_stress_03.sh
TEST_PROGS += test_stress_04.sh
TEST_PROGS += test_stress_05.sh
TEST_PROGS += test_stress_06.sh
TEST_PROGS += test_stress_07.sh
TEST_PROGS += test_stress_08.sh
TEST_PROGS += test_stress_09.sh
TEST_FILES := settings
TEST_GEN_PROGS_EXTENDED = kublk metadata_size
STANDALONE_UTILS := metadata_size.c
LOCAL_HDRS += $(wildcard *.h)
include ../lib.mk
$(OUTPUT)/kublk: $(filter-out $(STANDALONE_UTILS),$(wildcard *.c))
check:
shellcheck -x -f gcc *.sh
# Test groups for running subsets of tests
# JOBS=1 (default): sequential with kselftest TAP output
# JOBS>1: parallel execution with xargs -P
# Usage: make run_null JOBS=4
JOBS ?= 1
export JOBS
# Auto-detect test groups from TEST_PROGS (test_<group>_<num>.sh -> group)
TEST_GROUPS := $(shell echo "$(TEST_PROGS)" | tr ' ' '\n' | \
sed 's/test_\([^_]*\)_.*/\1/' | sort -u)
# Template for group test targets
# $(1) = group name (e.g., null, generic, stress)
define RUN_GROUP
run_$(1): all
@if [ $$(JOBS) -gt 1 ]; then \
echo $$(filter test_$(1)_%.sh,$$(TEST_PROGS)) | tr ' ' '\n' | \
xargs -P $$(JOBS) -n1 sh -c './"$$$$0"' || true; \
else \
$$(call RUN_TESTS, $$(filter test_$(1)_%.sh,$$(TEST_PROGS))); \
fi
.PHONY: run_$(1)
endef
# Generate targets for each discovered test group
$(foreach group,$(TEST_GROUPS),$(eval $(call RUN_GROUP,$(group))))
# Run all tests (parallel when JOBS>1)
run_all: all
@if [ $(JOBS) -gt 1 ]; then \
echo $(TEST_PROGS) | tr ' ' '\n' | \
xargs -P $(JOBS) -n1 sh -c './"$$0"' || true; \
else \
$(call RUN_TESTS, $(TEST_PROGS)); \
fi
.PHONY: run_all
@@ -0,0 +1,607 @@
/* SPDX-License-Identifier: MIT */
/*
* Description: UBLK_F_BATCH_IO buffer management
*/
#include "kublk.h"
static inline void *ublk_get_commit_buf(struct ublk_thread *t,
unsigned short buf_idx)
{
unsigned idx;
if (buf_idx < t->commit_buf_start ||
buf_idx >= t->commit_buf_start + t->nr_commit_buf)
return NULL;
idx = buf_idx - t->commit_buf_start;
return t->commit_buf + idx * t->commit_buf_size;
}
/*
* Allocate one buffer for UBLK_U_IO_PREP_IO_CMDS or UBLK_U_IO_COMMIT_IO_CMDS
*
* Buffer index is returned.
*/
static inline unsigned short ublk_alloc_commit_buf(struct ublk_thread *t)
{
int idx = allocator_get(&t->commit_buf_alloc);
if (idx >= 0)
return idx + t->commit_buf_start;
return UBLKS_T_COMMIT_BUF_INV_IDX;
}
/*
* Free one commit buffer which is used by UBLK_U_IO_PREP_IO_CMDS or
* UBLK_U_IO_COMMIT_IO_CMDS
*/
static inline void ublk_free_commit_buf(struct ublk_thread *t,
unsigned short i)
{
unsigned short idx = i - t->commit_buf_start;
ublk_assert(idx < t->nr_commit_buf);
ublk_assert(allocator_get_val(&t->commit_buf_alloc, idx) != 0);
allocator_put(&t->commit_buf_alloc, idx);
}
static unsigned char ublk_commit_elem_buf_size(struct ublk_dev *dev)
{
if (dev->dev_info.flags & (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_USER_COPY |
UBLK_F_AUTO_BUF_REG))
return 8;
/* one extra 8bytes for carrying buffer address */
return 16;
}
static unsigned ublk_commit_buf_size(struct ublk_thread *t)
{
struct ublk_dev *dev = t->dev;
unsigned elem_size = ublk_commit_elem_buf_size(dev);
unsigned int total = elem_size * dev->dev_info.queue_depth;
unsigned int page_sz = getpagesize();
return round_up(total, page_sz);
}
static void free_batch_commit_buf(struct ublk_thread *t)
{
if (t->commit_buf) {
unsigned buf_size = ublk_commit_buf_size(t);
unsigned int total = buf_size * t->nr_commit_buf;
munlock(t->commit_buf, total);
free(t->commit_buf);
}
allocator_deinit(&t->commit_buf_alloc);
free(t->commit);
}
static int alloc_batch_commit_buf(struct ublk_thread *t)
{
unsigned buf_size = ublk_commit_buf_size(t);
unsigned int total = buf_size * t->nr_commit_buf;
unsigned int page_sz = getpagesize();
void *buf = NULL;
int i, ret, j = 0;
t->commit = calloc(t->nr_queues, sizeof(*t->commit));
for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) {
if (t->q_map[i])
t->commit[j++].q_id = i;
}
allocator_init(&t->commit_buf_alloc, t->nr_commit_buf);
t->commit_buf = NULL;
ret = posix_memalign(&buf, page_sz, total);
if (ret || !buf)
goto fail;
t->commit_buf = buf;
/* lock commit buffer pages for fast access */
if (mlock(t->commit_buf, total))
ublk_err("%s: can't lock commit buffer %s\n", __func__,
strerror(errno));
return 0;
fail:
free_batch_commit_buf(t);
return ret;
}
static unsigned int ublk_thread_nr_queues(const struct ublk_thread *t)
{
int i;
int ret = 0;
for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++)
ret += !!t->q_map[i];
return ret;
}
void ublk_batch_prepare(struct ublk_thread *t)
{
/*
* We only handle single device in this thread context.
*
* All queues have same feature flags, so use queue 0's for
* calculate uring_cmd flags.
*
* This way looks not elegant, but it works so far.
*/
struct ublk_queue *q = &t->dev->q[0];
/* cache nr_queues because we don't support dynamic load-balance yet */
t->nr_queues = ublk_thread_nr_queues(t);
t->commit_buf_elem_size = ublk_commit_elem_buf_size(t->dev);
t->commit_buf_size = ublk_commit_buf_size(t);
t->commit_buf_start = t->nr_bufs;
t->nr_commit_buf = 2 * t->nr_queues;
t->nr_bufs += t->nr_commit_buf;
t->cmd_flags = 0;
if (ublk_queue_use_auto_zc(q)) {
if (ublk_queue_auto_zc_fallback(q))
t->cmd_flags |= UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK;
} else if (!ublk_queue_no_buf(q))
t->cmd_flags |= UBLK_BATCH_F_HAS_BUF_ADDR;
t->state |= UBLKS_T_BATCH_IO;
ublk_log("%s: thread %d commit(nr_bufs %u, buf_size %u, start %u)\n",
__func__, t->idx,
t->nr_commit_buf, t->commit_buf_size,
t->nr_bufs);
}
static void free_batch_fetch_buf(struct ublk_thread *t)
{
int i;
for (i = 0; i < t->nr_fetch_bufs; i++) {
io_uring_free_buf_ring(&t->ring, t->fetch[i].br, 1, i);
munlock(t->fetch[i].fetch_buf, t->fetch[i].fetch_buf_size);
free(t->fetch[i].fetch_buf);
}
free(t->fetch);
}
static int alloc_batch_fetch_buf(struct ublk_thread *t)
{
/* page aligned fetch buffer, and it is mlocked for speedup delivery */
unsigned pg_sz = getpagesize();
unsigned buf_size = round_up(t->dev->dev_info.queue_depth * 2, pg_sz);
int ret;
int i = 0;
/* double fetch buffer for each queue */
t->nr_fetch_bufs = t->nr_queues * 2;
t->fetch = calloc(t->nr_fetch_bufs, sizeof(*t->fetch));
/* allocate one buffer for each queue */
for (i = 0; i < t->nr_fetch_bufs; i++) {
t->fetch[i].fetch_buf_size = buf_size;
if (posix_memalign((void **)&t->fetch[i].fetch_buf, pg_sz,
t->fetch[i].fetch_buf_size))
return -ENOMEM;
/* lock fetch buffer page for fast fetching */
if (mlock(t->fetch[i].fetch_buf, t->fetch[i].fetch_buf_size))
ublk_err("%s: can't lock fetch buffer %s\n", __func__,
strerror(errno));
t->fetch[i].br = io_uring_setup_buf_ring(&t->ring, 1,
i, IOU_PBUF_RING_INC, &ret);
if (!t->fetch[i].br) {
ublk_err("Buffer ring register failed %d\n", ret);
return ret;
}
}
return 0;
}
int ublk_batch_alloc_buf(struct ublk_thread *t)
{
int ret;
ublk_assert(t->nr_commit_buf < 2 * UBLK_MAX_QUEUES);
ret = alloc_batch_commit_buf(t);
if (ret)
return ret;
return alloc_batch_fetch_buf(t);
}
void ublk_batch_free_buf(struct ublk_thread *t)
{
free_batch_commit_buf(t);
free_batch_fetch_buf(t);
}
static void ublk_init_batch_cmd(struct ublk_thread *t, __u16 q_id,
struct io_uring_sqe *sqe, unsigned op,
unsigned short elem_bytes,
unsigned short nr_elem,
unsigned short buf_idx)
{
struct ublk_batch_io *cmd;
__u64 user_data;
cmd = (struct ublk_batch_io *)ublk_get_sqe_cmd(sqe);
ublk_set_sqe_cmd_op(sqe, op);
sqe->fd = 0; /* dev->fds[0] */
sqe->opcode = IORING_OP_URING_CMD;
sqe->flags = IOSQE_FIXED_FILE;
cmd->q_id = q_id;
cmd->flags = 0;
cmd->reserved = 0;
cmd->elem_bytes = elem_bytes;
cmd->nr_elem = nr_elem;
user_data = build_user_data(buf_idx, _IOC_NR(op), nr_elem, q_id, 0);
io_uring_sqe_set_data64(sqe, user_data);
t->cmd_inflight += 1;
ublk_dbg(UBLK_DBG_IO_CMD, "%s: thread %u qid %d cmd_op %x data %lx "
"nr_elem %u elem_bytes %u buf_size %u buf_idx %d "
"cmd_inflight %u\n",
__func__, t->idx, q_id, op, user_data,
cmd->nr_elem, cmd->elem_bytes,
nr_elem * elem_bytes, buf_idx, t->cmd_inflight);
}
static void ublk_setup_commit_sqe(struct ublk_thread *t,
struct io_uring_sqe *sqe,
unsigned short buf_idx)
{
struct ublk_batch_io *cmd;
cmd = (struct ublk_batch_io *)ublk_get_sqe_cmd(sqe);
/* Use plain user buffer instead of fixed buffer */
cmd->flags |= t->cmd_flags;
}
static void ublk_batch_queue_fetch(struct ublk_thread *t,
struct ublk_queue *q,
unsigned short buf_idx)
{
unsigned short nr_elem = t->fetch[buf_idx].fetch_buf_size / 2;
struct io_uring_sqe *sqe;
io_uring_buf_ring_add(t->fetch[buf_idx].br, t->fetch[buf_idx].fetch_buf,
t->fetch[buf_idx].fetch_buf_size,
0, 0, 0);
io_uring_buf_ring_advance(t->fetch[buf_idx].br, 1);
ublk_io_alloc_sqes(t, &sqe, 1);
ublk_init_batch_cmd(t, q->q_id, sqe, UBLK_U_IO_FETCH_IO_CMDS, 2, nr_elem,
buf_idx);
sqe->rw_flags= IORING_URING_CMD_MULTISHOT;
sqe->buf_group = buf_idx;
sqe->flags |= IOSQE_BUFFER_SELECT;
t->fetch[buf_idx].fetch_buf_off = 0;
}
void ublk_batch_start_fetch(struct ublk_thread *t)
{
int i;
int j = 0;
for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) {
if (t->q_map[i]) {
struct ublk_queue *q = &t->dev->q[i];
/* submit two fetch commands for each queue */
ublk_batch_queue_fetch(t, q, j++);
ublk_batch_queue_fetch(t, q, j++);
}
}
}
static unsigned short ublk_compl_batch_fetch(struct ublk_thread *t,
struct ublk_queue *q,
const struct io_uring_cqe *cqe)
{
unsigned short buf_idx = user_data_to_tag(cqe->user_data);
unsigned start = t->fetch[buf_idx].fetch_buf_off;
unsigned end = start + cqe->res;
void *buf = t->fetch[buf_idx].fetch_buf;
int i;
if (cqe->res < 0)
return buf_idx;
if ((end - start) / 2 > q->q_depth) {
ublk_err("%s: fetch duplicated ios offset %u count %u\n", __func__, start, cqe->res);
for (i = start; i < end; i += 2) {
unsigned short tag = *(unsigned short *)(buf + i);
ublk_err("%u ", tag);
}
ublk_err("\n");
}
for (i = start; i < end; i += 2) {
unsigned short tag = *(unsigned short *)(buf + i);
if (tag >= q->q_depth)
ublk_err("%s: bad tag %u\n", __func__, tag);
if (q->tgt_ops->queue_io)
q->tgt_ops->queue_io(t, q, tag);
}
t->fetch[buf_idx].fetch_buf_off = end;
return buf_idx;
}
static int __ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q)
{
unsigned short nr_elem = q->q_depth;
unsigned short buf_idx = ublk_alloc_commit_buf(t);
struct io_uring_sqe *sqe;
void *buf;
int i;
ublk_assert(buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX);
ublk_io_alloc_sqes(t, &sqe, 1);
ublk_assert(nr_elem == q->q_depth);
buf = ublk_get_commit_buf(t, buf_idx);
for (i = 0; i < nr_elem; i++) {
struct ublk_batch_elem *elem = (struct ublk_batch_elem *)(
buf + i * t->commit_buf_elem_size);
struct ublk_io *io = &q->ios[i];
elem->tag = i;
elem->result = 0;
if (ublk_queue_use_auto_zc(q))
elem->buf_index = ublk_batch_io_buf_idx(t, q, i);
else if (!ublk_queue_no_buf(q))
elem->buf_addr = (__u64)io->buf_addr;
}
sqe->addr = (__u64)buf;
sqe->len = t->commit_buf_elem_size * nr_elem;
ublk_init_batch_cmd(t, q->q_id, sqe, UBLK_U_IO_PREP_IO_CMDS,
t->commit_buf_elem_size, nr_elem, buf_idx);
ublk_setup_commit_sqe(t, sqe, buf_idx);
return 0;
}
int ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q)
{
int ret = 0;
pthread_spin_lock(&q->lock);
if (q->flags & UBLKS_Q_PREPARED)
goto unlock;
ret = __ublk_batch_queue_prep_io_cmds(t, q);
if (!ret)
q->flags |= UBLKS_Q_PREPARED;
unlock:
pthread_spin_unlock(&q->lock);
return ret;
}
static void ublk_batch_compl_commit_cmd(struct ublk_thread *t,
const struct io_uring_cqe *cqe,
unsigned op)
{
unsigned short buf_idx = user_data_to_tag(cqe->user_data);
if (op == _IOC_NR(UBLK_U_IO_PREP_IO_CMDS))
ublk_assert(cqe->res == 0);
else if (op == _IOC_NR(UBLK_U_IO_COMMIT_IO_CMDS)) {
int nr_elem = user_data_to_tgt_data(cqe->user_data);
ublk_assert(cqe->res == t->commit_buf_elem_size * nr_elem);
} else
ublk_assert(0);
ublk_free_commit_buf(t, buf_idx);
}
void ublk_batch_compl_cmd(struct ublk_thread *t,
const struct io_uring_cqe *cqe)
{
unsigned op = user_data_to_op(cqe->user_data);
struct ublk_queue *q;
unsigned buf_idx;
unsigned q_id;
if (op == _IOC_NR(UBLK_U_IO_PREP_IO_CMDS) ||
op == _IOC_NR(UBLK_U_IO_COMMIT_IO_CMDS)) {
t->cmd_inflight--;
ublk_batch_compl_commit_cmd(t, cqe, op);
return;
}
/* FETCH command is per queue */
q_id = user_data_to_q_id(cqe->user_data);
q = &t->dev->q[q_id];
buf_idx = ublk_compl_batch_fetch(t, q, cqe);
if (cqe->res < 0 && cqe->res != -ENOBUFS) {
t->cmd_inflight--;
t->state |= UBLKS_T_STOPPING;
} else if (!(cqe->flags & IORING_CQE_F_MORE) || cqe->res == -ENOBUFS) {
t->cmd_inflight--;
ublk_batch_queue_fetch(t, q, buf_idx);
}
}
static void __ublk_batch_commit_io_cmds(struct ublk_thread *t,
struct batch_commit_buf *cb)
{
struct io_uring_sqe *sqe;
unsigned short buf_idx;
unsigned short nr_elem = cb->done;
/* nothing to commit */
if (!nr_elem) {
ublk_free_commit_buf(t, cb->buf_idx);
return;
}
ublk_io_alloc_sqes(t, &sqe, 1);
buf_idx = cb->buf_idx;
sqe->addr = (__u64)cb->elem;
sqe->len = nr_elem * t->commit_buf_elem_size;
/* commit isn't per-queue command */
ublk_init_batch_cmd(t, cb->q_id, sqe, UBLK_U_IO_COMMIT_IO_CMDS,
t->commit_buf_elem_size, nr_elem, buf_idx);
ublk_setup_commit_sqe(t, sqe, buf_idx);
}
void ublk_batch_commit_io_cmds(struct ublk_thread *t)
{
int i;
for (i = 0; i < t->nr_queues; i++) {
struct batch_commit_buf *cb = &t->commit[i];
if (cb->buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX)
__ublk_batch_commit_io_cmds(t, cb);
}
}
static void __ublk_batch_init_commit(struct ublk_thread *t,
struct batch_commit_buf *cb,
unsigned short buf_idx)
{
/* so far only support 1:1 queue/thread mapping */
cb->buf_idx = buf_idx;
cb->elem = ublk_get_commit_buf(t, buf_idx);
cb->done = 0;
cb->count = t->commit_buf_size /
t->commit_buf_elem_size;
}
/* COMMIT_IO_CMDS is per-queue command, so use its own commit buffer */
static void ublk_batch_init_commit(struct ublk_thread *t,
struct batch_commit_buf *cb)
{
unsigned short buf_idx = ublk_alloc_commit_buf(t);
ublk_assert(buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX);
ublk_assert(!ublk_batch_commit_prepared(cb));
__ublk_batch_init_commit(t, cb, buf_idx);
}
void ublk_batch_prep_commit(struct ublk_thread *t)
{
int i;
for (i = 0; i < t->nr_queues; i++)
t->commit[i].buf_idx = UBLKS_T_COMMIT_BUF_INV_IDX;
}
void ublk_batch_complete_io(struct ublk_thread *t, struct ublk_queue *q,
unsigned tag, int res)
{
unsigned q_t_idx = ublk_queue_idx_in_thread(t, q);
struct batch_commit_buf *cb = &t->commit[q_t_idx];
struct ublk_batch_elem *elem;
struct ublk_io *io = &q->ios[tag];
if (!ublk_batch_commit_prepared(cb))
ublk_batch_init_commit(t, cb);
ublk_assert(q->q_id == cb->q_id);
elem = (struct ublk_batch_elem *)(cb->elem + cb->done * t->commit_buf_elem_size);
elem->tag = tag;
elem->buf_index = ublk_batch_io_buf_idx(t, q, tag);
elem->result = res;
if (!ublk_queue_no_buf(q))
elem->buf_addr = (__u64) (uintptr_t) io->buf_addr;
cb->done += 1;
ublk_assert(cb->done <= cb->count);
}
void ublk_batch_setup_map(unsigned char (*q_thread_map)[UBLK_MAX_QUEUES],
int nthreads, int queues)
{
int i, j;
/*
* Setup round-robin queue-to-thread mapping for arbitrary N:M combinations.
*
* This algorithm distributes queues across threads (and threads across queues)
* in a balanced round-robin fashion to ensure even load distribution.
*
* Examples:
* - 2 threads, 4 queues: T0=[Q0,Q2], T1=[Q1,Q3]
* - 4 threads, 2 queues: T0=[Q0], T1=[Q1], T2=[Q0], T3=[Q1]
* - 3 threads, 3 queues: T0=[Q0], T1=[Q1], T2=[Q2] (1:1 mapping)
*
* Phase 1: Mark which queues each thread handles (boolean mapping)
*/
for (i = 0, j = 0; i < queues || j < nthreads; i++, j++) {
q_thread_map[j % nthreads][i % queues] = 1;
}
/*
* Phase 2: Convert boolean mapping to sequential indices within each thread.
*
* Transform from: q_thread_map[thread][queue] = 1 (handles queue)
* To: q_thread_map[thread][queue] = N (queue index within thread)
*
* This allows each thread to know the local index of each queue it handles,
* which is essential for buffer allocation and management. For example:
* - Thread 0 handling queues [0,2] becomes: q_thread_map[0][0]=1, q_thread_map[0][2]=2
* - Thread 1 handling queues [1,3] becomes: q_thread_map[1][1]=1, q_thread_map[1][3]=2
*/
for (j = 0; j < nthreads; j++) {
unsigned char seq = 1;
for (i = 0; i < queues; i++) {
if (q_thread_map[j][i])
q_thread_map[j][i] = seq++;
}
}
#if 0
for (j = 0; j < nthreads; j++) {
printf("thread %0d: ", j);
for (i = 0; i < queues; i++) {
if (q_thread_map[j][i])
printf("%03u ", i);
}
printf("\n");
}
printf("\n");
for (j = 0; j < nthreads; j++) {
for (i = 0; i < queues; i++) {
printf("%03u ", q_thread_map[j][i]);
}
printf("\n");
}
#endif
}
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0
#include "kublk.h"
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]);
}
}
int backing_file_tgt_init(struct ublk_dev *dev, unsigned int nr_direct)
{
int fd, i;
ublk_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 | (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));
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;
}
@@ -0,0 +1 @@
CONFIG_BLK_DEV_UBLK=m
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Fault injection ublk target. Hack this up however you like for
* testing specific behaviors of ublk_drv. Currently is a null target
* with a configurable delay before completing each I/O. This delay can
* be used to test ublk_drv's handling of I/O outstanding to the ublk
* server when it dies.
*/
#include "kublk.h"
struct fi_opts {
long long delay_ns;
bool die_during_fetch;
};
static int ublk_fault_inject_tgt_init(const struct dev_ctx *ctx,
struct ublk_dev *dev)
{
const struct ublksrv_ctrl_dev_info *info = &dev->dev_info;
unsigned long dev_size = 250UL << 30;
struct fi_opts *opts = NULL;
if (ctx->auto_zc_fallback) {
ublk_err("%s: not support auto_zc_fallback\n", __func__);
return -EINVAL;
}
dev->tgt.dev_size = dev_size;
dev->tgt.params = (struct ublk_params) {
.types = UBLK_PARAM_TYPE_BASIC,
.basic = {
.logical_bs_shift = 9,
.physical_bs_shift = 12,
.io_opt_shift = 12,
.io_min_shift = 9,
.max_sectors = info->max_io_buf_bytes >> 9,
.dev_sectors = dev_size >> 9,
},
};
ublk_set_integrity_params(ctx, &dev->tgt.params);
opts = calloc(1, sizeof(*opts));
if (!opts) {
ublk_err("%s: couldn't allocate memory for opts\n", __func__);
return -ENOMEM;
}
opts->delay_ns = ctx->fault_inject.delay_us * 1000;
opts->die_during_fetch = ctx->fault_inject.die_during_fetch;
dev->private_data = opts;
return 0;
}
static void ublk_fault_inject_pre_fetch_io(struct ublk_thread *t,
struct ublk_queue *q, int tag,
bool batch)
{
struct fi_opts *opts = q->dev->private_data;
if (!opts->die_during_fetch)
return;
/*
* Each queue fetches its IOs in increasing order of tags, so
* dying just before we're about to fetch tag 1 (regardless of
* what queue we're on) guarantees that we've fetched a nonempty
* proper subset of the tags on that queue.
*/
if (tag == 1) {
/*
* Ensure our commands are actually live in the kernel
* before we die.
*/
io_uring_submit(&t->ring);
raise(SIGKILL);
}
}
static int ublk_fault_inject_queue_io(struct ublk_thread *t,
struct ublk_queue *q, int tag)
{
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
struct io_uring_sqe *sqe;
struct fi_opts *opts = q->dev->private_data;
struct __kernel_timespec ts = {
.tv_nsec = opts->delay_ns,
};
ublk_io_alloc_sqes(t, &sqe, 1);
io_uring_prep_timeout(sqe, &ts, 1, 0);
sqe->user_data = build_user_data(tag, ublksrv_get_op(iod), 0, q->q_id, 1);
ublk_queued_tgt_io(t, q, tag, 1);
return 0;
}
static void ublk_fault_inject_tgt_io_done(struct ublk_thread *t,
struct ublk_queue *q,
const struct io_uring_cqe *cqe)
{
unsigned tag = user_data_to_tag(cqe->user_data);
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
if (cqe->res != -ETIME)
ublk_err("%s: unexpected cqe res %d\n", __func__, cqe->res);
if (ublk_completed_tgt_io(t, q, tag))
ublk_complete_io(t, q, tag, iod->nr_sectors << 9);
else
ublk_err("%s: io not complete after 1 cqe\n", __func__);
}
static void ublk_fault_inject_cmd_line(struct dev_ctx *ctx, int argc, char *argv[])
{
static const struct option longopts[] = {
{ "delay_us", 1, NULL, 0 },
{ "die_during_fetch", 1, NULL, 0 },
{ 0, 0, 0, 0 }
};
int option_idx, opt;
ctx->fault_inject.delay_us = 0;
ctx->fault_inject.die_during_fetch = false;
while ((opt = getopt_long(argc, argv, "",
longopts, &option_idx)) != -1) {
switch (opt) {
case 0:
if (!strcmp(longopts[option_idx].name, "delay_us"))
ctx->fault_inject.delay_us = strtoll(optarg, NULL, 10);
if (!strcmp(longopts[option_idx].name, "die_during_fetch"))
ctx->fault_inject.die_during_fetch = strtoll(optarg, NULL, 10);
}
}
}
static void ublk_fault_inject_usage(const struct ublk_tgt_ops *ops)
{
printf("\tfault_inject: [--delay_us us (default 0)] [--die_during_fetch 1]\n");
}
const struct ublk_tgt_ops fault_inject_tgt_ops = {
.name = "fault_inject",
.init_tgt = ublk_fault_inject_tgt_init,
.pre_fetch_io = ublk_fault_inject_pre_fetch_io,
.queue_io = ublk_fault_inject_queue_io,
.tgt_io_done = ublk_fault_inject_tgt_io_done,
.parse_cmd_line = ublk_fault_inject_cmd_line,
.usage = ublk_fault_inject_usage,
};
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0
#include "kublk.h"
static enum io_uring_op ublk_to_uring_op(const struct ublksrv_io_desc *iod, int zc)
{
unsigned ublk_op = ublksrv_get_op(iod);
if (ublk_op == UBLK_IO_OP_READ)
return zc ? IORING_OP_READ_FIXED : IORING_OP_READ;
else if (ublk_op == UBLK_IO_OP_WRITE)
return zc ? IORING_OP_WRITE_FIXED : IORING_OP_WRITE;
ublk_assert(0);
}
static int loop_queue_flush_io(struct ublk_thread *t, struct ublk_queue *q,
const struct ublksrv_io_desc *iod, int tag)
{
unsigned ublk_op = ublksrv_get_op(iod);
struct io_uring_sqe *sqe[1];
ublk_io_alloc_sqes(t, sqe, 1);
io_uring_prep_fsync(sqe[0], ublk_get_registered_fd(q, 1) /*fds[1]*/, IORING_FSYNC_DATASYNC);
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;
}
/*
* Shared memory zero-copy I/O: when UBLK_IO_F_SHMEM_ZC is set, the
* request's data lives in a registered shared memory buffer. Decode
* index + offset from iod->addr and use the server's mmap of that
* buffer as the I/O buffer for the backing file.
*/
static int loop_queue_shmem_zc_io(struct ublk_thread *t, struct ublk_queue *q,
const struct ublksrv_io_desc *iod, int tag)
{
unsigned ublk_op = ublksrv_get_op(iod);
enum io_uring_op op = ublk_to_uring_op(iod, 0);
__u64 file_offset = iod->start_sector << 9;
__u32 len = iod->nr_sectors << 9;
__u32 shmem_idx = ublk_shmem_zc_index(iod->addr);
__u32 shmem_off = ublk_shmem_zc_offset(iod->addr);
struct io_uring_sqe *sqe[1];
void *addr;
if (shmem_idx >= UBLK_BUF_MAX || !shmem_table[shmem_idx].mmap_base)
return -EINVAL;
addr = shmem_table[shmem_idx].mmap_base + shmem_off;
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),
addr, len, file_offset);
io_uring_sqe_set_flags(sqe[0], IOSQE_FIXED_FILE);
sqe[0]->user_data = build_user_data(tag, ublk_op, 0, q->q_id, 1);
return 1;
}
static int loop_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q,
const struct ublksrv_io_desc *iod, int tag)
{
unsigned ublk_op = ublksrv_get_op(iod);
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);
__u64 offset = iod->start_sector << 9;
__u32 len = iod->nr_sectors << 9;
struct io_uring_sqe *sqe[3];
void *addr = io->buf_addr;
unsigned short buf_index = ublk_io_buf_idx(t, q, tag);
/* shared memory zero-copy path */
if (iod->op_flags & UBLK_IO_F_SHMEM_ZC)
return loop_queue_shmem_zc_io(t, q, iod, tag);
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,
len,
offset);
if (auto_zc)
sqe[0]->buf_index = buf_index;
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 !!(iod->op_flags & UBLK_IO_F_INTEGRITY) + 1;
}
ublk_io_alloc_sqes(t, sqe, 3);
io_uring_prep_buf_register(sqe[0], q, tag, q->q_id, 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);
io_uring_prep_rw(op, sqe[1], ublk_get_registered_fd(q, 1) /*fds[1]*/, 0,
len,
offset);
sqe[1]->buf_index = buf_index;
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, buf_index);
sqe[2]->user_data = build_user_data(tag, ublk_cmd_op_nr(sqe[2]->cmd_op), 0, q->q_id, 1);
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)
{
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
unsigned ublk_op = ublksrv_get_op(iod);
int ret;
switch (ublk_op) {
case UBLK_IO_OP_FLUSH:
ret = loop_queue_flush_io(t, q, iod, tag);
break;
case UBLK_IO_OP_WRITE_ZEROES:
case UBLK_IO_OP_DISCARD:
ret = -ENOTSUP;
break;
case UBLK_IO_OP_READ:
case UBLK_IO_OP_WRITE:
ret = loop_queue_tgt_rw_io(t, q, iod, tag);
break;
default:
ret = -EINVAL;
break;
}
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 ret;
}
static int ublk_loop_queue_io(struct ublk_thread *t, struct ublk_queue *q,
int tag)
{
int queued = loop_queue_tgt_io(t, q, tag);
ublk_queued_tgt_io(t, q, tag, queued);
return 0;
}
static void ublk_loop_io_done(struct ublk_thread *t, struct ublk_queue *q,
const struct io_uring_cqe *cqe)
{
unsigned tag = user_data_to_tag(cqe->user_data);
unsigned op = user_data_to_op(cqe->user_data);
struct ublk_io *io = ublk_get_io(q, tag);
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 */
if (op == ublk_cmd_op_nr(UBLK_U_IO_REGISTER_IO_BUF))
io->tgt_ios += 1;
if (ublk_completed_tgt_io(t, q, tag))
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,
.basic = {
.attrs = UBLK_ATTR_VOLATILE_CACHE,
.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,
},
.dma = {
.alignment = 511,
},
};
ublk_set_integrity_params(ctx, &p);
if (ctx->auto_zc_fallback) {
ublk_err("%s: not support auto_zc_fallback\n", __func__);
return -EINVAL;
}
/* Use O_DIRECT only for data file */
ret = backing_file_tgt_init(dev, 1);
if (ret)
return ret;
/* Expect a second file for integrity data */
if (dev->tgt.nr_backing_files != 1 + !!ctx->metadata_size)
return -EINVAL;
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;
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,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,628 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef KUBLK_INTERNAL_H
#define KUBLK_INTERNAL_H
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <pthread.h>
#include <getopt.h>
#include <limits.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/inotify.h>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <linux/io_uring.h>
#include <liburing.h>
#include <semaphore.h>
/* allow ublk_dep.h to override ublk_cmd.h */
#include "ublk_dep.h"
#include <linux/ublk_cmd.h>
#include "utils.h"
#define MAX_BACK_FILES 4
/****************** part 1: libublk ********************/
#define CTRL_DEV "/dev/ublk-control"
#define UBLKC_DEV "/dev/ublkc"
#define UBLKB_DEV "/dev/ublkb"
#define UBLK_CTRL_RING_DEPTH 32
#define ERROR_EVTFD_DEVID -2
#define UBLK_IO_MAX_BYTES (1 << 20)
#define UBLK_MAX_QUEUES_SHIFT 5
#define UBLK_MAX_QUEUES (1 << UBLK_MAX_QUEUES_SHIFT)
#define UBLK_MAX_THREADS_SHIFT 5
#define UBLK_MAX_THREADS (1 << UBLK_MAX_THREADS_SHIFT)
#define UBLK_QUEUE_DEPTH 1024
struct ublk_dev;
struct ublk_queue;
struct ublk_thread;
struct stripe_ctx {
/* stripe */
unsigned int chunk_size;
};
struct fault_inject_ctx {
/* fault_inject */
unsigned long delay_us;
bool die_during_fetch;
};
struct dev_ctx {
char tgt_type[16];
unsigned long flags;
unsigned nr_hw_queues;
unsigned short nthreads;
unsigned queue_depth;
int dev_id;
int nr_files;
char *files[MAX_BACK_FILES];
unsigned int logging:1;
unsigned int all:1;
unsigned int fg:1;
unsigned int recovery:1;
unsigned int auto_zc_fallback:1;
unsigned int per_io_tasks:1;
unsigned int no_ublk_fixed_fd:1;
unsigned int safe_stop:1;
unsigned int no_auto_part_scan:1;
unsigned int rdonly_shmem_buf:1;
__u32 integrity_flags;
__u8 metadata_size;
__u8 pi_offset;
__u8 csum_type;
__u8 tag_size;
int _evtfd;
int _shmid;
/* built from shmem, only for ublk_dump_dev() */
struct ublk_dev *shadow_dev;
/* for 'update_size' command */
unsigned long long size;
char *htlb_path;
union {
struct stripe_ctx stripe;
struct fault_inject_ctx fault_inject;
};
};
struct ublk_ctrl_cmd_data {
__u32 cmd_op;
#define CTRL_CMD_HAS_DATA 1
#define CTRL_CMD_HAS_BUF 2
__u32 flags;
__u64 data[2];
__u64 addr;
__u32 len;
};
struct ublk_io {
char *buf_addr;
void *integrity_buf;
#define UBLKS_IO_NEED_FETCH_RQ (1UL << 0)
#define UBLKS_IO_NEED_COMMIT_RQ_COMP (1UL << 1)
#define UBLKS_IO_FREE (1UL << 2)
#define UBLKS_IO_NEED_GET_DATA (1UL << 3)
#define UBLKS_IO_NEED_REG_BUF (1UL << 4)
unsigned short flags;
unsigned short refs; /* used by target code only */
int tag;
int result;
unsigned short buf_index;
unsigned short tgt_ios;
void *private_data;
};
struct ublk_tgt_ops {
const char *name;
int (*init_tgt)(const struct dev_ctx *ctx, struct ublk_dev *);
void (*deinit_tgt)(struct ublk_dev *);
void (*pre_fetch_io)(struct ublk_thread *t, struct ublk_queue *q,
int tag, bool batch);
int (*queue_io)(struct ublk_thread *, struct ublk_queue *, int tag);
void (*tgt_io_done)(struct ublk_thread *, struct ublk_queue *,
const struct io_uring_cqe *);
/*
* Target specific command line handling
*
* each option requires argument for target command line
*/
void (*parse_cmd_line)(struct dev_ctx *ctx, int argc, char *argv[]);
void (*usage)(const struct ublk_tgt_ops *ops);
/* return buffer index for UBLK_F_AUTO_BUF_REG */
unsigned short (*buf_index)(const struct ublk_thread *t,
const struct ublk_queue *, int tag);
};
struct ublk_tgt {
unsigned long dev_size;
unsigned int sq_depth;
unsigned int cq_depth;
const struct ublk_tgt_ops *ops;
struct ublk_params params;
int nr_backing_files;
unsigned long backing_file_size[MAX_BACK_FILES];
char backing_file[MAX_BACK_FILES][PATH_MAX];
};
struct ublk_queue {
int q_id;
int q_depth;
struct ublk_dev *dev;
const struct ublk_tgt_ops *tgt_ops;
struct ublksrv_io_desc *io_cmd_buf;
/* borrow three bit of ublk uapi flags, which may never be used */
#define UBLKS_Q_AUTO_BUF_REG_FALLBACK (1ULL << 63)
#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;
struct ublk_io ios[UBLK_QUEUE_DEPTH];
/* used for prep io commands */
pthread_spinlock_t lock;
};
/* align with `ublk_elem_header` */
struct ublk_batch_elem {
__u16 tag;
__u16 buf_index;
__s32 result;
__u64 buf_addr;
};
struct batch_commit_buf {
unsigned short q_id;
unsigned short buf_idx;
void *elem;
unsigned short done;
unsigned short count;
};
struct batch_fetch_buf {
struct io_uring_buf_ring *br;
void *fetch_buf;
unsigned int fetch_buf_size;
unsigned int fetch_buf_off;
};
struct ublk_thread {
/* Thread-local copy of queue-to-thread mapping for this thread */
unsigned char q_map[UBLK_MAX_QUEUES];
struct ublk_dev *dev;
unsigned short idx;
unsigned short nr_queues;
#define UBLKS_T_STOPPING (1U << 0)
#define UBLKS_T_IDLE (1U << 1)
#define UBLKS_T_BATCH_IO (1U << 31) /* readonly */
unsigned state;
unsigned int cmd_inflight;
unsigned int io_inflight;
unsigned short nr_bufs;
/* followings are for BATCH_IO */
unsigned short commit_buf_start;
unsigned char commit_buf_elem_size;
/*
* We just support single device, so pre-calculate commit/prep flags
*/
unsigned short cmd_flags;
unsigned int nr_commit_buf;
unsigned int commit_buf_size;
void *commit_buf;
#define UBLKS_T_COMMIT_BUF_INV_IDX ((unsigned short)-1)
struct allocator commit_buf_alloc;
struct batch_commit_buf *commit;
/* FETCH_IO_CMDS buffer */
unsigned short nr_fetch_bufs;
struct batch_fetch_buf *fetch;
struct io_uring ring;
};
struct ublk_dev {
struct ublk_tgt tgt;
struct ublksrv_ctrl_dev_info dev_info;
struct ublk_queue q[UBLK_MAX_QUEUES];
unsigned nthreads;
unsigned per_io_tasks;
int fds[MAX_BACK_FILES + 1]; /* fds[0] points to /dev/ublkcN */
int nr_fds;
int ctrl_fd;
struct io_uring ring;
void *private_data;
};
extern int ublk_queue_io_cmd(struct ublk_thread *t, struct ublk_io *io);
static inline int __ublk_use_batch_io(__u64 flags)
{
return flags & UBLK_F_BATCH_IO;
}
static inline int ublk_queue_batch_io(const struct ublk_queue *q)
{
return __ublk_use_batch_io(q->flags);
}
static inline int ublk_dev_batch_io(const struct ublk_dev *dev)
{
return __ublk_use_batch_io(dev->dev_info.flags);
}
/* only work for handle single device in this pthread context */
static inline int ublk_thread_batch_io(const struct ublk_thread *t)
{
return t->state & UBLKS_T_BATCH_IO;
}
static inline void ublk_set_integrity_params(const struct dev_ctx *ctx,
struct ublk_params *params)
{
if (!ctx->metadata_size)
return;
params->types |= UBLK_PARAM_TYPE_INTEGRITY;
params->integrity = (struct ublk_param_integrity) {
.flags = ctx->integrity_flags,
.interval_exp = params->basic.logical_bs_shift,
.metadata_size = ctx->metadata_size,
.pi_offset = ctx->pi_offset,
.csum_type = ctx->csum_type,
.tag_size = ctx->tag_size,
};
}
static inline size_t ublk_integrity_len(const struct ublk_queue *q, size_t len)
{
/* All targets currently use interval_exp = logical_bs_shift = 9 */
return (len >> 9) * q->metadata_size;
}
static inline size_t
ublk_integrity_data_len(const struct ublk_queue *q, size_t integrity_len)
{
return (integrity_len / q->metadata_size) << 9;
}
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;
}
static inline __u64 build_user_data(unsigned tag, unsigned op,
unsigned tgt_data, unsigned q_id, unsigned is_target_io)
{
/* we only have 7 bits to encode q_id */
_Static_assert(UBLK_MAX_QUEUES_SHIFT <= 7, "UBLK_MAX_QUEUES_SHIFT must be <= 7");
ublk_assert(!(tag >> 16) && !(op >> 8) && !(tgt_data >> 16) && !(q_id >> 7));
return tag | ((__u64)op << 16) | ((__u64)tgt_data << 24) |
(__u64)q_id << 56 | (__u64)is_target_io << 63;
}
static inline unsigned int user_data_to_tag(__u64 user_data)
{
return user_data & 0xffff;
}
static inline unsigned int user_data_to_op(__u64 user_data)
{
return (user_data >> 16) & 0xff;
}
static inline unsigned int user_data_to_tgt_data(__u64 user_data)
{
return (user_data >> 24) & 0xffff;
}
static inline unsigned int user_data_to_q_id(__u64 user_data)
{
return (user_data >> 56) & 0x7f;
}
static inline unsigned short ublk_cmd_op_nr(unsigned int op)
{
return _IOC_NR(op);
}
static inline struct ublk_queue *ublk_io_to_queue(const struct ublk_io *io)
{
return container_of(io, struct ublk_queue, ios[io->tag]);
}
static inline int ublk_io_alloc_sqes(struct ublk_thread *t,
struct io_uring_sqe *sqes[], int nr_sqes)
{
struct io_uring *ring = &t->ring;
unsigned left = io_uring_sq_space_left(ring);
int i;
if (left < nr_sqes)
io_uring_submit(ring);
for (i = 0; i < nr_sqes; i++) {
sqes[i] = io_uring_get_sqe(ring);
if (!sqes[i])
return i;
}
return nr_sqes;
}
static inline int ublk_get_registered_fd(struct ublk_queue *q, int fd_index)
{
if (q->flags & UBLKS_Q_NO_UBLK_FIXED_FD) {
if (fd_index == 0)
/* Return the raw ublk FD for index 0 */
return q->ublk_fd;
/* Adjust index for backing files (index 1 becomes 0, etc.) */
return fd_index - 1;
}
return fd_index;
}
static inline void __io_uring_prep_buf_reg_unreg(struct io_uring_sqe *sqe,
struct ublk_queue *q, int tag, int q_id, __u64 index)
{
struct ublksrv_io_cmd *cmd = (struct ublksrv_io_cmd *)sqe->cmd;
int dev_fd = ublk_get_registered_fd(q, 0);
io_uring_prep_read(sqe, dev_fd, 0, 0, 0);
sqe->opcode = IORING_OP_URING_CMD;
if (q->flags & UBLKS_Q_NO_UBLK_FIXED_FD)
sqe->flags &= ~IOSQE_FIXED_FILE;
else
sqe->flags |= IOSQE_FIXED_FILE;
cmd->tag = tag;
cmd->addr = index;
cmd->q_id = q_id;
}
static inline void io_uring_prep_buf_register(struct io_uring_sqe *sqe,
struct ublk_queue *q, int tag, int q_id, __u64 index)
{
__io_uring_prep_buf_reg_unreg(sqe, q, tag, q_id, index);
sqe->cmd_op = UBLK_U_IO_REGISTER_IO_BUF;
}
static inline void io_uring_prep_buf_unregister(struct io_uring_sqe *sqe,
struct ublk_queue *q, int tag, int q_id, __u64 index)
{
__io_uring_prep_buf_reg_unreg(sqe, q, tag, q_id, index);
sqe->cmd_op = UBLK_U_IO_UNREGISTER_IO_BUF;
}
static inline void *ublk_get_sqe_cmd(const struct io_uring_sqe *sqe)
{
return (void *)&sqe->cmd;
}
static inline void ublk_set_io_res(struct ublk_queue *q, int tag, int res)
{
q->ios[tag].result = res;
}
static inline int ublk_get_io_res(const struct ublk_queue *q, unsigned tag)
{
return q->ios[tag].result;
}
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)
{
return &q->io_cmd_buf[tag];
}
static inline void ublk_set_sqe_cmd_op(struct io_uring_sqe *sqe, __u32 cmd_op)
{
__u32 *addr = (__u32 *)&sqe->off;
addr[0] = cmd_op;
addr[1] = 0;
}
static inline unsigned short ublk_batch_io_buf_idx(
const struct ublk_thread *t, const struct ublk_queue *q,
unsigned tag);
static inline unsigned short ublk_io_buf_idx(const struct ublk_thread *t,
const struct ublk_queue *q,
unsigned tag)
{
if (ublk_queue_batch_io(q))
return ublk_batch_io_buf_idx(t, q, tag);
return q->ios[tag].buf_index;
}
static inline struct ublk_io *ublk_get_io(struct ublk_queue *q, unsigned tag)
{
return &q->ios[tag];
}
static inline int ublk_completed_tgt_io(struct ublk_thread *t,
struct ublk_queue *q, unsigned tag)
{
struct ublk_io *io = ublk_get_io(q, tag);
t->io_inflight--;
return --io->tgt_ios == 0;
}
static inline bool ublk_queue_use_zc(const struct ublk_queue *q)
{
return !!(q->flags & UBLK_F_SUPPORT_ZERO_COPY);
}
static inline bool ublk_queue_use_auto_zc(const struct ublk_queue *q)
{
return !!(q->flags & UBLK_F_AUTO_BUF_REG);
}
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);
}
static inline int ublk_batch_commit_prepared(struct batch_commit_buf *cb)
{
return cb->buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX;
}
static inline unsigned ublk_queue_idx_in_thread(const struct ublk_thread *t,
const struct ublk_queue *q)
{
unsigned char idx;
idx = t->q_map[q->q_id];
ublk_assert(idx != 0);
return idx - 1;
}
/*
* Each IO's buffer index has to be calculated by this helper for
* UBLKS_T_BATCH_IO
*/
static inline unsigned short ublk_batch_io_buf_idx(
const struct ublk_thread *t, const struct ublk_queue *q,
unsigned tag)
{
return ublk_queue_idx_in_thread(t, q) * q->q_depth + tag;
}
/* Queue UBLK_U_IO_PREP_IO_CMDS for a specific queue with batch elements */
int ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q);
/* Start fetching I/O commands using multishot UBLK_U_IO_FETCH_IO_CMDS */
void ublk_batch_start_fetch(struct ublk_thread *t);
/* Handle completion of batch I/O commands (prep/commit) */
void ublk_batch_compl_cmd(struct ublk_thread *t,
const struct io_uring_cqe *cqe);
/* Initialize batch I/O state and calculate buffer parameters */
void ublk_batch_prepare(struct ublk_thread *t);
/* Allocate and register commit buffers for batch operations */
int ublk_batch_alloc_buf(struct ublk_thread *t);
/* Free commit buffers and cleanup batch allocator */
void ublk_batch_free_buf(struct ublk_thread *t);
/* Prepare a new commit buffer for batching completed I/O operations */
void ublk_batch_prep_commit(struct ublk_thread *t);
/* Submit UBLK_U_IO_COMMIT_IO_CMDS with batched completed I/O operations */
void ublk_batch_commit_io_cmds(struct ublk_thread *t);
/* Add a completed I/O operation to the current batch commit buffer */
void ublk_batch_complete_io(struct ublk_thread *t, struct ublk_queue *q,
unsigned tag, int res);
void ublk_batch_setup_map(unsigned char (*q_thread_map)[UBLK_MAX_QUEUES],
int nthreads, int queues);
static inline int ublk_complete_io(struct ublk_thread *t, struct ublk_queue *q,
unsigned tag, int res)
{
if (ublk_queue_batch_io(q)) {
ublk_batch_complete_io(t, q, tag, res);
return 0;
} else {
struct ublk_io *io = &q->ios[tag];
ublk_mark_io_done(io, res);
return ublk_queue_io_cmd(t, io);
}
}
static inline void ublk_queued_tgt_io(struct ublk_thread *t, struct ublk_queue *q,
unsigned tag, int queued)
{
if (queued < 0)
ublk_complete_io(t, q, tag, queued);
else {
struct ublk_io *io = ublk_get_io(q, tag);
t->io_inflight += queued;
io->tgt_ios = queued;
io->result = 0;
}
}
/* shared memory zero-copy support */
#define UBLK_BUF_MAX 256
struct ublk_shmem_entry {
int fd;
void *mmap_base;
size_t size;
};
extern struct ublk_shmem_entry shmem_table[UBLK_BUF_MAX];
extern int shmem_count;
extern const struct ublk_tgt_ops null_tgt_ops;
extern const struct ublk_tgt_ops loop_tgt_ops;
extern const struct ublk_tgt_ops stripe_tgt_ops;
extern const struct ublk_tgt_ops fault_inject_tgt_ops;
void backing_file_tgt_deinit(struct ublk_dev *dev);
int backing_file_tgt_init(struct ublk_dev *dev, unsigned int nr_direct);
#endif
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0
#include <fcntl.h>
#include <linux/fs.h>
#include <stdio.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
struct logical_block_metadata_cap cap = {};
const char *filename;
int fd;
int result;
if (argc != 2) {
fprintf(stderr, "Usage: %s BLOCK_DEVICE\n", argv[0]);
return 1;
}
filename = argv[1];
fd = open(filename, O_RDONLY);
if (fd < 0) {
perror(filename);
return 1;
}
result = ioctl(fd, FS_IOC_GETLBMD_CAP, &cap);
if (result < 0) {
perror("ioctl");
return 1;
}
printf("metadata_size: %u\n", cap.lbmd_size);
printf("pi_offset: %u\n", cap.lbmd_pi_offset);
printf("pi_tuple_size: %u\n", cap.lbmd_pi_size);
return 0;
}
@@ -0,0 +1,155 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include "kublk.h"
#ifndef IORING_NOP_INJECT_RESULT
#define IORING_NOP_INJECT_RESULT (1U << 0)
#endif
#ifndef IORING_NOP_FIXED_BUFFER
#define IORING_NOP_FIXED_BUFFER (1U << 3)
#endif
static int ublk_null_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev)
{
const struct ublksrv_ctrl_dev_info *info = &dev->dev_info;
unsigned long dev_size = 250UL << 30;
dev->tgt.dev_size = dev_size;
dev->tgt.params = (struct ublk_params) {
.types = UBLK_PARAM_TYPE_BASIC | UBLK_PARAM_TYPE_DMA_ALIGN |
UBLK_PARAM_TYPE_SEGMENT,
.basic = {
.logical_bs_shift = 9,
.physical_bs_shift = 12,
.io_opt_shift = 12,
.io_min_shift = 9,
.max_sectors = info->max_io_buf_bytes >> 9,
.dev_sectors = dev_size >> 9,
},
.dma = {
.alignment = 4095,
},
.seg = {
.seg_boundary_mask = 4095,
.max_segment_size = 32 << 10,
.max_segments = 32,
},
};
ublk_set_integrity_params(ctx, &dev->tgt.params);
if (info->flags & UBLK_F_SUPPORT_ZERO_COPY)
dev->tgt.sq_depth = dev->tgt.cq_depth = 2 * info->queue_depth;
return 0;
}
static void __setup_nop_io(int tag, const struct ublksrv_io_desc *iod,
struct io_uring_sqe *sqe, int q_id, unsigned buf_idx)
{
unsigned ublk_op = ublksrv_get_op(iod);
io_uring_prep_nop(sqe);
sqe->buf_index = buf_idx;
sqe->flags |= IOSQE_FIXED_FILE;
sqe->rw_flags = IORING_NOP_FIXED_BUFFER | IORING_NOP_INJECT_RESULT;
sqe->len = iod->nr_sectors << 9; /* injected result */
sqe->user_data = build_user_data(tag, ublk_op, 0, q_id, 1);
}
static int null_queue_zc_io(struct ublk_thread *t, struct ublk_queue *q,
int tag)
{
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
struct io_uring_sqe *sqe[3];
unsigned short buf_idx = ublk_io_buf_idx(t, q, tag);
ublk_io_alloc_sqes(t, sqe, 3);
io_uring_prep_buf_register(sqe[0], q, tag, q->q_id, buf_idx);
sqe[0]->user_data = build_user_data(tag,
ublk_cmd_op_nr(sqe[0]->cmd_op), 0, q->q_id, 1);
sqe[0]->flags |= IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_HARDLINK;
__setup_nop_io(tag, iod, sqe[1], q->q_id, buf_idx);
sqe[1]->flags |= IOSQE_IO_HARDLINK;
io_uring_prep_buf_unregister(sqe[2], q, tag, q->q_id, buf_idx);
sqe[2]->user_data = build_user_data(tag, ublk_cmd_op_nr(sqe[2]->cmd_op), 0, q->q_id, 1);
// buf register is marked as IOSQE_CQE_SKIP_SUCCESS
return 2;
}
static int null_queue_auto_zc_io(struct ublk_thread *t, struct ublk_queue *q,
int tag)
{
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
struct io_uring_sqe *sqe[1];
ublk_io_alloc_sqes(t, sqe, 1);
__setup_nop_io(tag, iod, sqe[0], q->q_id, ublk_io_buf_idx(t, q, tag));
return 1;
}
static void ublk_null_io_done(struct ublk_thread *t, struct ublk_queue *q,
const struct io_uring_cqe *cqe)
{
unsigned tag = user_data_to_tag(cqe->user_data);
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);
}
/* buffer register op is IOSQE_CQE_SKIP_SUCCESS */
if (op == ublk_cmd_op_nr(UBLK_U_IO_REGISTER_IO_BUF))
io->tgt_ios += 1;
if (ublk_completed_tgt_io(t, q, tag))
ublk_complete_io(t, q, tag, io->result);
}
static int ublk_null_queue_io(struct ublk_thread *t, struct ublk_queue *q,
int tag)
{
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
unsigned auto_zc = ublk_queue_use_auto_zc(q);
unsigned zc = ublk_queue_use_zc(q);
int queued;
if (auto_zc && !ublk_io_auto_zc_fallback(iod))
queued = null_queue_auto_zc_io(t, q, tag);
else if (zc)
queued = null_queue_zc_io(t, q, tag);
else {
ublk_complete_io(t, q, tag, iod->nr_sectors << 9);
return 0;
}
ublk_queued_tgt_io(t, q, tag, queued);
return 0;
}
/*
* return invalid buffer index for triggering auto buffer register failure,
* then UBLK_IO_RES_NEED_REG_BUF handling is covered
*/
static unsigned short ublk_null_buf_index(const struct ublk_thread *t,
const struct ublk_queue *q, int tag)
{
if (ublk_queue_auto_zc_fallback(q))
return (unsigned short)-1;
return ublk_io_buf_idx(t, q, tag);
}
const struct ublk_tgt_ops null_tgt_ops = {
.name = "null",
.init_tgt = ublk_null_tgt_init,
.queue_io = ublk_null_queue_io,
.tgt_io_done = ublk_null_io_done,
.buf_index = ublk_null_buf_index,
};
@@ -0,0 +1 @@
timeout=150
@@ -0,0 +1,396 @@
// SPDX-License-Identifier: GPL-2.0
#include "kublk.h"
#define NR_STRIPE MAX_BACK_FILES
struct stripe_conf {
unsigned nr_files;
unsigned shift;
};
struct stripe {
loff_t start;
unsigned nr_sects;
int seq;
struct iovec *vec;
unsigned nr_vec;
unsigned cap;
};
struct stripe_array {
struct stripe s[NR_STRIPE];
unsigned nr;
struct iovec _vec[];
};
static inline const struct stripe_conf *get_chunk_shift(const struct ublk_queue *q)
{
return (struct stripe_conf *)q->dev->private_data;
}
static inline unsigned calculate_nr_vec(const struct stripe_conf *conf,
const struct ublksrv_io_desc *iod)
{
const unsigned shift = conf->shift - 9;
const unsigned unit_sects = conf->nr_files << shift;
loff_t start = iod->start_sector;
loff_t end = start + iod->nr_sectors;
return (end / unit_sects) - (start / unit_sects) + 1;
}
static struct stripe_array *alloc_stripe_array(const struct stripe_conf *conf,
const struct ublksrv_io_desc *iod)
{
unsigned nr_vecs = calculate_nr_vec(conf, iod);
unsigned total = nr_vecs * conf->nr_files;
struct stripe_array *s;
int i;
s = malloc(sizeof(*s) + total * sizeof(struct iovec));
s->nr = 0;
for (i = 0; i < conf->nr_files; i++) {
struct stripe *t = &s->s[i];
t->nr_vec = 0;
t->vec = &s->_vec[i * nr_vecs];
t->nr_sects = 0;
t->cap = nr_vecs;
}
return s;
}
static void free_stripe_array(struct stripe_array *s)
{
free(s);
}
static void calculate_stripe_array(const struct stripe_conf *conf,
const struct ublksrv_io_desc *iod, struct stripe_array *s, void *base)
{
const unsigned shift = conf->shift - 9;
const unsigned chunk_sects = 1 << shift;
const unsigned unit_sects = conf->nr_files << shift;
off64_t start = iod->start_sector;
off64_t end = start + iod->nr_sectors;
unsigned long done = 0;
unsigned idx = 0;
while (start < end) {
unsigned nr_sects = chunk_sects - (start & (chunk_sects - 1));
loff_t unit_off = (start / unit_sects) * unit_sects;
unsigned seq = (start - unit_off) >> shift;
struct stripe *this = &s->s[idx];
loff_t stripe_off = (unit_off / conf->nr_files) +
(start & (chunk_sects - 1));
if (nr_sects > end - start)
nr_sects = end - start;
if (this->nr_sects == 0) {
this->nr_sects = nr_sects;
this->start = stripe_off;
this->seq = seq;
s->nr += 1;
} else {
ublk_assert(seq == this->seq);
ublk_assert(this->start + this->nr_sects == stripe_off);
this->nr_sects += nr_sects;
}
ublk_assert(this->nr_vec < this->cap);
this->vec[this->nr_vec].iov_base = (void *)(base + done);
this->vec[this->nr_vec++].iov_len = nr_sects << 9;
start += nr_sects;
done += nr_sects << 9;
idx = (idx + 1) % conf->nr_files;
}
}
static inline enum io_uring_op stripe_to_uring_op(
const struct ublksrv_io_desc *iod, int zc)
{
unsigned ublk_op = ublksrv_get_op(iod);
if (ublk_op == UBLK_IO_OP_READ)
return zc ? IORING_OP_READV_FIXED : IORING_OP_READV;
else if (ublk_op == UBLK_IO_OP_WRITE)
return zc ? IORING_OP_WRITEV_FIXED : IORING_OP_WRITEV;
ublk_assert(0);
}
static int stripe_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q,
const struct ublksrv_io_desc *iod, int tag)
{
const struct stripe_conf *conf = get_chunk_shift(q);
unsigned auto_zc = (ublk_queue_use_auto_zc(q) != 0);
unsigned zc = (ublk_queue_use_zc(q) != 0);
enum io_uring_op op = stripe_to_uring_op(iod, zc | auto_zc);
struct io_uring_sqe *sqe[NR_STRIPE];
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 = io->buf_addr;
unsigned short buf_idx = ublk_io_buf_idx(t, q, tag);
io->private_data = s;
calculate_stripe_array(conf, iod, s, base);
ublk_io_alloc_sqes(t, sqe, s->nr + extra);
if (zc) {
io_uring_prep_buf_register(sqe[0], q, tag, q->q_id, buf_idx);
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);
}
for (i = zc; i < s->nr + extra - zc; i++) {
struct stripe *t = &s->s[i - zc];
io_uring_prep_rw(op, sqe[i],
t->seq + 1,
(void *)t->vec,
t->nr_vec,
t->start << 9);
io_uring_sqe_set_flags(sqe[i], IOSQE_FIXED_FILE);
if (auto_zc || zc) {
sqe[i]->buf_index = buf_idx;
if (zc)
sqe[i]->flags |= IOSQE_IO_HARDLINK;
}
/* bit63 marks us as tgt io */
sqe[i]->user_data = build_user_data(tag, ublksrv_get_op(iod), i - zc, q->q_id, 1);
}
if (zc) {
struct io_uring_sqe *unreg = sqe[s->nr + 1];
io_uring_prep_buf_unregister(unreg, q, tag, q->q_id, buf_idx);
unreg->user_data = build_user_data(
tag, ublk_cmd_op_nr(unreg->cmd_op), 0, q->q_id, 1);
}
/* register buffer is skip_success */
return s->nr + zc;
}
static int handle_flush(struct ublk_thread *t, struct ublk_queue *q,
const struct ublksrv_io_desc *iod, int tag)
{
const struct stripe_conf *conf = get_chunk_shift(q);
struct io_uring_sqe *sqe[NR_STRIPE];
int i;
ublk_io_alloc_sqes(t, sqe, conf->nr_files);
for (i = 0; i < conf->nr_files; i++) {
io_uring_prep_fsync(sqe[i], i + 1, IORING_FSYNC_DATASYNC);
io_uring_sqe_set_flags(sqe[i], IOSQE_FIXED_FILE);
sqe[i]->user_data = build_user_data(tag, UBLK_IO_OP_FLUSH, 0, q->q_id, 1);
}
return conf->nr_files;
}
static int stripe_queue_tgt_io(struct ublk_thread *t, struct ublk_queue *q,
int tag)
{
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
unsigned ublk_op = ublksrv_get_op(iod);
int ret = 0;
switch (ublk_op) {
case UBLK_IO_OP_FLUSH:
ret = handle_flush(t, q, iod, tag);
break;
case UBLK_IO_OP_WRITE_ZEROES:
case UBLK_IO_OP_DISCARD:
ret = -ENOTSUP;
break;
case UBLK_IO_OP_READ:
case UBLK_IO_OP_WRITE:
ret = stripe_queue_tgt_rw_io(t, q, iod, tag);
break;
default:
ret = -EINVAL;
break;
}
ublk_dbg(UBLK_DBG_IO, "%s: tag %d ublk io %x %llx %u ret %d\n", __func__, tag,
iod->op_flags, iod->start_sector, iod->nr_sectors << 9, ret);
return ret;
}
static int ublk_stripe_queue_io(struct ublk_thread *t, struct ublk_queue *q,
int tag)
{
int queued = stripe_queue_tgt_io(t, q, tag);
ublk_queued_tgt_io(t, q, tag, queued);
return 0;
}
static void ublk_stripe_io_done(struct ublk_thread *t, struct ublk_queue *q,
const struct io_uring_cqe *cqe)
{
unsigned tag = user_data_to_tag(cqe->user_data);
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
unsigned op = user_data_to_op(cqe->user_data);
struct ublk_io *io = ublk_get_io(q, tag);
int res = cqe->res;
if (res < 0 || op != ublk_cmd_op_nr(UBLK_U_IO_UNREGISTER_IO_BUF)) {
if (!io->result)
io->result = res;
if (res < 0)
ublk_err("%s: io failure %d tag %u\n", __func__, res, tag);
}
/* buffer register op is IOSQE_CQE_SKIP_SUCCESS */
if (op == ublk_cmd_op_nr(UBLK_U_IO_REGISTER_IO_BUF))
io->tgt_ios += 1;
/* fail short READ/WRITE simply */
if (op == UBLK_IO_OP_READ || op == UBLK_IO_OP_WRITE) {
unsigned seq = user_data_to_tgt_data(cqe->user_data);
struct stripe_array *s = io->private_data;
if (res < s->s[seq].nr_sects << 9) {
io->result = -EIO;
ublk_err("%s: short rw op %u res %d exp %u tag %u\n",
__func__, op, res, s->s[seq].vec->iov_len, tag);
}
}
if (ublk_completed_tgt_io(t, q, tag)) {
int res = io->result;
if (!res)
res = iod->nr_sectors << 9;
ublk_complete_io(t, q, tag, res);
free_stripe_array(io->private_data);
io->private_data = NULL;
}
}
static int ublk_stripe_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev)
{
struct ublk_params p = {
.types = UBLK_PARAM_TYPE_BASIC,
.basic = {
.attrs = UBLK_ATTR_VOLATILE_CACHE,
.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,
},
};
unsigned chunk_size = ctx->stripe.chunk_size;
struct stripe_conf *conf;
unsigned chunk_shift;
loff_t bytes = 0;
int ret, i, mul = 1;
if (ctx->auto_zc_fallback) {
ublk_err("%s: not support auto_zc_fallback\n", __func__);
return -EINVAL;
}
if (ctx->metadata_size) {
ublk_err("%s: integrity not supported\n", __func__);
return -EINVAL;
}
if ((chunk_size & (chunk_size - 1)) || !chunk_size) {
ublk_err("invalid chunk size %u\n", chunk_size);
return -EINVAL;
}
if (chunk_size < 4096 || chunk_size > 512 * 1024) {
ublk_err("invalid chunk size %u\n", chunk_size);
return -EINVAL;
}
chunk_shift = ilog2(chunk_size);
ret = backing_file_tgt_init(dev, dev->tgt.nr_backing_files);
if (ret)
return ret;
if (!dev->tgt.nr_backing_files || dev->tgt.nr_backing_files > NR_STRIPE)
return -EINVAL;
ublk_assert(dev->nr_fds == dev->tgt.nr_backing_files + 1);
for (i = 0; i < dev->tgt.nr_backing_files; i++)
dev->tgt.backing_file_size[i] &= ~((1 << chunk_shift) - 1);
for (i = 0; i < dev->tgt.nr_backing_files; i++) {
unsigned long size = dev->tgt.backing_file_size[i];
if (size != dev->tgt.backing_file_size[0])
return -EINVAL;
bytes += size;
}
conf = malloc(sizeof(*conf));
conf->shift = chunk_shift;
conf->nr_files = dev->tgt.nr_backing_files;
dev->private_data = conf;
dev->tgt.dev_size = bytes;
p.basic.dev_sectors = bytes >> 9;
dev->tgt.params = p;
if (dev->dev_info.flags & UBLK_F_SUPPORT_ZERO_COPY)
mul = 2;
dev->tgt.sq_depth = mul * dev->dev_info.queue_depth * conf->nr_files;
dev->tgt.cq_depth = mul * dev->dev_info.queue_depth * conf->nr_files;
printf("%s: shift %u files %u\n", __func__, conf->shift, conf->nr_files);
return 0;
}
static void ublk_stripe_tgt_deinit(struct ublk_dev *dev)
{
free(dev->private_data);
backing_file_tgt_deinit(dev);
}
static void ublk_stripe_cmd_line(struct dev_ctx *ctx, int argc, char *argv[])
{
static const struct option longopts[] = {
{ "chunk_size", 1, NULL, 0 },
{ 0, 0, 0, 0 }
};
int option_idx, opt;
ctx->stripe.chunk_size = 65536;
while ((opt = getopt_long(argc, argv, "",
longopts, &option_idx)) != -1) {
switch (opt) {
case 0:
if (!strcmp(longopts[option_idx].name, "chunk_size"))
ctx->stripe.chunk_size = strtol(optarg, NULL, 10);
}
}
}
static void ublk_stripe_usage(const struct ublk_tgt_ops *ops)
{
printf("\tstripe: [--chunk_size chunk_size (default 65536)]\n");
}
const struct ublk_tgt_ops stripe_tgt_ops = {
.name = "stripe",
.init_tgt = ublk_stripe_tgt_init,
.deinit_tgt = ublk_stripe_tgt_deinit,
.queue_io = ublk_stripe_queue_io,
.tgt_io_done = ublk_stripe_io_done,
.parse_cmd_line = ublk_stripe_cmd_line,
.usage = ublk_stripe_usage,
};
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_feature "BATCH_IO"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "generic" "test basic function of UBLK_F_BATCH_IO"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -t loop -q 2 -b "${UBLK_BACKFILES[0]}")
_check_add_dev $TID $?
if ! _mkfs_mount_test /dev/ublkb"${dev_id}"; then
_cleanup_test
_show_result $TID 255
fi
dev_id=$(_add_ublk_dev -t stripe -b --auto_zc "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_feature "BATCH_IO"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "generic" "test UBLK_F_BATCH_IO with 4_threads vs. 1_queues"
_create_backfile 0 512M
dev_id=$(_add_ublk_dev -t loop -q 1 --nthreads 4 -b "${UBLK_BACKFILES[0]}")
_check_add_dev $TID $?
# run fio over the ublk disk
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite \
--iodepth=32 --size=100M --numjobs=4 > /dev/null 2>&1
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_feature "BATCH_IO"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "generic" "test UBLK_F_BATCH_IO with 1_threads vs. 4_queues"
_create_backfile 0 512M
dev_id=$(_add_ublk_dev -t loop -q 4 --nthreads 1 -b "${UBLK_BACKFILES[0]}")
_check_add_dev $TID $?
# run fio over the ublk disk
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite \
--iodepth=32 --size=100M --numjobs=4 > /dev/null 2>&1
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+443
View File
@@ -0,0 +1,443 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Derive TID from script name: test_<type>_<num>.sh -> <type>_<num>
# Can be overridden in test script after sourcing this file
TID=$(basename "$0" .sh)
TID=${TID#test_}
UBLK_SKIP_CODE=4
_have_program() {
if command -v "$1" >/dev/null 2>&1; then
return 0
fi
return 1
}
# Sleep with awareness of parallel execution.
# Usage: _ublk_sleep <normal_secs> <parallel_secs>
_ublk_sleep() {
if [ "${JOBS:-1}" -gt 1 ]; then
sleep "$2"
else
sleep "$1"
fi
}
_get_disk_dev_t() {
local dev_id=$1
local dev
local major
local minor
dev=/dev/ublkb"${dev_id}"
major="0x"$(stat -c '%t' "$dev")
minor="0x"$(stat -c '%T' "$dev")
echo $(( (major & 0xfff) << 20 | (minor & 0xfffff) ))
}
_get_disk_size()
{
lsblk -b -o SIZE -n "$1"
}
_run_fio_verify_io() {
fio --name=verify --rw=randwrite --direct=1 --ioengine=libaio \
--bs=8k --iodepth=32 --verify=crc32c --do_verify=1 \
--verify_state_save=0 "$@" > /dev/null
}
_create_backfile() {
local index=$1
local new_size=$2
local old_file
local new_file
old_file="${UBLK_BACKFILES[$index]}"
[ -f "$old_file" ] && rm -f "$old_file"
new_file=$(mktemp ${UBLK_TEST_DIR}/ublk_file_"${new_size}"_XXXXX)
truncate -s "${new_size}" "${new_file}"
UBLK_BACKFILES["$index"]="$new_file"
}
_remove_files() {
local file
for file in "${UBLK_BACKFILES[@]}"; do
[ -f "$file" ] && rm -f "$file"
done
[ -f "$UBLK_TMP" ] && rm -f "$UBLK_TMP"
}
_create_tmp_dir() {
local my_file;
my_file=$(mktemp -d ${UBLK_TEST_DIR}/ublk_dir_XXXXX)
echo "$my_file"
}
_remove_tmp_dir() {
local dir=$1
[ -d "$dir" ] && rmdir "$dir"
}
_mkfs_mount_test()
{
local dev=$1
shift
local err_code=0
local mnt_dir;
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
if [ $# -gt 0 ]; then
cd "$mnt_dir" && "$@"
err_code=$?
cd - > /dev/null
fi
umount "$dev"
if [ $err_code -eq 0 ]; then
err_code=$?
fi
_remove_tmp_dir "$mnt_dir"
return $err_code
}
_check_root() {
local ksft_skip=4
if [ $UID != 0 ]; then
echo please run this as root >&2
exit $ksft_skip
fi
}
_get_ublk_dev_state() {
${UBLK_PROG} list -n "$1" | grep "state" | awk '{print $11}'
}
_get_ublk_daemon_pid() {
${UBLK_PROG} list -n "$1" | grep "pid" | awk '{print $7}'
}
_prep_test() {
_check_root
local type=$1
shift 1
modprobe ublk_drv > /dev/null 2>&1
local base_dir=${TMPDIR:-./ublktest-dir}
mkdir -p "$base_dir"
UBLK_TEST_DIR=$(mktemp -d ${base_dir}/${TID}.XXXXXX)
UBLK_TEST_DIR=$(realpath ${UBLK_TEST_DIR})
UBLK_TMP=$(mktemp ${UBLK_TEST_DIR}/ublk_test_XXXXX)
[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "ublk $type: $*"
echo "ublk selftest: $TID starting at $(date '+%F %T')" | tee /dev/kmsg
}
_remove_test_files()
{
local files=$*
for file in ${files}; do
[ -f "${file}" ] && rm -f "${file}"
done
}
_show_result()
{
if [ "$UBLK_TEST_SHOW_RESULT" -ne 0 ]; then
if [ "$2" -eq 0 ]; then
echo "$1 : [PASS]"
elif [ "$2" -eq 4 ]; then
echo "$1 : [SKIP]"
else
echo "$1 : [FAIL]"
fi
fi
if [ "$2" -ne 0 ]; then
_remove_files
exit "$2"
fi
return 0
}
# don't call from sub-shell, otherwise can't exit
_check_add_dev()
{
local tid=$1
local code=$2
if [ "${code}" -ne 0 ]; then
_show_result "${tid}" "${code}"
fi
}
_cleanup_test() {
if [ -f "${UBLK_TEST_DIR}/.ublk_devs" ]; then
while read -r dev_id; do
${UBLK_PROG} del -n "${dev_id}"
done < "${UBLK_TEST_DIR}/.ublk_devs"
rm -f "${UBLK_TEST_DIR}/.ublk_devs"
fi
_remove_files
rmdir ${UBLK_TEST_DIR}
echo "ublk selftest: $TID done at $(date '+%F %T')" | tee /dev/kmsg
}
_have_feature()
{
if $UBLK_PROG "features" | grep "$1" > /dev/null 2>&1; then
return 0
fi
return 1
}
_create_ublk_dev() {
local dev_id;
local cmd=$1
local settle=$2
shift 2
if [ ! -c /dev/ublk-control ]; then
return ${UBLK_SKIP_CODE}
fi
if echo "$@" | grep -q "\-z"; then
if ! _have_feature "ZERO_COPY"; then
return ${UBLK_SKIP_CODE}
fi
fi
if ! dev_id=$("${UBLK_PROG}" "$cmd" "$@" | grep "dev id" | awk -F '[ :]' '{print $3}'); then
echo "fail to add ublk dev $*"
return 255
fi
if [ "$settle" = "yes" ]; then
udevadm settle --timeout=20
fi
if [[ "$dev_id" =~ ^[0-9]+$ ]]; then
echo "$dev_id" >> "${UBLK_TEST_DIR}/.ublk_devs"
echo "${dev_id}"
else
return 255
fi
}
_add_ublk_dev() {
_create_ublk_dev "add" "yes" "$@"
}
_add_ublk_dev_no_settle() {
_create_ublk_dev "add" "no" "$@"
}
_recover_ublk_dev() {
local dev_id
local state
dev_id=$(_create_ublk_dev "recover" "yes" "$@")
for ((j=0;j<100;j++)); do
state=$(_get_ublk_dev_state "${dev_id}")
[ "$state" == "LIVE" ] && break
sleep 1
done
echo "$state"
}
# quiesce device and return ublk device state
__ublk_quiesce_dev()
{
local dev_id=$1
local exp_state=$2
local state
if ! ${UBLK_PROG} quiesce -n "${dev_id}"; then
state=$(_get_ublk_dev_state "${dev_id}")
return "$state"
fi
for ((j=0;j<100;j++)); do
state=$(_get_ublk_dev_state "${dev_id}")
[ "$state" == "$exp_state" ] && break
sleep 1
done
echo "$state"
}
# kill the ublk daemon and return ublk device state
__ublk_kill_daemon()
{
local dev_id=$1
local exp_state=$2
local daemon_pid
local state
daemon_pid=$(_get_ublk_daemon_pid "${dev_id}")
state=$(_get_ublk_dev_state "${dev_id}")
for ((j=0;j<100;j++)); do
[ "$state" == "$exp_state" ] && break
kill -9 "$daemon_pid" > /dev/null 2>&1
sleep 1
state=$(_get_ublk_dev_state "${dev_id}")
done
echo "$state"
}
_ublk_del_dev() {
local dev_id=$1
${UBLK_PROG} del -n "${dev_id}"
# Remove from tracking file
if [ -f "${UBLK_TEST_DIR}/.ublk_devs" ]; then
sed -i "/^${dev_id}$/d" "${UBLK_TEST_DIR}/.ublk_devs"
fi
}
__remove_ublk_dev_return() {
local dev_id=$1
_ublk_del_dev "${dev_id}"
local res=$?
udevadm settle --timeout=20
return ${res}
}
__run_io_and_remove()
{
local dev_id=$1
local size=$2
local kill_server=$3
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
--rw=randrw --norandommap --iodepth=256 --size="${size}" --numjobs="$(nproc)" \
--runtime=20 --time_based > /dev/null 2>&1 &
fio --name=batchjob --filename=/dev/ublkb"${dev_id}" --ioengine=io_uring \
--rw=randrw --norandommap --iodepth=256 --size="${size}" \
--numjobs="$(nproc)" --runtime=20 --time_based \
--iodepth_batch_submit=32 --iodepth_batch_complete_min=32 \
--force_async=7 > /dev/null 2>&1 &
sleep 2
if [ "${kill_server}" = "yes" ]; then
local state
state=$(__ublk_kill_daemon "${dev_id}" "DEAD")
if [ "$state" != "DEAD" ]; then
echo "device isn't dead($state) after killing daemon"
return 255
fi
fi
if ! __remove_ublk_dev_return "${dev_id}"; then
echo "delete dev ${dev_id} failed"
return 255
fi
wait
}
run_io_and_remove()
{
local size=$1
local dev_id
shift 1
dev_id=$(_add_ublk_dev "$@")
_check_add_dev "$TID" $?
[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs. remove device(ublk add $*)"
if ! __run_io_and_remove "$dev_id" "${size}" "no"; then
echo "/dev/ublkc$dev_id isn't removed"
exit 255
fi
}
run_io_and_kill_daemon()
{
local size=$1
local dev_id
shift 1
dev_id=$(_add_ublk_dev "$@")
_check_add_dev "$TID" $?
[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs kill ublk server(ublk add $*)"
if ! __run_io_and_remove "$dev_id" "${size}" "yes"; then
echo "/dev/ublkc$dev_id isn't removed res ${res}"
exit 255
fi
}
run_io_and_recover()
{
local size=$1
local action=$2
local state
local dev_id
shift 2
dev_id=$(_add_ublk_dev "$@")
_check_add_dev "$TID" $?
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
--rw=randread --iodepth=256 --size="${size}" --numjobs=4 \
--runtime=20 --time_based > /dev/null 2>&1 &
sleep 4
if [ "$action" == "kill_daemon" ]; then
state=$(__ublk_kill_daemon "${dev_id}" "QUIESCED")
elif [ "$action" == "quiesce_dev" ]; then
state=$(__ublk_quiesce_dev "${dev_id}" "QUIESCED")
fi
if [ "$state" != "QUIESCED" ]; then
echo "device isn't quiesced($state) after $action"
return 255
fi
state=$(_recover_ublk_dev -n "$dev_id" "$@")
if [ "$state" != "LIVE" ]; then
echo "faile to recover to LIVE($state)"
return 255
fi
if ! __remove_ublk_dev_return "${dev_id}"; then
echo "delete dev ${dev_id} failed"
return 255
fi
wait
}
_ublk_test_top_dir()
{
cd "$(dirname "$0")" && pwd
}
METADATA_SIZE_PROG="$(_ublk_test_top_dir)/metadata_size"
_get_metadata_size()
{
local dev_id=$1
local field=$2
"$METADATA_SIZE_PROG" "/dev/ublkb$dev_id" | grep "$field" | grep -o "[0-9]*"
}
UBLK_PROG=$(_ublk_test_top_dir)/kublk
UBLK_TEST_QUIET=1
UBLK_TEST_SHOW_RESULT=1
UBLK_BACKFILES=()
export UBLK_PROG
export UBLK_TEST_QUIET
export UBLK_TEST_SHOW_RESULT
@@ -0,0 +1,55 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_program bpftrace; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "null" "ublk dispatch won't reorder IO for MQ"
dev_id=$(_add_ublk_dev -t null -q 2)
_check_add_dev $TID $?
dev_t=$(_get_disk_dev_t "$dev_id")
bpftrace trace/seq_io.bt "$dev_t" "W" 1 > "$UBLK_TMP" 2>&1 &
btrace_pid=$!
# Wait for bpftrace probes to be attached (BEGIN block prints BPFTRACE_READY)
for _ in $(seq 100); do
grep -q "BPFTRACE_READY" "$UBLK_TMP" 2>/dev/null && break
sleep 0.1
done
if ! kill -0 "$btrace_pid" 2>/dev/null; then
_cleanup_test
exit "$UBLK_SKIP_CODE"
fi
# run fio over this ublk disk (pinned to CPU 0)
taskset -c 0 fio --name=write_seq \
--filename=/dev/ublkb"${dev_id}" \
--ioengine=libaio --iodepth=16 \
--rw=write \
--size=512M \
--direct=1 \
--bs=4k > /dev/null 2>&1
ERR_CODE=$?
kill "$btrace_pid"
wait
# Check for out-of-order completions detected by bpftrace
if grep -q "^out_of_order:" "$UBLK_TMP"; then
echo "I/O reordering detected:"
grep "^out_of_order:" "$UBLK_TMP"
ERR_CODE=255
fi
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,27 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "null" "check dma & segment limits for zero copy"
dev_id=$(_add_ublk_dev -t null -z)
_check_add_dev $TID $?
sysfs_path=/sys/block/ublkb"${dev_id}"
dma_align=$(cat "$sysfs_path"/queue/dma_alignment)
max_segments=$(cat "$sysfs_path"/queue/max_segments)
max_segment_size=$(cat "$sysfs_path"/queue/max_segment_size)
if [ "$dma_align" != "4095" ]; then
ERR_CODE=255
fi
if [ "$max_segments" != "32" ]; then
ERR_CODE=255
fi
if [ "$max_segment_size" != "32768" ]; then
ERR_CODE=255
fi
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,40 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "fault_inject" "fast cleanup when all I/Os of one hctx are in server"
# configure ublk server to sleep 2s before completing each I/O
dev_id=$(_add_ublk_dev -t fault_inject -q 2 -d 1 --delay_us 2000000)
_check_add_dev $TID $?
STARTTIME=${SECONDS}
dd if=/dev/urandom of=/dev/ublkb${dev_id} oflag=direct bs=4k count=1 status=none > /dev/null 2>&1 &
dd_pid=$!
__ublk_kill_daemon ${dev_id} "DEAD" >/dev/null
wait $dd_pid
dd_exitcode=$?
ENDTIME=${SECONDS}
ELAPSED=$(($ENDTIME - $STARTTIME))
# assert that dd sees an error and exits quickly after ublk server is
# killed. previously this relied on seeing an I/O timeout and so would
# take ~30s
if [ $dd_exitcode -eq 0 ]; then
echo "dd unexpectedly exited successfully!"
ERR_CODE=255
fi
if [ $ELAPSED -ge 5 ]; then
echo "dd took $ELAPSED seconds to exit (>= 5s tolerance)!"
ERR_CODE=255
fi
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,27 @@
#!/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 "generic" "test UBLK_F_NEED_GET_DATA"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -t loop -q 2 -g "${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=$?
if [ "$ERR_CODE" -eq 0 ]; then
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
fi
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,31 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_feature "AUTO_BUF_REG"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "generic" "test UBLK_F_AUTO_BUF_REG"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -t loop -q 2 --auto_zc "${UBLK_BACKFILES[0]}")
_check_add_dev $TID $?
if ! _mkfs_mount_test /dev/ublkb"${dev_id}"; then
_cleanup_test
_show_result $TID 255
fi
dev_id=$(_add_ublk_dev -t stripe --auto_zc "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,27 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_feature "AUTO_BUF_REG"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "null" "basic IO test"
dev_id=$(_add_ublk_dev -t null -z --auto_zc --auto_zc_fallback)
_check_add_dev $TID $?
# run fio over the two disks
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite --iodepth=32 --size=256M > /dev/null 2>&1
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,29 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_feature "UPDATE_SIZE"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "null" "check update size"
dev_id=$(_add_ublk_dev -t null)
_check_add_dev $TID $?
size=$(_get_disk_size /dev/ublkb"${dev_id}")
size=$(( size / 2 ))
if ! "$UBLK_PROG" update_size -n "$dev_id" -s "$size"; then
ERR_CODE=255
fi
new_size=$(_get_disk_size /dev/ublkb"${dev_id}")
if [ "$new_size" != "$size" ]; then
ERR_CODE=255
fi
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,58 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
if ! _have_program bpftrace; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "null" "do imbalanced load, it should be balanced over I/O threads"
NTHREADS=6
dev_id=$(_add_ublk_dev -t null -q 4 -d 16 --nthreads $NTHREADS --per_io_tasks)
_check_add_dev $TID $?
dev_t=$(_get_disk_dev_t "$dev_id")
bpftrace trace/count_ios_per_tid.bt "$dev_t" > "$UBLK_TMP" 2>&1 &
btrace_pid=$!
sleep 2
if ! kill -0 "$btrace_pid" > /dev/null 2>&1; then
_cleanup_test
exit "$UBLK_SKIP_CODE"
fi
# do imbalanced I/O on the ublk device
# pin to cpu 0 to prevent migration/only target one queue
fio --name=write_seq \
--filename=/dev/ublkb"${dev_id}" \
--ioengine=libaio --iodepth=16 \
--rw=write \
--size=512M \
--direct=1 \
--bs=4k \
--cpus_allowed=0 > /dev/null 2>&1
ERR_CODE=$?
kill "$btrace_pid"
wait
# check that every task handles some I/O, even though all I/O was issued
# from a single CPU. when ublk gets support for round-robin tag
# allocation, this check can be strengthened to assert that every thread
# handles the same number of I/Os
NR_THREADS_THAT_HANDLED_IO=$(grep -c '@' ${UBLK_TMP})
if [[ $NR_THREADS_THAT_HANDLED_IO -ne $NTHREADS ]]; then
echo "only $NR_THREADS_THAT_HANDLED_IO handled I/O! expected $NTHREADS"
cat "$UBLK_TMP"
ERR_CODE=255
fi
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,19 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "null" "check that feature list is complete"
if ${UBLK_PROG} features | grep -q unknown; then
echo "# unknown feature detected!"
echo "# did you add a feature and forget to update feat_map in kublk?"
echo "# this failure is expected if running an older test suite against"
echo "# a newer kernel with new features added"
ERR_CODE=255
fi
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,56 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "null" "stop --safe command"
# Check if SAFE_STOP_DEV feature is supported
if ! _have_feature "SAFE_STOP_DEV"; then
_cleanup_test
exit "$UBLK_SKIP_CODE"
fi
# Test 1: stop --safe on idle device should succeed
dev_id=$(_add_ublk_dev -t null -q 2 -d 32)
_check_add_dev $TID $?
# Device is idle (no openers), stop --safe should succeed
if ! ${UBLK_PROG} stop -n "${dev_id}" --safe; then
echo "stop --safe on idle device failed unexpectedly!"
ERR_CODE=255
fi
# Clean up device
_ublk_del_dev "${dev_id}" > /dev/null 2>&1
udevadm settle
# Test 2: stop --safe on device with active opener should fail
dev_id=$(_add_ublk_dev -t null -q 2 -d 32)
_check_add_dev $TID $?
# Open device in background (dd reads indefinitely)
dd if=/dev/ublkb${dev_id} of=/dev/null bs=4k iflag=direct > /dev/null 2>&1 &
dd_pid=$!
# Give dd time to start
sleep 0.2
# Device has active opener, stop --safe should fail with -EBUSY
if ${UBLK_PROG} stop -n "${dev_id}" --safe 2>/dev/null; then
echo "stop --safe on busy device succeeded unexpectedly!"
ERR_CODE=255
fi
# Kill dd and clean up
kill $dd_pid 2>/dev/null
wait $dd_pid 2>/dev/null
# Now device should be idle, regular delete should work
_ublk_del_dev "${dev_id}"
udevadm settle
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,35 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "fault_inject" "teardown after incomplete recovery"
# First start and stop a ublk server with device configured for recovery
dev_id=$(_add_ublk_dev -t fault_inject -r 1)
_check_add_dev $TID $?
state=$(__ublk_kill_daemon "${dev_id}" "QUIESCED")
if [ "$state" != "QUIESCED" ]; then
echo "device isn't quiesced($state) after $action"
ERR_CODE=255
fi
# Then recover the device, but use --die_during_fetch to have the ublk
# server die while a queue has some (but not all) I/Os fetched
${UBLK_PROG} recover -n "${dev_id}" --foreground -t fault_inject --die_during_fetch 1
RECOVER_RES=$?
# 137 is the result when dying of SIGKILL
if (( RECOVER_RES != 137 )); then
echo "recover command exited with unexpected code ${RECOVER_RES}!"
ERR_CODE=255
fi
# Clean up the device. This can only succeed once teardown of the above
# exited ublk server completes. So if teardown never completes, we will
# time out here
_ublk_del_dev "${dev_id}"
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,105 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_check_value() {
local name=$1
local actual=$2
local expected=$3
if [ "$actual" != "$expected" ]; then
echo "$name $actual != $expected"
ERR_CODE=255
return 1
fi
return 0
}
_test_metadata_only() {
local dev_id
dev_id=$(_add_ublk_dev -t null -u --no_auto_part_scan --metadata_size 8)
_check_add_dev "$TID" $?
_check_value "metadata_size" "$(_get_metadata_size "$dev_id" metadata_size)" 8 &&
_check_value "pi_offset" "$(_get_metadata_size "$dev_id" pi_offset)" 0 &&
_check_value "pi_tuple_size" "$(_get_metadata_size "$dev_id" pi_tuple_size)" 0 &&
_check_value "device_is_integrity_capable" \
"$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable")" 0 &&
_check_value "format" "$(cat "/sys/block/ublkb$dev_id/integrity/format")" nop &&
_check_value "protection_interval_bytes" \
"$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes")" 512 &&
_check_value "tag_size" "$(cat "/sys/block/ublkb$dev_id/integrity/tag_size")" 0
_ublk_del_dev "${dev_id}"
}
_test_integrity_capable_ip() {
local dev_id
dev_id=$(_add_ublk_dev -t null -u --no_auto_part_scan --integrity_capable --metadata_size 64 --pi_offset 56 --csum_type ip)
_check_add_dev "$TID" $?
_check_value "metadata_size" "$(_get_metadata_size "$dev_id" metadata_size)" 64 &&
_check_value "pi_offset" "$(_get_metadata_size "$dev_id" pi_offset)" 56 &&
_check_value "pi_tuple_size" "$(_get_metadata_size "$dev_id" pi_tuple_size)" 8 &&
_check_value "device_is_integrity_capable" \
"$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable")" 1 &&
_check_value "format" "$(cat "/sys/block/ublkb$dev_id/integrity/format")" T10-DIF-TYPE3-IP &&
_check_value "protection_interval_bytes" \
"$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes")" 512 &&
_check_value "tag_size" "$(cat "/sys/block/ublkb$dev_id/integrity/tag_size")" 0
_ublk_del_dev "${dev_id}"
}
_test_integrity_reftag_t10dif() {
local dev_id
dev_id=$(_add_ublk_dev -t null -u --no_auto_part_scan --integrity_reftag --metadata_size 8 --csum_type t10dif)
_check_add_dev "$TID" $?
_check_value "metadata_size" "$(_get_metadata_size "$dev_id" metadata_size)" 8 &&
_check_value "pi_offset" "$(_get_metadata_size "$dev_id" pi_offset)" 0 &&
_check_value "pi_tuple_size" "$(_get_metadata_size "$dev_id" pi_tuple_size)" 8 &&
_check_value "device_is_integrity_capable" \
"$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable")" 0 &&
_check_value "format" "$(cat "/sys/block/ublkb$dev_id/integrity/format")" T10-DIF-TYPE1-CRC &&
_check_value "protection_interval_bytes" \
"$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes")" 512 &&
_check_value "tag_size" "$(cat "/sys/block/ublkb$dev_id/integrity/tag_size")" 0
_ublk_del_dev "${dev_id}"
}
_test_nvme_csum() {
local dev_id
dev_id=$(_add_ublk_dev -t null -u --no_auto_part_scan --metadata_size 16 --csum_type nvme --tag_size 8)
_check_add_dev "$TID" $?
_check_value "metadata_size" "$(_get_metadata_size "$dev_id" metadata_size)" 16 &&
_check_value "pi_offset" "$(_get_metadata_size "$dev_id" pi_offset)" 0 &&
_check_value "pi_tuple_size" "$(_get_metadata_size "$dev_id" pi_tuple_size)" 16 &&
_check_value "device_is_integrity_capable" \
"$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable")" 0 &&
_check_value "format" "$(cat "/sys/block/ublkb$dev_id/integrity/format")" EXT-DIF-TYPE3-CRC64 &&
_check_value "protection_interval_bytes" \
"$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes")" 512 &&
_check_value "tag_size" "$(cat "/sys/block/ublkb$dev_id/integrity/tag_size")" 8
_ublk_del_dev "${dev_id}"
}
_prep_test "null" "integrity params"
_test_metadata_only
_test_integrity_capable_ip
_test_integrity_reftag_t10dif
_test_nvme_csum
_cleanup_test
_show_result "$TID" $ERR_CODE
@@ -0,0 +1,142 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
if ! _have_program fio; then
exit $UBLK_SKIP_CODE
fi
min_fio_version=fio-3.42
fio_version=$(fio --version)
if ! sort --version-sort --check=quiet <(printf "%s\n%s\n" "$min_fio_version" "$fio_version"); then
echo "Requires fio version with https://github.com/axboe/fio/pull/1992"
exit $UBLK_SKIP_CODE
fi
ERR_CODE=0
# Global variables set during device setup
dev_id=""
fio_args=""
fio_err=""
_setup_device() {
_create_backfile 0 256M
_create_backfile 1 32M # 256M * (64 integrity bytes / 512 data bytes)
local integrity_params="--integrity_capable --integrity_reftag
--metadata_size 64 --pi_offset 56 --csum_type t10dif"
dev_id=$(_add_ublk_dev -t loop -u $integrity_params "${UBLK_BACKFILES[@]}")
_check_add_dev "$TID" $?
# 1M * (64 integrity bytes / 512 data bytes) = 128K
fio_args="--ioengine io_uring --direct 1 --bsrange 512-1M --iodepth 32
--md_per_io_size 128K --pi_act 0 --pi_chk GUARD,REFTAG,APPTAG
--filename /dev/ublkb$dev_id"
fio_err=$(mktemp "${UBLK_TEST_DIR}"/fio_err_XXXXX)
}
_test_fill_and_verify() {
fio --name fill --rw randwrite $fio_args > /dev/null
if [ $? != 0 ]; then
echo "fio fill failed"
ERR_CODE=255
return 1
fi
fio --name verify --rw randread $fio_args > /dev/null
if [ $? != 0 ]; then
echo "fio verify failed"
ERR_CODE=255
return 1
fi
}
_test_corrupted_reftag() {
local dd_reftag_args="bs=1 seek=60 count=4 oflag=dsync conv=notrunc status=none"
local expected_err="REFTAG compare error: LBA: 0 Expected=0, Actual="
# Overwrite 4-byte reftag at offset 56 + 4 = 60
dd if=/dev/urandom "of=${UBLK_BACKFILES[1]}" $dd_reftag_args
if [ $? != 0 ]; then
echo "dd corrupted_reftag failed"
ERR_CODE=255
return 1
fi
if fio --name corrupted_reftag --rw randread $fio_args > /dev/null 2> "$fio_err"; then
echo "fio corrupted_reftag unexpectedly succeeded"
ERR_CODE=255
return 1
fi
if ! grep -q "$expected_err" "$fio_err"; then
echo "fio corrupted_reftag message not found: $expected_err"
ERR_CODE=255
return 1
fi
# Reset to 0
dd if=/dev/zero "of=${UBLK_BACKFILES[1]}" $dd_reftag_args
if [ $? != 0 ]; then
echo "dd restore corrupted_reftag failed"
ERR_CODE=255
return 1
fi
}
_test_corrupted_data() {
local dd_data_args="bs=512 count=1 oflag=direct,dsync conv=notrunc status=none"
local expected_err="Guard compare error: LBA: 0 Expected=0, Actual="
dd if=/dev/zero "of=${UBLK_BACKFILES[0]}" $dd_data_args
if [ $? != 0 ]; then
echo "dd corrupted_data failed"
ERR_CODE=255
return 1
fi
if fio --name corrupted_data --rw randread $fio_args > /dev/null 2> "$fio_err"; then
echo "fio corrupted_data unexpectedly succeeded"
ERR_CODE=255
return 1
fi
if ! grep -q "$expected_err" "$fio_err"; then
echo "fio corrupted_data message not found: $expected_err"
ERR_CODE=255
return 1
fi
}
_test_bad_apptag() {
local expected_err="APPTAG compare error: LBA: [0-9]* Expected=4321, Actual=1234"
if fio --name bad_apptag --rw randread $fio_args --apptag 0x4321 > /dev/null 2> "$fio_err"; then
echo "fio bad_apptag unexpectedly succeeded"
ERR_CODE=255
return 1
fi
if ! grep -q "$expected_err" "$fio_err"; then
echo "fio bad_apptag message not found: $expected_err"
ERR_CODE=255
return 1
fi
}
_prep_test "loop" "end-to-end integrity"
_setup_device
_test_fill_and_verify && \
_test_corrupted_reftag && \
_test_corrupted_data && \
_test_bad_apptag
rm -f "$fio_err"
_cleanup_test
_show_result "$TID" $ERR_CODE
@@ -0,0 +1,103 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
if ! _have_program fio; then
exit $UBLK_SKIP_CODE
fi
_test_fill_and_verify() {
fio --name fill --rw randwrite $fio_args > /dev/null
if [ $? != 0 ]; then
echo "fio fill failed"
ERR_CODE=255
return 1
fi
fio --name verify --rw randread $fio_args > /dev/null
if [ $? != 0 ]; then
echo "fio verify failed"
ERR_CODE=255
return 1
fi
}
_test_corrupted_reftag() {
local dd_reftag_args="bs=1 seek=58 count=6 oflag=dsync conv=notrunc status=none"
# Overwrite 6-byte reftag at offset 48 + 10 = 58
dd if=/dev/urandom "of=${UBLK_BACKFILES[1]}" $dd_reftag_args
if [ $? != 0 ]; then
echo "dd corrupted_reftag failed"
ERR_CODE=255
return 1
fi
if fio --name corrupted_reftag --rw randread $fio_args > /dev/null 2> "$fio_err"; then
echo "fio corrupted_reftag unexpectedly succeeded"
ERR_CODE=255
return 1
fi
if ! grep -q "$expected_err" "$fio_err"; then
echo "fio corrupted_reftag message not found: $expected_err"
ERR_CODE=255
return 1
fi
# Reset to 0
dd if=/dev/zero "of=${UBLK_BACKFILES[1]}" $dd_reftag_args
if [ $? != 0 ]; then
echo "dd restore corrupted_reftag failed"
ERR_CODE=255
return 1
fi
}
_test_corrupted_data() {
local dd_data_args="bs=512 count=1 oflag=direct,dsync conv=notrunc status=none"
dd if=/dev/zero "of=${UBLK_BACKFILES[0]}" $dd_data_args
if [ $? != 0 ]; then
echo "dd corrupted_data failed"
ERR_CODE=255
return 1
fi
if fio --name corrupted_data --rw randread $fio_args > /dev/null 2> "$fio_err"; then
echo "fio corrupted_data unexpectedly succeeded"
ERR_CODE=255
return 1
fi
if ! grep -q "$expected_err" "$fio_err"; then
echo "fio corrupted_data message not found: $expected_err"
ERR_CODE=255
return 1
fi
}
_prep_test "loop" "end-to-end auto integrity"
_create_backfile 0 256M
_create_backfile 1 32M # 256M * (64 integrity bytes / 512 data bytes)
integrity_params="--integrity_capable --integrity_reftag
--metadata_size 64 --pi_offset 48 --csum_type nvme"
dev_id=$(_add_ublk_dev -t loop -u $integrity_params "${UBLK_BACKFILES[@]}")
_check_add_dev "$TID" $?
fio_args="--ioengine libaio --direct 1 --bsrange 512-1M --iodepth 32
--filename /dev/ublkb$dev_id"
fio_err=$(mktemp "${UBLK_TEST_DIR}"/fio_err_XXXXX)
ERR_CODE=0
expected_err="Invalid or incomplete multibyte or wide character: read offset=0"
_test_fill_and_verify && \
_test_corrupted_reftag && \
_test_corrupted_data
rm -f "$fio_err"
_cleanup_test
_show_result "$TID" $ERR_CODE
+25
View File
@@ -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 test"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -t loop "${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
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "loop" "mkfs & mount & umount"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -t loop "${UBLK_BACKFILES[0]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+24
View File
@@ -0,0 +1,24 @@
#!/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 over zero copy"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -t loop -z "${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
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "loop" "mkfs & mount & umount with zero copy"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -t loop -z "${UBLK_BACKFILES[0]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+25
View File
@@ -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 test"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -q 2 -t loop "${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
+24
View File
@@ -0,0 +1,24 @@
#!/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 over user copy"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -t loop -u "${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
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "loop" "mkfs & mount & umount with user copy"
_create_backfile 0 256M
dev_id=$(_add_ublk_dev -t loop -u "${UBLK_BACKFILES[0]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+23
View File
@@ -0,0 +1,23 @@
#!/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 "null" "basic IO test"
dev_id=$(_add_ublk_dev -t null)
_check_add_dev $TID $?
# run fio over the two disks
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite --iodepth=32 --size=256M > /dev/null 2>&1
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+23
View File
@@ -0,0 +1,23 @@
#!/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 "null" "basic IO test with zero copy"
dev_id=$(_add_ublk_dev -t null -z)
_check_add_dev $TID $?
# run fio over the two disks
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite --iodepth=32 --size=256M > /dev/null 2>&1
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+23
View File
@@ -0,0 +1,23 @@
#!/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 "null" "basic IO test with user copy"
dev_id=$(_add_ublk_dev -t null -u)
_check_add_dev $TID $?
# run fio over the two disks
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite --iodepth=32 --size=256M > /dev/null 2>&1
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
format_backing_file()
{
local backing_file=$1
# Create ublk device to write partition table
local tmp_dev=$(_add_ublk_dev -t loop "${backing_file}")
[ $? -ne 0 ] && return 1
# Write partition table with sfdisk
sfdisk /dev/ublkb"${tmp_dev}" > /dev/null 2>&1 <<EOF
label: dos
start=2048, size=100MiB, type=83
start=206848, size=100MiB, type=83
EOF
local ret=$?
"${UBLK_PROG}" del -n "${tmp_dev}"
return $ret
}
test_auto_part_scan()
{
local backing_file=$1
# Create device WITHOUT --no_auto_part_scan
local dev_id=$(_add_ublk_dev -t loop "${backing_file}")
[ $? -ne 0 ] && return 1
udevadm settle
# Partitions should be auto-detected
if [ ! -e /dev/ublkb"${dev_id}"p1 ] || [ ! -e /dev/ublkb"${dev_id}"p2 ]; then
"${UBLK_PROG}" del -n "${dev_id}"
return 1
fi
"${UBLK_PROG}" del -n "${dev_id}"
return 0
}
test_no_auto_part_scan()
{
local backing_file=$1
# Create device WITH --no_auto_part_scan
local dev_id=$(_add_ublk_dev -t loop --no_auto_part_scan "${backing_file}")
[ $? -ne 0 ] && return 1
udevadm settle
# Partitions should NOT be auto-detected
if [ -e /dev/ublkb"${dev_id}"p1 ]; then
"${UBLK_PROG}" del -n "${dev_id}"
return 1
fi
# Manual scan should work
blockdev --rereadpt /dev/ublkb"${dev_id}" > /dev/null 2>&1
udevadm settle
if [ ! -e /dev/ublkb"${dev_id}"p1 ] || [ ! -e /dev/ublkb"${dev_id}"p2 ]; then
"${UBLK_PROG}" del -n "${dev_id}"
return 1
fi
"${UBLK_PROG}" del -n "${dev_id}"
return 0
}
if ! _have_program sfdisk || ! _have_program blockdev; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "generic" "test UBLK_F_NO_AUTO_PART_SCAN"
if ! _have_feature "UBLK_F_NO_AUTO_PART_SCAN"; then
_cleanup_test
exit "$UBLK_SKIP_CODE"
fi
# Create and format backing file with partition table
_create_backfile 0 256M
format_backing_file "${UBLK_BACKFILES[0]}"
[ $? -ne 0 ] && ERR_CODE=255
# Test normal auto partition scan
[ "$ERR_CODE" -eq 0 ] && test_auto_part_scan "${UBLK_BACKFILES[0]}"
[ $? -ne 0 ] && ERR_CODE=255
# Test no auto partition scan with manual scan
[ "$ERR_CODE" -eq 0 ] && test_no_auto_part_scan "${UBLK_BACKFILES[0]}"
[ $? -ne 0 ] && ERR_CODE=255
_cleanup_test
_show_result $TID $ERR_CODE
+67
View File
@@ -0,0 +1,67 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_test_partition_scan_no_hang()
{
local recovery_flag=$1
local expected_state=$2
local dev_id
local state
local daemon_pid
local start_time
local elapsed
# Create ublk device with fault_inject target and very large delay
# to simulate hang during partition table read
# --delay_us 60000000 = 60 seconds delay
# Use _add_ublk_dev_no_settle to avoid udevadm settle hang waiting
# for partition scan events to complete
if [ "$recovery_flag" = "yes" ]; then
echo "Testing partition scan with recovery support..."
dev_id=$(_add_ublk_dev_no_settle -t fault_inject -q 1 -d 1 --delay_us 60000000 -r 1)
else
echo "Testing partition scan without recovery..."
dev_id=$(_add_ublk_dev_no_settle -t fault_inject -q 1 -d 1 --delay_us 60000000)
fi
_check_add_dev "$TID" $?
# The add command should return quickly because partition scan is async.
# Now sleep briefly to let the async partition scan work start and hit
# the delay in the fault_inject handler.
_ublk_sleep 1 5
# Kill the ublk daemon while partition scan is potentially blocked
# And check state transitions properly
start_time=${SECONDS}
daemon_pid=$(_get_ublk_daemon_pid "${dev_id}")
state=$(__ublk_kill_daemon "${dev_id}" "${expected_state}")
elapsed=$((SECONDS - start_time))
# Verify the device transitioned to expected state
if [ "$state" != "${expected_state}" ]; then
echo "FAIL: Device state is $state, expected ${expected_state}"
ERR_CODE=255
_ublk_del_dev "${dev_id}" > /dev/null 2>&1
return
fi
echo "PASS: Device transitioned to ${expected_state} in ${elapsed}s without hanging"
# Clean up the device
_ublk_del_dev "${dev_id}" > /dev/null 2>&1
}
_prep_test "partition_scan" "verify async partition scan prevents IO hang"
# Test 1: Without recovery support - should transition to DEAD
_test_partition_scan_no_hang "no" "DEAD"
# Test 2: With recovery support - should transition to QUIESCED
_test_partition_scan_no_hang "yes" "QUIESCED"
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,44 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_run_recover_test()
{
run_io_and_recover 256M "kill_daemon" "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "recover" "basic recover function verification"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_run_recover_test -t null -q 2 -r 1 -b &
ublk_run_recover_test -t loop -q 2 -r 1 -b "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 -b "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_run_recover_test -t null -q 2 -r 1 &
ublk_run_recover_test -t loop -q 2 -r 1 "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_run_recover_test -t null -q 2 -r 1 -i 1 &
ublk_run_recover_test -t loop -q 2 -r 1 -i 1 "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 -i 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,48 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_run_recover_test()
{
run_io_and_recover 256M "kill_daemon" "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "ZERO_COPY"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "recover" "basic recover function verification (zero copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_run_recover_test -t null -q 2 -r 1 -z -b &
ublk_run_recover_test -t loop -q 2 -r 1 -z -b "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 -z -b "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_run_recover_test -t null -q 2 -r 1 -z &
ublk_run_recover_test -t loop -q 2 -r 1 -z "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 -z "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_run_recover_test -t null -q 2 -r 1 -z -i 1 &
ublk_run_recover_test -t loop -q 2 -r 1 -z -i 1 "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 -z -i 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,43 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_run_quiesce_recover()
{
run_io_and_recover 256M "quiesce_dev" "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_feature "QUIESCE"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "quiesce" "basic quiesce & recover function verification"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_run_quiesce_recover -t null -q 2 -r 1 &
ublk_run_quiesce_recover -t loop -q 2 -r 1 "${UBLK_BACKFILES[0]}" &
ublk_run_quiesce_recover -t stripe -q 2 -r 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_run_quiesce_recover -t null -q 2 -r 1 -i 1 &
ublk_run_quiesce_recover -t loop -q 2 -r 1 -i 1 "${UBLK_BACKFILES[0]}" &
ublk_run_quiesce_recover -t stripe -q 2 -r 1 -i 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,39 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_run_recover_test()
{
run_io_and_recover 256M "kill_daemon" "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "recover" "basic recover function verification (user copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_run_recover_test -t null -q 2 -r 1 -u &
ublk_run_recover_test -t loop -q 2 -r 1 -u "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 -u "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_run_recover_test -t null -q 2 -r 1 -u -i 1 &
ublk_run_recover_test -t loop -q 2 -r 1 -u -i 1 "${UBLK_BACKFILES[0]}" &
ublk_run_recover_test -t stripe -q 2 -r 1 -u -i 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,72 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Test: shmem_zc with hugetlbfs buffer on null target
#
# kublk and fio both mmap the same hugetlbfs file (MAP_SHARED),
# so they share physical pages. The kernel PFN match enables
# zero-copy I/O without socket-based fd passing.
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "shmem_zc" "null target hugetlbfs shmem zero-copy test"
if ! _have_program fio; then
echo "SKIP: fio not available"
exit "$UBLK_SKIP_CODE"
fi
if ! grep -q hugetlbfs /proc/filesystems; then
echo "SKIP: hugetlbfs not supported"
exit "$UBLK_SKIP_CODE"
fi
# Allocate hugepages
OLD_NR_HP=$(cat /proc/sys/vm/nr_hugepages)
echo 10 > /proc/sys/vm/nr_hugepages
NR_HP=$(cat /proc/sys/vm/nr_hugepages)
if [ "$NR_HP" -lt 2 ]; then
echo "SKIP: cannot allocate hugepages"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
# Mount hugetlbfs
HTLB_MNT=$(mktemp -d "${UBLK_TEST_DIR}/htlb_mnt_XXXXXX")
if ! mount -t hugetlbfs none "$HTLB_MNT"; then
echo "SKIP: cannot mount hugetlbfs"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
HTLB_FILE="$HTLB_MNT/ublk_buf"
fallocate -l 4M "$HTLB_FILE"
dev_id=$(_add_ublk_dev -t null --shmem_zc --htlb "$HTLB_FILE")
_check_add_dev $TID $?
fio --name=htlb_zc \
--filename=/dev/ublkb"${dev_id}" \
--ioengine=io_uring \
--rw=randwrite \
--direct=1 \
--bs=4k \
--size=4M \
--iodepth=32 \
--mem=mmaphuge:"$HTLB_FILE" \
> /dev/null 2>&1
ERR_CODE=$?
# Delete device first so daemon releases the htlb mmap
_ublk_del_dev "${dev_id}"
rm -f "$HTLB_FILE"
umount "$HTLB_MNT"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,68 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Test: shmem_zc with hugetlbfs buffer on loop target
#
# kublk and fio both mmap the same hugetlbfs file (MAP_SHARED),
# so they share physical pages. The kernel PFN match enables
# zero-copy I/O without socket-based fd passing.
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "shmem_zc" "loop target hugetlbfs shmem zero-copy test"
if ! _have_program fio; then
echo "SKIP: fio not available"
exit "$UBLK_SKIP_CODE"
fi
if ! grep -q hugetlbfs /proc/filesystems; then
echo "SKIP: hugetlbfs not supported"
exit "$UBLK_SKIP_CODE"
fi
# Allocate hugepages
OLD_NR_HP=$(cat /proc/sys/vm/nr_hugepages)
echo 10 > /proc/sys/vm/nr_hugepages
NR_HP=$(cat /proc/sys/vm/nr_hugepages)
if [ "$NR_HP" -lt 2 ]; then
echo "SKIP: cannot allocate hugepages"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
# Mount hugetlbfs
HTLB_MNT=$(mktemp -d "${UBLK_TEST_DIR}/htlb_mnt_XXXXXX")
if ! mount -t hugetlbfs none "$HTLB_MNT"; then
echo "SKIP: cannot mount hugetlbfs"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
HTLB_FILE="$HTLB_MNT/ublk_buf"
fallocate -l 4M "$HTLB_FILE"
_create_backfile 0 128M
BACKFILE="${UBLK_BACKFILES[0]}"
dev_id=$(_add_ublk_dev -t loop --shmem_zc --htlb "$HTLB_FILE" "$BACKFILE")
_check_add_dev $TID $?
_run_fio_verify_io --filename=/dev/ublkb"${dev_id}" \
--size=128M \
--mem=mmaphuge:"$HTLB_FILE"
ERR_CODE=$?
# Delete device first so daemon releases the htlb mmap
_ublk_del_dev "${dev_id}"
rm -f "$HTLB_FILE"
umount "$HTLB_MNT"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,69 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Test: shmem_zc with fio verify over filesystem on loop target
#
# mkfs + mount ext4 on the ublk device, then run fio verify on a
# file inside that filesystem. Exercises the full stack:
# filesystem -> block layer -> ublk shmem_zc -> loop target backing file.
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "shmem_zc" "loop target hugetlbfs shmem zero-copy fs verify test"
if ! _have_program fio; then
echo "SKIP: fio not available"
exit "$UBLK_SKIP_CODE"
fi
if ! grep -q hugetlbfs /proc/filesystems; then
echo "SKIP: hugetlbfs not supported"
exit "$UBLK_SKIP_CODE"
fi
# Allocate hugepages
OLD_NR_HP=$(cat /proc/sys/vm/nr_hugepages)
echo 10 > /proc/sys/vm/nr_hugepages
NR_HP=$(cat /proc/sys/vm/nr_hugepages)
if [ "$NR_HP" -lt 2 ]; then
echo "SKIP: cannot allocate hugepages"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
# Mount hugetlbfs
HTLB_MNT=$(mktemp -d "${UBLK_TEST_DIR}/htlb_mnt_XXXXXX")
if ! mount -t hugetlbfs none "$HTLB_MNT"; then
echo "SKIP: cannot mount hugetlbfs"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
HTLB_FILE="$HTLB_MNT/ublk_buf"
fallocate -l 4M "$HTLB_FILE"
_create_backfile 0 256M
BACKFILE="${UBLK_BACKFILES[0]}"
dev_id=$(_add_ublk_dev -t loop --shmem_zc --htlb "$HTLB_FILE" "$BACKFILE")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}" \
_run_fio_verify_io --filename=testfile \
--size=128M \
--mem=mmaphuge:"$HTLB_FILE"
ERR_CODE=$?
# Delete device first so daemon releases the htlb mmap
_ublk_del_dev "${dev_id}"
rm -f "$HTLB_FILE"
umount "$HTLB_MNT"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,72 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Test: shmem_zc with read-only buffer registration on null target
#
# Same as test_shmemzc_01 but with --rdonly_shmem_buf: pages are pinned
# without FOLL_WRITE (UBLK_BUF_F_READ). Write I/O works because
# the server only reads from the shared buffer.
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "shmem_zc" "null target hugetlbfs shmem zero-copy rdonly_buf test"
if ! _have_program fio; then
echo "SKIP: fio not available"
exit "$UBLK_SKIP_CODE"
fi
if ! grep -q hugetlbfs /proc/filesystems; then
echo "SKIP: hugetlbfs not supported"
exit "$UBLK_SKIP_CODE"
fi
# Allocate hugepages
OLD_NR_HP=$(cat /proc/sys/vm/nr_hugepages)
echo 10 > /proc/sys/vm/nr_hugepages
NR_HP=$(cat /proc/sys/vm/nr_hugepages)
if [ "$NR_HP" -lt 2 ]; then
echo "SKIP: cannot allocate hugepages"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
# Mount hugetlbfs
HTLB_MNT=$(mktemp -d "${UBLK_TEST_DIR}/htlb_mnt_XXXXXX")
if ! mount -t hugetlbfs none "$HTLB_MNT"; then
echo "SKIP: cannot mount hugetlbfs"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
exit "$UBLK_SKIP_CODE"
fi
HTLB_FILE="$HTLB_MNT/ublk_buf"
fallocate -l 4M "$HTLB_FILE"
dev_id=$(_add_ublk_dev -t null --shmem_zc --htlb "$HTLB_FILE" --rdonly_shmem_buf)
_check_add_dev $TID $?
fio --name=htlb_zc_rdonly \
--filename=/dev/ublkb"${dev_id}" \
--ioengine=io_uring \
--rw=randwrite \
--direct=1 \
--bs=4k \
--size=4M \
--iodepth=32 \
--mem=mmaphuge:"$HTLB_FILE" \
> /dev/null 2>&1
ERR_CODE=$?
# Delete device first so daemon releases the htlb mmap
_ublk_del_dev "${dev_id}"
rm -f "$HTLB_FILE"
umount "$HTLB_MNT"
rmdir "$HTLB_MNT"
echo "$OLD_NR_HP" > /proc/sys/vm/nr_hugepages
_cleanup_test
_show_result $TID $ERR_CODE
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_io_and_remove()
{
run_io_and_remove "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "stress" "run IO and remove device"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_io_and_remove 8G -t null -q 4 &
ublk_io_and_remove 256M -t loop -q 4 "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 256M -t stripe -q 4 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
_cleanup_test
_show_result $TID $ERR_CODE
+35
View File
@@ -0,0 +1,35 @@
#!/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
ublk_io_and_kill_daemon()
{
run_io_and_kill_daemon "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
_prep_test "stress" "run IO and kill ublk server"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
for nr_queue in 1 4; do
ublk_io_and_kill_daemon 8G -t null -q "$nr_queue" &
ublk_io_and_kill_daemon 256M -t loop -q "$nr_queue" "${UBLK_BACKFILES[0]}" &
ublk_io_and_kill_daemon 256M -t stripe -q "$nr_queue" "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
done
_cleanup_test
_show_result $TID $ERR_CODE
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_io_and_remove()
{
run_io_and_remove "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "ZERO_COPY"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "stress" "run IO and remove device(zero copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_io_and_remove 8G -t null -q 4 -z &
ublk_io_and_remove 256M -t loop -q 4 -z "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 256M -t stripe -q 4 -z "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
if _have_feature "AUTO_BUF_REG"; then
ublk_io_and_remove 8G -t null -q 4 --auto_zc &
ublk_io_and_remove 256M -t loop -q 4 --auto_zc "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 256M -t stripe -q 4 --auto_zc "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
ublk_io_and_remove 8G -t null -q 4 -z --auto_zc --auto_zc_fallback &
wait
fi
if _have_feature "PER_IO_DAEMON"; then
ublk_io_and_remove 8G -t null -q 4 --auto_zc --nthreads 8 --per_io_tasks &
ublk_io_and_remove 256M -t loop -q 4 --auto_zc --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 256M -t stripe -q 4 --auto_zc --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
ublk_io_and_remove 8G -t null -q 4 -z --auto_zc --auto_zc_fallback --nthreads 8 --per_io_tasks &
wait
fi
_cleanup_test
_show_result $TID $ERR_CODE
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_io_and_kill_daemon()
{
run_io_and_kill_daemon "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "ZERO_COPY"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "stress" "run IO and kill ublk server(zero copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_io_and_kill_daemon 8G -t null -q 4 -z --no_ublk_fixed_fd &
ublk_io_and_kill_daemon 256M -t loop -q 4 -z --no_ublk_fixed_fd "${UBLK_BACKFILES[0]}" &
ublk_io_and_kill_daemon 256M -t stripe -q 4 -z "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
if _have_feature "AUTO_BUF_REG"; then
ublk_io_and_kill_daemon 8G -t null -q 4 --auto_zc &
ublk_io_and_kill_daemon 256M -t loop -q 4 --auto_zc "${UBLK_BACKFILES[0]}" &
ublk_io_and_kill_daemon 256M -t stripe -q 4 --auto_zc --no_ublk_fixed_fd "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
ublk_io_and_kill_daemon 8G -t null -q 4 -z --auto_zc --auto_zc_fallback &
wait
fi
if _have_feature "PER_IO_DAEMON"; then
ublk_io_and_kill_daemon 8G -t null -q 4 --auto_zc --nthreads 8 --per_io_tasks &
ublk_io_and_kill_daemon 256M -t loop -q 4 --auto_zc --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[0]}" &
ublk_io_and_kill_daemon 256M -t stripe -q 4 --auto_zc --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
ublk_io_and_kill_daemon 8G -t null -q 4 -z --auto_zc --auto_zc_fallback --nthreads 8 --per_io_tasks &
wait
fi
_cleanup_test
_show_result $TID $ERR_CODE
+83
View File
@@ -0,0 +1,83 @@
#!/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
run_io_and_remove()
{
local size=$1
local dev_id
local dev_pid
shift 1
dev_id=$(_add_ublk_dev "$@")
_check_add_dev $TID $?
[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs. remove device(ublk add $*)"
fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
--rw=readwrite --iodepth=128 --size="${size}" --numjobs=4 \
--runtime=40 --time_based > /dev/null 2>&1 &
sleep 4
dev_pid=$(_get_ublk_daemon_pid "$dev_id")
kill -9 "$dev_pid"
if ! __remove_ublk_dev_return "${dev_id}"; then
echo "delete dev ${dev_id} failed"
return 255
fi
}
ublk_io_and_remove()
{
run_io_and_remove "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
_prep_test "stress" "run IO and remove device with recovery enabled"
_create_backfile 0 256M
_create_backfile 1 256M
for reissue in $(seq 0 1); do
ublk_io_and_remove 8G -t null -q 4 -g -r 1 -i "$reissue" &
ublk_io_and_remove 256M -t loop -q 4 -g -r 1 -i "$reissue" "${UBLK_BACKFILES[0]}" &
wait
done
if _have_feature "ZERO_COPY"; then
for reissue in $(seq 0 1); do
ublk_io_and_remove 8G -t null -q 4 -z -r 1 -i "$reissue" &
ublk_io_and_remove 256M -t loop -q 4 -z -r 1 -i "$reissue" "${UBLK_BACKFILES[1]}" &
wait
done
fi
if _have_feature "AUTO_BUF_REG"; then
for reissue in $(seq 0 1); do
ublk_io_and_remove 8G -t null -q 4 --auto_zc -r 1 -i "$reissue" &
ublk_io_and_remove 256M -t loop -q 4 --auto_zc -r 1 -i "$reissue" "${UBLK_BACKFILES[1]}" &
ublk_io_and_remove 8G -t null -q 4 -z --auto_zc --auto_zc_fallback -r 1 -i "$reissue" &
wait
done
fi
if _have_feature "PER_IO_DAEMON"; then
ublk_io_and_remove 8G -t null -q 4 --nthreads 8 --per_io_tasks -r 1 -i "$reissue" &
ublk_io_and_remove 256M -t loop -q 4 --nthreads 8 --per_io_tasks -r 1 -i "$reissue" "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 8G -t null -q 4 --nthreads 8 --per_io_tasks -r 1 -i "$reissue" &
fi
wait
_cleanup_test
_show_result $TID $ERR_CODE
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_io_and_remove()
{
run_io_and_remove "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "stress" "run IO and remove device (user copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_io_and_remove 8G -t null -q 4 -u &
ublk_io_and_remove 256M -t loop -q 4 -u "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 256M -t stripe -q 4 -u "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_io_and_remove 8G -t null -q 4 -u --nthreads 8 --per_io_tasks &
ublk_io_and_remove 256M -t loop -q 4 -u --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 256M -t stripe -q 4 -u --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
_cleanup_test
_show_result $TID $ERR_CODE
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_io_and_kill_daemon()
{
run_io_and_kill_daemon "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "stress" "run IO and kill ublk server (user copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_io_and_kill_daemon 8G -t null -q 4 -u --no_ublk_fixed_fd &
ublk_io_and_kill_daemon 256M -t loop -q 4 -u --no_ublk_fixed_fd "${UBLK_BACKFILES[0]}" &
ublk_io_and_kill_daemon 256M -t stripe -q 4 -u "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
ublk_io_and_kill_daemon 8G -t null -q 4 -u --nthreads 8 --per_io_tasks &
ublk_io_and_kill_daemon 256M -t loop -q 4 -u --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[0]}" &
ublk_io_and_kill_daemon 256M -t stripe -q 4 -u --nthreads 8 --per_io_tasks "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
wait
_cleanup_test
_show_result $TID $ERR_CODE
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_io_and_remove()
{
run_io_and_remove "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "ZERO_COPY"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "AUTO_BUF_REG"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "BATCH_IO"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "stress" "run IO and remove device(zero copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_io_and_remove 8G -t null -q 4 -b &
ublk_io_and_remove 256M -t loop -q 4 --auto_zc -b "${UBLK_BACKFILES[0]}" &
ublk_io_and_remove 256M -t stripe -q 4 --auto_zc -b "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
ublk_io_and_remove 8G -t null -q 4 -z --auto_zc --auto_zc_fallback -b &
wait
_cleanup_test
_show_result $TID $ERR_CODE
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
ublk_io_and_kill_daemon()
{
run_io_and_kill_daemon "$@"
ERR_CODE=$?
if [ ${ERR_CODE} -ne 0 ]; then
echo "$TID failure: $*"
_show_result $TID $ERR_CODE
fi
}
if ! _have_program fio; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "ZERO_COPY"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "AUTO_BUF_REG"; then
exit "$UBLK_SKIP_CODE"
fi
if ! _have_feature "BATCH_IO"; then
exit "$UBLK_SKIP_CODE"
fi
_prep_test "stress" "run IO and kill ublk server(zero copy)"
_create_backfile 0 256M
_create_backfile 1 128M
_create_backfile 2 128M
ublk_io_and_kill_daemon 8G -t null -q 4 -z -b &
ublk_io_and_kill_daemon 256M -t loop -q 4 --auto_zc -b "${UBLK_BACKFILES[0]}" &
ublk_io_and_kill_daemon 256M -t stripe -q 4 -b "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" &
ublk_io_and_kill_daemon 8G -t null -q 4 -z --auto_zc --auto_zc_fallback -b &
wait
_cleanup_test
_show_result $TID $ERR_CODE
+25
View File
@@ -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 "stripe" "write and verify test"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -t stripe "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
# run fio over the ublk disk
_run_fio_verify_io --filename=/dev/ublkb"${dev_id}" --size=512M
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "stripe" "mkfs & mount & umount"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -t stripe "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+25
View File
@@ -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 "stripe" "write and verify test"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -q 2 -t stripe "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
# run fio over the ublk disk
_run_fio_verify_io --filename=/dev/ublkb"${dev_id}" --size=512M
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "stripe" "mkfs & mount & umount on zero copy"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -t stripe -z -q 2 "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+25
View File
@@ -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 "stripe" "write and verify test on user copy"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -t stripe -q 2 -u "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
# run fio over the ublk disk
_run_fio_verify_io --filename=/dev/ublkb"${dev_id}" --size=512M
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
_prep_test "stripe" "mkfs & mount & umount on user copy"
_create_backfile 0 256M
_create_backfile 1 256M
dev_id=$(_add_ublk_dev -t stripe -u -q 2 "${UBLK_BACKFILES[0]}" "${UBLK_BACKFILES[1]}")
_check_add_dev $TID $?
_mkfs_mount_test /dev/ublkb"${dev_id}"
ERR_CODE=$?
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,11 @@
/*
* Tabulates and prints I/O completions per thread for the given device
*
* $1: dev_t
*/
tracepoint:block:block_rq_complete
{
if (args.dev == $1) {
@[tid] = count();
}
}
@@ -0,0 +1,53 @@
/*
$1: dev_t
$2: RWBS
$3: strlen($2)
Track request order between block_io_start and block_rq_complete.
Sequence starts at 1 so 0 means "never seen". On first valid
completion, sync complete_seq to handle probe attachment races.
block_rq_complete listed first to reduce missed completion window.
*/
BEGIN {
@start_seq = (uint64)1;
@complete_seq = (uint64)0;
@out_of_order = (uint64)0;
@start_order[0] = (uint64)0;
delete(@start_order[0]);
printf("BPFTRACE_READY\n");
}
tracepoint:block:block_rq_complete
/(int64)args.dev == $1 && !strncmp(args.rwbs, str($2), $3)/
{
$expected = @start_order[args.sector];
if ($expected > 0) {
if (@complete_seq == 0) {
@complete_seq = $expected;
}
if ($expected != @complete_seq) {
printf("out_of_order: sector %llu started at seq %llu but completed at seq %llu\n",
args.sector, $expected, @complete_seq);
@out_of_order = @out_of_order + 1;
}
delete(@start_order[args.sector]);
@complete_seq = @complete_seq + 1;
}
}
tracepoint:block:block_io_start
/(int64)args.dev == $1 && !strncmp(args.rwbs, str($2), $3)/
{
@start_order[args.sector] = @start_seq;
@start_seq = @start_seq + 1;
}
END {
printf("total_start: %llu total_complete: %llu out_of_order: %llu\n",
@start_seq - 1, @complete_seq, @out_of_order);
clear(@start_order);
clear(@start_seq);
clear(@complete_seq);
clear(@out_of_order);
}
@@ -0,0 +1,18 @@
#ifndef UBLK_DEP_H
#define UBLK_DEP_H
#ifndef UBLK_U_IO_REGISTER_IO_BUF
#define UBLK_U_IO_REGISTER_IO_BUF \
_IOWR('u', 0x23, struct ublksrv_io_cmd)
#define UBLK_U_IO_UNREGISTER_IO_BUF \
_IOWR('u', 0x24, struct ublksrv_io_cmd)
#endif
#ifndef UBLK_F_USER_RECOVERY_FAIL_IO
#define UBLK_F_USER_RECOVERY_FAIL_IO (1ULL << 9)
#endif
#ifndef UBLK_F_ZONED
#define UBLK_F_ZONED (1ULL << 8)
#endif
#endif
@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef KUBLK_UTILS_H
#define KUBLK_UTILS_H
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) ({ \
unsigned long __mptr = (unsigned long)(ptr); \
((type *)(__mptr - offsetof(type, member))); })
#endif
#define round_up(val, rnd) \
(((val) + ((rnd) - 1)) & ~((rnd) - 1))
/* small sized & per-thread allocator */
struct allocator {
unsigned int size;
cpu_set_t *set;
};
static inline int allocator_init(struct allocator *a, unsigned size)
{
a->set = CPU_ALLOC(size);
a->size = size;
if (a->set)
return 0;
return -ENOMEM;
}
static inline void allocator_deinit(struct allocator *a)
{
CPU_FREE(a->set);
a->set = NULL;
a->size = 0;
}
static inline int allocator_get(struct allocator *a)
{
int i;
for (i = 0; i < a->size; i += 1) {
size_t set_size = CPU_ALLOC_SIZE(a->size);
if (!CPU_ISSET_S(i, set_size, a->set)) {
CPU_SET_S(i, set_size, a->set);
return i;
}
}
return -1;
}
static inline void allocator_put(struct allocator *a, int i)
{
size_t set_size = CPU_ALLOC_SIZE(a->size);
if (i >= 0 && i < a->size)
CPU_CLR_S(i, set_size, a->set);
}
static inline int allocator_get_val(struct allocator *a, int i)
{
size_t set_size = CPU_ALLOC_SIZE(a->size);
return CPU_ISSET_S(i, set_size, a->set);
}
static inline unsigned int ilog2(unsigned int x)
{
if (x == 0)
return 0;
return (sizeof(x) * 8 - 1) - __builtin_clz(x);
}
#define UBLK_DBG_DEV (1U << 0)
#define UBLK_DBG_THREAD (1U << 1)
#define UBLK_DBG_IO_CMD (1U << 2)
#define UBLK_DBG_IO (1U << 3)
#define UBLK_DBG_CTRL_CMD (1U << 4)
#define UBLK_LOG (1U << 5)
extern unsigned int ublk_dbg_mask;
static inline void ublk_err(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
static inline void ublk_log(const char *fmt, ...)
{
if (ublk_dbg_mask & UBLK_LOG) {
va_list ap;
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
}
}
static inline void ublk_dbg(int level, const char *fmt, ...)
{
if (level & ublk_dbg_mask) {
va_list ap;
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
}
}
#define ublk_assert(x) do { \
if (!(x)) { \
ublk_err("%s %d: assert!\n", __func__, __LINE__); \
assert(x); \
} \
} while (0)
#endif