memcpy_mc() is the Machine-Check safe memcpy variant that returns the number of bytes NOT copied on a hardware memory error, or 0 on success.
Add two test cases modeled after the existing memcpy_test() and memcpy_large_test() implementations: Signed-off-by: Ruidong Tian <[email protected]> --- lib/tests/memcpy_kunit.c | 113 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/lib/tests/memcpy_kunit.c b/lib/tests/memcpy_kunit.c index 85df53ccfb0c..b4b2dafb50f1 100644 --- a/lib/tests/memcpy_kunit.c +++ b/lib/tests/memcpy_kunit.c @@ -552,6 +552,115 @@ static void copy_mc_page_test(struct kunit *test) memcmp(page_dst + PAGE_SIZE, page_zero, PAGE_SIZE), 0, "copy_mc_page overflow into adjacent page"); } +/* + * memcpy_mc() is a Machine-Check safe memcpy variant. + * Signature: int memcpy_mc(void *dst, const void *src, size_t len) + * Returns: 0 on success, or number of bytes NOT copied on MC error. + * + * In the normal (no-poison) path it must behave identically to memcpy() + * and always return 0. + */ +static void memcpy_mc_test(struct kunit *test) +{ +#define TEST_OP "memcpy_mc" + struct some_bytes control = { + .data = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }, + }; + struct some_bytes zero = { }; + struct some_bytes middle = { + .data = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }, + }; + struct some_bytes three = { + .data = { 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }, + }; + struct some_bytes dest = { }; + int ret, count; + u8 *ptr; + + /* Verify static initializers. */ + check(control, 0x20); + check(zero, 0); + compare("static initializers", dest, zero); + + /* Verify assignment. */ + dest = control; + compare("direct assignment", dest, control); + + /* Verify complete overwrite. */ + ret = memcpy_mc(dest.data, control.data, sizeof(dest.data)); + KUNIT_ASSERT_EQ(test, ret, 0); + compare("complete overwrite", dest, control); + + /* Verify middle overwrite: 7 bytes at offset 12. */ + dest = control; + ret = memcpy_mc(dest.data + 12, zero.data, 7); + KUNIT_ASSERT_EQ(test, ret, 0); + compare("middle overwrite", dest, middle); + + /* Verify zero-length copy is a no-op. */ + dest = control; + ret = memcpy_mc(dest.data, zero.data, 0); + KUNIT_ASSERT_EQ(test, ret, 0); + compare("zero length", dest, control); + + /* Verify argument side-effects aren't repeated. */ + dest = control; + ptr = dest.data; + count = 1; + memcpy(ptr++, zero.data, count++); + ptr += 8; + memcpy(ptr++, zero.data, count++); + compare("argument side-effects", dest, three); +#undef TEST_OP +} + +static void memcpy_mc_large_test(struct kunit *test) +{ + init_large(test); + + /* Sweep 1..1024 bytes x shifting offset to cover all template paths. */ + for (int bytes = 1; bytes <= ARRAY_SIZE(large_src); bytes++) { + for (int offset = 0; offset < ARRAY_SIZE(large_src); offset++) { + int right_zero_pos = offset + bytes; + int right_zero_size = ARRAY_SIZE(large_dst) - right_zero_pos; + int ret; + + ret = memcpy_mc(large_dst + offset, large_src, bytes); + KUNIT_ASSERT_EQ_MSG(test, ret, 0, + "memcpy_mc returned %d with size %d at offset %d", + ret, bytes, offset); + + /* No write before copy area. */ + KUNIT_ASSERT_EQ_MSG(test, + memcmp(large_dst, large_zero, offset), 0, + "with size %d at offset %d", bytes, offset); + /* No write after copy area. */ + KUNIT_ASSERT_EQ_MSG(test, + memcmp(&large_dst[right_zero_pos], large_zero, + right_zero_size), 0, + "with size %d at offset %d", bytes, offset); + /* Byte-for-byte exact. */ + KUNIT_ASSERT_EQ_MSG(test, + memcmp(large_dst + offset, large_src, bytes), 0, + "with size %d at offset %d", bytes, offset); + + memset(large_dst + offset, 0, bytes); + } + cond_resched(); + } +} #endif /* CONFIG_ARCH_HAS_COPY_MC */ static struct kunit_case memcpy_test_cases[] = { @@ -564,6 +673,8 @@ static struct kunit_case memcpy_test_cases[] = { KUNIT_CASE(copy_page_test), #ifdef CONFIG_ARCH_HAS_COPY_MC KUNIT_CASE(copy_mc_page_test), + KUNIT_CASE(memcpy_mc_test), + KUNIT_CASE_SLOW(memcpy_mc_large_test), #endif {} }; @@ -575,5 +686,5 @@ static struct kunit_suite memcpy_test_suite = { kunit_test_suite(memcpy_test_suite); -MODULE_DESCRIPTION("test cases for memcpy(), memmove(), memset() and copy_page()"); +MODULE_DESCRIPTION("test cases for memcpy(), memmove(), memset(), copy_page() and memcpy_mc()"); MODULE_LICENSE("GPL"); -- 2.39.3
