diff --git a/doc/KUNIT.md b/doc/KUNIT.md index 4b8a96c7d..11427d7da 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 EVENT LIB ORDER UNMAP` | +| `scst.ko` | `ALUA CDB CM EVENT 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/scripts/generate-kernel-patch b/scripts/generate-kernel-patch index aa04256af..bdcd26544 100755 --- a/scripts/generate-kernel-patch +++ b/scripts/generate-kernel-patch @@ -311,7 +311,8 @@ scst_04_main="scst/src/scst_main.c scst/src/scst_priv.h \ scst/src/scst_copy_mgr.c scst/src/scst_dlm.c scst/src/scst_dlm.h \ scst/src/scst_event.c scst/src/scst_event_priv.h scst/src/scst_no_dlm.c \ scst/src/scst_test.h \ -scst/src/scst_event_test.h" +scst/src/scst_event_test.h \ +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" diff --git a/scst/src/kunit/cm/suites/scst_kunit_cm.c b/scst/src/kunit/cm/suites/scst_kunit_cm.c new file mode 100644 index 000000000..521b7514e --- /dev/null +++ b/scst/src/kunit/cm/suites/scst_kunit_cm.c @@ -0,0 +1,243 @@ +// 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_copy_mgr_test.h" + +struct cm_fixture { + struct scst_device dev; + struct scst_acg acg; + struct scst_acg other_acg; + struct scst_acg_dev acg_dev; + unsigned int registrations; + int register_result; + struct completion removing; + unsigned int lun; + unsigned int lookups; + unsigned int designator_clears; + unsigned int inquiries; + int inquiry_result; +}; + +static unsigned int cm_lookup_stub(const struct scst_device *dev) +{ + struct kunit *test = kunit_get_current_test(); + struct cm_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev); + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex)); + lockdep_assert_held(&scst_mutex); + f->lookups++; + return f->lun; +} + +static void cm_clear_stub(struct scst_device *dev) +{ + struct kunit *test = kunit_get_current_test(); + struct cm_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev); + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex)); + f->designator_clears++; +} + +static int cm_inquiry_stub(struct scst_device *dev, unsigned int lun, + struct scst_cm_init_inq_priv *priv) +{ + struct kunit *test = kunit_get_current_test(); + struct cm_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev); + KUNIT_EXPECT_PTR_EQ(test, priv, NULL); + KUNIT_EXPECT_EQ(test, lun, f->lun); + KUNIT_EXPECT_GT(test, dev->block_count, 0); + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex)); + KUNIT_EXPECT_EQ(test, f->designator_clears, f->inquiries + 1); + f->inquiries++; + return f->inquiry_result; +} + +static int cm_test_init(struct kunit *test) +{ + struct cm_fixture *f; + + f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL); + if (!f) + return -ENOMEM; + test->priv = f; + f->dev.virt_name = "kunit-private-cm-device"; + spin_lock_init(&f->dev.dev_lock); + INIT_LIST_HEAD(&f->dev.blocked_cmd_list); + atomic_set(&f->dev.cm_update_req_cnt, 0); + init_completion(&f->removing); + f->lun = 9; + kunit_activate_static_stub(test, scst_cm_get_lun, cm_lookup_stub); + kunit_activate_static_stub(test, scst_cm_dev_free_designators, cm_clear_stub); + kunit_activate_static_stub(test, scst_cm_send_init_inquiry, cm_inquiry_stub); + return 0; +} + +struct cm_update_case { + const char *name; + unsigned int pending; + int error; + bool removing; + int initial_blocks; +}; + +static const struct cm_update_case cm_updates[] = { + { "single", 0 }, + { "coalesce-two", 1 }, + { "coalesce-many", 10 }, + { "inquiry-enomem", 0, -ENOMEM }, + { "inquiry-eio", 0, -EIO }, + { "preserve-existing-block", 0, -ENOMEM, false, 1 }, + { "unregistered-before-update", 0, 0, true }, + { "unregistered-before-retry", 3, 0, true }, + { "retry-enomem", 3, -ENOMEM }, + { "retry-eio", 3, -EIO }, +}; + +static void cm_update_desc(const struct cm_update_case *p, char *desc) +{ + strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(cm_update, cm_updates, cm_update_desc); + +static void cm_update_test(struct kunit *test) +{ + const struct cm_update_case *p = test->param_value; + struct cm_fixture *f = test->priv; + unsigned int i; + + f->dev.block_count = p->initial_blocks; + if (!p->pending) { + f->inquiry_result = p->error; + if (p->removing) { + f->lun = SCST_MAX_LUN; + f->dev.remove_completion = &f->removing; + } + } + scst_cm_update_dev_start(&f->dev); + if (p->pending) { + KUNIT_ASSERT_EQ(test, f->inquiries, 1U); + for (i = 0; i < p->pending; i++) + scst_cm_update_dev_start(&f->dev); + KUNIT_EXPECT_EQ(test, f->inquiries, 1U); + KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), (int)p->pending + 1); + /* The real inquiry completion unblocks before calling update_fini. */ + spin_lock_bh(&f->dev.dev_lock); + scst_unblock_dev(&f->dev); + spin_unlock_bh(&f->dev.dev_lock); + f->inquiry_result = p->error; + if (p->removing) { + f->lun = SCST_MAX_LUN; + f->dev.remove_completion = &f->removing; + } + scst_cm_update_dev_fini(&f->dev); + } + KUNIT_EXPECT_EQ(test, f->lookups, p->pending ? 2U : 1U); + KUNIT_EXPECT_EQ(test, f->inquiries, + (unsigned int)!p->removing + (unsigned int)!!p->pending); + if (p->error || p->removing) { + KUNIT_EXPECT_EQ(test, f->dev.block_count, p->initial_blocks); + KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), 0); + } else { + KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), 1); + spin_lock_bh(&f->dev.dev_lock); + scst_unblock_dev(&f->dev); + spin_unlock_bh(&f->dev.dev_lock); + scst_cm_update_dev_fini(&f->dev); + KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), 0); + KUNIT_EXPECT_EQ(test, f->dev.block_count, p->initial_blocks); + } + KUNIT_EXPECT_EQ(test, f->designator_clears, f->inquiries); + KUNIT_EXPECT_FALSE(test, completion_done(&f->removing)); +} + +static struct scst_acg *cm_default_group(void) +{ + struct cm_fixture *f = kunit_get_current_test()->priv; + + return &f->acg; +} + +static int cm_register_stub(struct scst_device *dev, uint64_t lun) +{ + struct kunit *test = kunit_get_current_test(); + struct cm_fixture *f = test->priv; + + KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev); + KUNIT_EXPECT_EQ(test, lun, (u64)9); + lockdep_assert_held(&scst_mutex); + f->registrations++; + return f->register_result; +} + +struct cm_lun_case { + const char *name; + bool foreign; + bool lun_readonly; + bool dev_readonly; + int register_error; +}; + +static const struct cm_lun_case cm_luns[] = { + { "foreign-writable", true }, + { "foreign-readonly", true, true, true }, + { "readonly-lun", false, true }, + { "readonly-device", false, false, true }, + { "readonly-both", false, true, true }, + { "writable" }, + { "register-enomem", false, false, false, -ENOMEM }, + { "register-eio", false, false, false, -EIO }, +}; + +static void cm_lun_desc(const struct cm_lun_case *p, char *desc) +{ + strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(cm_lun, cm_luns, cm_lun_desc); + +static void cm_lun_test(struct kunit *test) +{ + const struct cm_lun_case *p = test->param_value; + struct cm_fixture *f = test->priv; + unsigned int flags = SCST_ADD_LUN_GEN_UA | SCST_ADD_LUN_READ_ONLY; + bool readonly = p->lun_readonly || p->dev_readonly; + bool registered = !p->foreign && !readonly; + int rc; + + f->acg_dev.acg = p->foreign ? &f->other_acg : &f->acg; + f->acg_dev.dev = &f->dev; + f->acg_dev.acg_dev_rd_only = p->lun_readonly; + f->dev.dev_rd_only = p->dev_readonly; + f->register_result = p->register_error; + kunit_activate_static_stub(test, scst_cm_default_acg, cm_default_group); + kunit_activate_static_stub(test, scst_cm_dev_register, cm_register_stub); + mutex_lock(&scst_mutex); + rc = scst_cm_on_add_lun(&f->acg_dev, 9, &flags); + mutex_unlock(&scst_mutex); + KUNIT_EXPECT_EQ(test, rc, p->foreign ? 0 : (readonly ? -EINVAL : p->register_error)); + KUNIT_EXPECT_EQ(test, f->registrations, (unsigned int)registered); + KUNIT_EXPECT_EQ(test, !!(flags & SCST_ADD_LUN_GEN_UA), !registered); + KUNIT_EXPECT_TRUE(test, !!(flags & SCST_ADD_LUN_READ_ONLY)); +} + +static struct kunit_case cm_cases[] = { + KUNIT_CASE_PARAM(cm_update_test, cm_update_gen_params), + KUNIT_CASE_PARAM(cm_lun_test, cm_lun_gen_params), + {} +}; + +scst_kunit_suite(cm_suite, "CM", "", + .init = cm_test_init, + .test_cases = cm_cases, +); diff --git a/scst/src/kunit/objects.mk b/scst/src/kunit/objects.mk index d4bf3f19f..5fc3e86d5 100644 --- a/scst/src/kunit/objects.mk +++ b/scst/src/kunit/objects.mk @@ -4,6 +4,7 @@ scst-kunit-scst := \ kunit/alua/suites/scst_kunit_alua.o \ kunit/cdb/suites/scst_kunit_cdb.o \ + kunit/cm/suites/scst_kunit_cm.o \ kunit/event/scst_event_fixture.o \ kunit/event/suites/scst_kunit_event_import.o \ kunit/event/suites/scst_kunit_event_delivery.o \ diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c index 520c71155..797b76e76 100644 --- a/scst/src/scst_copy_mgr.c +++ b/scst/src/scst_copy_mgr.c @@ -17,6 +17,15 @@ #endif #include "scst_priv.h" #include "scst_pres.h" +#include "scst_copy_mgr_test.h" + +#ifndef SCST_KUNIT_ENABLED +struct scst_cm_init_inq_priv; + +static int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun, + struct scst_cm_init_inq_priv *priv); +static void scst_cm_update_dev_fini(struct scst_device *dev); +#endif #undef DEFAULT_SYMBOL_NAMESPACE #define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE @@ -2238,9 +2247,6 @@ struct scst_cm_init_inq_priv { struct scst_device *dev; }; -static int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun, - struct scst_cm_init_inq_priv *priv); - static void scst_cm_inq_retry_fn(struct scst_cmd *cmd) { struct scst_cm_init_inq_priv *priv = cmd->tgt_i_priv; @@ -2254,8 +2260,6 @@ static void scst_cm_inq_retry_fn(struct scst_cmd *cmd) TRACE_EXIT(); } -static void scst_cm_update_dev_fini(struct scst_device *dev); - static void scst_cm_init_inq_finish(struct scst_cmd *cmd) { int length, page_len, off, rc; @@ -2380,14 +2384,17 @@ out: TRACE_EXIT(); } -static int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun, - struct scst_cm_init_inq_priv *priv) +SCST_KUNIT_VISIBLE +int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun, + struct scst_cm_init_inq_priv *priv) { static const uint8_t inq_cdb[6] = { INQUIRY, 1, 0x83, 0x10, 0, 0 }; __be64 lun; struct scst_cmd *cmd; int res = 0; + SCST_KUNIT_REDIRECT(scst_cm_send_init_inquiry, dev, unpacked_lun, priv); + TRACE_ENTRY(); if (WARN_ON_ONCE(!dev)) { @@ -2458,11 +2465,14 @@ static bool scst_cm_is_lun_free(unsigned int lun) } /* scst_mutex supposed to be held */ -static unsigned int scst_cm_get_lun(const struct scst_device *dev) +SCST_KUNIT_VISIBLE +unsigned int scst_cm_get_lun(const struct scst_device *dev) { unsigned int res = SCST_MAX_LUN; int i; + SCST_KUNIT_REDIRECT(scst_cm_get_lun, dev); + TRACE_ENTRY(); rcu_read_lock(); @@ -2506,11 +2516,14 @@ static uint64_t scst_cm_get_free_lun(void) return lun; } -static int scst_cm_dev_register(struct scst_device *dev, uint64_t lun) +SCST_KUNIT_VISIBLE +int scst_cm_dev_register(struct scst_device *dev, uint64_t lun) { int res; struct scst_acg_dev *acg_dev; + SCST_KUNIT_REDIRECT(scst_cm_dev_register, dev, lun); + TRACE_ENTRY(); lockdep_assert_held(&scst_mutex); @@ -2561,10 +2574,13 @@ out_err: goto out; } -static void scst_cm_dev_free_designators(struct scst_device *dev) +SCST_KUNIT_VISIBLE +void scst_cm_dev_free_designators(struct scst_device *dev) { struct scst_cm_desig *des, *t; + SCST_KUNIT_REDIRECT(scst_cm_dev_free_designators, dev); + TRACE_ENTRY(); mutex_lock(&scst_cm_mutex); @@ -2648,8 +2664,8 @@ out_unblock: goto out_unlock; } -static void -scst_cm_update_dev_start(struct scst_device *dev) +SCST_KUNIT_VISIBLE +void scst_cm_update_dev_start(struct scst_device *dev) { int update_req_cnt, rc; @@ -2662,8 +2678,8 @@ scst_cm_update_dev_start(struct scst_device *dev) atomic_set(&dev->cm_update_req_cnt, 0); } -static void -scst_cm_update_dev_fini(struct scst_device *dev) +SCST_KUNIT_VISIBLE +void scst_cm_update_dev_fini(struct scst_device *dev) { int update_req_cnt, rc; @@ -2765,6 +2781,13 @@ void scst_cm_on_del_acg(struct scst_acg *acg) /* Nothing to do */ } +SCST_KUNIT_VISIBLE +struct scst_acg *scst_cm_default_acg(void) +{ + SCST_KUNIT_REDIRECT(scst_cm_default_acg); + return scst_cm_tgt->default_acg; +} + /* scst_mutex supposed to be held */ int scst_cm_on_add_lun(struct scst_acg_dev *acg_dev, uint64_t lun, unsigned int *flags) { @@ -2774,7 +2797,7 @@ int scst_cm_on_add_lun(struct scst_acg_dev *acg_dev, uint64_t lun, unsigned int lockdep_assert_held(&scst_mutex); - if (acg_dev->acg != scst_cm_tgt->default_acg) + if (acg_dev->acg != scst_cm_default_acg()) goto out; if (acg_dev->acg_dev_rd_only || acg_dev->dev->dev_rd_only) { diff --git a/scst/src/scst_copy_mgr_test.h b/scst/src/scst_copy_mgr_test.h new file mode 100644 index 000000000..e93e7f9d9 --- /dev/null +++ b/scst/src/scst_copy_mgr_test.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef SCST_COPY_MGR_TEST_H +#define SCST_COPY_MGR_TEST_H + +#include "scst_test.h" + +#ifdef SCST_KUNIT_ENABLED + +struct scst_cm_init_inq_priv; + +int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun, + struct scst_cm_init_inq_priv *priv); +unsigned int scst_cm_get_lun(const struct scst_device *dev); +int scst_cm_dev_register(struct scst_device *dev, uint64_t lun); +void scst_cm_dev_free_designators(struct scst_device *dev); +void scst_cm_update_dev_start(struct scst_device *dev); +void scst_cm_update_dev_fini(struct scst_device *dev); +struct scst_acg *scst_cm_default_acg(void); + +#endif /* SCST_KUNIT_ENABLED */ +#endif /* SCST_COPY_MGR_TEST_H */