From bd6671cdda114a370208f8e7f7c05cd3ca50cd00 Mon Sep 17 00:00:00 2001 From: Gleb Chesnokov Date: Tue, 13 Oct 2026 11:55:58 +0300 Subject: [PATCH] kunit: Test command ordering with bounded worker teardown Exercise queue policy, barriers, sequence wrap, slot ownership and concurrent producers through a private declaration of the existing ordering function. Keep worker memory outside KUnit-managed resources and pin the module until completion. Bound teardown waits and report retained state when a worker cannot finish. --- doc/KUNIT.md | 2 +- scst/src/kunit/objects.mk | 1 + .../src/kunit/order/suites/scst_kunit_order.c | 415 ++++++++++++++++++ scst/src/scst_targ.c | 9 +- scst/src/scst_targ_test.h | 1 + 5 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 scst/src/kunit/order/suites/scst_kunit_order.c diff --git a/doc/KUNIT.md b/doc/KUNIT.md index 2abb2d6a3..d911f3899 100644 --- a/doc/KUNIT.md +++ b/doc/KUNIT.md @@ -60,7 +60,7 @@ validates the selection against the supplied `.ko` files. | Module | Labels | | --- | --- | -| `scst.ko` | `ALUA CDB LIB UNMAP` | +| `scst.ko` | `ALUA CDB LIB ORDER UNMAP` | The runner reads suite metadata from the modules and loads selected owners with their dependencies. Unselected suites are not counted as skipped. diff --git a/scst/src/kunit/objects.mk b/scst/src/kunit/objects.mk index c9fd47441..eb9e7cceb 100644 --- a/scst/src/kunit/objects.mk +++ b/scst/src/kunit/objects.mk @@ -5,4 +5,5 @@ scst-kunit-scst := \ kunit/alua/suites/scst_kunit_alua.o \ kunit/cdb/suites/scst_kunit_cdb.o \ kunit/lib/suites/scst_kunit_lib.o \ + kunit/order/suites/scst_kunit_order.o \ kunit/unmap/suites/scst_kunit_unmap.o diff --git a/scst/src/kunit/order/suites/scst_kunit_order.c b/scst/src/kunit/order/suites/scst_kunit_order.c new file mode 100644 index 000000000..e4403fcea --- /dev/null +++ b/scst/src/kunit/order/suites/scst_kunit_order.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../../scst_kunit.h" + +#ifdef INSIDE_KERNEL_TREE +#include +#else +#include "scst.h" +#endif +#include "../../../scst_priv.h" +#include "../../../scst_targ_test.h" +#include +#include +#include + +#define ORDER_TEST_COMMANDS 128 + +struct order_test_worker { + bool started; + bool finished; + struct completion done; + struct order_test_fixture *fixture; + unsigned int first; +}; + +struct order_test_fixture { + struct scst_device dev; + struct scst_order_data order; + struct scst_cmd commands[ORDER_TEST_COMMANDS]; + struct completion start; + atomic_t stop; + struct order_test_worker workers[2]; +}; + +static int order_test_init(struct kunit *test) +{ + struct order_test_fixture *f; + unsigned int i; + + test->priv = NULL; + /* Workers may outlive a failed test; KUnit must not reclaim their state. */ + f = kzalloc_obj(*f, GFP_KERNEL); + if (!f) + return -ENOMEM; + if (!try_module_get(THIS_MODULE)) { + kfree(f); + return -ENODEV; + } + test->priv = f; + atomic_set(&f->stop, 0); + spin_lock_init(&f->order.sn_lock); + spin_lock_init(&f->order.init_done_lock); + INIT_LIST_HEAD(&f->order.skipped_sn_list); + INIT_LIST_HEAD(&f->order.deferred_cmd_list); + f->order.curr_sn = 100; + f->order.expected_sn = 100; + f->order.cur_sn_slot = f->order.sn_slots; + f->dev.queue_alg = SCST_QUEUE_ALG_1_UNRESTRICTED_REORDER; + f->dev.virt_name = "kunit-order"; + init_completion(&f->start); + for (i = 0; i < ARRAY_SIZE(f->commands); i++) { + f->commands[i].dev = &f->dev; + f->commands[i].cur_order_data = &f->order; + f->commands[i].queue_type = SCST_CMD_QUEUE_ORDERED; + f->commands[i].op_name = "kunit"; + } + for (i = 0; i < ARRAY_SIZE(f->workers); i++) { + init_completion(&f->workers[i].done); + f->workers[i].fixture = f; + f->workers[i].first = i * ORDER_TEST_COMMANDS / 2; + } + return 0; +} + +static void order_test_exit(struct kunit *test) +{ + struct order_test_fixture *f = test->priv; + unsigned int i; + + test->priv = NULL; + if (!f) + return; + atomic_set(&f->stop, 1); + complete_all(&f->start); + for (i = 0; i < ARRAY_SIZE(f->workers); i++) { + struct order_test_worker *w = &f->workers[i]; + + if (!w->started || w->finished) + continue; + if (!wait_for_completion_timeout(&w->done, 5 * HZ)) { + /* Preserve both the data and module text until the VM is restarted. */ + scst_kunit_retain(test, "ORDER worker did not finish"); + return; + } + } + kfree(f); + module_put(THIS_MODULE); +} + +static bool order_test_normal_policy(struct kunit *test) +{ + if (IS_ENABLED(CONFIG_SCST_DEBUG_SN)) { + kunit_skip(test, "SCST_DEBUG_SN deliberately randomizes queue types"); + return false; + } + return true; +} + +struct order_sequence_case { + const char *name; + const char *types; + int sn[4]; /* Offsets from 100; -1 means no sequence number. */ +}; + +static const struct order_sequence_case order_sequence_cases[] = { + { "simple", "S", { 0 } }, + { "simple_batch", "SSSS", { 0, 0, 0, 0 } }, + { "untagged", "U", { 0 } }, + { "untagged_and_simple", "US", { 0, 0 } }, + { "ordered_idle", "O", { 1 } }, + { "ordered_batch", "OOOO", { 1, 2, 3, 4 } }, + { "simple_then_ordered", "SO", { 0, 1 } }, + { "ordered_then_simple", "OS", { 1, 2 } }, + { "simple_ordered_simple", "SOS", { 0, 1, 2 } }, + { "two_ordered_barriers", "SOOS", { 0, 1, 2, 3 } }, + { "head_of_queue", "H", { -1 } }, + { "head_does_not_split_simple", "SHS", { 0, -1, 0 } }, + { "head_between_ordered", "OHO", { 1, -1, 2 } }, + { "aca", "A", { -1 } }, + { "aca_does_not_split_simple", "SAS", { 0, -1, 0 } }, +}; + +static void order_sequence_desc(const struct order_sequence_case *p, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name); +} + +KUNIT_ARRAY_PARAM(order_sequence, order_sequence_cases, order_sequence_desc); + +static void order_sequence_test(struct kunit *test) +{ + const struct order_sequence_case *p = test->param_value; + struct order_test_fixture *f = test->priv; + unsigned int i, ordered = 0, heads = 0; + + if (!order_test_normal_policy(test)) + return; + for (i = 0; p->types[i]; i++) { + struct scst_cmd *cmd = &f->commands[i]; + int expected = p->sn[i]; + + switch (p->types[i]) { + case 'S': + cmd->queue_type = SCST_CMD_QUEUE_SIMPLE; + break; + case 'U': + cmd->queue_type = SCST_CMD_QUEUE_UNTAGGED; + break; + case 'O': + cmd->queue_type = SCST_CMD_QUEUE_ORDERED; + break; + case 'H': + cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE; + break; + case 'A': + cmd->queue_type = SCST_CMD_QUEUE_ACA; + break; + } + if (IS_ENABLED(CONFIG_SCST_STRICT_SERIALIZING) && p->types[i] != 'H') + expected = ++ordered; + scst_cmd_set_sn(cmd); + KUNIT_EXPECT_EQ(test, !!cmd->sn_set, expected >= 0); + if (expected >= 0) + KUNIT_EXPECT_EQ(test, cmd->sn, 100U + expected); + if (p->types[i] == 'H') + heads++; + KUNIT_EXPECT_EQ(test, !!cmd->hq_cmd_inced, p->types[i] == 'H'); + KUNIT_EXPECT_EQ(test, f->order.hq_cmd_count, (int)heads); + } + KUNIT_EXPECT_TRUE(test, list_empty(&f->order.deferred_cmd_list)); + KUNIT_EXPECT_TRUE(test, list_empty(&f->order.skipped_sn_list)); +} + +struct order_policy_case { + const char *name; + int queue; + bool restricted; + bool implicit; + int expected; +}; + +static const struct order_policy_case order_policy_cases[] = { + { "restricted_simple", SCST_CMD_QUEUE_SIMPLE, true, false, SCST_CMD_QUEUE_ORDERED }, + { "restricted_untagged", SCST_CMD_QUEUE_UNTAGGED, true, false, SCST_CMD_QUEUE_ORDERED }, + { "restricted_head", SCST_CMD_QUEUE_HEAD_OF_QUEUE, true, false, + SCST_CMD_QUEUE_HEAD_OF_QUEUE }, + { "implicit_head", SCST_CMD_QUEUE_SIMPLE, false, true, SCST_CMD_QUEUE_HEAD_OF_QUEUE }, + { "implicit_head_beats_restricted", SCST_CMD_QUEUE_SIMPLE, true, true, + SCST_CMD_QUEUE_HEAD_OF_QUEUE }, + { "implicit_flag_does_not_override_ordered", SCST_CMD_QUEUE_ORDERED, false, true, + SCST_CMD_QUEUE_ORDERED }, +}; + +static void order_policy_desc(const struct order_policy_case *p, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name); +} + +KUNIT_ARRAY_PARAM(order_policy, order_policy_cases, order_policy_desc); + +static void order_policy_test(struct kunit *test) +{ + const struct order_policy_case *p = test->param_value; + struct order_test_fixture *f = test->priv; + struct scst_cmd *cmd = f->commands; + + if (!order_test_normal_policy(test)) + return; + cmd->queue_type = p->queue; + cmd->op_flags = p->implicit ? SCST_IMPLICIT_HQ : 0; + if (p->restricted) + f->dev.queue_alg = SCST_QUEUE_ALG_0_RESTRICTED_REORDER; + scst_cmd_set_sn(cmd); + KUNIT_EXPECT_EQ(test, cmd->queue_type, p->expected); + KUNIT_EXPECT_EQ(test, !!cmd->sn_set, p->expected == SCST_CMD_QUEUE_ORDERED); + KUNIT_EXPECT_EQ(test, f->order.hq_cmd_count, + p->expected == SCST_CMD_QUEUE_HEAD_OF_QUEUE ? 1 : 0); +} + +static void order_completion_test(struct kunit *test) +{ + struct order_test_fixture *f = test->priv; + unsigned int i; + + if (!order_test_normal_policy(test)) + return; + if (IS_ENABLED(CONFIG_SCST_STRICT_SERIALIZING)) { + kunit_skip(test, "SIMPLE batching is disabled by strict serialization"); + return; + } + f->commands[0].queue_type = SCST_CMD_QUEUE_SIMPLE; + f->commands[1].queue_type = SCST_CMD_QUEUE_SIMPLE; + for (i = 0; i < 3; i++) + scst_cmd_set_sn(&f->commands[i]); + KUNIT_EXPECT_EQ(test, f->order.pending_simple_inc_expected_sn, 1); + KUNIT_EXPECT_EQ(test, f->order.expected_sn, 100U); + KUNIT_EXPECT_FALSE(test, scst_inc_expected_sn(&f->commands[1])); + KUNIT_EXPECT_EQ(test, f->order.expected_sn, 100U); + KUNIT_EXPECT_EQ(test, atomic_read(&f->order.sn_slots[0]), 1); + KUNIT_EXPECT_TRUE(test, scst_inc_expected_sn(&f->commands[0])); + KUNIT_EXPECT_EQ(test, f->order.expected_sn, 101U); + KUNIT_EXPECT_EQ(test, f->order.pending_simple_inc_expected_sn, 0); + KUNIT_EXPECT_TRUE(test, scst_inc_expected_sn(&f->commands[2])); + KUNIT_EXPECT_EQ(test, f->order.expected_sn, 102U); + KUNIT_EXPECT_EQ(test, atomic_read(&f->order.sn_slots[0]), 0); +} + +static const bool order_slot_busy[] = { false, true }; + +static void order_slot_desc(const bool *busy, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", *busy ? "exhausted_ring" : "wrap_to_free_slot"); +} + +KUNIT_ARRAY_PARAM(order_slot, order_slot_busy, order_slot_desc); + +static void order_slot_test(struct kunit *test) +{ + struct order_test_fixture *f = test->priv; + bool busy = *(const bool *)test->param_value; + struct scst_cmd *cmd = f->commands; + unsigned int i; + + if (!order_test_normal_policy(test)) + return; + if (IS_ENABLED(CONFIG_SCST_STRICT_SERIALIZING)) { + kunit_skip(test, "SIMPLE slots are disabled by strict serialization"); + return; + } + f->order.cur_sn_slot = &f->order.sn_slots[ARRAY_SIZE(f->order.sn_slots) - 1]; + atomic_set(f->order.cur_sn_slot, 1); + if (busy) { + for (i = 0; i < ARRAY_SIZE(f->order.sn_slots); i++) + atomic_set(&f->order.sn_slots[i], 1); + } + f->order.prev_cmd_ordered = true; + cmd->queue_type = SCST_CMD_QUEUE_SIMPLE; + scst_cmd_set_sn(cmd); + KUNIT_EXPECT_EQ(test, cmd->sn, 101U); + KUNIT_EXPECT_EQ(test, f->order.expected_sn, 100U); + KUNIT_EXPECT_PTR_EQ(test, cmd->sn_slot, busy ? NULL : &f->order.sn_slots[0]); + KUNIT_EXPECT_TRUE(test, !!cmd->sn_set); + KUNIT_EXPECT_EQ(test, f->order.prev_cmd_ordered, busy); + KUNIT_EXPECT_EQ(test, atomic_read(&f->order.sn_slots[0]), 1); +} + +static int order_test_thread(void *data) +{ + struct order_test_worker *w = data; + unsigned int i; + + wait_for_completion(&w->fixture->start); + for (i = w->first; i < w->first + ORDER_TEST_COMMANDS / 2; i++) { + if (atomic_read(&w->fixture->stop)) + break; + scst_cmd_set_sn(&w->fixture->commands[i]); + cond_resched(); + } + /* Completion must not precede a return through unloadable module text. */ + kthread_complete_and_exit(&w->done, 0); +} + +struct order_parallel_case { + const char *name; + unsigned int base; + bool mixed; +}; + +static const struct order_parallel_case order_parallel_cases[] = { + { "ordered", 100 }, + { "ordered-wrap", UINT_MAX - 63 }, + { "simple-versus-ordered", 100, true }, + { "simple-versus-ordered-wrap", UINT_MAX - 63, true }, +}; + +static void order_parallel_desc(const struct order_parallel_case *p, char *desc) +{ + strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(order_parallel, order_parallel_cases, order_parallel_desc); + +static void order_parallel_test(struct kunit *test) +{ + const struct order_parallel_case *p = test->param_value; + struct order_test_fixture *f = test->priv; + unsigned int counts[ORDER_TEST_COMMANDS + 1] = {}; + atomic_t *slots[ORDER_TEST_COMMANDS + 1] = {}; + unsigned int i, first, last, simple_groups = 0; + + if (!order_test_normal_policy(test)) + return; + f->order.curr_sn = p->base; + f->order.expected_sn = p->base; + if (p->mixed) + for (i = 0; i < ORDER_TEST_COMMANDS / 2; i++) + f->commands[i].queue_type = SCST_CMD_QUEUE_SIMPLE; + for (i = 0; i < ARRAY_SIZE(f->workers); i++) { + struct task_struct *task = kthread_create(order_test_thread, &f->workers[i], + "scst-kunit-order"); + + KUNIT_ASSERT_FALSE(test, IS_ERR(task)); + f->workers[i].started = true; + wake_up_process(task); + } + complete_all(&f->start); + for (i = 0; i < ARRAY_SIZE(f->workers); i++) { + unsigned long done = wait_for_completion_timeout(&f->workers[i].done, 5 * HZ); + + KUNIT_ASSERT_NE(test, done, 0UL); + f->workers[i].finished = true; + } + for (i = 0; i < ARRAY_SIZE(f->commands); i++) { + struct scst_cmd *cmd = &f->commands[i]; + unsigned int index = cmd->sn - p->base; + + KUNIT_ASSERT_TRUE(test, !!cmd->sn_set); + KUNIT_ASSERT_LE(test, index, (unsigned int)ORDER_TEST_COMMANDS); + if (counts[index]) { + /* A barrier may never share a sequence number with another command. */ + KUNIT_EXPECT_NOT_NULL(test, cmd->sn_slot); + KUNIT_EXPECT_PTR_EQ(test, slots[index], cmd->sn_slot); + } else { + slots[index] = cmd->sn_slot; + } + counts[index]++; + if (i % (ORDER_TEST_COMMANDS / 2)) + KUNIT_EXPECT_GE(test, index, f->commands[i - 1].sn - p->base); + } + first = counts[0] ? 0 : 1; + last = f->order.curr_sn - p->base; + KUNIT_ASSERT_LE(test, last, (unsigned int)ORDER_TEST_COMMANDS); + KUNIT_EXPECT_EQ(test, f->order.expected_sn, p->base + first); + for (i = first; i <= last; i++) { + KUNIT_EXPECT_GT(test, counts[i], 0U); + if (slots[i]) { + KUNIT_EXPECT_EQ(test, atomic_read(slots[i]), (int)counts[i]); + simple_groups++; + } else { + KUNIT_EXPECT_EQ(test, counts[i], 1U); + } + } + KUNIT_EXPECT_EQ(test, f->order.pending_simple_inc_expected_sn, + (int)(simple_groups - !!slots[last])); + for (i = 0; i < ARRAY_SIZE(f->commands); i++) + scst_inc_expected_sn(&f->commands[i]); + KUNIT_EXPECT_EQ(test, f->order.expected_sn, p->base + last + !slots[last]); + KUNIT_EXPECT_EQ(test, f->order.pending_simple_inc_expected_sn, 0); + for (i = 0; i < ARRAY_SIZE(f->order.sn_slots); i++) + KUNIT_EXPECT_EQ(test, atomic_read(&f->order.sn_slots[i]), 0); +} + +static struct kunit_case order_cases[] = { + KUNIT_CASE_PARAM(order_sequence_test, order_sequence_gen_params), + KUNIT_CASE_PARAM(order_policy_test, order_policy_gen_params), + KUNIT_CASE(order_completion_test), + KUNIT_CASE_PARAM(order_slot_test, order_slot_gen_params), + KUNIT_CASE_PARAM(order_parallel_test, order_parallel_gen_params), + {} +}; + +scst_kunit_suite(order_suite, "ORDER", "", + .init = order_test_init, + .exit = order_test_exit, + .test_cases = order_cases, +); diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 3d64af0da..5e05eaed9 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -44,6 +44,10 @@ #include "scst_pres.h" #include "scst_targ_test.h" +#ifndef SCST_KUNIT_ENABLED +static void scst_cmd_set_sn(struct scst_cmd *cmd); +#endif + #undef DEFAULT_SYMBOL_NAMESPACE #define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE @@ -3816,7 +3820,8 @@ static inline void scst_inc_expected_sn_idle(struct scst_order_data *order_data) * command identifier in SAM-5 or comments about task tags and command * reordering in previous SAM revisions. */ -static void scst_cmd_set_sn(struct scst_cmd *cmd) +SCST_KUNIT_VISIBLE +void scst_cmd_set_sn(struct scst_cmd *cmd) { struct scst_order_data *order_data = cmd->cur_order_data; unsigned long flags; @@ -4582,8 +4587,6 @@ void scst_process_active_cmd(struct scst_cmd *cmd, bool atomic) EXPORT_SYMBOL_GPL(scst_process_active_cmd); /* Called under cmd_list_lock and IRQs disabled */ -static void scst_cmd_set_sn(struct scst_cmd *cmd); - static void scst_do_job_active(struct list_head *cmd_list, spinlock_t *cmd_list_lock, bool atomic) __releases(cmd_list_lock) __acquires(cmd_list_lock) diff --git a/scst/src/scst_targ_test.h b/scst/src/scst_targ_test.h index fcd95d350..de0caa01c 100644 --- a/scst/src/scst_targ_test.h +++ b/scst/src/scst_targ_test.h @@ -10,6 +10,7 @@ struct scst_cmd; bool scst_cmd_overlap(struct scst_cmd *chk_cmd, struct scst_cmd *cmd); int scst_parse_cmd(struct scst_cmd *cmd); +void scst_cmd_set_sn(struct scst_cmd *cmd); #endif /* SCST_KUNIT_ENABLED */ #endif /* SCST_TARG_TEST_H */