From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A04B4352C3C for ; Wed, 10 Jun 2026 22:39:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781131179; cv=none; b=Ec7OmLmX9H5c6yCSnW2T4f9ALXmUyE5gHbXqcGJDkpARveemzSZ/HrrB+oiLzlKF0jioTAVTRNj8txcavvYJJIxweTXE/+Wt4hZ+hsF4ebrcKCJDk3tQXizUZJl88GCZDMYJgz3HJ0dY4Sjn9ONGCy/WHR92zruE1o4SWxBc9TQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781131179; c=relaxed/simple; bh=73VAhH1LI4yBHfbybtvUyD2V6EocnPt3eiazm4xrTwM=; h=Date:From:To:Subject:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=rMd82akNlmFQL0VAGKxarmwOI5WNwqpwsdy5AMpqKT4W2zoyujjteaev5rIjMf5eC56oLWj3D4RQOPR0GsN8EMYSd81PVyOgf7yDuyqdZWbbRaeX3xiaaEb8oXkU8nx9dj80PqWaFmqyhjDsgBr2sQe8uHRRZgQRrcsSgOCXUmw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=bLdP2gCu; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="bLdP2gCu" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-490d1e54b3bso35977555e9.1 for ; Wed, 10 Jun 2026 15:39:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781131175; x=1781735975; darn=vger.kernel.org; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:subject:to:from:date:from:to:cc:subject:date:message-id :reply-to; bh=TmMllbZ8j7ku/DzVmSOIdvY9RjRvjL4eqrd9gxhTRgI=; b=bLdP2gCuaA4OXP3pFQWsmaCylco8jZVqEAfnUcvVmN4a357L/SmnKGb09UR2VOo2rc xDbyXwQGmcwmszE8vqhAX+PU3vjlT2/gOVUFUzHHMHULFASTYSNAGE3izHVrn6/DcygJ 3dsa1rjvCEmXaIlTR+bVb6pTa5eRXKU3bfhnKP2yeSk3T6N40mROqn/5fjljO4JRdLRI CEO0AazDToRbj2PueiwnivH5j3Z9/ACukA0bptDkwf7QvXkx0Y9eFdOPaKl6WmWagD9J iJlE0R2CrUeq6nD29i4ZmL2SZsGpyciM38Qx7BvWrTS6torml7KtleWj1ys7bSGO8N8y jN+Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781131175; x=1781735975; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:subject:to:from:date:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=TmMllbZ8j7ku/DzVmSOIdvY9RjRvjL4eqrd9gxhTRgI=; b=fE7Lt2StaXsdxqXNSkqSvRYqmVKJJ06KmGwsg3zbElRhx3NOKoXMhae6mya/a65Ax6 3/KjJiVggqpviGvWYPXKH2lhrV+bFnToPjb8t1U8KLYaGkAr+4HBOJpClP3+qw/puqnd Sa5WzTA6OsIaMiIl6uE/a9JKSsicpw7I+u9DNFmrNup244LRSlfhZXjcLxS9rPmy8d+U PiSiwajLg7ir+bhFUAeCg0YirP76k3yuFZ7jlWh+3jncuDCfjbGDJ0pq+ZimoX0PYF6F +Q78L5jCeuVqEsYZn0Uz/Kp8vP94xsa5CmkjfXO9PhuMMiB1sz4vP6hMza9FjXRCW73U zNWQ== X-Forwarded-Encrypted: i=1; AFNElJ8aAfLTI4hMgTLv8ysYN63D1U/ViDOKhHp91Zn4SUbsL5PH9PLZxraSgYU+awQp5UdqiBt66eOyh+Vu23c=@vger.kernel.org X-Gm-Message-State: AOJu0YzaJ7JqiRI30zNeHKfLmMMDhqJOjAQJhVXiF8qrAWn/bcK3Ll4K zmndTKRyszY6M/FbIV5eBal2V61N387B5wjiR0bSHuBYAw6F8G6tDrFD X-Gm-Gg: Acq92OFyaaPijHCxiVIqNjzUUB8w+R4Gm8B70CN4DmyPrc4Gtz8cg2YtHtGWCMry2gh tgIqd310dwXO0GM/CLR9oWcOavCirmig25lJTgepLZ9k0oQFmjI/NWNlNdalGHSkY/ihON+xvlB 068UtWCZvxjh7TNOJZorgJxLuDua5T5V/6Txc3BBHDx1LjAHCO+GweJ66D7QpJdrCLNK2PU5REz YUO3qGuSV/IKSOFUXXNxAcZQAaYnkvBCn9ZFBt5ur8rR1dFDTX1BzA43eSBim/TeYtRg1uijM+E u2wH1mHkHfgBjdfLoAhPZOJ7YX0194s9xNv3rLGzDz1ZaxNVb+TFz932ohgZrZOKV5fJllEluZd Qqt7vv1EQF/GdSo+7A8bwz6eove+2htU0UPdFikjZ7s1L1RkU6jkfPd2wPPZDbHZN8Ba//LXd5C BRpNaPn1LuWO9Yc826df2reRvuvd+YkhzCszngsa/wWiAC0/n5pikbypGw X-Received: by 2002:a05:600c:620b:b0:48a:53ea:140b with SMTP id 5b1f17b1804b1-490c25cf45bmr442617075e9.28.1781131174820; Wed, 10 Jun 2026 15:39:34 -0700 (PDT) Received: from [127.0.0.1] ([141.255.129.1]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4601f360bd6sm81616932f8f.36.2026.06.10.15.39.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 10 Jun 2026 15:39:34 -0700 (PDT) Date: Wed, 10 Jun 2026 15:39:34 -0700 (PDT) From: Charles Pellegrini To: akpm@linux-foundation.org, egorenar-dev@posteo.net, robert.jarzmik@free.fr, t-pratham@ti.com, david@davidgow.net, linux-kernel@vger.kernel.org Subject: [PATCH v2 3/5] lib: scatterlist: add KUnit tests for sg_split() Message-ID: <178113124324.90620.15726328110868523164@gmail.com> In-Reply-To: <178113124323.90620.6403136846887207198@gmail.com> References: <178027099087.72481.1976843064458686851@gmail.com> <178113124323.90620.6403136846887207198@gmail.com> Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add a KUnit suite for lib/sg_split.c covering the (input shape, skip, split sizes) matrix: full coverage, edge-aligned partial coverage, mid-entry partial coverage at several boundary positions across one to three input entries, skip-position variants, and multi-split runs. Each case checks the two documented post-conditions of sg_split(): the sum of each output split's entry lengths equals the requested split size, and the bytes exposed by each split match the corresponding region of the input. Backing memory uses a position-unique byte pattern so a content mismatch pinpoints which input region was mis-exposed. Each case also asserts that exactly one entry per split carries the end-of-list marker. Every case is run twice: unmapped (in_mapped_nents = 0, exercising sg_split_phys()) and with an identity DMA mapping (dma_len == length, contiguous synthetic IOVA, exercising sg_split_mapped() and asserting the DMA addresses and lengths). A further group exercises divergent CPU/DMA geometry (coalescing, where the DMA segmentation is coarser than the CPU one); those need a distinct dma_length field and are skipped where CONFIG_NEED_SG_DMA_LENGTH is not selected. The mid-entry partial cases fail without the sg_calculate_split() overshoot fix in patch 1; sg_split_t10_edge_first_two_trailing additionally guards the edge-aligned case that the 2021 fix proposal regressed. The unmapped multi-entry cases fail without the sg_split_phys() ->length fix in patch 2. Run with: tools/testing/kunit/kunit.py run sg_split Assisted-by: Claude:claude-opus-4-8 hegel-c Signed-off-by: Charles Pellegrini --- lib/Kconfig.debug | 14 + lib/tests/Makefile | 1 + lib/tests/sg_split_kunit.c | 511 +++++++++++++++++++++++++++++++++++++ 3 files changed, 526 insertions(+) create mode 100644 lib/tests/sg_split_kunit.c diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 8ff5adcfe1e0..378414619c88 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2516,6 +2516,20 @@ config SEQ_BUF_KUNIT_TEST If unsure, say N. +config SG_SPLIT_KUNIT_TEST + tristate "KUnit tests for sg_split()" if !KUNIT_ALL_TESTS + depends on KUNIT + select SG_SPLIT + default KUNIT_ALL_TESTS + help + This builds the sg_split() KUnit test suite. + It tests the scatterlist splitting helper defined in + lib/sg_split.c. For more information on KUnit and unit tests + in general please refer to the KUnit documentation in + Documentation/dev-tools/kunit/. + + If unsure, say N. + config STRING_KUNIT_TEST tristate "KUnit test string functions at runtime" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/tests/Makefile b/lib/tests/Makefile index 7e9c2fa52e35..80c727f6498b 100644 --- a/lib/tests/Makefile +++ b/lib/tests/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o obj-$(CONFIG_SEQ_BUF_KUNIT_TEST) += seq_buf_kunit.o +obj-$(CONFIG_SG_SPLIT_KUNIT_TEST) += sg_split_kunit.o obj-$(CONFIG_SIPHASH_KUNIT_TEST) += siphash_kunit.o obj-$(CONFIG_SLUB_KUNIT_TEST) += slub_kunit.o obj-$(CONFIG_TEST_SORT) += test_sort.o diff --git a/lib/tests/sg_split_kunit.c b/lib/tests/sg_split_kunit.c new file mode 100644 index 000000000000..82eef313944d --- /dev/null +++ b/lib/tests/sg_split_kunit.c @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for lib/sg_split.c. + * + * Each case sets up a deterministic input scatterlist (page + offset + + * length per entry), calls sg_split(), and verifies the two documented + * post-conditions: + * + * 1. sum of out[k] entry lengths equals split_sizes[k] (per-split size) + * 2. the bytes walked from out[k] match input bytes + * [skip + sum(prev sizes) .. +sizes[k]) (per-split content) + * + * Backing memory is a position-unique byte pattern, so a content mismatch + * identifies exactly which input region got mis-exposed. + * + * COVERAGE + * -------- + * Every base case T1..T16 (+ T8b) runs in two modes via sg_split_run(): + * - unmapped : in_mapped_nents = 0 -> the CPU-view path (sg_split_phys). + * - mapped : an identity DMA mapping (dma_len == length, IOVA laid out + * contiguously from SG_SPLIT_DMA_BASE) -> additionally the + * pass-2 / sg_split_mapped() path, asserting the DMA addresses + * and lengths as well. This is the path the upstream callers + * that pass a non-zero in_mapped_nents actually hit. + * + * The D* cases exercise DIVERGENT geometry, where the DMA segmentation is + * coarser than the CPU one (coalescing): dma_len != length, so pass 2 + * computes a different per-split nents than pass 1. They require a real, + * separate dma_length field and so are skipped on !NEED_SG_DMA_LENGTH + * arches (where sg_dma_len() aliases ->length and divergence cannot be + * represented). + */ + +#include +#include +#include +#include + +#define SG_SPLIT_MAX_IN_ENTRIES 8 +#define SG_SPLIT_MAX_SPLITS 4 +#define SG_SPLIT_PAGE_BYTES 4096 +/* Synthetic IOVA base, fits a 32-bit dma_addr_t, clearly not a kernel VA. */ +#define SG_SPLIT_DMA_BASE ((dma_addr_t)0x40000000UL) + +struct sg_split_in_spec { + int page; + unsigned int off; + unsigned int len; +}; + +static u8 sg_split_pattern(int page, unsigned int off) +{ + return (u8)(((page * 31u) + off * 17u + 0x5a) & 0xffu); +} + +/* Verify the DMA view of one split: addresses tile the contiguous IOVA + * window [SG_SPLIT_DMA_BASE + cursor ..] and the DMA lengths sum to want. + */ +static void sg_split_check_dma(struct kunit *test, const char *mode, int k, + struct scatterlist *out, int dma_nents, + size_t want, unsigned long cursor) +{ + struct scatterlist *sg; + unsigned long sum = 0, pos = cursor; + int j; + + for_each_sg(out, sg, dma_nents, j) { + KUNIT_EXPECT_EQ_MSG(test, (u64)sg_dma_address(sg), + (u64)(SG_SPLIT_DMA_BASE + pos), + "[%s] split[%d] dma addr[%d]=0x%llx want 0x%llx", + mode, k, j, (u64)sg_dma_address(sg), + (u64)(SG_SPLIT_DMA_BASE + pos)); + sum += sg_dma_len(sg); + pos += sg_dma_len(sg); + } + KUNIT_EXPECT_EQ_MSG(test, sum, want, + "[%s] split[%d] DMA sum=%lu requested=%zu", + mode, k, sum, want); +} + +/* Verify the CPU view of one split: ->length sums to want and the page + * bytes match the linear oracle window. nents is the number of CPU + * entries to walk (out_nents when geometry matches, sg_nents() under + * coalescing). + */ +static void sg_split_check_cpu(struct kunit *test, const char *mode, int k, + struct scatterlist *out, int nents, size_t want, + const u8 *oracle, unsigned long cursor) +{ + struct scatterlist *sg; + unsigned long sum = 0; + size_t copied = 0; + u8 *got; + int j; + + for_each_sg(out, sg, nents, j) + sum += sg->length; + KUNIT_EXPECT_EQ_MSG(test, sum, want, + "[%s] split[%d] CPU sum=%lu requested=%zu", + mode, k, sum, want); + + got = kunit_kzalloc(test, want + 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, got); + for_each_sg(out, sg, nents, j) { + size_t take = min_t(size_t, sg->length, want - copied); + + memcpy(got + copied, sg_virt(sg), take); + copied += take; + if (copied >= want) + break; + } + KUNIT_EXPECT_EQ_MSG(test, + memcmp(got, oracle + cursor, + min_t(size_t, copied, want)), + 0, "[%s] split[%d] CPU bytes mismatch", mode, k); +} + +/* Allocate + pattern-fill backing, build the input SG, and build the + * linear concatenation oracle. Returns the input SG; *oracle_out and + * *backing_out get the two kunit-managed buffers. + */ +static struct scatterlist * +sg_split_build_input(struct kunit *test, + const struct sg_split_in_spec *in_spec, int n_in, + u8 **oracle_out) +{ + u8 *backing, *oracle; + struct scatterlist *in_sgl; + size_t total = 0, op = 0; + int i; + + backing = kunit_kzalloc(test, + SG_SPLIT_MAX_IN_ENTRIES * SG_SPLIT_PAGE_BYTES, + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, backing); + for (int p = 0; p < SG_SPLIT_MAX_IN_ENTRIES; p++) + for (unsigned int o = 0; o < SG_SPLIT_PAGE_BYTES; o++) + backing[p * SG_SPLIT_PAGE_BYTES + o] = + sg_split_pattern(p, o); + + in_sgl = kunit_kzalloc(test, sizeof(*in_sgl) * n_in, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, in_sgl); + sg_init_table(in_sgl, n_in); + for (i = 0; i < n_in; i++) { + u8 *buf = backing + in_spec[i].page * SG_SPLIT_PAGE_BYTES + + in_spec[i].off; + + sg_set_buf(&in_sgl[i], buf, in_spec[i].len); + total += in_spec[i].len; + } + + oracle = kunit_kzalloc(test, total ? total : 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, oracle); + for (i = 0; i < n_in; i++) { + memcpy(oracle + op, + backing + in_spec[i].page * SG_SPLIT_PAGE_BYTES + + in_spec[i].off, + in_spec[i].len); + op += in_spec[i].len; + } + + *oracle_out = oracle; + return in_sgl; +} + +/* Run one case in one mode (unmapped or identity-mapped). */ +static void sg_split_run_mode(struct kunit *test, const char *mode, + const struct sg_split_in_spec *in_spec, int n_in, + off_t skip, const size_t *sizes, int nb_splits, + bool dma_mapped) +{ + struct scatterlist *in_sgl; + struct scatterlist *out[SG_SPLIT_MAX_SPLITS] = { NULL }; + int out_nents[SG_SPLIT_MAX_SPLITS] = { 0 }; + unsigned long cursor = (unsigned long)skip; + u8 *oracle; + int rc, k; + + KUNIT_ASSERT_LE(test, n_in, SG_SPLIT_MAX_IN_ENTRIES); + KUNIT_ASSERT_LE(test, nb_splits, SG_SPLIT_MAX_SPLITS); + + in_sgl = sg_split_build_input(test, in_spec, n_in, &oracle); + + if (dma_mapped) { + /* Identity mapping: dma_len == length, contiguous IOVA. */ + dma_addr_t a = SG_SPLIT_DMA_BASE; + int i; + + for (i = 0; i < n_in; i++) { + sg_dma_address(&in_sgl[i]) = a; + sg_dma_len(&in_sgl[i]) = in_spec[i].len; + a += in_spec[i].len; + } + } + + rc = sg_split(in_sgl, dma_mapped ? n_in : 0, skip, nb_splits, sizes, + out, out_nents, GFP_KERNEL); + KUNIT_ASSERT_EQ_MSG(test, rc, 0, "[%s] sg_split returned %d", mode, rc); + + for (k = 0; k < nb_splits; k++) { + KUNIT_ASSERT_NOT_NULL_MSG(test, out[k], "[%s] split[%d] NULL", + mode, k); + /* Exactly one end marker, on the final entry: each entry is + * end-marked iff it is the last. out_nents == 0 (the + * degenerate zero-size split, out[k] == ZERO_SIZE_PTR) walks + * zero times, so we never index out[k][-1]. + */ + for (int j = 0; j < out_nents[k]; j++) + KUNIT_EXPECT_EQ_MSG(test, !!sg_is_last(&out[k][j]), + j == out_nents[k] - 1, + "[%s] split[%d] entry[%d] end-mark wrong", + mode, k, j); + /* Identity mapping keeps CPU and DMA geometry equal, so + * out_nents[k] is both counts; it is also 0 for a degenerate + * zero-size split, keeping the walk safe. + */ + sg_split_check_cpu(test, mode, k, out[k], out_nents[k], + sizes[k], oracle, cursor); + if (dma_mapped) + sg_split_check_dma(test, mode, k, out[k], out_nents[k], + sizes[k], cursor); + cursor += sizes[k]; + } + + for (k = 0; k < nb_splits; k++) + kfree(out[k]); +} + +/* Run a case both unmapped and identity-mapped. */ +static void sg_split_run(struct kunit *test, + const struct sg_split_in_spec *in_spec, int n_in, + off_t skip, const size_t *sizes, int nb_splits) +{ + sg_split_run_mode(test, "unmapped", in_spec, n_in, skip, sizes, + nb_splits, false); + sg_split_run_mode(test, "mapped", in_spec, n_in, skip, sizes, + nb_splits, true); +} + +/* ======================================================================== + * Group A — full or edge-aligned coverage. + */ +static void sg_split_t1_single_entry_full(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 100 } }; + const size_t sizes[] = { 100 }; + + sg_split_run(test, in, 1, 0, sizes, 1); +} + +static void sg_split_t2_two_entries_full_coverage(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 60 }, { 1, 0, 40 } }; + const size_t sizes[] = { 100 }; + + sg_split_run(test, in, 2, 0, sizes, 1); +} + +static void sg_split_t3_two_entries_two_splits_full(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 60 }, { 1, 0, 40 } }; + const size_t sizes[] = { 50, 50 }; + + sg_split_run(test, in, 2, 0, sizes, 2); +} + +static void sg_split_t4_edge_aligned_partial(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 60 }, { 1, 0, 40 } }; + const size_t sizes[] = { 60 }; + + sg_split_run(test, in, 2, 0, sizes, 1); +} + +/* ======================================================================== + * Group B — partial coverage with mid-entry boundary. + */ +static void sg_split_t5_minimal_repro(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 2 }, { 1, 0, 1 } }; + const size_t sizes[] = { 1 }; + + sg_split_run(test, in, 2, 0, sizes, 1); +} + +static void sg_split_t6_dthev2_aead_shape(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 64 }, { 1, 0, 100 }, { 2, 0, 16 } }; + const size_t sizes[] = { 100 }; + + sg_split_run(test, in, 3, 64, sizes, 1); +} + +static void sg_split_t7_skip_plus_partial_midentry(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 100 }, { 1, 0, 50 } }; + const size_t sizes[] = { 30 }; + + sg_split_run(test, in, 2, 20, sizes, 1); +} + +/* ======================================================================== + * Group C — trailing zero-size split. + */ +static void sg_split_t8_sizes_trailing_zero(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 10 }, { 1, 0, 5 } }; + const size_t sizes[] = { 10, 0 }; + + sg_split_run(test, in, 2, 0, sizes, 2); +} + +/* ======================================================================== + * Group D — boundary-position matrix (3 x 10-byte entries). + */ +static void sg_split_t9_mid_first_two_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } }; + const size_t sizes[] = { 5 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +static void sg_split_t10_edge_first_two_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } }; + const size_t sizes[] = { 10 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +static void sg_split_t11_mid_middle_one_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } }; + const size_t sizes[] = { 15 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +static void sg_split_t12_mid_last_no_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } }; + const size_t sizes[] = { 25 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +/* ======================================================================== + * Group E — skip-position variations. + */ +static void sg_split_t13_skip_at_entry_boundary(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } }; + const size_t sizes[] = { 5 }; + + sg_split_run(test, in, 3, 10, sizes, 1); +} + +static void sg_split_t14_skip_fastforward_partial(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } }; + const size_t sizes[] = { 5 }; + + sg_split_run(test, in, 3, 12, sizes, 1); +} + +/* ======================================================================== + * Group F — multi-split, mid-entry internal transition. + */ +static void sg_split_t15_two_splits_last_mid_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } }; + const size_t sizes[] = { 5, 10 }; + + sg_split_run(test, in, 3, 0, sizes, 2); +} + +static void sg_split_t16_two_splits_full_coverage_mid_first(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { { 0, 0, 10 }, { 1, 0, 10 } }; + const size_t sizes[] = { 3, 17 }; + + sg_split_run(test, in, 2, 0, sizes, 2); +} + +/* ======================================================================== + * Group G — divergent CPU/DMA geometry (coalescing). NEED arches only: + * needs a separate dma_length field so dma_len != length is representable. + */ +static void sg_split_run_divergent(struct kunit *test, + const struct sg_split_in_spec *in_spec, + int n_in, off_t skip, + const size_t *sizes, int nb_splits, + const unsigned int *dma_seg, int n_dma) +{ +#ifndef CONFIG_NEED_SG_DMA_LENGTH + kunit_skip(test, + "divergent CPU/DMA geometry needs a separate dma_length field (NEED_SG_DMA_LENGTH)"); +#else + struct scatterlist *in_sgl; + struct scatterlist *out[SG_SPLIT_MAX_SPLITS] = { NULL }; + int out_nents[SG_SPLIT_MAX_SPLITS] = { 0 }; + unsigned long cursor = (unsigned long)skip; + dma_addr_t a = SG_SPLIT_DMA_BASE; + u8 *oracle; + int rc, k, i; + + KUNIT_ASSERT_LE(test, n_in, SG_SPLIT_MAX_IN_ENTRIES); + KUNIT_ASSERT_LE(test, nb_splits, SG_SPLIT_MAX_SPLITS); + + in_sgl = sg_split_build_input(test, in_spec, n_in, &oracle); + + /* Coalesced DMA view on the first n_dma entries, contiguous IOVA. */ + for (i = 0; i < n_dma; i++) { + sg_dma_address(&in_sgl[i]) = a; + sg_dma_len(&in_sgl[i]) = dma_seg[i]; + a += dma_seg[i]; + } + + rc = sg_split(in_sgl, n_dma, skip, nb_splits, sizes, + out, out_nents, GFP_KERNEL); + KUNIT_ASSERT_EQ_MSG(test, rc, 0, "divergent sg_split returned %d", rc); + + for (k = 0; k < nb_splits; k++) { + KUNIT_ASSERT_NOT_NULL_MSG(test, out[k], "split[%d] NULL", k); + /* CPU view describes the scattered pages (pass 1): its entry + * count is the end-marker count sg_nents(), not the (smaller) + * DMA count out_nents[k]. + */ + sg_split_check_cpu(test, "divergent", k, out[k], + sg_nents(out[k]), sizes[k], oracle, cursor); + /* DMA view describes the coalesced contiguous IOVA (pass 2). */ + sg_split_check_dma(test, "divergent", k, out[k], out_nents[k], + sizes[k], cursor); + cursor += sizes[k]; + } + + for (k = 0; k < nb_splits; k++) + kfree(out[k]); +#endif +} + +static void sg_split_d1_coalesce_3to1(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 4096 }, { 1, 0, 4096 }, { 2, 0, 4096 } }; + const size_t sizes[] = { 5000, 7288 }; + const unsigned int dma[] = { 12288 }; + + sg_split_run_divergent(test, in, 3, 0, sizes, 2, dma, 1); +} + +static void sg_split_d2_coalesce_3to2(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 4096 }, { 1, 0, 4096 }, { 2, 0, 4096 } }; + const size_t sizes[] = { 6000, 6288 }; + const unsigned int dma[] = { 8192, 4096 }; + + sg_split_run_divergent(test, in, 3, 0, sizes, 2, dma, 2); +} + +static void sg_split_d3_coalesce_3to1_skip(struct kunit *test) +{ + const struct sg_split_in_spec in[] = { + { 0, 0, 4096 }, { 1, 0, 4096 }, { 2, 0, 4096 } }; + const size_t sizes[] = { 5000 }; + const unsigned int dma[] = { 12288 }; + + sg_split_run_divergent(test, in, 3, 1000, sizes, 1, dma, 1); +} + +static struct kunit_case sg_split_test_cases[] = { + KUNIT_CASE(sg_split_t1_single_entry_full), + KUNIT_CASE(sg_split_t2_two_entries_full_coverage), + KUNIT_CASE(sg_split_t3_two_entries_two_splits_full), + KUNIT_CASE(sg_split_t4_edge_aligned_partial), + KUNIT_CASE(sg_split_t5_minimal_repro), + KUNIT_CASE(sg_split_t6_dthev2_aead_shape), + KUNIT_CASE(sg_split_t7_skip_plus_partial_midentry), + KUNIT_CASE(sg_split_t8_sizes_trailing_zero), + KUNIT_CASE(sg_split_t9_mid_first_two_trailing), + KUNIT_CASE(sg_split_t10_edge_first_two_trailing), + KUNIT_CASE(sg_split_t11_mid_middle_one_trailing), + KUNIT_CASE(sg_split_t12_mid_last_no_trailing), + KUNIT_CASE(sg_split_t13_skip_at_entry_boundary), + KUNIT_CASE(sg_split_t14_skip_fastforward_partial), + KUNIT_CASE(sg_split_t15_two_splits_last_mid_trailing), + KUNIT_CASE(sg_split_t16_two_splits_full_coverage_mid_first), + KUNIT_CASE(sg_split_d1_coalesce_3to1), + KUNIT_CASE(sg_split_d2_coalesce_3to2), + KUNIT_CASE(sg_split_d3_coalesce_3to1_skip), + {} +}; + +static struct kunit_suite sg_split_test_suite = { + .name = "sg_split", + .test_cases = sg_split_test_cases, +}; + +kunit_test_suite(sg_split_test_suite); +MODULE_DESCRIPTION("KUnit tests for lib/sg_split.c"); +MODULE_LICENSE("GPL"); -- 2.47.3