mirror of
https://github.com/SCST-project/scst.git
synced 2026-10-22 06:02:33 +00:00
kunit: Cover VDISK FUA decoding and cache command completion
Issue #395 combined an advanced write offset, an incorrect flush end, missing asynchronous FUA propagation and an explicit-flush bypass for direct I/O. The existing backend tests supplied parsed parameters and covered only successful queued completion. Decode WRITE(10/12/16/32) through the core and handler parsers, including large LBAs, FUA controls and both sector sizes. Check SYNCHRONIZE CACHE range coverage, zero-length-to-end semantics, errors and IMMED ordering with its temporary command reference. Also exercise commandless direct flushes, queued FUA errors and residuals, and short-write-before-flush ordering. Add the detached-handler guard case for issue #394. Use a zero-length data buffer for cache commands. Their core parser leaves data_len zero and vdisk flushes to the end of the device, so require the flush to cover the CDB range without rejecting that wider flush. The private fixture records filesystem calls and completion ordering; it does not claim persistence across a crash. Keep the existing skips for the separate commandless cache-bypass defect. Link: https://github.com/SCST-project/scst/issues/394 Link: https://github.com/SCST-project/scst/issues/395
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user