diff --git a/doc/KUNIT.md b/doc/KUNIT.md index cc02339e4..2abb2d6a3 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 LIB UNMAP` | +| `scst.ko` | `ALUA CDB LIB 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/cdb/suites/scst_kunit_cdb.c b/scst/src/kunit/cdb/suites/scst_kunit_cdb.c new file mode 100644 index 000000000..72c5785c8 --- /dev/null +++ b/scst/src/kunit/cdb/suites/scst_kunit_cdb.c @@ -0,0 +1,216 @@ +// 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" + +struct cdb_test_fixture { + struct scst_cmd cmd; + struct scst_device dev; + struct scst_session session; + struct scst_tgt_dev tgt_dev; + u8 sense[SCST_SENSE_BUFFERSIZE]; +}; + +static int cdb_test_parse(struct scst_cmd *cmd) +{ + int ret = cmd->dev->type == TYPE_TAPE ? scst_tape_generic_parse(cmd) : + scst_sbc_generic_parse(cmd); + + return ret ? SCST_CMD_STATE_PRE_XMIT_RESP1 : SCST_CMD_STATE_DEFAULT; +} + +static struct scst_dev_type cdb_test_handler = { + .name = "kunit-cdb", + .parse = cdb_test_parse, +}; + +static struct scst_tgt_template cdb_test_target = { + .name = "kunit-cdb", +}; + +static int cdb_test_init(struct kunit *test) +{ + struct cdb_test_fixture *f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL); + + if (!f) + return -ENOMEM; + test->priv = f; + f->dev.virt_name = "kunit-cdb"; + f->dev.handler = &cdb_test_handler; + f->dev.block_shift = 9; + f->dev.block_size = 512; + f->tgt_dev.dev = &f->dev; + f->tgt_dev.tgtt = &cdb_test_target; + f->tgt_dev.sess = &f->session; + f->cmd.tgt_dev = &f->tgt_dev; + f->cmd.dev = &f->dev; + f->cmd.devt = &cdb_test_handler; + f->cmd.tgtt = &cdb_test_target; + f->cmd.sess = &f->session; + f->cmd.cdb = f->cmd.cdb_buf; + f->cmd.sense = f->sense; + f->cmd.sense_buflen = sizeof(f->sense); + f->cmd.state = SCST_CMD_STATE_PARSE; + f->cmd.resp_data_len = -1; + return 0; +} + +struct cdb_decode_case { + const char *name; + u8 cdb[16]; + int type; + int direction; + unsigned int length; + u64 lba; + bool blocks; + bool unknown; + bool large_blocks; +}; + +static const struct cdb_decode_case cdb_decode_cases[] = { + { "rewind", { 0x01 }, TYPE_TAPE, SCST_DATA_NONE }, + { "rewind_immediate", { 0x01, 0x01 }, TYPE_TAPE, SCST_DATA_NONE }, + { "rewind_not_disk_opcode", { 0x01 }, TYPE_DISK, .unknown = true }, + { "tape_space", { 0x11, 0, 0, 0, 3 }, TYPE_TAPE, SCST_DATA_NONE }, + { "write_filemarks", { 0x10, 0, 0, 0, 1 }, TYPE_TAPE, SCST_DATA_NONE }, + { "erase", { 0x19 }, TYPE_TAPE, SCST_DATA_NONE }, + { "load_tape", { 0x1b, 0, 0, 0, 1 }, TYPE_TAPE, SCST_DATA_NONE }, + { "test_unit_ready", { 0x00 }, TYPE_DISK, SCST_DATA_NONE }, + { "prevent_removal", { 0x1e, 0, 0, 0, 1 }, TYPE_TAPE, SCST_DATA_NONE }, + { "start_stop", { 0x1b, 0, 0, 0, 1 }, TYPE_DISK, SCST_DATA_NONE }, + { "inquiry", { 0x12, 0, 0, 0, 36 }, TYPE_DISK, SCST_DATA_READ, 36 }, + { "inquiry_zero_allocation", { 0x12 }, TYPE_DISK, SCST_DATA_READ }, + { "request_sense", { 0x03, 0, 0, 0, 18 }, TYPE_DISK, SCST_DATA_READ, 18 }, + { "mode_sense_6", { 0x1a, 0, 0, 0, 64 }, TYPE_DISK, SCST_DATA_READ, 64 }, + { "mode_select_6", { 0x15, 0x10, 0, 0, 12 }, TYPE_DISK, SCST_DATA_WRITE, 12 }, + { "read_6_zero_means_256", { 0x08 }, TYPE_DISK, SCST_DATA_READ, 256, .blocks = true }, + { "read_6_lba", { 0x08, 0x1f, 0x23, 0x45, 2 }, TYPE_DISK, SCST_DATA_READ, + 2, 0x1f2345, .blocks = true }, + { "write_6", { 0x0a, 0, 0, 1, 3 }, TYPE_DISK, SCST_DATA_WRITE, 3, 1, .blocks = true }, + { "read_10_zero", { 0x28 }, TYPE_DISK, SCST_DATA_READ, .blocks = true }, + { "read_10", { 0x28, 0, 0x12, 0x34, 0x56, 0x78, 0, 0, 2 }, + TYPE_DISK, SCST_DATA_READ, 2, 0x12345678, .blocks = true }, + { "write_10_4k", { 0x2a, 0, 0, 0, 0, 1, 0, 0, 2 }, TYPE_DISK, + SCST_DATA_WRITE, 2, 1, .blocks = true, .large_blocks = true }, + { "read_12", { 0xa8, 0, 0, 0, 0, 1, 0, 0, 0, 3 }, TYPE_DISK, + SCST_DATA_READ, 3, 1, .blocks = true }, + { "read_16_large_lba", { 0x88, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1 }, + TYPE_DISK, SCST_DATA_READ, 1, 0x100000002ULL, .blocks = true }, + { "write_16_zero", { 0x8a }, TYPE_DISK, SCST_DATA_WRITE, .blocks = true }, +}; + +static void cdb_decode_desc(const struct cdb_decode_case *p, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name); +} + +KUNIT_ARRAY_PARAM(cdb_decode, cdb_decode_cases, cdb_decode_desc); + +static void cdb_decode_test(struct kunit *test) +{ + const struct cdb_decode_case *p = test->param_value; + struct cdb_test_fixture *f = test->priv; + struct scst_cmd *cmd = &f->cmd; + unsigned int block_size = p->large_blocks ? 4096 : 512; + int ret; + + memcpy(cmd->cdb, p->cdb, sizeof(p->cdb)); + f->dev.type = p->type; + f->dev.block_size = block_size; + f->dev.block_shift = p->large_blocks ? 12 : 9; + ret = scst_get_cdb_info(cmd); + if (p->unknown) { + KUNIT_EXPECT_LT(test, ret, 0); + KUNIT_EXPECT_FALSE(test, !!(cmd->op_flags & SCST_INFO_VALID)); + KUNIT_EXPECT_TRUE(test, !!(cmd->op_flags & SCST_LBA_NOT_VALID)); + return; + } + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, !!(cmd->op_flags & SCST_INFO_VALID)); + KUNIT_EXPECT_EQ(test, cmd->data_direction, p->direction); + KUNIT_EXPECT_EQ(test, cmd->bufflen, (int)p->length); + if (p->blocks) { + KUNIT_EXPECT_EQ(test, cmd->lba, p->lba); + KUNIT_ASSERT_EQ(test, scst_sbc_generic_parse(cmd), 0); + KUNIT_EXPECT_EQ(test, cmd->bufflen, (int)(p->length * block_size)); + KUNIT_EXPECT_EQ(test, cmd->data_len, (s64)p->length * block_size); + } + KUNIT_EXPECT_EQ(test, cmd->status, 0); +} + +struct cdb_direction_case { + const char *name; + u8 opcode; + int direction; + unsigned int length; + bool invalid; +}; + +static const struct cdb_direction_case cdb_direction_cases[] = { + { "rewind_without_data", 0x01, SCST_DATA_NONE }, + { "rewind_read_flag", 0x01, SCST_DATA_READ, 0, true }, + { "rewind_write_flag", 0x01, SCST_DATA_WRITE, 0, true }, + { "rewind_unexpected_transfer", 0x01, SCST_DATA_READ, 512, true }, + { "tur_read_exception", 0x00, SCST_DATA_READ }, + { "tur_without_data", 0x00, SCST_DATA_NONE }, + { "tur_write_rejected", 0x00, SCST_DATA_WRITE, 512, true }, + { "zero_read_10_no_transfer", 0x28, SCST_DATA_NONE }, + { "zero_read_10_read_flag", 0x28, SCST_DATA_READ }, + { "zero_write_10_no_transfer", 0x2a, SCST_DATA_NONE }, +}; + +static void cdb_direction_desc(const struct cdb_direction_case *p, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name); +} + +KUNIT_ARRAY_PARAM(cdb_direction, cdb_direction_cases, cdb_direction_desc); + +static void cdb_direction_test(struct kunit *test) +{ + const struct cdb_direction_case *p = test->param_value; + struct cdb_test_fixture *f = test->priv; + struct scst_cmd *cmd = &f->cmd; + bool invalid = p->invalid && !IS_ENABLED(CONFIG_SCST_USE_EXPECTED_VALUES); + + f->dev.type = p->opcode == 0x01 ? TYPE_TAPE : TYPE_DISK; + cmd->cdb[0] = p->opcode; + scst_cmd_set_expected(cmd, p->direction, p->length); + KUNIT_ASSERT_EQ(test, scst_get_cdb_info(cmd), 0); + scst_kunit_log(test, "%s: opcode=0x%02x direction=%d expected=%d length=%u", + p->name, p->opcode, cmd->data_direction, p->direction, p->length); + scst_parse_cmd(cmd); + KUNIT_EXPECT_EQ(test, cmd->status, invalid ? SAM_STAT_CHECK_CONDITION : 0); + if (invalid) { + bool sense_valid; + + KUNIT_EXPECT_TRUE(test, !!cmd->completed); + sense_valid = scst_analyze_sense(cmd->sense, cmd->sense_valid_len, + SCST_SENSE_ALL_VALID, + SCST_LOAD_SENSE(scst_sense_invalid_message)); + KUNIT_EXPECT_TRUE(test, sense_valid); + } else { + KUNIT_EXPECT_EQ(test, cmd->state, SCST_CMD_STATE_PREPARE_SPACE); + KUNIT_EXPECT_EQ(test, cmd->bufflen, + IS_ENABLED(CONFIG_SCST_USE_EXPECTED_VALUES) ? (int)p->length : 0); + if (!cmd->bufflen) + KUNIT_EXPECT_EQ(test, cmd->data_direction, SCST_DATA_NONE); + } +} + +static struct kunit_case cdb_cases[] = { + KUNIT_CASE_PARAM(cdb_decode_test, cdb_decode_gen_params), + KUNIT_CASE_PARAM(cdb_direction_test, cdb_direction_gen_params), + {} +}; + +scst_kunit_suite(cdb_suite, "CDB", "", + .init = cdb_test_init, + .test_cases = cdb_cases, +); diff --git a/scst/src/kunit/objects.mk b/scst/src/kunit/objects.mk index a6b701ef7..c9fd47441 100644 --- a/scst/src/kunit/objects.mk +++ b/scst/src/kunit/objects.mk @@ -3,5 +3,6 @@ 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/unmap/suites/scst_kunit_unmap.o diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 00d7dc313..3d64af0da 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -1012,7 +1012,8 @@ static bool scst_bufflen_eq_expecten_len(struct scst_cmd *cmd) return b == cmd->expected_transfer_len_full; } -static int scst_parse_cmd(struct scst_cmd *cmd) +SCST_KUNIT_VISIBLE +int scst_parse_cmd(struct scst_cmd *cmd) { int res = SCST_CMD_STATE_RES_CONT_SAME; int state; diff --git a/scst/src/scst_targ_test.h b/scst/src/scst_targ_test.h index 318340cb1..fcd95d350 100644 --- a/scst/src/scst_targ_test.h +++ b/scst/src/scst_targ_test.h @@ -9,6 +9,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); #endif /* SCST_KUNIT_ENABLED */ #endif /* SCST_TARG_TEST_H */