| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * KUnit tests for scsi_lib.c. |
| * |
| * Copyright (C) 2023, Oracle Corporation |
| */ |
| #include <kunit/test.h> |
| |
| #include <scsi/scsi_proto.h> |
| #include <scsi/scsi_cmnd.h> |
| #include <scsi/scsi_device.h> |
| |
| #define SCSI_LIB_TEST_MAX_ALLOWED 3 |
| #define SCSI_LIB_TEST_TOTAL_MAX_ALLOWED 5 |
| |
| static void scsi_lib_test_multiple_sense(struct kunit *test) |
| { |
| struct scsi_failure multiple_sense_failure_defs[] = { |
| { |
| .sense = DATA_PROTECT, |
| .asc = 0x1, |
| .ascq = 0x1, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| { |
| .sense = UNIT_ATTENTION, |
| .asc = 0x11, |
| .ascq = 0x0, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| { |
| .sense = NOT_READY, |
| .asc = 0x11, |
| .ascq = 0x22, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| { |
| .sense = ABORTED_COMMAND, |
| .asc = 0x11, |
| .ascq = SCMD_FAILURE_ASCQ_ANY, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| { |
| .sense = HARDWARE_ERROR, |
| .asc = SCMD_FAILURE_ASC_ANY, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| { |
| .sense = ILLEGAL_REQUEST, |
| .asc = 0x91, |
| .ascq = 0x36, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| {} |
| }; |
| struct scsi_failures failures = { |
| .failure_definitions = multiple_sense_failure_defs, |
| }; |
| u8 sense[SCSI_SENSE_BUFFERSIZE] = {}; |
| struct scsi_cmnd sc = { |
| .sense_buffer = sense, |
| }; |
| int i; |
| |
| /* Match end of array */ |
| scsi_build_sense(&sc, 0, ILLEGAL_REQUEST, 0x91, 0x36); |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| /* Basic match in array */ |
| scsi_build_sense(&sc, 0, UNIT_ATTENTION, 0x11, 0x0); |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| /* No matching sense entry */ |
| scsi_build_sense(&sc, 0, MISCOMPARE, 0x11, 0x11); |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| /* Match using SCMD_FAILURE_ASCQ_ANY */ |
| scsi_build_sense(&sc, 0, ABORTED_COMMAND, 0x11, 0x22); |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| /* Fail to match */ |
| scsi_build_sense(&sc, 0, ABORTED_COMMAND, 0x22, 0x22); |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| /* Match using SCMD_FAILURE_ASC_ANY */ |
| scsi_build_sense(&sc, 0, HARDWARE_ERROR, 0x11, 0x22); |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| /* No matching status entry */ |
| sc.result = SAM_STAT_RESERVATION_CONFLICT; |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| |
| /* Test hitting allowed limit */ |
| scsi_build_sense(&sc, 0, NOT_READY, 0x11, 0x22); |
| for (i = 0; i < SCSI_LIB_TEST_MAX_ALLOWED; i++) |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, |
| &failures)); |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| |
| /* reset retries so we can retest */ |
| failures.failure_definitions = multiple_sense_failure_defs; |
| scsi_failures_reset_retries(&failures); |
| |
| /* Test no retries allowed */ |
| scsi_build_sense(&sc, 0, DATA_PROTECT, 0x1, 0x1); |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| } |
| |
| static void scsi_lib_test_any_sense(struct kunit *test) |
| { |
| struct scsi_failure any_sense_failure_defs[] = { |
| { |
| .result = SCMD_FAILURE_SENSE_ANY, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| }, |
| {} |
| }; |
| struct scsi_failures failures = { |
| .failure_definitions = any_sense_failure_defs, |
| }; |
| u8 sense[SCSI_SENSE_BUFFERSIZE] = {}; |
| struct scsi_cmnd sc = { |
| .sense_buffer = sense, |
| }; |
| |
| /* Match using SCMD_FAILURE_SENSE_ANY */ |
| failures.failure_definitions = any_sense_failure_defs; |
| scsi_build_sense(&sc, 0, MEDIUM_ERROR, 0x11, 0x22); |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| } |
| |
| static void scsi_lib_test_host(struct kunit *test) |
| { |
| struct scsi_failure retryable_host_failure_defs[] = { |
| { |
| .result = DID_TRANSPORT_DISRUPTED << 16, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| }, |
| { |
| .result = DID_TIME_OUT << 16, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| }, |
| {} |
| }; |
| struct scsi_failures failures = { |
| .failure_definitions = retryable_host_failure_defs, |
| }; |
| u8 sense[SCSI_SENSE_BUFFERSIZE] = {}; |
| struct scsi_cmnd sc = { |
| .sense_buffer = sense, |
| }; |
| |
| /* No matching host byte entry */ |
| failures.failure_definitions = retryable_host_failure_defs; |
| sc.result = DID_NO_CONNECT << 16; |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| /* Matching host byte entry */ |
| sc.result = DID_TIME_OUT << 16; |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| } |
| |
| static void scsi_lib_test_any_failure(struct kunit *test) |
| { |
| struct scsi_failure any_failure_defs[] = { |
| { |
| .result = SCMD_FAILURE_RESULT_ANY, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| }, |
| {} |
| }; |
| struct scsi_failures failures = { |
| .failure_definitions = any_failure_defs, |
| }; |
| u8 sense[SCSI_SENSE_BUFFERSIZE] = {}; |
| struct scsi_cmnd sc = { |
| .sense_buffer = sense, |
| }; |
| |
| /* Match SCMD_FAILURE_RESULT_ANY */ |
| failures.failure_definitions = any_failure_defs; |
| sc.result = DID_TRANSPORT_FAILFAST << 16; |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| } |
| |
| static void scsi_lib_test_any_status(struct kunit *test) |
| { |
| struct scsi_failure any_status_failure_defs[] = { |
| { |
| .result = SCMD_FAILURE_STAT_ANY, |
| .allowed = SCSI_LIB_TEST_MAX_ALLOWED, |
| }, |
| {} |
| }; |
| struct scsi_failures failures = { |
| .failure_definitions = any_status_failure_defs, |
| }; |
| u8 sense[SCSI_SENSE_BUFFERSIZE] = {}; |
| struct scsi_cmnd sc = { |
| .sense_buffer = sense, |
| }; |
| |
| /* Test any status handling */ |
| failures.failure_definitions = any_status_failure_defs; |
| sc.result = SAM_STAT_RESERVATION_CONFLICT; |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| } |
| |
| static void scsi_lib_test_total_allowed(struct kunit *test) |
| { |
| struct scsi_failure total_allowed_defs[] = { |
| { |
| .sense = UNIT_ATTENTION, |
| .asc = SCMD_FAILURE_ASC_ANY, |
| .ascq = SCMD_FAILURE_ASCQ_ANY, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| /* Fail all CCs except the UA above */ |
| { |
| .sense = SCMD_FAILURE_SENSE_ANY, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| /* Retry any other errors not listed above */ |
| { |
| .result = SCMD_FAILURE_RESULT_ANY, |
| }, |
| {} |
| }; |
| struct scsi_failures failures = { |
| .failure_definitions = total_allowed_defs, |
| }; |
| u8 sense[SCSI_SENSE_BUFFERSIZE] = {}; |
| struct scsi_cmnd sc = { |
| .sense_buffer = sense, |
| }; |
| int i; |
| |
| /* Test total_allowed */ |
| failures.failure_definitions = total_allowed_defs; |
| scsi_failures_reset_retries(&failures); |
| failures.total_allowed = SCSI_LIB_TEST_TOTAL_MAX_ALLOWED; |
| |
| scsi_build_sense(&sc, 0, UNIT_ATTENTION, 0x28, 0x0); |
| for (i = 0; i < SCSI_LIB_TEST_TOTAL_MAX_ALLOWED; i++) |
| /* Retry since we under the total_allowed limit */ |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, |
| &failures)); |
| sc.result = DID_TIME_OUT << 16; |
| /* We have now hit the total_allowed limit so no more retries */ |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| } |
| |
| static void scsi_lib_test_mixed_total(struct kunit *test) |
| { |
| struct scsi_failure mixed_total_defs[] = { |
| { |
| .sense = UNIT_ATTENTION, |
| .asc = 0x28, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| { |
| .sense = UNIT_ATTENTION, |
| .asc = 0x29, |
| .result = SAM_STAT_CHECK_CONDITION, |
| }, |
| { |
| .allowed = 1, |
| .result = DID_TIME_OUT << 16, |
| }, |
| {} |
| }; |
| u8 sense[SCSI_SENSE_BUFFERSIZE] = {}; |
| struct scsi_failures failures = { |
| .failure_definitions = mixed_total_defs, |
| }; |
| struct scsi_cmnd sc = { |
| .sense_buffer = sense, |
| }; |
| int i; |
| |
| /* |
| * Test total_allowed when there is a mix of per failure allowed |
| * and total_allowed limits. |
| */ |
| failures.failure_definitions = mixed_total_defs; |
| scsi_failures_reset_retries(&failures); |
| failures.total_allowed = SCSI_LIB_TEST_TOTAL_MAX_ALLOWED; |
| |
| scsi_build_sense(&sc, 0, UNIT_ATTENTION, 0x28, 0x0); |
| for (i = 0; i < SCSI_LIB_TEST_TOTAL_MAX_ALLOWED; i++) |
| /* Retry since we under the total_allowed limit */ |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, |
| &failures)); |
| /* Do not retry since we are now over total_allowed limit */ |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| |
| scsi_failures_reset_retries(&failures); |
| scsi_build_sense(&sc, 0, UNIT_ATTENTION, 0x28, 0x0); |
| for (i = 0; i < SCSI_LIB_TEST_TOTAL_MAX_ALLOWED; i++) |
| /* Retry since we under the total_allowed limit */ |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, |
| &failures)); |
| sc.result = DID_TIME_OUT << 16; |
| /* Retry because this failure has a per failure limit */ |
| KUNIT_EXPECT_EQ(test, -EAGAIN, scsi_check_passthrough(&sc, &failures)); |
| scsi_build_sense(&sc, 0, UNIT_ATTENTION, 0x29, 0x0); |
| /* total_allowed is now hit so no more retries */ |
| KUNIT_EXPECT_EQ(test, 0, scsi_check_passthrough(&sc, &failures)); |
| } |
| |
| static void scsi_lib_test_check_passthough(struct kunit *test) |
| { |
| scsi_lib_test_multiple_sense(test); |
| scsi_lib_test_any_sense(test); |
| scsi_lib_test_host(test); |
| scsi_lib_test_any_failure(test); |
| scsi_lib_test_any_status(test); |
| scsi_lib_test_total_allowed(test); |
| scsi_lib_test_mixed_total(test); |
| } |
| |
| static struct kunit_case scsi_lib_test_cases[] = { |
| KUNIT_CASE(scsi_lib_test_check_passthough), |
| {} |
| }; |
| |
| static struct kunit_suite scsi_lib_test_suite = { |
| .name = "scsi_lib", |
| .test_cases = scsi_lib_test_cases, |
| }; |
| |
| kunit_test_suite(scsi_lib_test_suite); |