diff --git a/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.c b/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.c index 4c4b14651..987db5ef5 100644 --- a/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.c +++ b/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.c @@ -64,6 +64,9 @@ static int vdisk_test_fsync(struct file *file, loff_t start, loff_t end, int dat } f->flushes[n] = (struct vdisk_test_flush) { .file = file, .start = start, .end = end, + .io_count = f->io_count, .written_len = f->written_len, + .completions = f->completions, + .cmd_refs = atomic_read(&f->cmd.cmd_ref), }; return backend->sync_result; } @@ -142,6 +145,8 @@ int vdisk_test_init(struct kunit *test) vdisk_test_file_init(&f->data, f); vdisk_test_file_init(&f->dif, f); f->vdev.fd = &f->data.file; + f->cmd.cdb = f->cdb; + atomic_set(&f->cmd.cmd_ref, 1); f->cmd.dev = &f->dev; f->cmd.sess = &f->sess; f->cmd.cmd_gfp_mask = GFP_KERNEL; diff --git a/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.h b/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.h index 39a9b030e..1f7b5d157 100644 --- a/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.h +++ b/scst/src/dev_handlers/kunit/vdisk/scst_vdisk_fixture.h @@ -20,6 +20,10 @@ struct vdisk_test_flush { struct file *file; loff_t start; loff_t end; + unsigned int io_count; + size_t written_len; + unsigned int completions; + int cmd_refs; }; struct vdisk_test_fixture { @@ -32,6 +36,7 @@ struct vdisk_test_fixture { struct vdisk_test_file dif; struct block_device bdev; struct scatterlist sg[2]; + u8 cdb[32]; u8 sense[SCST_SENSE_BUFFERSIZE]; u8 *input[2]; u8 written[1024]; diff --git a/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_io_test.c b/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_io_test.c index cc1d075d3..597548617 100644 --- a/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_io_test.c +++ b/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_io_test.c @@ -100,6 +100,9 @@ static void vdisk_write_test(struct kunit *test) /* Failed writes above check status and progress, not FUA durability. */ KUNIT_EXPECT_EQ(test, f->flush_count, 1U); vdisk_test_expect_flush(test, 0, &f->data.file, p->start, p->start + 1023); + KUNIT_EXPECT_EQ(test, f->flushes[0].io_count, p->calls); + KUNIT_EXPECT_EQ(test, f->flushes[0].written_len, p->written); + KUNIT_EXPECT_EQ(test, f->flushes[0].completions, 0U); } KUNIT_EXPECT_EQ(test, f->block_flush_count, 0U); } @@ -113,6 +116,7 @@ struct vdisk_async_case { bool nv_cache; bool dsync; ssize_t result; + ssize_t completion_result; unsigned int calls; u8 status; }; @@ -140,7 +144,12 @@ static const struct vdisk_async_case vdisk_async_cases[] = { .calls = 1, .status = SAM_STAT_CHECK_CONDITION }, { .name = "async_read_error", .read = true, .result = -EIO, .calls = 1, .status = SAM_STAT_CHECK_CONDITION }, - { .name = "queued_fua_completion", .dsync = true, .result = -EIOCBQUEUED, .calls = 1 }, + { .name = "queued_fua_completion", .dsync = true, .result = -EIOCBQUEUED, + .completion_result = 1024, .calls = 1 }, + { .name = "queued_fua_error", .dsync = true, .result = -EIOCBQUEUED, + .completion_result = -EIO, .calls = 1, .status = SAM_STAT_CHECK_CONDITION }, + { .name = "queued_fua_short", .dsync = true, .result = -EIOCBQUEUED, + .completion_result = 256, .calls = 1 }, }; static void vdisk_async_desc(const struct vdisk_async_case *p, char *desc) @@ -190,7 +199,9 @@ static void vdisk_async_test(struct kunit *test) KUNIT_EXPECT_FALSE(test, f->cmd.completed); KUNIT_ASSERT_NOT_NULL(test, pending); f->pending = NULL; - pending->ki_complete(pending, 1024); + pending->ki_complete(pending, p->completion_result); + if (p->completion_result >= 0) + KUNIT_EXPECT_EQ(test, f->cmd.resp_data_len, (int)p->completion_result); } KUNIT_EXPECT_EQ(test, f->completions, 1U); KUNIT_EXPECT_EQ(test, f->completion_state, SCST_CMD_STATE_DEFAULT); diff --git a/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_protocol_test.c b/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_protocol_test.c new file mode 100644 index 000000000..cbb4269e3 --- /dev/null +++ b/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_protocol_test.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../scst_vdisk_fixture.h" + +struct vdisk_cdb_case { + const char *name; + u8 opcode; + u8 subcode; + u8 shift; + u64 lba; + bool fua; +}; + +static const struct vdisk_cdb_case vdisk_cdb_cases[] = { + { "write10_fua", WRITE_10, 0, 9, 16, true }, + { "write10_plain", WRITE_10, 0, 12, 16 }, + { "write12_fua", WRITE_12, 0, 12, 16, true }, + { "write12_plain", WRITE_12, 0, 9, 16 }, + { "write16_fua_high_lba", WRITE_16, 0, 9, 0x100000001ULL, true }, + { "write16_plain", WRITE_16, 0, 12, 16 }, + { "write32_fua_high_lba", VARIABLE_LENGTH_CMD, SUBCODE_WRITE_32, 12, + 0x100000001ULL, true }, + { "write32_plain", VARIABLE_LENGTH_CMD, SUBCODE_WRITE_32, 9, 16 }, + { "read16_fua_is_not_write_fua", READ_16, 0, 9, 16, true }, +}; + +static void vdisk_cdb_desc(const struct vdisk_cdb_case *p, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name); +} + +KUNIT_ARRAY_PARAM(vdisk_cdb, vdisk_cdb_cases, vdisk_cdb_desc); + +static void vdisk_cdb_test(struct kunit *test) +{ + const struct vdisk_cdb_case *p = test->param_value; + struct vdisk_test_fixture *f = test->priv; + u8 *cdb = f->cmd.cdb; + loff_t offset = p->lba << p->shift; + + f->dev.block_shift = p->shift; + f->dev.block_size = 1U << p->shift; + f->vdev.file_size = offset + 2 * f->dev.block_size; + cdb[0] = p->opcode; + cdb[1] = p->fua ? 8 : 0; + switch (p->opcode) { + case WRITE_10: + put_unaligned_be32(p->lba, &cdb[2]); + put_unaligned_be16(2, &cdb[7]); + break; + case WRITE_12: + put_unaligned_be32(p->lba, &cdb[2]); + put_unaligned_be32(2, &cdb[6]); + break; + case VARIABLE_LENGTH_CMD: + cdb[1] = 0; + cdb[7] = 24; + cdb[9] = p->subcode; + cdb[10] = p->fua ? 8 : 0; + put_unaligned_be64(p->lba, &cdb[12]); + put_unaligned_be32(2, &cdb[28]); + /* The core permits 32-byte read/write CDBs only for DIF type 2. */ + f->dev.dev_dif_type = 2; + break; + default: + put_unaligned_be64(p->lba, &cdb[2]); + put_unaligned_be32(2, &cdb[10]); + break; + } + + KUNIT_ASSERT_EQ(test, scst_get_cdb_info(&f->cmd), 0); + KUNIT_ASSERT_EQ(test, scst_sbc_generic_parse(&f->cmd), 0); + /* Poison the output so a parser that leaves it untouched cannot pass. */ + f->params.loff = -1; + f->params.fua = !p->fua; + KUNIT_ASSERT_TRUE(test, vdisk_parse_offset(&f->params, &f->cmd)); + KUNIT_EXPECT_EQ(test, f->params.loff, offset); + KUNIT_EXPECT_EQ(test, !!f->params.fua, p->fua && p->opcode != READ_16); + KUNIT_EXPECT_EQ(test, f->cmd.data_len, (s64)2 * f->dev.block_size); + KUNIT_EXPECT_EQ(test, f->cmd.status, SAM_STAT_GOOD); +} + +struct vdisk_cache_case { + const char *name; + bool sixteen; + bool immed; + bool direct; + u8 shift; + u32 blocks; + int error; +}; + +static const struct vdisk_cache_case vdisk_cache_cases[] = { + { "cache10_range", .shift = 9, .blocks = 2 }, + { "cache16_range_4k", .sixteen = true, .shift = 12, .blocks = 2 }, + { "cache10_to_end", .shift = 9 }, + { "cache16_to_end", .sixteen = true, .shift = 12 }, + { "cache10_immed", .immed = true, .shift = 9, .blocks = 2 }, + { "cache16_immed_direct", .sixteen = true, .immed = true, + .direct = true, .shift = 12 }, + { "cache10_error", .shift = 9, .blocks = 2, .error = -EIO }, + { "cache16_enomem", .sixteen = true, .shift = 9, .blocks = 2, .error = -ENOMEM }, + { "immed_error_after_good", .sixteen = true, .immed = true, + .shift = 9, .blocks = 2, .error = -EIO }, +}; + +static void vdisk_cache_desc(const struct vdisk_cache_case *p, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name); +} + +KUNIT_ARRAY_PARAM(vdisk_cache, vdisk_cache_cases, vdisk_cache_desc); + +static void vdisk_cache_test(struct kunit *test) +{ + const struct vdisk_cache_case *p = test->param_value; + struct vdisk_test_fixture *f = test->priv; + u8 *cdb = f->cmd.cdb; + loff_t start = 16LL << p->shift; + loff_t end = (p->blocks ? 16LL + p->blocks : 32LL) << p->shift; + + f->dev.block_shift = p->shift; + f->dev.block_size = 1U << p->shift; + f->vdev.file_size = 32LL << p->shift; + f->vdev.o_direct_flag = p->direct; + f->data.sync_result = p->error; + /* Cache commands have no data transfer, unlike the shared write fixture. */ + f->cmd.bufflen = 0; + f->cmd.data_len = 0; + f->cmd.resp_data_len = 0; + f->cmd.sg = NULL; + f->cmd.sg_cnt = 0; + cdb[0] = p->sixteen ? SYNCHRONIZE_CACHE_16 : SYNCHRONIZE_CACHE; + cdb[1] = p->immed ? 2 : 0; + if (p->sixteen) { + put_unaligned_be64(16, &cdb[2]); + put_unaligned_be32(p->blocks, &cdb[10]); + } else { + put_unaligned_be32(16, &cdb[2]); + put_unaligned_be16(p->blocks, &cdb[7]); + } + KUNIT_ASSERT_EQ(test, scst_get_cdb_info(&f->cmd), 0); + KUNIT_ASSERT_EQ(test, scst_sbc_generic_parse(&f->cmd), 0); + KUNIT_ASSERT_EQ(test, f->cmd.data_direction, SCST_DATA_NONE); + KUNIT_ASSERT_EQ(test, f->cmd.bufflen, 0); + KUNIT_ASSERT_TRUE(test, vdisk_parse_offset(&f->params, &f->cmd)); + KUNIT_ASSERT_EQ(test, vdisk_synchronize_cache(&f->params), RUNNING_ASYNC); + KUNIT_ASSERT_EQ(test, f->flush_count, 1U); + KUNIT_EXPECT_PTR_EQ(test, f->flushes[0].file, &f->data.file); + KUNIT_EXPECT_EQ(test, f->flushes[0].start, start); + /* The core's no-data parser leaves data_len zero, so vdisk flushes to EOF. */ + KUNIT_EXPECT_GE(test, f->flushes[0].end, end - 1); + KUNIT_EXPECT_LE(test, f->flushes[0].end, f->vdev.file_size - 1); + KUNIT_EXPECT_EQ(test, f->flushes[0].completions, p->immed ? 1U : 0U); + KUNIT_EXPECT_EQ(test, f->flushes[0].cmd_refs, p->immed ? 2 : 1); + KUNIT_EXPECT_EQ(test, atomic_read(&f->cmd.cmd_ref), 1); + KUNIT_EXPECT_EQ(test, f->completions, 1U); + KUNIT_EXPECT_TRUE(test, f->completed_before_callback); + KUNIT_EXPECT_EQ(test, f->completion_state, SCST_CMD_STATE_DEFAULT); + KUNIT_EXPECT_EQ(test, f->cmd.status, p->immed || !p->error ? SAM_STAT_GOOD : + p->error == -ENOMEM ? SAM_STAT_BUSY : SAM_STAT_CHECK_CONDITION); +} + +static struct kunit_case vdisk_protocol_cases[] = { + KUNIT_CASE_PARAM(vdisk_cdb_test, vdisk_cdb_gen_params), + KUNIT_CASE_PARAM(vdisk_cache_test, vdisk_cache_gen_params), + {} +}; + +scst_kunit_suite(vdisk_protocol_suite, "VDISK", "-protocol", + .init = vdisk_test_init, + .exit = vdisk_test_exit, + .test_cases = vdisk_protocol_cases, +); diff --git a/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_sync_test.c b/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_sync_test.c index 2793dd67c..c9f7ce19e 100644 --- a/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_sync_test.c +++ b/scst/src/dev_handlers/kunit/vdisk/suites/scst_vdisk_sync_test.c @@ -85,6 +85,8 @@ static const struct vdisk_flush_case vdisk_flush_cases[] = { { .name = "commandless_async_fileio", .async = true, .no_cmd = true, .files = 1 }, { .name = "direct_fileio", .direct = true, .files = 1 }, { .name = "async_direct_fileio", .async = true, .direct = true, .files = 1 }, + { .name = "commandless_async_direct", .async = true, .no_cmd = true, + .direct = true, .files = 1 }, { .name = "async_fileio_error", .async = true, .data_error = -EIO, .files = 1, .error = -EIO }, { .name = "async_write_through", .async = true, .wt = true }, @@ -122,7 +124,7 @@ static void vdisk_flush_test(struct kunit *test) int ret; /* The baseline bypass path dereferences cmd even when IMMED passed NULL. */ - if (p->async && p->no_cmd && (p->wt || p->nv_cache || p->nullio || p->direct)) { + if (p->async && p->no_cmd && (p->wt || p->nv_cache || p->nullio)) { kunit_skip(test, "commandless cache bypass needs a separate NULL-command fix"); return; } @@ -167,6 +169,7 @@ static void vdisk_flush_test(struct kunit *test) struct vdisk_backend_case { const char *name; bool closed; + bool detached; bool inactive; bool nullio; bool blockio; @@ -178,6 +181,7 @@ struct vdisk_backend_case { }; static const struct vdisk_backend_case vdisk_backend_cases[] = { + { .name = "detached_handler", .detached = true, .expected = -ENODEV }, { .name = "inactive_fileio", .closed = true, .inactive = true, .expected = -ENOMEDIUM }, { .name = "unmapped_fileio", .closed = true, .expected = -ENOMEDIUM }, { .name = "inactive_blockio", .closed = true, .inactive = true, .blockio = true, @@ -214,6 +218,8 @@ static void vdisk_backend_test(struct kunit *test) f->data.sync_result = p->error; f->block_flush_result = p->error; f->sysfs_sync = true; + if (p->detached) + f->dev.dh_priv = NULL; /* Keep task-scoped backend stubs in the test thread on this private device. */ ret = vdisk_flush_backend(&f->dev); diff --git a/scst/src/dev_handlers/scst_vdisk_main.c b/scst/src/dev_handlers/scst_vdisk_main.c index 6bc9787b2..f63b20d73 100644 --- a/scst/src/dev_handlers/scst_vdisk_main.c +++ b/scst/src/dev_handlers/scst_vdisk_main.c @@ -1522,7 +1522,8 @@ out: return res; } -static enum compl_status_e vdisk_synchronize_cache(struct vdisk_cmd_params *p) +SCST_KUNIT_VISIBLE +enum compl_status_e vdisk_synchronize_cache(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; const uint8_t *cdb = cmd->cdb; @@ -2449,7 +2450,8 @@ static int vcdrom_get_supported_opcodes(struct scst_cmd *cmd, * Compute p->loff and p->fua. * Returns true for success or false otherwise and set error in the command. */ -static bool vdisk_parse_offset(struct vdisk_cmd_params *p, struct scst_cmd *cmd) +SCST_KUNIT_VISIBLE +bool vdisk_parse_offset(struct vdisk_cmd_params *p, struct scst_cmd *cmd) { uint64_t lba_start; int64_t data_len; diff --git a/scst/src/dev_handlers/scst_vdisk_test.h b/scst/src/dev_handlers/scst_vdisk_test.h index 710409ac7..1ab07337d 100644 --- a/scst/src/dev_handlers/scst_vdisk_test.h +++ b/scst/src/dev_handlers/scst_vdisk_test.h @@ -18,6 +18,8 @@ void vdisk_on_free_cmd_params(const struct vdisk_cmd_params *p); enum compl_status_e vdisk_exec_sai_16(struct vdisk_cmd_params *p); enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p); enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p); +bool vdisk_parse_offset(struct vdisk_cmd_params *p, struct scst_cmd *cmd); +enum compl_status_e vdisk_synchronize_cache(struct vdisk_cmd_params *p); int vdisk_flush_backend(struct scst_device *dev); #endif /* SCST_KUNIT_ENABLED */ diff --git a/scst/src/kunit/objects.mk b/scst/src/kunit/objects.mk index 4e4a2194b..b2747c83a 100644 --- a/scst/src/kunit/objects.mk +++ b/scst/src/kunit/objects.mk @@ -24,4 +24,5 @@ scst-kunit-scst_vdisk := \ kunit/vdisk/scst_vdisk_fixture.o \ kunit/vdisk/suites/scst_vdisk_capacity_test.o \ kunit/vdisk/suites/scst_vdisk_io_test.o \ + kunit/vdisk/suites/scst_vdisk_protocol_test.o \ kunit/vdisk/suites/scst_vdisk_sync_test.o