Use the KHO kunit stubs to mock the KHO preserve/unpreserve/restore API and tests the preservation of DMA allocations with, - Device being coherent or non-coherent. - Atomic or non-atomic context. - Various allocation sizes. - Managed DMA allocations Signed-off-by: Samiullah Khawaja --- kernel/dma/Kconfig | 8 ++ kernel/dma/Makefile | 1 + kernel/dma/direct_test.c | 208 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 kernel/dma/direct_test.c diff --git a/kernel/dma/Kconfig b/kernel/dma/Kconfig index d93958931303..48bc74af551a 100644 --- a/kernel/dma/Kconfig +++ b/kernel/dma/Kconfig @@ -269,3 +269,11 @@ config DMA_MAP_BENCHMARK config DMA_LIVEUPDATE bool "Enable preservation of DMA direct allocations" depends on LIVEUPDATE + +config DMA_LIVEUPDATE_KUNIT_TEST + tristate "KUnit test for DMA direct liveupdate preservation" + depends on KUNIT + depends on DMA_LIVEUPDATE + help + This builds the KUnit tests for the DMA direct liveupdate + preservation and restoration logic. diff --git a/kernel/dma/Makefile b/kernel/dma/Makefile index 6977033444a3..03703542ee79 100644 --- a/kernel/dma/Makefile +++ b/kernel/dma/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_SWIOTLB) += swiotlb.o obj-$(CONFIG_DMA_COHERENT_POOL) += pool.o obj-$(CONFIG_MMU) += remap.o obj-$(CONFIG_DMA_MAP_BENCHMARK) += map_benchmark.o +obj-$(CONFIG_DMA_LIVEUPDATE_KUNIT_TEST) += direct_test.o diff --git a/kernel/dma/direct_test.c b/kernel/dma/direct_test.c new file mode 100644 index 000000000000..f8403ec47807 --- /dev/null +++ b/kernel/dma/direct_test.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for DMA allocation live update preservation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const size_t dma_test_sizes[] = { + 128, + PAGE_SIZE, + 2 * PAGE_SIZE, + 3 * PAGE_SIZE, + 8 * PAGE_SIZE, +}; + +static void dma_size_desc(const size_t *size, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "size=%zu", *size); +} + +KUNIT_ARRAY_PARAM(dma_size, dma_test_sizes, dma_size_desc); + +static struct page *mock_kho_restore_pages(phys_addr_t phys, unsigned long nr_pages) +{ + struct page *page = phys_to_page(phys); + + if (!kho_test_pages_preserved(phys, nr_pages)) + return NULL; + + kho_unpreserve_pages(page, nr_pages); + return page; +} + +static struct folio *mock_kho_restore_folio(phys_addr_t phys) +{ + struct folio *folio = page_folio(phys_to_page(phys)); + + if (!kho_test_pages_preserved(phys, (1 << folio_order(folio)))) + return NULL; + + kho_unpreserve_folio(folio); + return folio; +} + +static void test_dma_direct_preserve_restore_common(struct kunit *test, + bool coherent, + bool atomic, + size_t size) +{ + unsigned long nr_pages = 1 << get_order(size); + dma_addr_t handle1, handle2; + int ret, expected_ret = 0; + struct device dev = {0}; + gfp_t gfp = GFP_KERNEL; + void *addr1, *addr2; + u64 state; + + kunit_activate_static_stub(test, kho_restore_pages, mock_kho_restore_pages); + kunit_activate_static_stub(test, kho_restore_folio, mock_kho_restore_folio); + + device_initialize(&dev); + dev.coherent_dma_mask = DMA_BIT_MASK(64); + dev.dma_mask = &dev.coherent_dma_mask; +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + dev.dma_coherent = coherent; +#endif + + if (atomic) + gfp = GFP_ATOMIC; + + addr1 = dma_alloc_coherent(&dev, size, &handle1, gfp); + if (!addr1) { + kunit_skip(test, "DMA allocation failed (unsupported configuration)"); + return; + } + + if (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) && dma_is_from_pool(addr1, size)) { + expected_ret = -EOPNOTSUPP; + kunit_info(test, "DMA allocation using pool, expecting -EOPNOTSUPP"); + } else if (dma_is_from_cma(dma_to_phys(&dev, handle1), size)) { + expected_ret = -EOPNOTSUPP; + kunit_info(test, "DMA allocation using CMA, expecting -EOPNOTSUPP"); + } + + ret = dma_preserve_coherent_allocation(&dev, addr1, size, handle1, &state); + KUNIT_ASSERT_EQ(test, ret, expected_ret); + + if (!expected_ret) { + KUNIT_EXPECT_TRUE_MSG(test, + kho_test_pages_preserved(dma_to_phys(&dev, handle1), nr_pages), + "Allocated block not tracked in KHO"); + + addr2 = dma_restore_coherent_allocation(&dev, size, &handle2, gfp, state); + KUNIT_ASSERT_NOT_NULL(test, addr2); + + KUNIT_EXPECT_EQ(test, handle1, handle2); + dma_free_coherent(&dev, size, addr2, handle2); + KUNIT_EXPECT_FALSE_MSG(test, + kho_test_pages_preserved(dma_to_phys(&dev, handle1), nr_pages), + "Allocated block still tracked after free"); + } else { + dma_free_coherent(&dev, size, addr1, handle1); + } +} + +static void test_dma_direct_coherent(struct kunit *test) +{ + const size_t *size = test->param_value; + + test_dma_direct_preserve_restore_common(test, true, false, *size); +} + +static void test_dma_direct_non_coherent(struct kunit *test) +{ + const size_t *size = test->param_value; + + test_dma_direct_preserve_restore_common(test, false, false, *size); +} + +static void test_dma_direct_coherent_atomic(struct kunit *test) +{ + const size_t *size = test->param_value; + + test_dma_direct_preserve_restore_common(test, true, true, *size); +} + +static void test_dma_direct_non_coherent_atomic(struct kunit *test) +{ + const size_t *size = test->param_value; + + test_dma_direct_preserve_restore_common(test, false, true, *size); +} + +static void test_dmam_preservation(struct kunit *test) +{ + dma_addr_t handle, non_dmam_handle; + void *addr, *non_dmam_addr; + struct device dev = {0}; + size_t size = 128; + u64 state; + int ret; + + kunit_activate_static_stub(test, kho_restore_pages, mock_kho_restore_pages); + kunit_activate_static_stub(test, kho_restore_folio, mock_kho_restore_folio); + + device_initialize(&dev); + dev.coherent_dma_mask = DMA_BIT_MASK(64); + dev.dma_mask = &dev.coherent_dma_mask; +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + dev.dma_coherent = true; +#endif + + non_dmam_addr = dma_alloc_coherent(&dev, size, &non_dmam_handle, GFP_KERNEL); + if (!non_dmam_addr) { + kunit_skip(test, "DMA allocation failed"); + return; + } + + ret = dmam_preserve_coherent_allocation(&dev, non_dmam_addr, size, non_dmam_handle, &state); + KUNIT_EXPECT_EQ_MSG(test, ret, -EINVAL, "Preserving non-devres allocation should fail"); + dma_free_coherent(&dev, size, non_dmam_addr, non_dmam_handle); + + addr = dmam_alloc_coherent(&dev, size, &handle, GFP_KERNEL); + if (!addr) { + kunit_skip(test, "DMA allocation failed"); + return; + } + + ret = dmam_preserve_coherent_allocation(&dev, addr, size, handle, &state); + KUNIT_ASSERT_EQ_MSG(test, ret, 0, "Failed to preserve dmam allocation"); + KUNIT_EXPECT_TRUE(test, kho_test_pages_preserved(dma_to_phys(&dev, handle), 1)); + + dmam_unpreserve_coherent_allocation(&dev, addr, size, handle, state); + KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(dma_to_phys(&dev, handle), 1)); + + dmam_free_coherent(&dev, size, addr, handle); +} + +static struct kunit_case dma_direct_test_cases[] = { + KUNIT_CASE_PARAM(test_dma_direct_coherent, dma_size_gen_params), + KUNIT_CASE_PARAM(test_dma_direct_non_coherent, dma_size_gen_params), + KUNIT_CASE_PARAM(test_dma_direct_coherent_atomic, dma_size_gen_params), + KUNIT_CASE_PARAM(test_dma_direct_non_coherent_atomic, dma_size_gen_params), + KUNIT_CASE(test_dmam_preservation), + {} +}; + +static struct kunit_suite dma_direct_test_suite = { + .name = "dma_direct_liveupdate", + .test_cases = dma_direct_test_cases, +}; +kunit_test_suite(dma_direct_test_suite); + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); +MODULE_DESCRIPTION("KUnit test for DMA direct live update preservation"); +MODULE_LICENSE("GPL"); -- 2.55.0.795.g602f6c329a-goog