diff --git a/doc/KUNIT.md b/doc/KUNIT.md index 11427d7da..b3cbd4ad3 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 CM EVENT LIB ORDER UNMAP` | +| `scst.ko` | `ALUA CDB CM EVENT LIB LIFECYCLE 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/scripts/generate-kernel-patch b/scripts/generate-kernel-patch index bdcd26544..b7651a0c1 100755 --- a/scripts/generate-kernel-patch +++ b/scripts/generate-kernel-patch @@ -316,7 +316,8 @@ scst/src/scst_copy_mgr_test.h" scst_05_targ="scst/src/scst_targ.c scst/src/scst_local_cmd.c \ scst/src/scst_local_cmd.h \ scst/src/scst_targ_test.h" -scst_06_lib="scst/src/scst_lib.c" +scst_06_lib="scst/src/scst_lib.c \ +scst/src/scst_lib_test.h" scst_07_pres="scst/src/scst_pres.h scst/src/scst_pres.c" scst_08_sysfs="scst/src/scst_sysfs.c" scst_09_debug="scst/include/scst_debug.h scst/src/scst_debug.c" diff --git a/scst/src/kunit/lifecycle/suites/scst_kunit_blocking.c b/scst/src/kunit/lifecycle/suites/scst_kunit_blocking.c new file mode 100644 index 000000000..814f577e8 --- /dev/null +++ b/scst/src/kunit/lifecycle/suites/scst_kunit_blocking.c @@ -0,0 +1,192 @@ +// 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_lib_test.h" + +struct blocking_fixture { + struct scst_device dev; + wait_queue_entry_t waiter; + wait_queue_head_t *queue; + unsigned int waits; + unsigned int wakes; + unsigned int callbacks; + int wait_result; + u8 payload[4]; +}; + +static int blocking_wait(struct scst_device *dev, wait_queue_head_t *w) +{ + struct kunit *test = kunit_get_current_test(); + struct blocking_fixture *f = test->priv; + struct scst_ext_blocker *b; + + lockdep_assert_held(&dev->dev_lock); + b = list_first_entry(&dev->ext_blockers_list, typeof(*b), ext_blockers_list_entry); + KUNIT_EXPECT_PTR_EQ(test, w, (wait_queue_head_t *)b->ext_blocker_data); + KUNIT_EXPECT_EQ(test, kref_read(&b->refcount), 2U); + KUNIT_EXPECT_TRUE(test, !!dev->ext_blocking_pending); + f->waits++; + /* Return before the completion worker, as in the reported interleaving. */ + if (!f->wait_result) + dev->on_dev_cmd_count = 0; + return f->wait_result; +} + +static int blocking_wake(wait_queue_entry_t *entry, unsigned int mode, int flags, void *key) +{ + struct blocking_fixture *f = container_of(entry, typeof(*f), waiter); + + f->wakes++; + list_del_init(&entry->entry); + f->queue = NULL; + return 1; +} + +static void blocking_callback(struct scst_device *dev, u8 *data, int len) +{ + struct kunit *test = kunit_get_current_test(); + struct blocking_fixture *f = test->priv; + bool unlocked; + + KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev); + KUNIT_EXPECT_EQ(test, len, (int)sizeof(f->payload)); + KUNIT_EXPECT_PTR_NE(test, data, &f->payload[0]); + KUNIT_EXPECT_EQ(test, memcmp(data, f->payload, sizeof(f->payload)), 0); + unlocked = spin_trylock_bh(&dev->dev_lock); + if (unlocked) + spin_unlock_bh(&dev->dev_lock); + KUNIT_EXPECT_TRUE(test, unlocked); + f->callbacks++; +} + +static int blocking_init(struct kunit *test) +{ + struct blocking_fixture *f; + + test->priv = NULL; + f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL); + if (!f) + return -ENOMEM; + test->priv = f; + f->dev.virt_name = "kunit-private-blocking"; + spin_lock_init(&f->dev.dev_lock); + INIT_LIST_HEAD(&f->dev.ext_blockers_list); + INIT_LIST_HEAD(&f->dev.blocked_cmd_list); + INIT_WORK(&f->dev.ext_blockers_work, scst_ext_blocking_done_fn); + init_waitqueue_func_entry(&f->waiter, blocking_wake); + memcpy(f->payload, "test", sizeof(f->payload)); + kunit_activate_static_stub(test, scst_wait_ext_block_dev, blocking_wait); + return 0; +} + +static void blocking_exit(struct kunit *test) +{ + struct blocking_fixture *f = test->priv; + struct scst_ext_blocker *b, *tmp; + + if (!f) + return; + test->priv = NULL; + if (f->queue) + remove_wait_queue(f->queue, &f->waiter); + list_for_each_entry_safe(b, tmp, &f->dev.ext_blockers_list, ext_blockers_list_entry) { + list_del(&b->ext_blockers_list_entry); + scst_ext_blocker_put(b); + } +} + +struct blocking_case { + const char *name; + bool active; + bool interrupted; + bool serialized; + bool asynchronous; + bool stpg; +}; + +static const struct blocking_case blocking_cases_data[] = { + { "sync-idle" }, + { "sync-caller-returns-before-worker", true }, + { "sync-interrupted-before-worker", true, true }, + { "sync-reuses-serialization", false, false, true }, + { "sync-interrupted-reuses-serialization", true, true, true }, + { "async-immediate", false, false, false, true }, + { "async-deferred", true, false, false, true }, + { "async-stpg", true, false, false, true, true }, + { "async-serialized", true, false, true, true }, +}; + +static void blocking_desc(const struct blocking_case *p, char *desc) +{ + strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(blocking, blocking_cases_data, blocking_desc); + +static void blocking_test(struct kunit *test) +{ + const struct blocking_case *p = test->param_value; + struct blocking_fixture *f = test->priv; + struct scst_ext_blocker *b; + int rc; + + f->dev.on_dev_cmd_count = p->active; + f->dev.strictly_serialized_cmd_waiting = p->serialized; + f->dev.block_count = p->serialized; + f->wait_result = p->interrupted ? -ERESTARTSYS : 0; + if (p->asynchronous) + rc = scst_ext_block_dev(&f->dev, blocking_callback, f->payload, + sizeof(f->payload), p->stpg); + else + rc = scst_sync_ext_block_dev(&f->dev); + KUNIT_EXPECT_EQ(test, rc, p->interrupted ? -ERESTARTSYS : 0); + KUNIT_EXPECT_EQ(test, f->dev.block_count, 1); + KUNIT_EXPECT_EQ(test, f->dev.ext_blocks_cnt, 1); + KUNIT_EXPECT_FALSE(test, !!f->dev.strictly_serialized_cmd_waiting); + KUNIT_EXPECT_EQ(test, f->waits, (unsigned int)(p->active && !p->asynchronous)); + if (p->active) { + KUNIT_ASSERT_FALSE(test, list_empty(&f->dev.ext_blockers_list)); + b = list_first_entry(&f->dev.ext_blockers_list, typeof(*b), + ext_blockers_list_entry); + KUNIT_EXPECT_EQ(test, kref_read(&b->refcount), 1U); + if (!p->asynchronous) { + /* The caller's reference is gone; its heap wait queue must still live. */ + f->queue = (void *)b->ext_blocker_data; + add_wait_queue(f->queue, &f->waiter); + } + f->dev.on_dev_cmd_count = 0; + f->dev.ext_unblock_scheduled = 1; + scst_ext_blocking_done_fn(&f->dev.ext_blockers_work); + } + KUNIT_EXPECT_EQ(test, f->wakes, (unsigned int)(p->active && !p->asynchronous)); + KUNIT_EXPECT_EQ(test, f->callbacks, (unsigned int)p->asynchronous); + KUNIT_EXPECT_TRUE(test, list_empty(&f->dev.ext_blockers_list)); + KUNIT_EXPECT_FALSE(test, !!f->dev.ext_unblock_scheduled); + KUNIT_EXPECT_FALSE(test, !!f->dev.ext_blocking_pending); + if (p->stpg) { + scst_ext_unblock_dev(&f->dev, false); + KUNIT_EXPECT_EQ(test, f->dev.block_count, 1); + } + scst_ext_unblock_dev(&f->dev, p->stpg); + KUNIT_EXPECT_EQ(test, f->dev.block_count, 0); + KUNIT_EXPECT_EQ(test, f->dev.ext_blocks_cnt, 0); + scst_ext_unblock_dev(&f->dev, false); + KUNIT_EXPECT_EQ(test, f->dev.block_count, 0); +} + +static struct kunit_case blocking_cases[] = { + KUNIT_CASE_PARAM(blocking_test, blocking_gen_params), + {} +}; + +scst_kunit_suite(blocking_suite, "LIFECYCLE", "-blocking", + .init = blocking_init, + .exit = blocking_exit, + .test_cases = blocking_cases, +); diff --git a/scst/src/kunit/lifecycle/suites/scst_kunit_lifecycle.c b/scst/src/kunit/lifecycle/suites/scst_kunit_lifecycle.c new file mode 100644 index 000000000..0d631a953 --- /dev/null +++ b/scst/src/kunit/lifecycle/suites/scst_kunit_lifecycle.c @@ -0,0 +1,368 @@ +// 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_lib_test.h" + +#define LIFECYCLE_MAPPINGS 12 + +struct lifecycle_fixture { + struct scst_device dev; + struct scst_tgt tgt; + struct scst_tgt_template tgtt; + struct scst_acg acg; + struct scst_session sessions[3]; + struct scst_tgt_dev mappings[LIFECYCLE_MAPPINGS]; + bool published[LIFECYCLE_MAPPINGS]; + bool removed[LIFECYCLE_MAPPINGS]; + bool freed[LIFECYCLE_MAPPINGS]; + unsigned int retired[LIFECYCLE_MAPPINGS]; + int alloc_results[3]; + unsigned int alloc_calls; + unsigned int deletes; + unsigned int frees; + unsigned int grace_periods; + unsigned int cm_adds; + unsigned int cm_deletes; + unsigned int sysfs_creates; + unsigned int acg_frees; + int cm_result; + int sysfs_result; + bool batch; +}; + +static void lifecycle_publish(struct lifecycle_fixture *f, unsigned int index, + struct scst_session *sess, u64 lun) +{ + struct scst_tgt_dev *td = &f->mappings[index]; + + td->dev = &f->dev; + td->sess = sess; + td->tgtt = &f->tgtt; + td->lun = lun; + list_add_tail(&td->dev_tgt_dev_list_entry, &f->dev.dev_tgt_dev_list); + list_add_tail_rcu(&td->sess_tgt_dev_list_entry, + &sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(lun)]); + f->published[index] = true; +} + +static int lifecycle_alloc(struct scst_session *sess, struct scst_acg_dev *ad, + struct scst_tgt_dev **out) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + unsigned int index = f->alloc_calls++; + + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex)); + if (index >= ARRAY_SIZE(f->alloc_results)) + return -EOVERFLOW; + if (f->alloc_results[index]) + return f->alloc_results[index]; + lifecycle_publish(f, index, sess, ad->lun); + f->mappings[index].acg_dev = ad; + *out = &f->mappings[index]; + return 0; +} + +static void lifecycle_sysfs_remove(struct scst_tgt_dev *td) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + unsigned int index = td - f->mappings; + + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex)); + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&td->sess->tgt_dev_list_mutex)); + rcu_read_lock(); + KUNIT_EXPECT_PTR_EQ(test, scst_lookup_tgt_dev(td->sess, td->lun), NULL); + rcu_read_unlock(); + KUNIT_EXPECT_FALSE(test, f->removed[index]); + f->removed[index] = true; + f->deletes++; +} + +static void lifecycle_grace_period(void) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + unsigned int i; + + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex)); + for (i = 0; i < ARRAY_SIZE(f->sessions); i++) + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&f->sessions[i].tgt_dev_list_mutex)); + f->grace_periods++; + for (i = 0; i < ARRAY_SIZE(f->mappings); i++) + if (f->removed[i]) + f->retired[i] = f->grace_periods; +} + +static void lifecycle_free(struct scst_tgt_dev *td) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + unsigned int index = td - f->mappings; + + KUNIT_EXPECT_TRUE(test, f->removed[index]); + KUNIT_EXPECT_GT(test, f->retired[index], 0U); + KUNIT_EXPECT_FALSE(test, f->freed[index]); + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&td->sess->tgt_dev_list_mutex)); + if (f->batch) + KUNIT_EXPECT_TRUE(test, list_empty(&f->dev.dev_tgt_dev_list)); + f->freed[index] = true; + f->frees++; +} + +static int lifecycle_cm_add(struct scst_acg_dev *ad, u64 lun, unsigned int *flags) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, ad->acg, &f->acg); + KUNIT_EXPECT_EQ(test, lun, ad->lun); + KUNIT_EXPECT_EQ(test, (bool)ad->acg_dev_rd_only, !!(*flags & SCST_ADD_LUN_READ_ONLY)); + f->cm_adds++; + return f->cm_result; +} + +static void lifecycle_cm_del(struct scst_acg_dev *ad) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, ad->dev, &f->dev); + KUNIT_EXPECT_EQ(test, f->frees, 0U); + f->cm_deletes++; +} + +static int lifecycle_sysfs_create(struct scst_acg_dev *ad, struct kobject *parent) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, ad->dev, &f->dev); + KUNIT_EXPECT_PTR_EQ(test, parent, &f->tgt.tgt_kobj); + f->sysfs_creates++; + return f->sysfs_result; +} + +static void lifecycle_ref_release(struct percpu_ref *ref) +{ +} + +static int lifecycle_init(struct kunit *test) +{ + struct lifecycle_fixture *f; + unsigned int i, j; + int rc; + + test->priv = NULL; + f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL); + if (!f) + return -ENOMEM; + rc = percpu_ref_init(&f->dev.refcnt, lifecycle_ref_release, + PERCPU_REF_INIT_ATOMIC, GFP_KERNEL); + if (rc) + return rc; + f->dev.virt_name = "kunit-private-device"; + spin_lock_init(&f->dev.dev_lock); + INIT_LIST_HEAD(&f->dev.dev_tgt_dev_list); + INIT_LIST_HEAD(&f->dev.dev_acg_dev_list); + f->tgt.tgt_name = "kunit-private-target"; + f->tgt.tgtt = &f->tgtt; + f->acg.tgt = &f->tgt; + f->acg.acg_name = "kunit-private-group"; + INIT_LIST_HEAD(&f->acg.acg_dev_list); + INIT_LIST_HEAD(&f->acg.acg_sess_list); + for (i = 0; i < ARRAY_SIZE(f->sessions); i++) { + f->sessions[i].tgt = &f->tgt; + mutex_init(&f->sessions[i].tgt_dev_list_mutex); + INIT_LIST_HEAD(&f->sessions[i].acg_sess_list_entry); + for (j = 0; j < SESS_TGT_DEV_LIST_HASH_SIZE; j++) + INIT_LIST_HEAD(&f->sessions[i].sess_tgt_dev_list[j]); + } + test->priv = f; + kunit_activate_static_stub(test, scst_alloc_add_tgt_dev, lifecycle_alloc); + kunit_activate_static_stub(test, scst_tgt_dev_sysfs_remove, lifecycle_sysfs_remove); + kunit_activate_static_stub(test, scst_free_tgt_dev, lifecycle_free); + kunit_activate_static_stub(test, scst_tgt_dev_grace_period, lifecycle_grace_period); + kunit_activate_static_stub(test, scst_lun_cm_add, lifecycle_cm_add); + kunit_activate_static_stub(test, scst_lun_cm_del, lifecycle_cm_del); + kunit_activate_static_stub(test, scst_lun_sysfs_create, lifecycle_sysfs_create); + return 0; +} + +static void lifecycle_exit(struct kunit *test) +{ + struct lifecycle_fixture *f = test->priv; + struct scst_acg_dev *ad, *tmp; + unsigned int i; + + if (!f) + return; + /* Also reclaim an ACG allocation if an assertion interrupted a success case. */ + for (i = 0; i < ARRAY_SIZE(f->mappings); i++) { + if (f->published[i] && !f->removed[i]) { + list_del(&f->mappings[i].dev_tgt_dev_list_entry); + list_del(&f->mappings[i].sess_tgt_dev_list_entry); + } + } + mutex_lock(&scst_mutex); + list_for_each_entry_safe(ad, tmp, &f->acg.acg_dev_list, acg_dev_list_entry) + scst_del_free_acg_dev(ad, false); + mutex_unlock(&scst_mutex); + percpu_ref_exit(&f->dev.refcnt); + test->priv = NULL; +} + +struct lifecycle_lun_case { + const char *name; + unsigned int sessions; + int alloc[3]; + int cm_error; + int sysfs_error; + unsigned int flags; + int result; + unsigned int mappings; +}; + +static const struct lifecycle_lun_case lifecycle_luns[] = { + { "empty-group", 0, {}, 0, 0, 0, 0, 0 }, + { "three-sessions", 3, {}, 0, 0, 0, 0, 3 }, + { "read-only", 3, {}, 0, 0, SCST_ADD_LUN_READ_ONLY, 0, 3 }, + { "first-allocation-fails", 3, { -ENOMEM }, 0, 0, 0, -ENOMEM, 0 }, + { "second-allocation-fails", 3, { 0, -ENOMEM }, 0, 0, 0, -ENOMEM, 1 }, + { "third-allocation-fails", 3, { 0, 0, -EIO }, 0, 0, 0, -EIO, 2 }, + { "skip-denied-session", 3, { 0, -EPERM }, 0, 0, 0, 0, 2 }, + { "all-sessions-denied", 3, { -EPERM, -EPERM, -EPERM }, 0, 0, 0, 0, 0 }, + { "cm-reject-empty", 0, {}, -EINVAL, 0, 0, -EINVAL, 0 }, + { "cm-reject-one", 1, {}, -EINVAL, 0, SCST_ADD_LUN_READ_ONLY, -EINVAL, 1 }, + { "cm-reject-three", 3, {}, -EINVAL, 0, SCST_ADD_LUN_READ_ONLY, -EINVAL, 3 }, + { "sysfs-fails", 3, {}, 0, -ENOMEM, 0, -ENOMEM, 3 }, + { "cm-origin-skips-cm", 3, {}, -EIO, 0, SCST_ADD_LUN_CM, 0, 3 }, + { "cm-origin-sysfs-fails", 3, {}, 0, -EIO, SCST_ADD_LUN_CM, -EIO, 3 }, +}; + +static void lifecycle_lun_desc(const struct lifecycle_lun_case *p, char *desc) +{ + strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(lifecycle_lun, lifecycle_luns, lifecycle_lun_desc); + +static void lifecycle_lun_test(struct kunit *test) +{ + const struct lifecycle_lun_case *p = test->param_value; + struct lifecycle_fixture *f = test->priv; + struct scst_acg_dev *out = NULL; + unsigned int i; + int rc; + + memcpy(f->alloc_results, p->alloc, sizeof(p->alloc)); + f->cm_result = p->cm_error; + f->sysfs_result = p->sysfs_error; + for (i = 0; i < p->sessions; i++) + list_add_tail(&f->sessions[i].acg_sess_list_entry, &f->acg.acg_sess_list); + mutex_lock(&scst_mutex); + rc = scst_acg_add_lun(&f->acg, &f->tgt.tgt_kobj, &f->dev, 7, p->flags, &out); + mutex_unlock(&scst_mutex); + KUNIT_EXPECT_EQ(test, rc, p->result); + KUNIT_EXPECT_EQ(test, f->cm_deletes, + (unsigned int)(p->sysfs_error && !(p->flags & SCST_ADD_LUN_CM))); + if (rc) { + KUNIT_EXPECT_PTR_EQ(test, out, NULL); + KUNIT_EXPECT_EQ(test, f->deletes, p->mappings); + KUNIT_EXPECT_EQ(test, f->frees, p->mappings); + KUNIT_EXPECT_EQ(test, f->grace_periods, p->mappings); + KUNIT_EXPECT_TRUE(test, list_empty(&f->dev.dev_tgt_dev_list)); + KUNIT_EXPECT_TRUE(test, list_empty(&f->dev.dev_acg_dev_list)); + KUNIT_EXPECT_TRUE(test, list_empty(&f->acg.acg_dev_list)); + } else { + KUNIT_ASSERT_NOT_NULL(test, out); + KUNIT_EXPECT_EQ(test, out->lun, (u64)7); + KUNIT_EXPECT_EQ(test, f->frees, 0U); + KUNIT_EXPECT_EQ(test, f->sysfs_creates, 1U); + KUNIT_EXPECT_EQ(test, f->cm_adds, (unsigned int)!(p->flags & SCST_ADD_LUN_CM)); + } +} + +struct lifecycle_batch_case { + const char *name; + unsigned int count; + unsigned int stride; +}; + +static const struct lifecycle_batch_case lifecycle_batches[] = { + { "empty", 0, 1 }, + { "one", 1, 1 }, + { "separate-buckets", 12, 1 }, + { "same-bucket", 12, SESS_TGT_DEV_LIST_HASH_SIZE }, +}; + +static void lifecycle_batch_desc(const struct lifecycle_batch_case *p, char *desc) +{ + strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(lifecycle_batch, lifecycle_batches, lifecycle_batch_desc); + +static void lifecycle_batch_test(struct kunit *test) +{ + const struct lifecycle_batch_case *p = test->param_value; + struct lifecycle_fixture *f = test->priv; + unsigned int i; + + f->batch = true; + for (i = 0; i < p->count; i++) + lifecycle_publish(f, i, &f->sessions[0], i * p->stride); + mutex_lock(&scst_mutex); + scst_sess_free_tgt_devs(&f->sessions[0]); + mutex_unlock(&scst_mutex); + KUNIT_EXPECT_EQ(test, f->grace_periods, 1U); + KUNIT_EXPECT_EQ(test, f->deletes, p->count); + KUNIT_EXPECT_EQ(test, f->frees, p->count); + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) + KUNIT_EXPECT_TRUE(test, list_empty(&f->sessions[0].sess_tgt_dev_list[i])); +} + +static void lifecycle_free_acg(struct scst_acg *acg) +{ + struct kunit *test = kunit_get_current_test(); + struct lifecycle_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, acg, &f->acg); + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex)); + lockdep_assert_held(&scst_mutex); + f->acg_frees++; +} + +static void lifecycle_acg_worker_test(struct kunit *test) +{ + struct lifecycle_fixture *f = test->priv; + struct scst_acg_release_work *work; + + /* Invoke the actual worker synchronously: static stubs are task-local. */ + work = kzalloc_obj(*work); + KUNIT_ASSERT_NOT_NULL(test, work); + work->acg = &f->acg; + INIT_WORK(&work->work, scst_release_acg_work); + kunit_activate_static_stub(test, scst_free_acg, lifecycle_free_acg); + scst_release_acg_work(&work->work); + KUNIT_EXPECT_EQ(test, f->acg_frees, 1U); +} + +static struct kunit_case lifecycle_cases[] = { + KUNIT_CASE_PARAM(lifecycle_lun_test, lifecycle_lun_gen_params), + KUNIT_CASE_PARAM(lifecycle_batch_test, lifecycle_batch_gen_params), + KUNIT_CASE(lifecycle_acg_worker_test), + {} +}; + +scst_kunit_suite(lifecycle_suite, "LIFECYCLE", "", + .init = lifecycle_init, + .exit = lifecycle_exit, + .test_cases = lifecycle_cases, +); diff --git a/scst/src/kunit/objects.mk b/scst/src/kunit/objects.mk index 5fc3e86d5..39181e493 100644 --- a/scst/src/kunit/objects.mk +++ b/scst/src/kunit/objects.mk @@ -9,5 +9,7 @@ scst-kunit-scst := \ kunit/event/suites/scst_kunit_event_import.o \ kunit/event/suites/scst_kunit_event_delivery.o \ kunit/lib/suites/scst_kunit_lib.o \ + kunit/lifecycle/suites/scst_kunit_lifecycle.o \ + kunit/lifecycle/suites/scst_kunit_blocking.o \ kunit/order/suites/scst_kunit_order.o \ kunit/unmap/suites/scst_kunit_unmap.o diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 36148be01..6d00a878a 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -55,6 +55,14 @@ #include "scst_priv.h" #include "scst_mem.h" #include "scst_pres.h" +#include "scst_lib_test.h" + +#ifndef SCST_KUNIT_ENABLED +static int scst_alloc_add_tgt_dev(struct scst_session *sess, + struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev); +static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev); +static void scst_ext_blocking_done_fn(struct work_struct *work); +#endif #undef DEFAULT_SYMBOL_NAMESPACE #define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE @@ -1636,7 +1644,6 @@ static const struct scst_sdbops scst_scsi_op_table[] = { #define SCST_CDB_TBL_SIZE (ARRAY_SIZE(scst_scsi_op_table)) static void scst_del_tgt_dev(struct scst_tgt_dev *tgt_dev); -static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev); static void scst_check_internal_sense(struct scst_device *dev, int result, uint8_t *sense, int sense_len); static void scst_queue_report_luns_changed_UA(struct scst_session *sess, @@ -1648,8 +1655,6 @@ static void scst_alloc_set_UA(struct scst_tgt_dev *tgt_dev, static void scst_free_all_UA(struct scst_tgt_dev *tgt_dev); static void scst_release_space(struct scst_cmd *cmd); static void scst_clear_reservation(struct scst_tgt_dev *tgt_dev); -static int scst_alloc_add_tgt_dev(struct scst_session *sess, - struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev); static void scst_tgt_retry_timer_fn(struct timer_list *timer); #ifdef CONFIG_SCST_DEBUG_TM @@ -4262,8 +4267,6 @@ static void scst_init_order_data(struct scst_order_data *order_data) spin_lock_init(&order_data->init_done_lock); } -static void scst_ext_blocking_done_fn(struct work_struct *work); - static int scst_dif_none(struct scst_cmd *cmd); #ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS static int scst_dif_none_type1(struct scst_cmd *cmd); @@ -4452,7 +4455,8 @@ static void scst_free_acg_dev(struct scst_acg_dev *acg_dev) * The activity supposed to be suspended and scst_mutex held or the * corresponding target supposed to be stopped. */ -static void scst_del_free_acg_dev(struct scst_acg_dev *acg_dev, bool del_sysfs) +SCST_KUNIT_VISIBLE +void scst_del_free_acg_dev(struct scst_acg_dev *acg_dev, bool del_sysfs) { TRACE_ENTRY(); scst_del_acg_dev(acg_dev, true, del_sysfs); @@ -4530,6 +4534,42 @@ out: return res; } +/* Boundaries shared by LUN rollback and session teardown. */ +SCST_KUNIT_VISIBLE +void scst_tgt_dev_grace_period(void) +{ + SCST_KUNIT_REDIRECT(scst_tgt_dev_grace_period); + synchronize_rcu(); +} + +SCST_KUNIT_VISIBLE +int scst_lun_cm_add(struct scst_acg_dev *acg_dev, u64 lun, unsigned int *flags) +{ + SCST_KUNIT_REDIRECT(scst_lun_cm_add, acg_dev, lun, flags); + return scst_cm_on_add_lun(acg_dev, lun, flags); +} + +SCST_KUNIT_VISIBLE +void scst_lun_cm_del(struct scst_acg_dev *acg_dev) +{ + SCST_KUNIT_REDIRECT(scst_lun_cm_del, acg_dev); + scst_cm_on_del_lun(acg_dev, false); +} + +SCST_KUNIT_VISIBLE +int scst_lun_sysfs_create(struct scst_acg_dev *acg_dev, struct kobject *parent) +{ + SCST_KUNIT_REDIRECT(scst_lun_sysfs_create, acg_dev, parent); + return scst_acg_dev_sysfs_create(acg_dev, parent); +} + +SCST_KUNIT_VISIBLE +void scst_tgt_dev_sysfs_remove(struct scst_tgt_dev *tgt_dev) +{ + SCST_KUNIT_REDIRECT(scst_tgt_dev_sysfs_remove, tgt_dev); + scst_tgt_dev_sysfs_del(tgt_dev); +} + /* The caller must hold scst_mutex. */ int scst_acg_add_lun(struct scst_acg *acg, struct kobject *parent, struct scst_device *dev, uint64_t lun, unsigned int flags, @@ -4581,12 +4621,12 @@ int scst_acg_add_lun(struct scst_acg *acg, struct kobject *parent, } if (!(flags & SCST_ADD_LUN_CM)) { - res = scst_cm_on_add_lun(acg_dev, lun, &flags); + res = scst_lun_cm_add(acg_dev, lun, &flags); if (res != 0) goto out_free; } - res = scst_acg_dev_sysfs_create(acg_dev, parent); + res = scst_lun_sysfs_create(acg_dev, parent); if (res != 0) goto out_on_del; @@ -4606,7 +4646,7 @@ out: out_on_del: if (!(flags & SCST_ADD_LUN_CM)) - scst_cm_on_del_lun(acg_dev, false); + scst_lun_cm_del(acg_dev); out_free: list_for_each_entry_safe(tgt_dev, tt, &tmp_tgt_dev_list, @@ -4617,7 +4657,7 @@ out_free: scst_del_tgt_dev(tgt_dev); mutex_unlock(&sess->tgt_dev_list_mutex); - synchronize_rcu(); + scst_tgt_dev_grace_period(); scst_free_tgt_dev(tgt_dev); } @@ -4983,7 +5023,8 @@ static void scst_del_acg(struct scst_acg *acg) * * The caller must hold scst_mutex and activity must have been suspended. */ -static void scst_free_acg(struct scst_acg *acg) +SCST_KUNIT_VISIBLE +void scst_free_acg(struct scst_acg *acg) { struct scst_acg_dev *acg_dev, *acg_dev_tmp; struct scst_acn *acn, *acn_tmp; @@ -4992,6 +5033,8 @@ static void scst_free_acg(struct scst_acg *acg) struct scst_tgt_dev *tgt_dev, *tt; LIST_HEAD(tgt_dev_list); + SCST_KUNIT_REDIRECT(scst_free_acg, acg); + lockdep_assert_held(&scst_mutex); TRACE_DBG("Freeing acg %s/%s", tgt->tgt_name, acg->acg_name); @@ -5026,12 +5069,8 @@ static void scst_free_acg(struct scst_acg *acg) kobject_put(&tgt->tgt_kobj); } -struct scst_acg_release_work { - struct work_struct work; - struct scst_acg *acg; -}; - -static void scst_release_acg_work(struct work_struct *work) +SCST_KUNIT_VISIBLE +void scst_release_acg_work(struct work_struct *work) { struct scst_acg_release_work *release_work = container_of(work, typeof(*release_work), work); @@ -5116,7 +5155,6 @@ int scst_del_free_acg(struct scst_acg *acg, bool close_sessions) return 0; } - /* The activity supposed to be suspended and scst_mutex held */ struct scst_acg *scst_tgt_find_acg(struct scst_tgt *tgt, const char *name) { @@ -5135,7 +5173,6 @@ struct scst_acg *scst_tgt_find_acg(struct scst_tgt *tgt, const char *name) return acg_ret; } - /* scst_mutex supposed to be held */ static struct scst_tgt_dev *scst_find_shared_io_tgt_dev( struct scst_tgt_dev *tgt_dev) @@ -5468,8 +5505,9 @@ static __be16 scst_dif_ip_fn(const void *data, unsigned int len); * session. May be invoked from inside scst_check_reassign_sessions() which * means that sess->acg can be NULL. */ -static int scst_alloc_add_tgt_dev(struct scst_session *sess, - struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev) +SCST_KUNIT_VISIBLE +int scst_alloc_add_tgt_dev(struct scst_session *sess, + struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev) { int res = 0; struct scst_tgt_template *tgtt = sess->tgt->tgtt; @@ -5480,6 +5518,8 @@ static int scst_alloc_add_tgt_dev(struct scst_session *sess, int sl; uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN]; + SCST_KUNIT_REDIRECT(scst_alloc_add_tgt_dev, sess, acg_dev, out_tgt_dev); + TRACE_ENTRY(); tgt_dev = kmem_cache_zalloc(scst_tgtd_cachep, GFP_KERNEL); @@ -5703,7 +5743,7 @@ static void scst_del_tgt_dev(struct scst_tgt_dev *tgt_dev) list_del_rcu(&tgt_dev->sess_tgt_dev_list_entry); - scst_tgt_dev_sysfs_del(tgt_dev); + scst_tgt_dev_sysfs_remove(tgt_dev); } /* @@ -5712,11 +5752,14 @@ static void scst_del_tgt_dev(struct scst_tgt_dev *tgt_dev) * if another thread could still be accessing @tgt_dev from inside an RCU * read-side critical section. */ -static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev) +SCST_KUNIT_VISIBLE +void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev) { struct scst_tgt_template *tgtt = tgt_dev->tgtt; struct scst_device *dev = tgt_dev->dev; + SCST_KUNIT_REDIRECT(scst_free_tgt_dev, tgt_dev); + TRACE_ENTRY(); #ifdef CONFIG_SCST_EXTRACHECKS @@ -5750,7 +5793,8 @@ static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev) } /* scst_mutex supposed to be held */ -static void scst_sess_free_tgt_devs(struct scst_session *sess) +SCST_KUNIT_VISIBLE +void scst_sess_free_tgt_devs(struct scst_session *sess) { int i; struct scst_tgt_dev *tgt_dev, *tt; @@ -5772,7 +5816,7 @@ static void scst_sess_free_tgt_devs(struct scst_session *sess) } mutex_unlock(&sess->tgt_dev_list_mutex); - synchronize_rcu(); + scst_tgt_dev_grace_period(); list_for_each_entry_safe(tgt_dev, tt, &tgt_dev_list, extra_tgt_dev_list_entry) scst_free_tgt_dev(tgt_dev); @@ -15022,7 +15066,8 @@ static inline void scst_ext_blocker_release(struct kref *kref) kfree(b); } -static inline void scst_ext_blocker_put(struct scst_ext_blocker *b) +SCST_KUNIT_VISIBLE_INLINE +void scst_ext_blocker_put(struct scst_ext_blocker *b) { kref_put(&b->refcount, scst_ext_blocker_release); } @@ -15070,7 +15115,8 @@ static void __scst_ext_blocking_done(struct scst_device *dev) TRACE_EXIT(); } -static void scst_ext_blocking_done_fn(struct work_struct *work) +SCST_KUNIT_VISIBLE +void scst_ext_blocking_done_fn(struct work_struct *work) { struct scst_device *dev = container_of(work, struct scst_device, ext_blockers_work); @@ -15155,6 +15201,15 @@ static void scst_dev_ext_block(struct scst_device *dev, bool block_stpg) TRACE_EXIT(); } +/* The caller holds dev_lock with BH disabled; the wait may drop the lock. */ +SCST_KUNIT_VISIBLE +int scst_wait_ext_block_dev(struct scst_device *dev, wait_queue_head_t *w) +{ + SCST_KUNIT_REDIRECT(scst_wait_ext_block_dev, dev, w); + return scst_wait_event_interruptible_lock_bh(*w, dev->on_dev_cmd_count == 0, + dev->dev_lock); +} + int scst_sync_ext_block_dev(struct scst_device *dev) { struct scst_ext_blocker *b; @@ -15192,7 +15247,7 @@ int scst_sync_ext_block_dev(struct scst_device *dev) b->ext_blocker_done_fn = scst_sync_ext_blocking_done; scst_ext_blocker_get(b); - res = scst_wait_event_interruptible_lock_bh(*w, dev->on_dev_cmd_count == 0, dev->dev_lock); + res = scst_wait_ext_block_dev(dev, w); spin_unlock_bh(&dev->dev_lock); diff --git a/scst/src/scst_lib_test.h b/scst/src/scst_lib_test.h new file mode 100644 index 000000000..390f70f62 --- /dev/null +++ b/scst/src/scst_lib_test.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef SCST_LIB_TEST_H +#define SCST_LIB_TEST_H + +#include "scst_test.h" + +#ifdef SCST_KUNIT_ENABLED + +void scst_del_free_acg_dev(struct scst_acg_dev *acg_dev, bool del_sysfs); +void scst_tgt_dev_grace_period(void); +int scst_lun_cm_add(struct scst_acg_dev *acg_dev, u64 lun, unsigned int *flags); +void scst_lun_cm_del(struct scst_acg_dev *acg_dev); +int scst_lun_sysfs_create(struct scst_acg_dev *acg_dev, struct kobject *parent); +void scst_tgt_dev_sysfs_remove(struct scst_tgt_dev *tgt_dev); +void scst_free_acg(struct scst_acg *acg); +void scst_release_acg_work(struct work_struct *work); +int scst_alloc_add_tgt_dev(struct scst_session *sess, + struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev); +void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev); +void scst_sess_free_tgt_devs(struct scst_session *sess); +void scst_ext_blocker_put(struct scst_ext_blocker *b); +void scst_ext_blocking_done_fn(struct work_struct *work); +int scst_wait_ext_block_dev(struct scst_device *dev, wait_queue_head_t *w); + +#endif /* SCST_KUNIT_ENABLED */ +#endif /* SCST_LIB_TEST_H */ diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index b24ead593..0fac9d21b 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -344,6 +345,11 @@ int scst_alloc_device(gfp_t gfp_mask, int nodeid, struct scst_device **out_dev); void scst_free_device(struct scst_device *dev); bool scst_device_is_exported(struct scst_device *dev); +struct scst_acg_release_work { + struct work_struct work; + struct scst_acg *acg; +}; + int scst_alloc_add_acg(struct scst_tgt *tgt, const char *acg_name, bool tgt_acg, struct scst_acg **out_acg); int scst_del_free_acg(struct scst_acg *acg, bool close_sessions);