* [PATCH v2 0/2] arm64: csum: Add fused copy and Internet checksum
@ 2026-11-14 3:56 Demian Shulhan
2026-11-14 3:57 ` [PATCH v2 1/2] " Demian Shulhan
2026-11-14 3:57 ` [PATCH v2 2/2] lib/tests: checksum: Add KUnit tests for csum_partial_copy_nocheck() Demian Shulhan
0 siblings, 2 replies; 3+ messages in thread
From: Demian Shulhan @ 2026-11-14 3:56 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon
Cc: Mark Rutland, David Laight, Jakub Kicinski, Nathan Chancellor,
Saeed Bishara, Eric Biggers, Ard Biesheuvel, Robin Murphy,
Andrew Morton, Marco Elver, David Gow, Brendan Higgins,
linux-arm-kernel, netdev, kunit-dev, linux-kernel
Add a single-pass csum_partial_copy_nocheck() implementation for arm64
and extend the checksum KUnit suite to cover copy-and-checksum across
alignments, lengths, guard pages, and interrupt contexts.
Changes in v2:
- Drop the NEON path and separate csum-copy* files, folding the scalar
implementation directly into arch/arm64/lib/csum.c (resolves the
<asm/neon-intrinsics.h> include issue noted by Nathan Chancellor).
- Switch the scalar loop to four __uint128_t accumulators (David Laight).
- Canonicalize 0xffff (-0) to 0x0000 (+0) after csum_fold() in KUnit to
fix x86_64 test failures on zero/all-zero inputs (Jakub Kicinski).
Link: https://lore.kernel.org/r/20260927131838.6774-1-demyansh@gmail.com
Demian Shulhan (2):
arm64: csum: Add fused copy and Internet checksum
lib/tests: checksum: Add KUnit tests for csum_partial_copy_nocheck()
arch/arm64/include/asm/checksum.h | 3 +
arch/arm64/lib/csum.c | 100 ++++++++
lib/Kconfig.debug | 10 +
lib/tests/checksum_kunit.c | 410 ++++++++++++++++++++++++++++++
4 files changed, 523 insertions(+)
base-commit: 97d73c8251226cf27cc6656b4c542869bb8c69fa
--
2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 1/2] arm64: csum: Add fused copy and Internet checksum
2026-11-14 3:56 [PATCH v2 0/2] arm64: csum: Add fused copy and Internet checksum Demian Shulhan
@ 2026-11-14 3:57 ` Demian Shulhan
2026-11-14 3:57 ` [PATCH v2 2/2] lib/tests: checksum: Add KUnit tests for csum_partial_copy_nocheck() Demian Shulhan
1 sibling, 0 replies; 3+ messages in thread
From: Demian Shulhan @ 2026-11-14 3:57 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon
Cc: Mark Rutland, David Laight, Jakub Kicinski, Nathan Chancellor,
Saeed Bishara, Eric Biggers, Ard Biesheuvel, Robin Murphy,
Andrew Morton, Marco Elver, David Gow, Brendan Higgins,
linux-arm-kernel, netdev, kunit-dev, linux-kernel
arm64 currently falls back to the generic csum_partial_copy_nocheck() in
lib/checksum.c, which performs memcpy() followed by a second pass in
csum_partial(). This double pass is hit on software GSO paths
(skb_segment() without SG or TX checksum offload, e.g. TCP over
WireGuard) and UDP/ICMP replies.
Implement a single-pass csum_partial_copy_nocheck() in
arch/arm64/lib/csum.c using four independent __uint128_t accumulators
over a 32-byte stride, followed by 16/8/4/2/1-byte tail handling. Each
word is read once into a GPR before storing and summing, emitting paired
ldp/stp and adds/cinc carry propagation with no stack frame.
Throughput on ARM Neoverse-V2 (Google Axion, 3.0 GHz, GCC 15.2 -O2,
warm L1 cache):
len memcpy + do_csum csum_partial_copy_nocheck speedup
---- ---------------- ------------------------- -------
64 10245 MB/s 15902 MB/s +55.2%
256 18851 MB/s 26026 MB/s +38.1%
512 22465 MB/s 28421 MB/s +26.5%
1024 24330 MB/s 29900 MB/s +22.9%
1368 24192 MB/s 30221 MB/s +24.9%
1448 24188 MB/s 30279 MB/s +25.2%
1500 24165 MB/s 30096 MB/s +24.5%
4096 25299 MB/s 29946 MB/s +18.4%
Suggested-by: David Laight <david.laight.linux@gmail.com>
Signed-off-by: Demian Shulhan <demyansh@gmail.com>
---
arch/arm64/include/asm/checksum.h | 3 +
arch/arm64/lib/csum.c | 100 ++++++++++++++++++++++++++++++
2 files changed, 103 insertions(+)
diff --git a/arch/arm64/include/asm/checksum.h b/arch/arm64/include/asm/checksum.h
index dc52b733675d..9f766db01152 100644
--- a/arch/arm64/include/asm/checksum.h
+++ b/arch/arm64/include/asm/checksum.h
@@ -44,6 +44,9 @@ static inline __sum16 ip_fast_csum(const void *iph, unsigned int ihl)
extern unsigned int do_csum(const unsigned char *buff, int len);
#define do_csum do_csum
+#define _HAVE_ARCH_CSUM_AND_COPY
+__wsum csum_partial_copy_nocheck(const void *src, void *dst, int len);
+
#include <asm-generic/checksum.h>
#endif /* __ASM_CHECKSUM_H */
diff --git a/arch/arm64/lib/csum.c b/arch/arm64/lib/csum.c
index 75db718e5b6a..a9617cde28c1 100644
--- a/arch/arm64/lib/csum.c
+++ b/arch/arm64/lib/csum.c
@@ -2,8 +2,10 @@
// Copyright (C) 2019-2020 Arm Ltd.
#include <linux/compiler.h>
+#include <linux/export.h>
#include <linux/kasan-checks.h>
#include <linux/kernel.h>
+#include <linux/unaligned.h>
#include <net/checksum.h>
@@ -116,6 +118,104 @@ unsigned int __no_sanitize_address do_csum(const unsigned char *buff, int len)
return sum >> 16;
}
+static __always_inline __wsum csum_fold128(__uint128_t sum)
+{
+ u64 s;
+
+ sum += (sum >> 64) | (sum << 64);
+ s = sum >> 64;
+ s += (s >> 32) | (s << 32);
+ return (__force __wsum)(u32)(s >> 32);
+}
+
+/*
+ * Single-pass copy and ones' complement sum. Each word is read once into a
+ * GPR before storing and summing, so the returned sum always matches @dst
+ * even if @src is concurrently modified.
+ */
+__wsum csum_partial_copy_nocheck(const void *src, void *dst, int len)
+{
+ const u8 *s = src;
+ u8 *d = dst;
+ __uint128_t sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0;
+ unsigned int n;
+
+ if (unlikely(len <= 0))
+ return 0;
+
+ n = (unsigned int)len >> 5;
+ if (n) {
+ do {
+ u64 v0, v1;
+
+ v0 = get_unaligned((const u64 *)s);
+ v1 = get_unaligned((const u64 *)(s + 8));
+ put_unaligned(v0, (u64 *)d);
+ put_unaligned(v1, (u64 *)(d + 8));
+ sum0 += v0;
+ sum1 += v1;
+
+ v0 = get_unaligned((const u64 *)(s + 16));
+ v1 = get_unaligned((const u64 *)(s + 24));
+ put_unaligned(v0, (u64 *)(d + 16));
+ put_unaligned(v1, (u64 *)(d + 24));
+ sum2 += v0;
+ sum3 += v1;
+
+ s += 32;
+ d += 32;
+ } while (--n);
+
+ sum0 += sum2;
+ sum1 += sum3;
+ }
+
+ if (len & 16) {
+ u64 v0 = get_unaligned((const u64 *)s);
+ u64 v1 = get_unaligned((const u64 *)(s + 8));
+
+ put_unaligned(v0, (u64 *)d);
+ put_unaligned(v1, (u64 *)(d + 8));
+ sum0 += v0;
+ sum1 += v1;
+ s += 16;
+ d += 16;
+ }
+ if (len & 8) {
+ u64 v = get_unaligned((const u64 *)s);
+
+ put_unaligned(v, (u64 *)d);
+ sum0 += v;
+ s += 8;
+ d += 8;
+ }
+ if (len & 4) {
+ u32 v = get_unaligned((const u32 *)s);
+
+ put_unaligned(v, (u32 *)d);
+ sum1 += v;
+ s += 4;
+ d += 4;
+ }
+ if (len & 2) {
+ u16 v = get_unaligned((const u16 *)s);
+
+ put_unaligned(v, (u16 *)d);
+ sum0 += v;
+ s += 2;
+ d += 2;
+ }
+ if (len & 1) {
+ u8 v = *s;
+
+ *d = v;
+ sum1 += v;
+ }
+
+ return csum_fold128(sum0 + sum1);
+}
+EXPORT_SYMBOL(csum_partial_copy_nocheck);
+
__sum16 csum_ipv6_magic(const struct in6_addr *saddr,
const struct in6_addr *daddr,
__u32 len, __u8 proto, __wsum csum)
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v2 2/2] lib/tests: checksum: Add KUnit tests for csum_partial_copy_nocheck()
2026-11-14 3:56 [PATCH v2 0/2] arm64: csum: Add fused copy and Internet checksum Demian Shulhan
2026-11-14 3:57 ` [PATCH v2 1/2] " Demian Shulhan
@ 2026-11-14 3:57 ` Demian Shulhan
1 sibling, 0 replies; 3+ messages in thread
From: Demian Shulhan @ 2026-11-14 3:57 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon
Cc: Mark Rutland, David Laight, Jakub Kicinski, Nathan Chancellor,
Saeed Bishara, Eric Biggers, Ard Biesheuvel, Robin Murphy,
Andrew Morton, Marco Elver, David Gow, Brendan Higgins,
linux-arm-kernel, netdev, kunit-dev, linux-kernel
Extend the checksum KUnit test suite to cover csum_partial_copy_nocheck()
across alignments, tail lengths, bit patterns, vmalloc() guard pages with
canary checks, and concurrent task/softirq/hardirq execution.
Compare folded checksums against both a standalone C reference and
csum_partial(), canonicalizing 0xffff (-0) to 0x0000 (+0) after
csum_fold() so zero and all-zero inputs match across architectures such
as x86_64.
Also add an optional throughput benchmark gated behind
CONFIG_CHECKSUM_KUNIT_BENCHMARK (default n).
Signed-off-by: Demian Shulhan <demyansh@gmail.com>
---
lib/Kconfig.debug | 10 +
lib/tests/checksum_kunit.c | 410 +++++++++++++++++++++++++++++++++++++
2 files changed, 420 insertions(+)
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 2d22638ec13a..588343ced260 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2770,6 +2770,16 @@ config CHECKSUM_KUNIT
If unsure, say N.
+config CHECKSUM_KUNIT_BENCHMARK
+ bool "Benchmark for the checksum functions"
+ depends on CHECKSUM_KUNIT
+ help
+ Include a benchmark of csum_partial_copy_nocheck() against
+ memcpy() followed by csum_partial() in the checksum KUnit test
+ suite, for a range of buffer lengths and misalignments. The
+ throughput of both is logged as test output. Only meaningful on
+ real hardware and is not intended for production builds.
+
config UTIL_MACROS_KUNIT
tristate "KUnit test util_macros.h functions at runtime" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/lib/tests/checksum_kunit.c b/lib/tests/checksum_kunit.c
index be04aa42125c..69611daa7c0c 100644
--- a/lib/tests/checksum_kunit.c
+++ b/lib/tests/checksum_kunit.c
@@ -3,8 +3,15 @@
* Test cases csum_partial, csum_fold, ip_fast_csum, csum_ipv6_magic
*/
+#include <kunit/run-in-irq-context.h>
#include <kunit/test.h>
+#include <linux/math64.h>
+#include <linux/prandom.h>
+#include <linux/preempt.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
#include <asm/checksum.h>
+#include <net/checksum.h>
#include <net/ip6_checksum.h>
#define MAX_LEN 512
@@ -619,18 +626,421 @@ static void test_csum_ipv6_magic(struct kunit *test)
}
}
+/*
+ * csum_partial_copy_nocheck() tests: verify copied bytes, folded checksum
+ * against a standalone reference and csum_partial(), canary/guard-page
+ * bounds, and concurrent task/softirq/hardirq execution.
+ */
+#define COPY_MAX_OFF 16
+#define COPY_MAX_LEN 4352
+#define COPY_BENCH_MAX_LEN 16384
+#define COPY_GUARD 128 /* canary span on each side of dst */
+#define COPY_CANARY 0xa5
+#define COPY_SEED 0x5eed
+
+static struct rnd_state copy_rng;
+/* vmalloc()ed with a page-aligned length, so each is followed by a guard page */
+static u8 *copy_src_area;
+static u8 *copy_dst_area;
+static u8 *copy_shadow; /* expected contents of copy_src_area */
+static size_t copy_area_len;
+
+static int checksum_suite_init(struct kunit_suite *suite)
+{
+ copy_area_len = round_up(COPY_BENCH_MAX_LEN + COPY_MAX_OFF, PAGE_SIZE);
+ copy_src_area = vmalloc(copy_area_len);
+ copy_dst_area = vmalloc(copy_area_len);
+ copy_shadow = vmalloc(copy_area_len);
+ if (!copy_src_area || !copy_dst_area || !copy_shadow) {
+ vfree(copy_src_area);
+ vfree(copy_dst_area);
+ vfree(copy_shadow);
+ return -ENOMEM;
+ }
+ prandom_seed_state(©_rng, COPY_SEED);
+ return 0;
+}
+
+static void checksum_suite_exit(struct kunit_suite *suite)
+{
+ vfree(copy_src_area);
+ vfree(copy_dst_area);
+ vfree(copy_shadow);
+}
+
+static void copy_fill_random(void)
+{
+ prandom_bytes_state(©_rng, copy_src_area, copy_area_len);
+ memcpy(copy_shadow, copy_src_area, copy_area_len);
+}
+
+static void copy_fill_bytes(const u8 *pattern, int pattern_len)
+{
+ size_t i;
+
+ for (i = 0; i < copy_area_len; i++)
+ copy_src_area[i] = pattern[i % pattern_len];
+ memcpy(copy_shadow, copy_src_area, copy_area_len);
+}
+
+/* Place @len bytes at misalignment @off as close as possible to the guard page. */
+static u8 *copy_place_at_end(u8 *area, int off, int len)
+{
+ u8 *end = area + copy_area_len;
+ int pad = ((unsigned long)(end - len) - off) & (COPY_MAX_OFF - 1);
+
+ return end - len - pad;
+}
+
+/* Naive reference: folded ones' complement sum, same semantics as csum_fold(). */
+static u16 ref_csum_fold(const u8 *buf, int len)
+{
+ u64 sum = 0;
+ u16 word;
+ int i;
+
+ for (i = 0; i + 1 < len; i += 2) {
+ memcpy(&word, buf + i, sizeof(word)); /* native endian */
+ sum += word;
+ }
+ if (len & 1) {
+ u8 pad[2] = { buf[len - 1], 0 };
+
+ memcpy(&word, pad, sizeof(word)); /* LE: low byte, BE: high byte */
+ sum += word;
+ }
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ sum = ~sum & 0xffff;
+ return sum == 0xffff ? 0 : sum;
+}
+
+/*
+ * RFC 1071: 0xffff (-0) and 0x0000 (+0) are equivalent. Canonicalize to 0 so
+ * ~0U-seeded archs (e.g. x86_64) match 0-seeded refs on zero/all-zero inputs.
+ */
+static u16 fold_csum(__wsum csum)
+{
+ u16 sum = (__force u16)csum_fold(csum);
+
+ return sum == 0xffff ? 0 : sum;
+}
+
+static int first_non_canary(const u8 *p, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++)
+ if (p[i] != COPY_CANARY)
+ return i;
+ return -1;
+}
+
+/* Verify one csum_partial_copy_nocheck() call with @before/@after canaries. */
+static void check_csum_copy_at(struct kunit *test, const u8 *src, u8 *dst,
+ int len, int before, int after)
+{
+ unsigned long src_off = (unsigned long)src & (COPY_MAX_OFF - 1);
+ unsigned long dst_off = (unsigned long)dst & (COPY_MAX_OFF - 1);
+ const u8 *lo, *hi;
+ u16 expect, got;
+ int bad;
+
+ memset(dst - before, COPY_CANARY, before + len + after);
+ expect = ref_csum_fold(src, len);
+
+ got = fold_csum(csum_partial_copy_nocheck(src, dst, len));
+
+ KUNIT_ASSERT_EQ_MSG(test, got, expect,
+ "checksum mismatch src_off=%lu dst_off=%lu len=%d",
+ src_off, dst_off, len);
+ KUNIT_ASSERT_EQ_MSG(test, memcmp(src, dst, len), 0,
+ "copied data mismatch src_off=%lu dst_off=%lu len=%d",
+ src_off, dst_off, len);
+
+ bad = first_non_canary(dst - before, before);
+ KUNIT_ASSERT_EQ_MSG(test, bad, -1,
+ "wrote %d bytes before dst (src_off=%lu dst_off=%lu len=%d)",
+ before - bad, src_off, dst_off, len);
+
+ bad = first_non_canary(dst + len, after);
+ KUNIT_ASSERT_EQ_MSG(test, bad, -1,
+ "wrote at dst+len+%d (src_off=%lu dst_off=%lu len=%d)",
+ bad, src_off, dst_off, len);
+
+ /* Source untouched, including a margin on either side. */
+ lo = max(src - COPY_GUARD, (const u8 *)copy_src_area);
+ hi = min(src + len + COPY_GUARD, (const u8 *)copy_src_area + copy_area_len);
+ KUNIT_ASSERT_EQ_MSG(test,
+ memcmp(lo, copy_shadow + (lo - copy_src_area), hi - lo), 0,
+ "source buffer modified (src_off=%lu dst_off=%lu len=%d)",
+ src_off, dst_off, len);
+
+ /* Must be interchangeable with the arch csum_partial() semantics. */
+ KUNIT_EXPECT_EQ_MSG(test, got,
+ fold_csum(csum_partial(src, len, 0)),
+ "disagrees with csum_partial() src_off=%lu dst_off=%lu len=%d",
+ src_off, dst_off, len);
+}
+
+/* Source against its guard page, destination between canaries. */
+static void check_csum_copy(struct kunit *test, int src_off, int dst_off,
+ int len)
+{
+ KUNIT_ASSERT_TRUE(test, src_off >= 0 && src_off < COPY_MAX_OFF);
+ KUNIT_ASSERT_TRUE(test, dst_off >= 0 && dst_off < COPY_MAX_OFF);
+ KUNIT_ASSERT_TRUE(test, len >= 0 && len <= COPY_MAX_LEN);
+
+ check_csum_copy_at(test, copy_place_at_end(copy_src_area, src_off, len),
+ copy_dst_area + COPY_GUARD + dst_off, len,
+ COPY_GUARD, COPY_GUARD);
+}
+
+/* Representative source offsets used with all 16 destination offsets. */
+static const int copy_src_offs[] = { 0, 1, 3, 8, 15 };
+
+/* Every len 0..64 with every src/dst misalignment: the scalar tail ladder. */
+static void test_csum_copy_small_all_alignments(struct kunit *test)
+{
+ int len, src_off, dst_off;
+
+ copy_fill_random();
+ for (len = 0; len <= 64; len++)
+ for (src_off = 0; src_off < COPY_MAX_OFF; src_off++)
+ for (dst_off = 0; dst_off < COPY_MAX_OFF; dst_off++)
+ check_csum_copy(test, src_off, dst_off, len);
+}
+
+/* Large unaligned buffers exercising multi-block strides and GSO/MTU lengths. */
+static void test_csum_copy_large_unaligned(struct kunit *test)
+{
+ static const int extra_lens[] = {
+ 1368, 1448, 1500, 2033, 2034, 2035, 2039, 2040, 2041, 2047,
+ 2048, 2049, 2055, 2056, 2057, 2062, 2063, 3072, 4095, 4096,
+ 4097, 4352,
+ };
+ int len, i, j, dst_off;
+
+ copy_fill_random();
+ for (len = 1024; len < 1024 + 80; len++)
+ for (i = 0; i < ARRAY_SIZE(copy_src_offs); i++)
+ for (dst_off = 0; dst_off < COPY_MAX_OFF; dst_off++)
+ check_csum_copy(test, copy_src_offs[i], dst_off,
+ len);
+
+ for (j = 0; j < ARRAY_SIZE(extra_lens); j++)
+ for (i = 0; i < ARRAY_SIZE(copy_src_offs); i++)
+ for (dst_off = 0; dst_off < COPY_MAX_OFF; dst_off++)
+ check_csum_copy(test, copy_src_offs[i], dst_off,
+ extra_lens[j]);
+}
+
+/* Patterns stressing carries, zero/MSB bytes, and odd-offset byte order. */
+static void test_csum_copy_patterns(struct kunit *test)
+{
+ static const u8 pat_ff[] = { 0xff };
+ static const u8 pat_00[] = { 0x00 };
+ static const u8 pat_80[] = { 0x80 };
+ static const u8 pat_ff00[] = { 0xff, 0x00 };
+ static const u8 pat_ramp[] = { 0x01, 0x02, 0x04, 0x08,
+ 0x10, 0x20, 0x40, 0x80 };
+ static const struct {
+ const u8 *pat;
+ int len;
+ } patterns[] = {
+ { pat_ff, ARRAY_SIZE(pat_ff) },
+ { pat_00, ARRAY_SIZE(pat_00) },
+ { pat_80, ARRAY_SIZE(pat_80) },
+ { pat_ff00, ARRAY_SIZE(pat_ff00) },
+ { pat_ramp, ARRAY_SIZE(pat_ramp) },
+ { NULL, 0 }, /* random */
+ };
+ static const int lens[] = {
+ 1, 2, 3, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128,
+ 255, 256, 511, 512, 1023, 1024, 1025, 1368, 1448, 1500, 2048,
+ 4095, 4096, 4352,
+ };
+ static const int offs[][2] = {
+ { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 }, { 3, 7 }, { 7, 3 },
+ { 9, 5 }, { 15, 15 },
+ };
+ int p, l, o;
+
+ for (p = 0; p < ARRAY_SIZE(patterns); p++) {
+ if (patterns[p].pat)
+ copy_fill_bytes(patterns[p].pat, patterns[p].len);
+ else
+ copy_fill_random();
+
+ for (l = 0; l < ARRAY_SIZE(lens); l++)
+ for (o = 0; o < ARRAY_SIZE(offs); o++)
+ check_csum_copy(test, offs[o][0], offs[o][1],
+ lens[l]);
+ }
+}
+
+/* Place dst against its trailing guard page so any over-write faults. */
+static void test_csum_copy_dst_guard_page(struct kunit *test)
+{
+ static const int lens[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 31, 32, 33, 63, 64, 65, 79, 80, 81, 127, 128, 129, 1023, 1024,
+ 1025, 1368, 1448, 1500, 2048, 4095, 4096, 4097,
+ };
+ int l, src_off, dst_off;
+
+ copy_fill_random();
+ for (l = 0; l < ARRAY_SIZE(lens); l++)
+ for (dst_off = 0; dst_off < COPY_MAX_OFF; dst_off++) {
+ u8 *dst = copy_place_at_end(copy_dst_area, dst_off, lens[l]);
+ int after = copy_dst_area + copy_area_len - (dst + lens[l]);
+
+ src_off = (dst_off * 7 + lens[l]) & (COPY_MAX_OFF - 1);
+ check_csum_copy_at(test,
+ copy_place_at_end(copy_src_area,
+ src_off, lens[l]),
+ dst, lens[l], COPY_GUARD, after);
+ }
+}
+
+/* len == 0: folded checksum must be zero and dst must not be touched. */
+static void test_csum_copy_zero_len(struct kunit *test)
+{
+ u8 *dst = copy_dst_area + COPY_GUARD;
+ __wsum sum;
+
+ copy_fill_random();
+ memset(copy_dst_area, COPY_CANARY, 2 * COPY_GUARD);
+ sum = csum_partial_copy_nocheck(copy_src_area, dst, 0);
+ KUNIT_EXPECT_EQ(test, fold_csum(sum), 0);
+ KUNIT_EXPECT_EQ(test, first_non_canary(copy_dst_area, 2 * COPY_GUARD), -1);
+}
+
+/* Concurrent execution across task, softirq, and hardirq contexts. */
+#define COPY_IRQ_NUM_MSGS 3
+#define COPY_IRQ_NUM_CTX 3 /* task, softirq, hardirq */
+
+static const int copy_irq_lens[COPY_IRQ_NUM_MSGS] = { 100, 1500, 4097 };
+
+struct copy_irq_test_state {
+ const u8 *src[COPY_IRQ_NUM_MSGS];
+ u16 expected[COPY_IRQ_NUM_MSGS];
+ u8 *dst[COPY_IRQ_NUM_CTX];
+ atomic_t seqno;
+};
+
+static bool copy_irq_test_func(void *state_)
+{
+ struct copy_irq_test_state *state = state_;
+ u32 i = (u32)atomic_inc_return(&state->seqno) % COPY_IRQ_NUM_MSGS;
+ int ctx = in_hardirq() ? 2 : in_serving_softirq() ? 1 : 0;
+ const u8 *src = state->src[i];
+ u8 *dst = state->dst[ctx];
+ int len = copy_irq_lens[i];
+ u16 got;
+
+ got = fold_csum(csum_partial_copy_nocheck(src, dst, len));
+ return got == state->expected[i] && memcmp(src, dst, len) == 0;
+}
+
+static void test_csum_copy_interrupt_context(struct kunit *test)
+{
+ struct copy_irq_test_state state = { };
+ int i;
+
+ copy_fill_random();
+ for (i = 0; i < COPY_IRQ_NUM_MSGS; i++) {
+ /* Distinct, misaligned messages. */
+ state.src[i] = copy_src_area + i * (COPY_MAX_LEN + 64) + 2 * i + 1;
+ state.expected[i] = ref_csum_fold(state.src[i], copy_irq_lens[i]);
+ }
+ for (i = 0; i < COPY_IRQ_NUM_CTX; i++)
+ state.dst[i] = copy_dst_area + i * (COPY_MAX_LEN + 64) + 2 * i + 1;
+
+ kunit_run_irq_test(test, copy_irq_test_func, 100000, &state);
+}
+
+/* Optional throughput benchmark vs memcpy() + csum_partial(). */
+static void test_csum_copy_benchmark(struct kunit *test)
+{
+ static const int lens[] = {
+ 16, 40, 64, 128, 256, 512, 1000, 1024, 1368, 1448, 1500,
+ 2048, 4096, 16384,
+ };
+ static const int offs[][2] = { { 0, 0 }, { 1, 3 } };
+ int l, o, i, len, num_iters;
+ u64 t, t_fused, t_split;
+ u32 acc = 0; /* keeps every call alive via OPTIMIZER_HIDE_VAR() */
+
+ if (!IS_ENABLED(CONFIG_CHECKSUM_KUNIT_BENCHMARK))
+ kunit_skip(test, "not enabled");
+
+ copy_fill_random();
+
+ /* warm-up */
+ for (i = 0; i < 10000000; i += COPY_BENCH_MAX_LEN)
+ acc ^= (__force u32)csum_partial_copy_nocheck(copy_src_area,
+ copy_dst_area,
+ COPY_BENCH_MAX_LEN);
+ OPTIMIZER_HIDE_VAR(acc);
+
+ for (o = 0; o < ARRAY_SIZE(offs); o++) {
+ const u8 *src = copy_src_area + offs[o][0];
+ u8 *dst = copy_dst_area + offs[o][1];
+
+ for (l = 0; l < ARRAY_SIZE(lens); l++) {
+ len = lens[l];
+ num_iters = 10000000 / (len + 128);
+
+ preempt_disable();
+ t = ktime_get_ns();
+ for (i = 0; i < num_iters; i++)
+ acc ^= (__force u32)csum_partial_copy_nocheck(src, dst, len);
+ t_fused = ktime_get_ns() - t;
+ OPTIMIZER_HIDE_VAR(acc);
+
+ t = ktime_get_ns();
+ for (i = 0; i < num_iters; i++) {
+ memcpy(dst, src, len);
+ acc ^= (__force u32)csum_partial(dst, len, 0);
+ }
+ t_split = ktime_get_ns() - t;
+ OPTIMIZER_HIDE_VAR(acc);
+ preempt_enable();
+
+ kunit_info(test,
+ "src+%d dst+%d len=%5d: csum_partial_copy_nocheck %6llu MB/s, memcpy+csum_partial %6llu MB/s\n",
+ offs[o][0], offs[o][1], len,
+ div64_u64((u64)len * num_iters * 1000,
+ max_t(u64, t_fused, 1)),
+ div64_u64((u64)len * num_iters * 1000,
+ max_t(u64, t_split, 1)));
+ }
+ }
+}
+
static struct kunit_case __refdata checksum_test_cases[] = {
KUNIT_CASE(test_csum_fixed_random_inputs),
KUNIT_CASE(test_csum_all_carry_inputs),
KUNIT_CASE(test_csum_no_carry_inputs),
KUNIT_CASE(test_ip_fast_csum),
KUNIT_CASE(test_csum_ipv6_magic),
+ KUNIT_CASE(test_csum_copy_small_all_alignments),
+ KUNIT_CASE(test_csum_copy_large_unaligned),
+ KUNIT_CASE(test_csum_copy_patterns),
+ KUNIT_CASE(test_csum_copy_dst_guard_page),
+ KUNIT_CASE(test_csum_copy_zero_len),
+ KUNIT_CASE(test_csum_copy_interrupt_context),
+ KUNIT_CASE(test_csum_copy_benchmark),
{}
};
static struct kunit_suite checksum_test_suite = {
.name = "checksum",
.test_cases = checksum_test_cases,
+ .suite_init = checksum_suite_init,
+ .suite_exit = checksum_suite_exit,
};
kunit_test_suites(&checksum_test_suite);
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-14 4:09 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 3:56 [PATCH v2 0/2] arm64: csum: Add fused copy and Internet checksum Demian Shulhan
2026-11-14 3:57 ` [PATCH v2 1/2] " Demian Shulhan
2026-11-14 3:57 ` [PATCH v2 2/2] lib/tests: checksum: Add KUnit tests for csum_partial_copy_nocheck() Demian Shulhan
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.