| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/28 20:55 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces an arm64-specific fused copy and checksum implementation (NEON and scalar variants of csum_partial_copy_nocheck) under arch/arm64/, which is structurally unreachable on the target architecture (amd64). The only generic changes are additions to the KUnit checksum test suite and benchmark in lib/tests/checksum_kunit.c and lib/Kconfig.debug, which are standalone unit tests rather than reachable production kernel code. Consequently, there are no reachable functional changes on amd64 to fuzz.",
"WorthFuzzing": false
} |
| 1/1 | 2026/09/28 20:55 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 3488c0ddc7779e84c662d5dfe3288a09267ad7af\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Sep 28 20:55:03 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/arch/arm64/include/asm/checksum.h b/arch/arm64/include/asm/checksum.h\nindex dc52b733675db..9f766db011526 100644\n--- a/arch/arm64/include/asm/checksum.h\n+++ b/arch/arm64/include/asm/checksum.h\n@@ -44,6 +44,9 @@ static inline __sum16 ip_fast_csum(const void *iph, unsigned int ihl)\n extern unsigned int do_csum(const unsigned char *buff, int len);\n #define do_csum do_csum\n \n+#define _HAVE_ARCH_CSUM_AND_COPY\n+__wsum csum_partial_copy_nocheck(const void *src, void *dst, int len);\n+\n #include \u003casm-generic/checksum.h\u003e\n \n #endif\t/* __ASM_CHECKSUM_H */\ndiff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile\nindex b33e1ca4a7815..5e1024c31f67a 100644\n--- a/arch/arm64/lib/Makefile\n+++ b/arch/arm64/lib/Makefile\n@@ -5,10 +5,15 @@ KCSAN_SANITIZE_delay.o := n\n \n lib-y\t\t:= clear_user.o delay.o copy_from_user.o\t\t\\\n \t\t copy_to_user.o copy_page.o\t\t\t\t\\\n-\t\t clear_page.o csum.o insn.o memchr.o memcpy.o\t\t\\\n+\t\t clear_page.o csum.o csum-copy.o csum-copy-neon.o\t\\\n+\t\t insn.o memchr.o memcpy.o\t\t\t\t\\\n \t\t memset.o memcmp.o strcmp.o strncmp.o strlen.o\t\\\n \t\t strnlen.o strchr.o strrchr.o tishift.o\n \n+# NEON intrinsics; only ever called between kernel_neon_begin()/end().\n+CFLAGS_csum-copy-neon.o\t\t+= $(CC_FLAGS_FPU)\n+CFLAGS_REMOVE_csum-copy-neon.o\t+= $(CC_FLAGS_NO_FPU)\n+\n lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o\n \n obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o\ndiff --git a/arch/arm64/lib/csum-copy-neon.c b/arch/arm64/lib/csum-copy-neon.c\nnew file mode 100644\nindex 0000000000000..bfeaf2beba39e\n--- /dev/null\n+++ b/arch/arm64/lib/csum-copy-neon.c\n@@ -0,0 +1,168 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Fused copy + Internet checksum for arm64: NEON path\n+ *\n+ * Copyright (C) 2026 Demian Shulhan \u003cdemyansh@gmail.com\u003e\n+ *\n+ * This file is compiled with CC_FLAGS_FPU (see Makefile) and must only be\n+ * entered between kernel_neon_begin() and kernel_neon_end(). Everything\n+ * else about the contract is in csum-copy.h.\n+ *\n+ * The routine runs to completion without chunking or yielding regardless\n+ * of @len. In task context kernel-mode NEON is preemptible (the caller's\n+ * FPSIMD state lives in a caller-provided buffer that is switched with the\n+ * task). In softirq context preemption is off, but the lengths that reach\n+ * this function are bounded by a single skb fragment or iov segment, i.e.\n+ * tens of KiB at the very most, which is a few microseconds here and well\n+ * within what the surrounding network softirq processing already spends.\n+ *\n+ * Main loop: 64 bytes per iteration. Each 16-byte vector is reduced with\n+ * UADDLP (8x16 -\u003e 4x32); pairs of vectors share a 32-bit accumulator via\n+ * UADALP, which is then drained into a 64-bit accumulator. This is six\n+ * vector ALU operations per iteration; giving every vector its own 64-bit\n+ * accumulator would need eight and measures ~10% slower, as the loop is\n+ * bound by vector ALU throughput rather than by dependency latency. A\n+ * 32-bit lane only ever holds the sum of four 16-bit words, so it cannot\n+ * overflow, and the 64-bit lanes cannot overflow for any 'int' length.\n+ * Two independent 64-bit accumulators keep the loop-carried dependency to\n+ * a single UADALP each. The dispatcher only enters this path for\n+ * len \u003e= CSUM_COPY_NEON_MIN_LEN, so the loop is entered unconditionally.\n+ *\n+ * Head: misaligned 16-byte stores are markedly more expensive than\n+ * misaligned loads, so @dst is aligned to 16 bytes first by copying one\n+ * full vector and masking the checksum contribution down to the leading\n+ * 'head' bytes. The main loop then runs on the byte grid starting at\n+ * @src + head; if head is odd, that grid is offset from the 16-bit word\n+ * grid of the buffer. Rather than fixing up every loop vector, the head\n+ * vector is byte-swapped within each lane (REV16) to match the loop grid\n+ * and the final folded 16-bit sum is rotated by 8 bits, which is exact for\n+ * ones' complement sums (the same trick as csum_block_add()/do_csum()).\n+ *\n+ * Tail (1..15 bytes): rather than dribbling bytes through GPRs, copy the\n+ * last 16 bytes of the buffer as one vector and mask off the bytes that\n+ * belong to the body. That vector overlaps up to 15 body bytes, so it is\n+ * copied *before* the body: the body's stores then overwrite the overlap\n+ * with the very bytes that were summed, and the destination always matches\n+ * the returned checksum even if the source is modified concurrently. If the\n+ * tail length is odd the tail vector is offset by one byte from the loop\n+ * grid, which is again corrected with REV16.\n+ *\n+ * Data is handled as uint8x16_t throughout: that is how it sits in memory,\n+ * and masking and REV16 are byte operations. The 16-bit word view only\n+ * appears in the two pairwise-add helpers.\n+ */\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003casm/neon-intrinsics.h\u003e\n+\n+#include \"csum-copy.h\"\n+\n+/*\n+ * Byte mask table. A 16-byte load at offset n (1..15) yields (16 - n) zero\n+ * bytes followed by n 0xff bytes, selecting the last n bytes of a vector;\n+ * a load at offset 32 - n yields n 0xff bytes followed by zeros, selecting\n+ * the first n bytes. One unaligned load, no lane-index compare.\n+ */\n+static const u8 csum_copy_mask[48] __aligned(16) = {\n+\t[16 ... 31] = 0xff,\n+};\n+\n+/* Adjacent 16-bit words of @v summed into 32-bit lanes (UADDLP). */\n+static __always_inline uint32x4_t words_sum(uint8x16_t v)\n+{\n+\treturn vpaddlq_u16(vreinterpretq_u16_u8(v));\n+}\n+\n+/* @acc += adjacent 16-bit words of @v (UADALP). */\n+static __always_inline uint32x4_t words_add(uint32x4_t acc, uint8x16_t v)\n+{\n+\treturn vpadalq_u16(acc, vreinterpretq_u16_u8(v));\n+}\n+\n+__wsum __csum_partial_copy_neon(const void *src, void *dst, int len)\n+{\n+\tconst u8 *s = src;\n+\tu8 *d = dst;\n+\tuint64x2_t acc0 = { }, acc1 = { };\n+\tunsigned int head = -(unsigned long)d \u0026 15;\n+\tunsigned int tail = (len - head) \u0026 15;\n+\tu32 sum;\n+\n+\tif (tail) {\n+\t\t/* Final vector, ending exactly at the buffer end; see above */\n+\t\tuint8x16_t v = vld1q_u8(s + len - 16);\n+\n+\t\tvst1q_u8(d + len - 16, v);\n+\t\tv \u0026= vld1q_u8(csum_copy_mask + tail);\n+\t\tif (tail \u0026 1)\n+\t\t\tv = vrev16q_u8(v);\t/* odd tail: match loop grid */\n+\t\tacc1 = vpadalq_u32(acc1, words_sum(v));\n+\t}\n+\n+\tif (head) {\n+\t\tuint8x16_t v = vld1q_u8(s);\n+\n+\t\tvst1q_u8(d, v);\n+\t\tv \u0026= vld1q_u8(csum_copy_mask + 32 - head);\n+\t\tif (head \u0026 1)\n+\t\t\tv = vrev16q_u8(v);\t/* odd head: match loop grid */\n+\t\tacc0 = vpadalq_u32(acc0, words_sum(v));\n+\n+\t\ts += head;\n+\t\td += head;\n+\t\tlen -= head;\n+\t}\n+\n+\tdo {\n+\t\tuint8x16_t v0 = vld1q_u8(s);\n+\t\tuint8x16_t v1 = vld1q_u8(s + 16);\n+\t\tuint8x16_t v2 = vld1q_u8(s + 32);\n+\t\tuint8x16_t v3 = vld1q_u8(s + 48);\n+\t\tuint32x4_t t0, t1;\n+\n+\t\tvst1q_u8(d, v0);\n+\t\tvst1q_u8(d + 16, v1);\n+\t\tvst1q_u8(d + 32, v2);\n+\t\tvst1q_u8(d + 48, v3);\n+\n+\t\tt0 = words_sum(v0);\n+\t\tt1 = words_sum(v1);\n+\t\tt0 = words_add(t0, v2);\n+\t\tt1 = words_add(t1, v3);\n+\t\tacc0 = vpadalq_u32(acc0, t0);\n+\t\tacc1 = vpadalq_u32(acc1, t1);\n+\n+\t\ts += 64;\n+\t\td += 64;\n+\t\tlen -= 64;\n+\t} while (len \u003e= 64);\n+\n+\tif (len \u0026 32) {\n+\t\tuint8x16_t v0 = vld1q_u8(s);\n+\t\tuint8x16_t v1 = vld1q_u8(s + 16);\n+\n+\t\tvst1q_u8(d, v0);\n+\t\tvst1q_u8(d + 16, v1);\n+\t\tacc0 = vpadalq_u32(acc0, words_add(words_sum(v0), v1));\n+\n+\t\ts += 32;\n+\t\td += 32;\n+\t}\n+\tif (len \u0026 16) {\n+\t\tuint8x16_t v = vld1q_u8(s);\n+\n+\t\tvst1q_u8(d, v);\n+\t\tacc1 = vpadalq_u32(acc1, words_sum(v));\n+\t}\n+\n+\tsum = (__force u32)csum_fold64(vaddvq_u64(acc0 + acc1));\n+\n+\tif (head \u0026 1) {\n+\t\t/* Fold to 16 bits and rotate back onto the buffer's word grid */\n+\t\tsum = (sum \u0026 0xffff) + (sum \u003e\u003e 16);\n+\t\tsum = (sum \u0026 0xffff) + (sum \u003e\u003e 16);\n+\t\tsum = ((sum \u0026 0xff) \u003c\u003c 8) | (sum \u003e\u003e 8);\n+\t}\n+\n+\treturn (__force __wsum)sum;\n+}\ndiff --git a/arch/arm64/lib/csum-copy.c b/arch/arm64/lib/csum-copy.c\nnew file mode 100644\nindex 0000000000000..d384b6be0abf7\n--- /dev/null\n+++ b/arch/arm64/lib/csum-copy.c\n@@ -0,0 +1,108 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Fused copy + Internet checksum for arm64: general-purpose register path\n+ *\n+ * Copyright (C) 2026 Demian Shulhan \u003cdemyansh@gmail.com\u003e\n+ *\n+ * Used for buffers too small to amortise kernel_neon_begin()/end(), and\n+ * whenever may_use_simd() is false (hardirq/NMI, or FPSIMD not usable).\n+ * See csum-copy.h for the contract.\n+ */\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/unaligned.h\u003e\n+\n+#include \"csum-copy.h\"\n+\n+/*\n+ * Ones' complement add: 64-bit add with end-around carry. Same result as\n+ * accumulate() in csum.c; written with a compare rather than __uint128_t\n+ * so that the carry chains of the four accumulators below stay independent.\n+ */\n+static __always_inline u64 csum_add64(u64 sum, u64 data)\n+{\n+\tu64 res = sum + data;\n+\n+\treturn res + (res \u003c data);\n+}\n+\n+__wsum __csum_partial_copy_scalar(const void *src, void *dst, int len)\n+{\n+\tconst u8 *s = src;\n+\tu8 *d = dst;\n+\tu64 sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0;\n+\n+\t/*\n+\t * 32 bytes per iteration into four independent accumulators. A single\n+\t * accumulator would serialise every word on both the register and the\n+\t * carry flag (one 64-bit word per cycle at best); with independent\n+\t * chains the loop becomes store-bound on out-of-order cores.\n+\t */\n+\twhile (len \u003e= 32) {\n+\t\tu64 a = get_unaligned((const u64 *)s);\n+\t\tu64 b = get_unaligned((const u64 *)(s + 8));\n+\t\tu64 c = get_unaligned((const u64 *)(s + 16));\n+\t\tu64 e = get_unaligned((const u64 *)(s + 24));\n+\n+\t\tput_unaligned(a, (u64 *)d);\n+\t\tput_unaligned(b, (u64 *)(d + 8));\n+\t\tput_unaligned(c, (u64 *)(d + 16));\n+\t\tput_unaligned(e, (u64 *)(d + 24));\n+\n+\t\tsum0 = csum_add64(sum0, a);\n+\t\tsum1 = csum_add64(sum1, b);\n+\t\tsum2 = csum_add64(sum2, c);\n+\t\tsum3 = csum_add64(sum3, e);\n+\n+\t\ts += 32;\n+\t\td += 32;\n+\t\tlen -= 32;\n+\t}\n+\n+\tif (len \u0026 16) {\n+\t\tu64 a = get_unaligned((const u64 *)s);\n+\t\tu64 b = get_unaligned((const u64 *)(s + 8));\n+\n+\t\tput_unaligned(a, (u64 *)d);\n+\t\tput_unaligned(b, (u64 *)(d + 8));\n+\t\tsum0 = csum_add64(sum0, a);\n+\t\tsum1 = csum_add64(sum1, b);\n+\t\ts += 16;\n+\t\td += 16;\n+\t}\n+\tif (len \u0026 8) {\n+\t\tu64 a = get_unaligned((const u64 *)s);\n+\n+\t\tput_unaligned(a, (u64 *)d);\n+\t\tsum2 = csum_add64(sum2, a);\n+\t\ts += 8;\n+\t\td += 8;\n+\t}\n+\tif (len \u0026 4) {\n+\t\tu32 w = get_unaligned((const u32 *)s);\n+\n+\t\tput_unaligned(w, (u32 *)d);\n+\t\tsum3 = csum_add64(sum3, w);\n+\t\ts += 4;\n+\t\td += 4;\n+\t}\n+\tif (len \u0026 2) {\n+\t\tu16 h = get_unaligned((const u16 *)s);\n+\n+\t\tput_unaligned(h, (u16 *)d);\n+\t\tsum0 = csum_add64(sum0, h);\n+\t\ts += 2;\n+\t\td += 2;\n+\t}\n+\tif (len \u0026 1) {\n+\t\tu8 b = *s;\n+\n+\t\t*d = b;\n+\t\t/* Zero-padded final word: the byte is its low half */\n+\t\tsum1 = csum_add64(sum1, b);\n+\t}\n+\n+\tsum0 = csum_add64(sum0, sum1);\n+\tsum2 = csum_add64(sum2, sum3);\n+\treturn csum_fold64(csum_add64(sum0, sum2));\n+}\ndiff --git a/arch/arm64/lib/csum-copy.h b/arch/arm64/lib/csum-copy.h\nnew file mode 100644\nindex 0000000000000..cd4c9238be38a\n--- /dev/null\n+++ b/arch/arm64/lib/csum-copy.h\n@@ -0,0 +1,84 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Fused copy + Internet checksum for arm64: internals shared between the\n+ * general-purpose register and NEON implementations and their dispatcher.\n+ *\n+ * Copyright (C) 2026 Demian Shulhan \u003cdemyansh@gmail.com\u003e\n+ */\n+#ifndef __ARM64_LIB_CSUM_COPY_H\n+#define __ARM64_LIB_CSUM_COPY_H\n+\n+#include \u003clinux/build_bug.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+/*\n+ * Both implement\n+ *\n+ *\t__wsum fn(const void *src, void *dst, int len)\n+ *\n+ * copying @len bytes from @src to @dst while accumulating the ones'\n+ * complement sum of the data, and return a 32-bit unfolded __wsum.\n+ *\n+ * The sum is taken over the data as laid out in memory, i.e. over 16-bit\n+ * little-endian words starting at @src (arm64 Linux is little-endian only:\n+ * CONFIG_CPU_BIG_ENDIAN depends on BROKEN), with a trailing odd byte\n+ * treated as the low half of a final zero-padded word. This is what\n+ * csum_partial() computes, so the result can be fed straight into\n+ * csum_block_add()/csum_fold().\n+ *\n+ * @src and @dst may have any alignment but must not overlap (memcpy() on\n+ * arm64 happens to be memmove()-safe; these routines are not). All loads\n+ * stay within [src, src + len) and all stores within [dst, dst + len).\n+ * Every byte of @dst is written from the very load that was summed, so the\n+ * returned value is the checksum of @dst even if @src is modified\n+ * concurrently.\n+ *\n+ * __csum_partial_copy_scalar() accepts any len \u003e= 0.\n+ * __csum_partial_copy_neon() requires len \u003e= CSUM_COPY_NEON_MIN_LEN_HW and\n+ * must be called between kernel_neon_begin() and kernel_neon_end().\n+ */\n+__wsum __csum_partial_copy_scalar(const void *src, void *dst, int len);\n+__wsum __csum_partial_copy_neon(const void *src, void *dst, int len);\n+\n+/*\n+ * Smallest length __csum_partial_copy_neon() can handle: up to 15 bytes to\n+ * align @dst to 16 bytes, then one unconditional 64-byte main loop\n+ * iteration.\n+ */\n+#define CSUM_COPY_NEON_MIN_LEN_HW\t(15 + 64)\n+\n+/*\n+ * Smallest length for which the dispatcher uses NEON at all. The fused\n+ * scalar path is already a single pass over the data, so NEON only has to\n+ * beat it by the fixed cost of kernel_neon_begin()/kernel_neon_end(): a\n+ * local_bh_disable()/enable() pair plus, when the interrupted context's\n+ * FPSIMD (or SVE/SME, when live) state has not been saved yet, that save\n+ * and its deferred reload on return to userspace. Where the two paths\n+ * break even depends on the core: ~512 bytes on an Apple M-series core,\n+ * ~1.25 KiB on Neoverse-N1 (both with the FPSIMD state already saved).\n+ * 1024 keeps MTU-sized buffers on the NEON path everywhere while costing\n+ * at most ~10% against the scalar path in the 1024..1280 window on N1.\n+ * The threshold is deliberately a constant rather than calibrated at boot:\n+ * a boot-time measurement is noisy (cold caches, low initial clock, steal\n+ * time in VMs), would differ between the core types of a big.LITTLE\n+ * system and would make the kernel's behaviour vary from boot to boot,\n+ * for a gain of a few percent in a narrow length window. See the commit\n+ * message for numbers.\n+ */\n+#define CSUM_COPY_NEON_MIN_LEN\t\t1024\n+\n+static_assert(CSUM_COPY_NEON_MIN_LEN \u003e= CSUM_COPY_NEON_MIN_LEN_HW);\n+\n+/*\n+ * Fold a 64-bit ones' complement accumulator to a 32-bit __wsum with\n+ * end-around carry. The first add produces at most a 33-bit value, so the\n+ * second add cannot carry again.\n+ */\n+static __always_inline __wsum csum_fold64(u64 sum)\n+{\n+\tsum = (sum \u0026 0xffffffff) + (sum \u003e\u003e 32);\n+\tsum = (sum \u0026 0xffffffff) + (sum \u003e\u003e 32);\n+\treturn (__force __wsum)(u32)sum;\n+}\n+\n+#endif /* __ARM64_LIB_CSUM_COPY_H */\ndiff --git a/arch/arm64/lib/csum.c b/arch/arm64/lib/csum.c\nindex 2432683e48a61..4f949b7085b58 100644\n--- a/arch/arm64/lib/csum.c\n+++ b/arch/arm64/lib/csum.c\n@@ -5,8 +5,12 @@\n #include \u003clinux/kasan-checks.h\u003e\n #include \u003clinux/kernel.h\u003e\n \n+#include \u003casm/simd.h\u003e\n+\n #include \u003cnet/checksum.h\u003e\n \n+#include \"csum-copy.h\"\n+\n /* Looks dumb, but generates nice-ish code */\n static u64 accumulate(u64 sum, u64 data)\n {\n@@ -129,6 +133,56 @@ unsigned int __no_sanitize_address do_csum(const unsigned char *buff, int len)\n \treturn sum \u003e\u003e 16;\n }\n \n+/*\n+ * scoped_ksimd() puts the caller-provided FPSIMD state buffer (a 528-byte\n+ * struct user_fpsimd_state) on the stack. Keep it out of line so that the\n+ * common, short, scalar calls do not carry that frame, which matters for\n+ * the deep softirq call chains csum_partial_copy_nocheck() is reached from.\n+ */\n+static noinline __wsum csum_partial_copy_neon(const void *src, void *dst,\n+\t\t\t\t\t int len)\n+{\n+\t__wsum sum;\n+\n+\tscoped_ksimd()\n+\t\tsum = __csum_partial_copy_neon(src, dst, len);\n+\n+\treturn sum;\n+}\n+\n+/**\n+ * csum_partial_copy_nocheck - copy a buffer and compute its Internet checksum\n+ * @src: source buffer\n+ * @dst: destination buffer; must not overlap @src\n+ * @len: number of bytes to copy; nothing is copied for len \u003c= 0\n+ *\n+ * Copies @len bytes from @src to @dst in a single pass and returns the\n+ * unfolded 32-bit ones' complement sum of the data, exactly as\n+ * csum_partial(@src, @len, 0) would, so the result can be passed to\n+ * csum_block_add() or csum_fold(). The returned sum is the checksum of the\n+ * bytes written to @dst even if @src is concurrently modified.\n+ *\n+ * Callable from task and softirq context. For len \u003e= CSUM_COPY_NEON_MIN_LEN\n+ * the copy uses kernel-mode NEON when may_use_simd() allows it, so it must\n+ * not be called from within another kernel_neon_begin()/kernel_neon_end()\n+ * section in task context (kernel_neon_begin() only nests from softirq).\n+ * From hardirq/NMI context, or when FPSIMD is not usable, the general\n+ * purpose register implementation is used for any length.\n+ *\n+ * Return: the unfolded ones' complement sum of the copied data; 0 if len \u003c= 0.\n+ */\n+__wsum csum_partial_copy_nocheck(const void *src, void *dst, int len)\n+{\n+\tif (unlikely(len \u003c= 0))\n+\t\treturn 0;\n+\n+\tif (len \u003e= CSUM_COPY_NEON_MIN_LEN \u0026\u0026 likely(may_use_simd()))\n+\t\treturn csum_partial_copy_neon(src, dst, len);\n+\n+\treturn __csum_partial_copy_scalar(src, dst, len);\n+}\n+EXPORT_SYMBOL(csum_partial_copy_nocheck);\n+\n __sum16 csum_ipv6_magic(const struct in6_addr *saddr,\n \t\t\tconst struct in6_addr *daddr,\n \t\t\t__u32 len, __u8 proto, __wsum csum)\ndiff --git a/lib/Kconfig.debug b/lib/Kconfig.debug\nindex 134b15a44625e..c56fe16f7dd9f 100644\n--- a/lib/Kconfig.debug\n+++ b/lib/Kconfig.debug\n@@ -2736,6 +2736,16 @@ config CHECKSUM_KUNIT\n \n \t If unsure, say N.\n \n+config CHECKSUM_KUNIT_BENCHMARK\n+\tbool \"Benchmark for the checksum functions\"\n+\tdepends on CHECKSUM_KUNIT\n+\thelp\n+\t Include a benchmark of csum_partial_copy_nocheck() against\n+\t memcpy() followed by csum_partial() in the checksum KUnit test\n+\t suite, for a range of buffer lengths and misalignments. The\n+\t throughput of both is logged as test output. Only meaningful on\n+\t real hardware; not intended for production builds.\n+\n config UTIL_MACROS_KUNIT\n \ttristate \"KUnit test util_macros.h functions at runtime\" if !KUNIT_ALL_TESTS\n \tdepends on KUNIT\ndiff --git a/lib/tests/checksum_kunit.c b/lib/tests/checksum_kunit.c\nindex be04aa42125ce..77d9d09922032 100644\n--- a/lib/tests/checksum_kunit.c\n+++ b/lib/tests/checksum_kunit.c\n@@ -3,8 +3,15 @@\n * Test cases csum_partial, csum_fold, ip_fast_csum, csum_ipv6_magic\n */\n \n+#include \u003ckunit/run-in-irq-context.h\u003e\n #include \u003ckunit/test.h\u003e\n+#include \u003clinux/math64.h\u003e\n+#include \u003clinux/prandom.h\u003e\n+#include \u003clinux/preempt.h\u003e\n+#include \u003clinux/string.h\u003e\n+#include \u003clinux/vmalloc.h\u003e\n #include \u003casm/checksum.h\u003e\n+#include \u003cnet/checksum.h\u003e\n #include \u003cnet/ip6_checksum.h\u003e\n \n #define MAX_LEN 512\n@@ -619,18 +626,474 @@ static void test_csum_ipv6_magic(struct kunit *test)\n \t}\n }\n \n+/*\n+ * csum_partial_copy_nocheck() tests.\n+ *\n+ * Only the public entry point is exercised, so the tests work unchanged\n+ * against every architecture's implementation and need neither arch #ifdefs\n+ * nor access to non-exported internals. The fused copy+checksum is compared\n+ * against a deliberately naive C reference (16-bit words accumulated into a\n+ * u64, odd trailing byte zero-padded, folded at the end) that shares no code\n+ * with csum_partial(). Every call is additionally checked for:\n+ * - dst == src over [0, len)\n+ * - canary bytes before dst and after dst + len untouched\n+ * - src untouched\n+ * - agreement with csum_fold(csum_partial(src, len, 0))\n+ *\n+ * The source is always placed so that src + len sits as close as possible\n+ * to the guard page that follows its vmalloc() area, flush against it\n+ * whenever (src_off + len) % 16 == 0, so a single byte of over-read faults\n+ * instead of going unnoticed. Over-writes are caught by the canaries and, in\n+ * test_csum_copy_dst_guard_page(), by the destination's guard page.\n+ *\n+ * The length and misalignment sweeps are dense enough to reach every branch\n+ * of the arm64 implementation through its dispatcher: the scalar path and\n+ * its 16/8/4/2/1-byte tail ladder below 1024 bytes, the switch to NEON at\n+ * 1024, the 1..15-byte head that aligns dst, the 32/16-byte remainder\n+ * blocks and the 1..15-byte overlapping tail. The interrupt context test\n+ * reaches the scalar fallback for large lengths from hardirq context, and\n+ * the nested kernel-mode NEON case from softirq context.\n+ */\n+#define COPY_MAX_OFF\t\t16\n+#define COPY_MAX_LEN\t\t4352\n+#define COPY_BENCH_MAX_LEN\t16384\n+#define COPY_GUARD\t\t128\t/* canary span on each side of dst */\n+#define COPY_CANARY\t\t0xa5\n+#define COPY_SEED\t\t0x5eed\n+\n+static struct rnd_state copy_rng;\n+/* vmalloc()ed with a page-aligned length, so each is followed by a guard page */\n+static u8 *copy_src_area;\n+static u8 *copy_dst_area;\n+static u8 *copy_shadow;\t\t\t/* expected contents of copy_src_area */\n+static size_t copy_area_len;\n+\n+static int checksum_suite_init(struct kunit_suite *suite)\n+{\n+\tcopy_area_len = round_up(COPY_BENCH_MAX_LEN + COPY_MAX_OFF, PAGE_SIZE);\n+\tcopy_src_area = vmalloc(copy_area_len);\n+\tcopy_dst_area = vmalloc(copy_area_len);\n+\tcopy_shadow = vmalloc(copy_area_len);\n+\tif (!copy_src_area || !copy_dst_area || !copy_shadow) {\n+\t\tvfree(copy_src_area);\n+\t\tvfree(copy_dst_area);\n+\t\tvfree(copy_shadow);\n+\t\treturn -ENOMEM;\n+\t}\n+\tprandom_seed_state(\u0026copy_rng, COPY_SEED);\n+\treturn 0;\n+}\n+\n+static void checksum_suite_exit(struct kunit_suite *suite)\n+{\n+\tvfree(copy_src_area);\n+\tvfree(copy_dst_area);\n+\tvfree(copy_shadow);\n+}\n+\n+static void copy_fill_random(void)\n+{\n+\tprandom_bytes_state(\u0026copy_rng, copy_src_area, copy_area_len);\n+\tmemcpy(copy_shadow, copy_src_area, copy_area_len);\n+}\n+\n+static void copy_fill_bytes(const u8 *pattern, int pattern_len)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i \u003c copy_area_len; i++)\n+\t\tcopy_src_area[i] = pattern[i % pattern_len];\n+\tmemcpy(copy_shadow, copy_src_area, copy_area_len);\n+}\n+\n+/*\n+ * Place @len bytes in @area (of copy_area_len bytes) with misalignment @off\n+ * and the end as close as possible to the trailing guard page: exactly at\n+ * it when (@off + @len) % 16 == 0, otherwise within 15 bytes of it.\n+ */\n+static u8 *copy_place_at_end(u8 *area, int off, int len)\n+{\n+\tu8 *end = area + copy_area_len;\n+\tint pad = ((unsigned long)(end - len) - off) \u0026 (COPY_MAX_OFF - 1);\n+\n+\treturn end - len - pad;\n+}\n+\n+/* Naive reference: folded ones' complement sum, same semantics as csum_fold(). */\n+static u16 ref_csum_fold(const u8 *buf, int len)\n+{\n+\tu64 sum = 0;\n+\tu16 word;\n+\tint i;\n+\n+\tfor (i = 0; i + 1 \u003c len; i += 2) {\n+\t\tmemcpy(\u0026word, buf + i, sizeof(word));\t/* native endian */\n+\t\tsum += word;\n+\t}\n+\tif (len \u0026 1) {\n+\t\tu8 pad[2] = { buf[len - 1], 0 };\n+\n+\t\tmemcpy(\u0026word, pad, sizeof(word));\t/* LE: low byte, BE: high byte */\n+\t\tsum += word;\n+\t}\n+\twhile (sum \u003e\u003e 16)\n+\t\tsum = (sum \u0026 0xffff) + (sum \u003e\u003e 16);\n+\n+\treturn ~sum \u0026 0xffff;\n+}\n+\n+static int first_non_canary(const u8 *p, int n)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c n; i++)\n+\t\tif (p[i] != COPY_CANARY)\n+\t\t\treturn i;\n+\treturn -1;\n+}\n+\n+/*\n+ * One call of csum_partial_copy_nocheck(src, dst, len) with @before canary\n+ * bytes ahead of @dst and @after canary bytes behind dst + len.\n+ */\n+static void check_csum_copy_at(struct kunit *test, const u8 *src, u8 *dst,\n+\t\t\t int len, int before, int after)\n+{\n+\tunsigned long src_off = (unsigned long)src \u0026 (COPY_MAX_OFF - 1);\n+\tunsigned long dst_off = (unsigned long)dst \u0026 (COPY_MAX_OFF - 1);\n+\tconst u8 *lo, *hi;\n+\tu16 expect, got;\n+\tint bad;\n+\n+\tmemset(dst - before, COPY_CANARY, before + len + after);\n+\texpect = ref_csum_fold(src, len);\n+\n+\tgot = (__force u16)csum_fold(csum_partial_copy_nocheck(src, dst, len));\n+\n+\tKUNIT_ASSERT_EQ_MSG(test, got, expect,\n+\t\t\t \"checksum mismatch src_off=%lu dst_off=%lu len=%d\",\n+\t\t\t src_off, dst_off, len);\n+\tKUNIT_ASSERT_EQ_MSG(test, memcmp(src, dst, len), 0,\n+\t\t\t \"copied data mismatch src_off=%lu dst_off=%lu len=%d\",\n+\t\t\t src_off, dst_off, len);\n+\n+\tbad = first_non_canary(dst - before, before);\n+\tKUNIT_ASSERT_EQ_MSG(test, bad, -1,\n+\t\t\t \"wrote %d bytes before dst (src_off=%lu dst_off=%lu len=%d)\",\n+\t\t\t before - bad, src_off, dst_off, len);\n+\n+\tbad = first_non_canary(dst + len, after);\n+\tKUNIT_ASSERT_EQ_MSG(test, bad, -1,\n+\t\t\t \"wrote at dst+len+%d (src_off=%lu dst_off=%lu len=%d)\",\n+\t\t\t bad, src_off, dst_off, len);\n+\n+\t/* Source untouched, including a margin on either side. */\n+\tlo = max(src - COPY_GUARD, (const u8 *)copy_src_area);\n+\thi = min(src + len + COPY_GUARD, (const u8 *)copy_src_area + copy_area_len);\n+\tKUNIT_ASSERT_EQ_MSG(test,\n+\t\t\t memcmp(lo, copy_shadow + (lo - copy_src_area), hi - lo), 0,\n+\t\t\t \"source buffer modified (src_off=%lu dst_off=%lu len=%d)\",\n+\t\t\t src_off, dst_off, len);\n+\n+\t/* Must be interchangeable with the arch csum_partial() semantics. */\n+\tKUNIT_EXPECT_EQ_MSG(test, got,\n+\t\t\t (__force u16)csum_fold(csum_partial(src, len, 0)),\n+\t\t\t \"disagrees with csum_partial() src_off=%lu dst_off=%lu len=%d\",\n+\t\t\t src_off, dst_off, len);\n+}\n+\n+/* Source against its guard page, destination between canaries. */\n+static void check_csum_copy(struct kunit *test, int src_off, int dst_off,\n+\t\t\t int len)\n+{\n+\tKUNIT_ASSERT_TRUE(test, src_off \u003e= 0 \u0026\u0026 src_off \u003c COPY_MAX_OFF);\n+\tKUNIT_ASSERT_TRUE(test, dst_off \u003e= 0 \u0026\u0026 dst_off \u003c COPY_MAX_OFF);\n+\tKUNIT_ASSERT_TRUE(test, len \u003e= 0 \u0026\u0026 len \u003c= COPY_MAX_LEN);\n+\n+\tcheck_csum_copy_at(test, copy_place_at_end(copy_src_area, src_off, len),\n+\t\t\t copy_dst_area + COPY_GUARD + dst_off, len,\n+\t\t\t COPY_GUARD, COPY_GUARD);\n+}\n+\n+/*\n+ * Source misalignments used by the longer sweeps. Destination misalignment\n+ * is what an implementation's head handling depends on, so those sweeps\n+ * still cover all 16 of them.\n+ */\n+static const int copy_src_offs[] = { 0, 1, 3, 8, 15 };\n+\n+/* Every len 0..64 with every src/dst misalignment: the scalar tail ladder. */\n+static void test_csum_copy_small_all_alignments(struct kunit *test)\n+{\n+\tint len, src_off, dst_off;\n+\n+\tcopy_fill_random();\n+\tfor (len = 0; len \u003c= 64; len++)\n+\t\tfor (src_off = 0; src_off \u003c COPY_MAX_OFF; src_off++)\n+\t\t\tfor (dst_off = 0; dst_off \u003c COPY_MAX_OFF; dst_off++)\n+\t\t\t\tcheck_csum_copy(test, src_off, dst_off, len);\n+}\n+\n+/* Lengths straddling the arm64 scalar/NEON switch-over at 1024. */\n+static void test_csum_copy_around_1024(struct kunit *test)\n+{\n+\tint len, i, dst_off;\n+\n+\tcopy_fill_random();\n+\tfor (len = 1024 - 16; len \u003c= 1024 + 16; len++)\n+\t\tfor (i = 0; i \u003c ARRAY_SIZE(copy_src_offs); i++)\n+\t\t\tfor (dst_off = 0; dst_off \u003c COPY_MAX_OFF; dst_off++)\n+\t\t\t\tcheck_csum_copy(test, copy_src_offs[i], dst_off,\n+\t\t\t\t\t\tlen);\n+}\n+\n+/*\n+ * Large buffers with unaligned src and dst. dst_off drives the 16-byte head\n+ * (odd and even heads), len drives the 32/16-byte remainder blocks and the\n+ * 1..15-byte overlapping tail: 1024..1103 covers every combination.\n+ */\n+static void test_csum_copy_large_unaligned(struct kunit *test)\n+{\n+\tstatic const int extra_lens[] = {\n+\t\t1500, 2033, 2034, 2035, 2039, 2040, 2041, 2047, 2048, 2049,\n+\t\t2055, 2056, 2057, 2062, 2063, 3072, 4095, 4096, 4097, 4352,\n+\t};\n+\tint len, i, j, dst_off;\n+\n+\tcopy_fill_random();\n+\tfor (len = 1024; len \u003c 1024 + 80; len++)\n+\t\tfor (i = 0; i \u003c ARRAY_SIZE(copy_src_offs); i++)\n+\t\t\tfor (dst_off = 0; dst_off \u003c COPY_MAX_OFF; dst_off++)\n+\t\t\t\tcheck_csum_copy(test, copy_src_offs[i], dst_off,\n+\t\t\t\t\t\tlen);\n+\n+\tfor (j = 0; j \u003c ARRAY_SIZE(extra_lens); j++)\n+\t\tfor (i = 0; i \u003c ARRAY_SIZE(copy_src_offs); i++)\n+\t\t\tfor (dst_off = 0; dst_off \u003c COPY_MAX_OFF; dst_off++)\n+\t\t\t\tcheck_csum_copy(test, copy_src_offs[i], dst_off,\n+\t\t\t\t\t\textra_lens[j]);\n+}\n+\n+/*\n+ * Data patterns that stress carries (all ones), the absence of carries,\n+ * the top bit of every byte, and byte-swap sensitivity (odd-head/odd-tail\n+ * fix-ups are only observable when adjacent bytes differ).\n+ */\n+static void test_csum_copy_patterns(struct kunit *test)\n+{\n+\tstatic const u8 pat_ff[] = { 0xff };\n+\tstatic const u8 pat_00[] = { 0x00 };\n+\tstatic const u8 pat_80[] = { 0x80 };\n+\tstatic const u8 pat_ff00[] = { 0xff, 0x00 };\n+\tstatic const u8 pat_ramp[] = { 0x01, 0x02, 0x04, 0x08,\n+\t\t\t\t 0x10, 0x20, 0x40, 0x80 };\n+\tstatic const struct {\n+\t\tconst u8 *pat;\n+\t\tint len;\n+\t} patterns[] = {\n+\t\t{ pat_ff, ARRAY_SIZE(pat_ff) },\n+\t\t{ pat_00, ARRAY_SIZE(pat_00) },\n+\t\t{ pat_80, ARRAY_SIZE(pat_80) },\n+\t\t{ pat_ff00, ARRAY_SIZE(pat_ff00) },\n+\t\t{ pat_ramp, ARRAY_SIZE(pat_ramp) },\n+\t\t{ NULL, 0 },\t/* random */\n+\t};\n+\tstatic const int lens[] = {\n+\t\t1, 2, 3, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128,\n+\t\t255, 256, 511, 512, 1023, 1024, 1025, 1039, 1040, 1041, 1087,\n+\t\t1088, 1089, 1500, 2048, 4095, 4096, 4352,\n+\t};\n+\tstatic const int offs[][2] = {\n+\t\t{ 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 }, { 3, 7 }, { 7, 3 },\n+\t\t{ 9, 5 }, { 15, 15 },\n+\t};\n+\tint p, l, o;\n+\n+\tfor (p = 0; p \u003c ARRAY_SIZE(patterns); p++) {\n+\t\tif (patterns[p].pat)\n+\t\t\tcopy_fill_bytes(patterns[p].pat, patterns[p].len);\n+\t\telse\n+\t\t\tcopy_fill_random();\n+\n+\t\tfor (l = 0; l \u003c ARRAY_SIZE(lens); l++)\n+\t\t\tfor (o = 0; o \u003c ARRAY_SIZE(offs); o++)\n+\t\t\t\tcheck_csum_copy(test, offs[o][0], offs[o][1],\n+\t\t\t\t\t\tlens[l]);\n+\t}\n+}\n+\n+/*\n+ * Destination flush against its guard page (for (dst_off + len) % 16 == 0;\n+ * within 15 bytes of it otherwise), so that over-writes fault rather than\n+ * relying on the canaries. Lengths around every block size and switch-over.\n+ */\n+static void test_csum_copy_dst_guard_page(struct kunit *test)\n+{\n+\tstatic const int lens[] = {\n+\t\t1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n+\t\t31, 32, 33, 63, 64, 65, 79, 80, 81, 127, 128, 129, 1023, 1024,\n+\t\t1025, 1039, 1040, 1041, 1055, 1056, 1057, 1087, 1088, 1089,\n+\t\t1500, 2048, 4095, 4096, 4097,\n+\t};\n+\tint l, src_off, dst_off;\n+\n+\tcopy_fill_random();\n+\tfor (l = 0; l \u003c ARRAY_SIZE(lens); l++)\n+\t\tfor (dst_off = 0; dst_off \u003c COPY_MAX_OFF; dst_off++) {\n+\t\t\tsrc_off = (dst_off * 7 + lens[l]) \u0026 (COPY_MAX_OFF - 1);\n+\t\t\tcheck_csum_copy_at(test,\n+\t\t\t\t\t copy_place_at_end(copy_src_area,\n+\t\t\t\t\t\t\t src_off, lens[l]),\n+\t\t\t\t\t copy_place_at_end(copy_dst_area,\n+\t\t\t\t\t\t\t dst_off, lens[l]),\n+\t\t\t\t\t lens[l], COPY_GUARD, 0);\n+\t\t}\n+}\n+\n+/* len == 0: return 0 and touch nothing. */\n+static void test_csum_copy_zero_len(struct kunit *test)\n+{\n+\tu8 *dst = copy_dst_area + COPY_GUARD;\n+\n+\tcopy_fill_random();\n+\tmemset(copy_dst_area, COPY_CANARY, 2 * COPY_GUARD);\n+\tCHECK_EQ(csum_partial_copy_nocheck(copy_src_area, dst, 0), 0);\n+\tCHECK_EQ(first_non_canary(copy_dst_area, 2 * COPY_GUARD), -1);\n+}\n+\n+/*\n+ * Interrupt context: csum_partial_copy_nocheck() is called from task,\n+ * softirq and hardirq context concurrently. Each context has its own\n+ * destination (so concurrent copies do not interfere) and the calls cycle\n+ * through a few source messages of different lengths, including ones that\n+ * an arch's SIMD path would normally handle so that the fallback path is\n+ * reached from hardirq context and the nested SIMD case from softirq.\n+ */\n+#define COPY_IRQ_NUM_MSGS\t3\n+#define COPY_IRQ_NUM_CTX\t3\t/* task, softirq, hardirq */\n+\n+static const int copy_irq_lens[COPY_IRQ_NUM_MSGS] = { 100, 1500, 4097 };\n+\n+struct copy_irq_test_state {\n+\tconst u8 *src[COPY_IRQ_NUM_MSGS];\n+\tu16 expected[COPY_IRQ_NUM_MSGS];\n+\tu8 *dst[COPY_IRQ_NUM_CTX];\n+\tatomic_t seqno;\n+};\n+\n+static bool copy_irq_test_func(void *state_)\n+{\n+\tstruct copy_irq_test_state *state = state_;\n+\tu32 i = (u32)atomic_inc_return(\u0026state-\u003eseqno) % COPY_IRQ_NUM_MSGS;\n+\tint ctx = in_hardirq() ? 2 : in_serving_softirq() ? 1 : 0;\n+\tconst u8 *src = state-\u003esrc[i];\n+\tu8 *dst = state-\u003edst[ctx];\n+\tint len = copy_irq_lens[i];\n+\tu16 got;\n+\n+\tgot = (__force u16)csum_fold(csum_partial_copy_nocheck(src, dst, len));\n+\treturn got == state-\u003eexpected[i] \u0026\u0026 memcmp(src, dst, len) == 0;\n+}\n+\n+static void test_csum_copy_interrupt_context(struct kunit *test)\n+{\n+\tstruct copy_irq_test_state state = { };\n+\tint i;\n+\n+\tcopy_fill_random();\n+\tfor (i = 0; i \u003c COPY_IRQ_NUM_MSGS; i++) {\n+\t\t/* Distinct, misaligned messages. */\n+\t\tstate.src[i] = copy_src_area + i * (COPY_MAX_LEN + 64) + 2 * i + 1;\n+\t\tstate.expected[i] = ref_csum_fold(state.src[i], copy_irq_lens[i]);\n+\t}\n+\tfor (i = 0; i \u003c COPY_IRQ_NUM_CTX; i++)\n+\t\tstate.dst[i] = copy_dst_area + i * (COPY_MAX_LEN + 64) + 2 * i + 1;\n+\n+\tkunit_run_irq_test(test, copy_irq_test_func, 100000, \u0026state);\n+}\n+\n+/*\n+ * Throughput of csum_partial_copy_nocheck() versus the pre-existing generic\n+ * form, memcpy() followed by csum_partial(), for aligned and for misaligned\n+ * buffers. Only run if CONFIG_CHECKSUM_KUNIT_BENCHMARK is set.\n+ */\n+static void test_csum_copy_benchmark(struct kunit *test)\n+{\n+\tstatic const int lens[] = {\n+\t\t16, 40, 64, 128, 256, 512, 1000, 1024, 1500, 2048, 4096, 16384,\n+\t};\n+\tstatic const int offs[][2] = { { 0, 0 }, { 1, 3 } };\n+\tint l, o, i, len, num_iters;\n+\tu64 t, t_fused, t_split;\n+\tu32 acc = 0;\t/* keeps every call alive; see OPTIMIZER_HIDE_VAR() */\n+\n+\tif (!IS_ENABLED(CONFIG_CHECKSUM_KUNIT_BENCHMARK))\n+\t\tkunit_skip(test, \"not enabled\");\n+\n+\tcopy_fill_random();\n+\n+\t/* warm-up */\n+\tfor (i = 0; i \u003c 10000000; i += COPY_BENCH_MAX_LEN)\n+\t\tacc ^= (__force u32)csum_partial_copy_nocheck(copy_src_area,\n+\t\t\t\t\t\t\t copy_dst_area,\n+\t\t\t\t\t\t\t COPY_BENCH_MAX_LEN);\n+\tOPTIMIZER_HIDE_VAR(acc);\n+\n+\tfor (o = 0; o \u003c ARRAY_SIZE(offs); o++) {\n+\t\tconst u8 *src = copy_src_area + offs[o][0];\n+\t\tu8 *dst = copy_dst_area + offs[o][1];\n+\n+\t\tfor (l = 0; l \u003c ARRAY_SIZE(lens); l++) {\n+\t\t\tlen = lens[l];\n+\t\t\tnum_iters = 10000000 / (len + 128);\n+\n+\t\t\tpreempt_disable();\n+\t\t\tt = ktime_get_ns();\n+\t\t\tfor (i = 0; i \u003c num_iters; i++)\n+\t\t\t\tacc ^= (__force u32)csum_partial_copy_nocheck(src, dst, len);\n+\t\t\tt_fused = ktime_get_ns() - t;\n+\t\t\tOPTIMIZER_HIDE_VAR(acc);\n+\n+\t\t\tt = ktime_get_ns();\n+\t\t\tfor (i = 0; i \u003c num_iters; i++) {\n+\t\t\t\tmemcpy(dst, src, len);\n+\t\t\t\tacc ^= (__force u32)csum_partial(dst, len, 0);\n+\t\t\t}\n+\t\t\tt_split = ktime_get_ns() - t;\n+\t\t\tOPTIMIZER_HIDE_VAR(acc);\n+\t\t\tpreempt_enable();\n+\n+\t\t\tkunit_info(test,\n+\t\t\t\t \"src+%d dst+%d len=%5d: csum_partial_copy_nocheck %6llu MB/s, memcpy+csum_partial %6llu MB/s\\n\",\n+\t\t\t\t offs[o][0], offs[o][1], len,\n+\t\t\t\t div64_u64((u64)len * num_iters * 1000, t_fused),\n+\t\t\t\t div64_u64((u64)len * num_iters * 1000, t_split));\n+\t\t}\n+\t}\n+}\n+\n static struct kunit_case __refdata checksum_test_cases[] = {\n \tKUNIT_CASE(test_csum_fixed_random_inputs),\n \tKUNIT_CASE(test_csum_all_carry_inputs),\n \tKUNIT_CASE(test_csum_no_carry_inputs),\n \tKUNIT_CASE(test_ip_fast_csum),\n \tKUNIT_CASE(test_csum_ipv6_magic),\n+\tKUNIT_CASE(test_csum_copy_small_all_alignments),\n+\tKUNIT_CASE(test_csum_copy_around_1024),\n+\tKUNIT_CASE(test_csum_copy_large_unaligned),\n+\tKUNIT_CASE(test_csum_copy_patterns),\n+\tKUNIT_CASE(test_csum_copy_dst_guard_page),\n+\tKUNIT_CASE(test_csum_copy_zero_len),\n+\tKUNIT_CASE(test_csum_copy_interrupt_context),\n+\tKUNIT_CASE(test_csum_copy_benchmark),\n \t{}\n };\n \n static struct kunit_suite checksum_test_suite = {\n \t.name = \"checksum\",\n \t.test_cases = checksum_test_cases,\n+\t.suite_init = checksum_suite_init,\n+\t.suite_exit = checksum_suite_exit,\n };\n \n kunit_test_suites(\u0026checksum_test_suite);\n"
} |
| 2/1 | 2026/09/28 20:55 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces an arm64-specific fused copy and checksum implementation (NEON and scalar variants of csum_partial_copy_nocheck) under arch/arm64/, which is structurally unreachable on the target architecture (amd64). The only generic changes are additions to the KUnit checksum test suite and benchmark in lib/tests/checksum_kunit.c and lib/Kconfig.debug, which are standalone unit tests rather than reachable production kernel code. Consequently, there are no reachable functional changes on amd64 to fuzz.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 3488c0ddc7779e84c662d5dfe3288a09267ad7af
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Sep 28 20:55:03 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/arm64/include/asm/checksum.h b/arch/arm64/include/asm/checksum.h
index dc52b733675db..9f766db011526 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/Makefile b/arch/arm64/lib/Makefile
index b33e1ca4a7815..5e1024c31f67a 100644
--- a/arch/arm64/lib/Makefile
+++ b/arch/arm64/lib/Makefile
@@ -5,10 +5,15 @@ KCSAN_SANITIZE_delay.o := n
lib-y := clear_user.o delay.o copy_from_user.o \
copy_to_user.o copy_page.o \
- clear_page.o csum.o insn.o memchr.o memcpy.o \
+ clear_page.o csum.o csum-copy.o csum-copy-neon.o \
+ insn.o memchr.o memcpy.o \
memset.o memcmp.o strcmp.o strncmp.o strlen.o \
strnlen.o strchr.o strrchr.o tishift.o
+# NEON intrinsics; only ever called between kernel_neon_begin()/end().
+CFLAGS_csum-copy-neon.o += $(CC_FLAGS_FPU)
+CFLAGS_REMOVE_csum-copy-neon.o += $(CC_FLAGS_NO_FPU)
+
lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o
obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o
diff --git a/arch/arm64/lib/csum-copy-neon.c b/arch/arm64/lib/csum-copy-neon.c
new file mode 100644
index 0000000000000..bfeaf2beba39e
--- /dev/null
+++ b/arch/arm64/lib/csum-copy-neon.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Fused copy + Internet checksum for arm64: NEON path
+ *
+ * Copyright (C) 2026 Demian Shulhan <demyansh@gmail.com>
+ *
+ * This file is compiled with CC_FLAGS_FPU (see Makefile) and must only be
+ * entered between kernel_neon_begin() and kernel_neon_end(). Everything
+ * else about the contract is in csum-copy.h.
+ *
+ * The routine runs to completion without chunking or yielding regardless
+ * of @len. In task context kernel-mode NEON is preemptible (the caller's
+ * FPSIMD state lives in a caller-provided buffer that is switched with the
+ * task). In softirq context preemption is off, but the lengths that reach
+ * this function are bounded by a single skb fragment or iov segment, i.e.
+ * tens of KiB at the very most, which is a few microseconds here and well
+ * within what the surrounding network softirq processing already spends.
+ *
+ * Main loop: 64 bytes per iteration. Each 16-byte vector is reduced with
+ * UADDLP (8x16 -> 4x32); pairs of vectors share a 32-bit accumulator via
+ * UADALP, which is then drained into a 64-bit accumulator. This is six
+ * vector ALU operations per iteration; giving every vector its own 64-bit
+ * accumulator would need eight and measures ~10% slower, as the loop is
+ * bound by vector ALU throughput rather than by dependency latency. A
+ * 32-bit lane only ever holds the sum of four 16-bit words, so it cannot
+ * overflow, and the 64-bit lanes cannot overflow for any 'int' length.
+ * Two independent 64-bit accumulators keep the loop-carried dependency to
+ * a single UADALP each. The dispatcher only enters this path for
+ * len >= CSUM_COPY_NEON_MIN_LEN, so the loop is entered unconditionally.
+ *
+ * Head: misaligned 16-byte stores are markedly more expensive than
+ * misaligned loads, so @dst is aligned to 16 bytes first by copying one
+ * full vector and masking the checksum contribution down to the leading
+ * 'head' bytes. The main loop then runs on the byte grid starting at
+ * @src + head; if head is odd, that grid is offset from the 16-bit word
+ * grid of the buffer. Rather than fixing up every loop vector, the head
+ * vector is byte-swapped within each lane (REV16) to match the loop grid
+ * and the final folded 16-bit sum is rotated by 8 bits, which is exact for
+ * ones' complement sums (the same trick as csum_block_add()/do_csum()).
+ *
+ * Tail (1..15 bytes): rather than dribbling bytes through GPRs, copy the
+ * last 16 bytes of the buffer as one vector and mask off the bytes that
+ * belong to the body. That vector overlaps up to 15 body bytes, so it is
+ * copied *before* the body: the body's stores then overwrite the overlap
+ * with the very bytes that were summed, and the destination always matches
+ * the returned checksum even if the source is modified concurrently. If the
+ * tail length is odd the tail vector is offset by one byte from the loop
+ * grid, which is again corrected with REV16.
+ *
+ * Data is handled as uint8x16_t throughout: that is how it sits in memory,
+ * and masking and REV16 are byte operations. The 16-bit word view only
+ * appears in the two pairwise-add helpers.
+ */
+
+#include <linux/types.h>
+#include <asm/neon-intrinsics.h>
+
+#include "csum-copy.h"
+
+/*
+ * Byte mask table. A 16-byte load at offset n (1..15) yields (16 - n) zero
+ * bytes followed by n 0xff bytes, selecting the last n bytes of a vector;
+ * a load at offset 32 - n yields n 0xff bytes followed by zeros, selecting
+ * the first n bytes. One unaligned load, no lane-index compare.
+ */
+static const u8 csum_copy_mask[48] __aligned(16) = {
+ [16 ... 31] = 0xff,
+};
+
+/* Adjacent 16-bit words of @v summed into 32-bit lanes (UADDLP). */
+static __always_inline uint32x4_t words_sum(uint8x16_t v)
+{
+ return vpaddlq_u16(vreinterpretq_u16_u8(v));
+}
+
+/* @acc += adjacent 16-bit words of @v (UADALP). */
+static __always_inline uint32x4_t words_add(uint32x4_t acc, uint8x16_t v)
+{
+ return vpadalq_u16(acc, vreinterpretq_u16_u8(v));
+}
+
+__wsum __csum_partial_copy_neon(const void *src, void *dst, int len)
+{
+ const u8 *s = src;
+ u8 *d = dst;
+ uint64x2_t acc0 = { }, acc1 = { };
+ unsigned int head = -(unsigned long)d & 15;
+ unsigned int tail = (len - head) & 15;
+ u32 sum;
+
+ if (tail) {
+ /* Final vector, ending exactly at the buffer end; see above */
+ uint8x16_t v = vld1q_u8(s + len - 16);
+
+ vst1q_u8(d + len - 16, v);
+ v &= vld1q_u8(csum_copy_mask + tail);
+ if (tail & 1)
+ v = vrev16q_u8(v); /* odd tail: match loop grid */
+ acc1 = vpadalq_u32(acc1, words_sum(v));
+ }
+
+ if (head) {
+ uint8x16_t v = vld1q_u8(s);
+
+ vst1q_u8(d, v);
+ v &= vld1q_u8(csum_copy_mask + 32 - head);
+ if (head & 1)
+ v = vrev16q_u8(v); /* odd head: match loop grid */
+ acc0 = vpadalq_u32(acc0, words_sum(v));
+
+ s += head;
+ d += head;
+ len -= head;
+ }
+
+ do {
+ uint8x16_t v0 = vld1q_u8(s);
+ uint8x16_t v1 = vld1q_u8(s + 16);
+ uint8x16_t v2 = vld1q_u8(s + 32);
+ uint8x16_t v3 = vld1q_u8(s + 48);
+ uint32x4_t t0, t1;
+
+ vst1q_u8(d, v0);
+ vst1q_u8(d + 16, v1);
+ vst1q_u8(d + 32, v2);
+ vst1q_u8(d + 48, v3);
+
+ t0 = words_sum(v0);
+ t1 = words_sum(v1);
+ t0 = words_add(t0, v2);
+ t1 = words_add(t1, v3);
+ acc0 = vpadalq_u32(acc0, t0);
+ acc1 = vpadalq_u32(acc1, t1);
+
+ s += 64;
+ d += 64;
+ len -= 64;
+ } while (len >= 64);
+
+ if (len & 32) {
+ uint8x16_t v0 = vld1q_u8(s);
+ uint8x16_t v1 = vld1q_u8(s + 16);
+
+ vst1q_u8(d, v0);
+ vst1q_u8(d + 16, v1);
+ acc0 = vpadalq_u32(acc0, words_add(words_sum(v0), v1));
+
+ s += 32;
+ d += 32;
+ }
+ if (len & 16) {
+ uint8x16_t v = vld1q_u8(s);
+
+ vst1q_u8(d, v);
+ acc1 = vpadalq_u32(acc1, words_sum(v));
+ }
+
+ sum = (__force u32)csum_fold64(vaddvq_u64(acc0 + acc1));
+
+ if (head & 1) {
+ /* Fold to 16 bits and rotate back onto the buffer's word grid */
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = ((sum & 0xff) << 8) | (sum >> 8);
+ }
+
+ return (__force __wsum)sum;
+}
diff --git a/arch/arm64/lib/csum-copy.c b/arch/arm64/lib/csum-copy.c
new file mode 100644
index 0000000000000..d384b6be0abf7
--- /dev/null
+++ b/arch/arm64/lib/csum-copy.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Fused copy + Internet checksum for arm64: general-purpose register path
+ *
+ * Copyright (C) 2026 Demian Shulhan <demyansh@gmail.com>
+ *
+ * Used for buffers too small to amortise kernel_neon_begin()/end(), and
+ * whenever may_use_simd() is false (hardirq/NMI, or FPSIMD not usable).
+ * See csum-copy.h for the contract.
+ */
+
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "csum-copy.h"
+
+/*
+ * Ones' complement add: 64-bit add with end-around carry. Same result as
+ * accumulate() in csum.c; written with a compare rather than __uint128_t
+ * so that the carry chains of the four accumulators below stay independent.
+ */
+static __always_inline u64 csum_add64(u64 sum, u64 data)
+{
+ u64 res = sum + data;
+
+ return res + (res < data);
+}
+
+__wsum __csum_partial_copy_scalar(const void *src, void *dst, int len)
+{
+ const u8 *s = src;
+ u8 *d = dst;
+ u64 sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0;
+
+ /*
+ * 32 bytes per iteration into four independent accumulators. A single
+ * accumulator would serialise every word on both the register and the
+ * carry flag (one 64-bit word per cycle at best); with independent
+ * chains the loop becomes store-bound on out-of-order cores.
+ */
+ while (len >= 32) {
+ u64 a = get_unaligned((const u64 *)s);
+ u64 b = get_unaligned((const u64 *)(s + 8));
+ u64 c = get_unaligned((const u64 *)(s + 16));
+ u64 e = get_unaligned((const u64 *)(s + 24));
+
+ put_unaligned(a, (u64 *)d);
+ put_unaligned(b, (u64 *)(d + 8));
+ put_unaligned(c, (u64 *)(d + 16));
+ put_unaligned(e, (u64 *)(d + 24));
+
+ sum0 = csum_add64(sum0, a);
+ sum1 = csum_add64(sum1, b);
+ sum2 = csum_add64(sum2, c);
+ sum3 = csum_add64(sum3, e);
+
+ s += 32;
+ d += 32;
+ len -= 32;
+ }
+
+ if (len & 16) {
+ u64 a = get_unaligned((const u64 *)s);
+ u64 b = get_unaligned((const u64 *)(s + 8));
+
+ put_unaligned(a, (u64 *)d);
+ put_unaligned(b, (u64 *)(d + 8));
+ sum0 = csum_add64(sum0, a);
+ sum1 = csum_add64(sum1, b);
+ s += 16;
+ d += 16;
+ }
+ if (len & 8) {
+ u64 a = get_unaligned((const u64 *)s);
+
+ put_unaligned(a, (u64 *)d);
+ sum2 = csum_add64(sum2, a);
+ s += 8;
+ d += 8;
+ }
+ if (len & 4) {
+ u32 w = get_unaligned((const u32 *)s);
+
+ put_unaligned(w, (u32 *)d);
+ sum3 = csum_add64(sum3, w);
+ s += 4;
+ d += 4;
+ }
+ if (len & 2) {
+ u16 h = get_unaligned((const u16 *)s);
+
+ put_unaligned(h, (u16 *)d);
+ sum0 = csum_add64(sum0, h);
+ s += 2;
+ d += 2;
+ }
+ if (len & 1) {
+ u8 b = *s;
+
+ *d = b;
+ /* Zero-padded final word: the byte is its low half */
+ sum1 = csum_add64(sum1, b);
+ }
+
+ sum0 = csum_add64(sum0, sum1);
+ sum2 = csum_add64(sum2, sum3);
+ return csum_fold64(csum_add64(sum0, sum2));
+}
diff --git a/arch/arm64/lib/csum-copy.h b/arch/arm64/lib/csum-copy.h
new file mode 100644
index 0000000000000..cd4c9238be38a
--- /dev/null
+++ b/arch/arm64/lib/csum-copy.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Fused copy + Internet checksum for arm64: internals shared between the
+ * general-purpose register and NEON implementations and their dispatcher.
+ *
+ * Copyright (C) 2026 Demian Shulhan <demyansh@gmail.com>
+ */
+#ifndef __ARM64_LIB_CSUM_COPY_H
+#define __ARM64_LIB_CSUM_COPY_H
+
+#include <linux/build_bug.h>
+#include <linux/types.h>
+
+/*
+ * Both implement
+ *
+ * __wsum fn(const void *src, void *dst, int len)
+ *
+ * copying @len bytes from @src to @dst while accumulating the ones'
+ * complement sum of the data, and return a 32-bit unfolded __wsum.
+ *
+ * The sum is taken over the data as laid out in memory, i.e. over 16-bit
+ * little-endian words starting at @src (arm64 Linux is little-endian only:
+ * CONFIG_CPU_BIG_ENDIAN depends on BROKEN), with a trailing odd byte
+ * treated as the low half of a final zero-padded word. This is what
+ * csum_partial() computes, so the result can be fed straight into
+ * csum_block_add()/csum_fold().
+ *
+ * @src and @dst may have any alignment but must not overlap (memcpy() on
+ * arm64 happens to be memmove()-safe; these routines are not). All loads
+ * stay within [src, src + len) and all stores within [dst, dst + len).
+ * Every byte of @dst is written from the very load that was summed, so the
+ * returned value is the checksum of @dst even if @src is modified
+ * concurrently.
+ *
+ * __csum_partial_copy_scalar() accepts any len >= 0.
+ * __csum_partial_copy_neon() requires len >= CSUM_COPY_NEON_MIN_LEN_HW and
+ * must be called between kernel_neon_begin() and kernel_neon_end().
+ */
+__wsum __csum_partial_copy_scalar(const void *src, void *dst, int len);
+__wsum __csum_partial_copy_neon(const void *src, void *dst, int len);
+
+/*
+ * Smallest length __csum_partial_copy_neon() can handle: up to 15 bytes to
+ * align @dst to 16 bytes, then one unconditional 64-byte main loop
+ * iteration.
+ */
+#define CSUM_COPY_NEON_MIN_LEN_HW (15 + 64)
+
+/*
+ * Smallest length for which the dispatcher uses NEON at all. The fused
+ * scalar path is already a single pass over the data, so NEON only has to
+ * beat it by the fixed cost of kernel_neon_begin()/kernel_neon_end(): a
+ * local_bh_disable()/enable() pair plus, when the interrupted context's
+ * FPSIMD (or SVE/SME, when live) state has not been saved yet, that save
+ * and its deferred reload on return to userspace. Where the two paths
+ * break even depends on the core: ~512 bytes on an Apple M-series core,
+ * ~1.25 KiB on Neoverse-N1 (both with the FPSIMD state already saved).
+ * 1024 keeps MTU-sized buffers on the NEON path everywhere while costing
+ * at most ~10% against the scalar path in the 1024..1280 window on N1.
+ * The threshold is deliberately a constant rather than calibrated at boot:
+ * a boot-time measurement is noisy (cold caches, low initial clock, steal
+ * time in VMs), would differ between the core types of a big.LITTLE
+ * system and would make the kernel's behaviour vary from boot to boot,
+ * for a gain of a few percent in a narrow length window. See the commit
+ * message for numbers.
+ */
+#define CSUM_COPY_NEON_MIN_LEN 1024
+
+static_assert(CSUM_COPY_NEON_MIN_LEN >= CSUM_COPY_NEON_MIN_LEN_HW);
+
+/*
+ * Fold a 64-bit ones' complement accumulator to a 32-bit __wsum with
+ * end-around carry. The first add produces at most a 33-bit value, so the
+ * second add cannot carry again.
+ */
+static __always_inline __wsum csum_fold64(u64 sum)
+{
+ sum = (sum & 0xffffffff) + (sum >> 32);
+ sum = (sum & 0xffffffff) + (sum >> 32);
+ return (__force __wsum)(u32)sum;
+}
+
+#endif /* __ARM64_LIB_CSUM_COPY_H */
diff --git a/arch/arm64/lib/csum.c b/arch/arm64/lib/csum.c
index 2432683e48a61..4f949b7085b58 100644
--- a/arch/arm64/lib/csum.c
+++ b/arch/arm64/lib/csum.c
@@ -5,8 +5,12 @@
#include <linux/kasan-checks.h>
#include <linux/kernel.h>
+#include <asm/simd.h>
+
#include <net/checksum.h>
+#include "csum-copy.h"
+
/* Looks dumb, but generates nice-ish code */
static u64 accumulate(u64 sum, u64 data)
{
@@ -129,6 +133,56 @@ unsigned int __no_sanitize_address do_csum(const unsigned char *buff, int len)
return sum >> 16;
}
+/*
+ * scoped_ksimd() puts the caller-provided FPSIMD state buffer (a 528-byte
+ * struct user_fpsimd_state) on the stack. Keep it out of line so that the
+ * common, short, scalar calls do not carry that frame, which matters for
+ * the deep softirq call chains csum_partial_copy_nocheck() is reached from.
+ */
+static noinline __wsum csum_partial_copy_neon(const void *src, void *dst,
+ int len)
+{
+ __wsum sum;
+
+ scoped_ksimd()
+ sum = __csum_partial_copy_neon(src, dst, len);
+
+ return sum;
+}
+
+/**
+ * csum_partial_copy_nocheck - copy a buffer and compute its Internet checksum
+ * @src: source buffer
+ * @dst: destination buffer; must not overlap @src
+ * @len: number of bytes to copy; nothing is copied for len <= 0
+ *
+ * Copies @len bytes from @src to @dst in a single pass and returns the
+ * unfolded 32-bit ones' complement sum of the data, exactly as
+ * csum_partial(@src, @len, 0) would, so the result can be passed to
+ * csum_block_add() or csum_fold(). The returned sum is the checksum of the
+ * bytes written to @dst even if @src is concurrently modified.
+ *
+ * Callable from task and softirq context. For len >= CSUM_COPY_NEON_MIN_LEN
+ * the copy uses kernel-mode NEON when may_use_simd() allows it, so it must
+ * not be called from within another kernel_neon_begin()/kernel_neon_end()
+ * section in task context (kernel_neon_begin() only nests from softirq).
+ * From hardirq/NMI context, or when FPSIMD is not usable, the general
+ * purpose register implementation is used for any length.
+ *
+ * Return: the unfolded ones' complement sum of the copied data; 0 if len <= 0.
+ */
+__wsum csum_partial_copy_nocheck(const void *src, void *dst, int len)
+{
+ if (unlikely(len <= 0))
+ return 0;
+
+ if (len >= CSUM_COPY_NEON_MIN_LEN && likely(may_use_simd()))
+ return csum_partial_copy_neon(src, dst, len);
+
+ return __csum_partial_copy_scalar(src, dst, len);
+}
+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)
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 134b15a44625e..c56fe16f7dd9f 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2736,6 +2736,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; 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 be04aa42125ce..77d9d09922032 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,474 @@ static void test_csum_ipv6_magic(struct kunit *test)
}
}
+/*
+ * csum_partial_copy_nocheck() tests.
+ *
+ * Only the public entry point is exercised, so the tests work unchanged
+ * against every architecture's implementation and need neither arch #ifdefs
+ * nor access to non-exported internals. The fused copy+checksum is compared
+ * against a deliberately naive C reference (16-bit words accumulated into a
+ * u64, odd trailing byte zero-padded, folded at the end) that shares no code
+ * with csum_partial(). Every call is additionally checked for:
+ * - dst == src over [0, len)
+ * - canary bytes before dst and after dst + len untouched
+ * - src untouched
+ * - agreement with csum_fold(csum_partial(src, len, 0))
+ *
+ * The source is always placed so that src + len sits as close as possible
+ * to the guard page that follows its vmalloc() area, flush against it
+ * whenever (src_off + len) % 16 == 0, so a single byte of over-read faults
+ * instead of going unnoticed. Over-writes are caught by the canaries and, in
+ * test_csum_copy_dst_guard_page(), by the destination's guard page.
+ *
+ * The length and misalignment sweeps are dense enough to reach every branch
+ * of the arm64 implementation through its dispatcher: the scalar path and
+ * its 16/8/4/2/1-byte tail ladder below 1024 bytes, the switch to NEON at
+ * 1024, the 1..15-byte head that aligns dst, the 32/16-byte remainder
+ * blocks and the 1..15-byte overlapping tail. The interrupt context test
+ * reaches the scalar fallback for large lengths from hardirq context, and
+ * the nested kernel-mode NEON case from softirq context.
+ */
+#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 in @area (of copy_area_len bytes) with misalignment @off
+ * and the end as close as possible to the trailing guard page: exactly at
+ * it when (@off + @len) % 16 == 0, otherwise within 15 bytes of it.
+ */
+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);
+
+ return ~sum & 0xffff;
+}
+
+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;
+}
+
+/*
+ * One call of csum_partial_copy_nocheck(src, dst, len) with @before canary
+ * bytes ahead of @dst and @after canary bytes behind dst + len.
+ */
+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 = (__force u16)csum_fold(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,
+ (__force u16)csum_fold(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);
+}
+
+/*
+ * Source misalignments used by the longer sweeps. Destination misalignment
+ * is what an implementation's head handling depends on, so those sweeps
+ * still cover all 16 of them.
+ */
+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);
+}
+
+/* Lengths straddling the arm64 scalar/NEON switch-over at 1024. */
+static void test_csum_copy_around_1024(struct kunit *test)
+{
+ int len, i, dst_off;
+
+ copy_fill_random();
+ for (len = 1024 - 16; len <= 1024 + 16; 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);
+}
+
+/*
+ * Large buffers with unaligned src and dst. dst_off drives the 16-byte head
+ * (odd and even heads), len drives the 32/16-byte remainder blocks and the
+ * 1..15-byte overlapping tail: 1024..1103 covers every combination.
+ */
+static void test_csum_copy_large_unaligned(struct kunit *test)
+{
+ static const int extra_lens[] = {
+ 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]);
+}
+
+/*
+ * Data patterns that stress carries (all ones), the absence of carries,
+ * the top bit of every byte, and byte-swap sensitivity (odd-head/odd-tail
+ * fix-ups are only observable when adjacent bytes differ).
+ */
+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, 1039, 1040, 1041, 1087,
+ 1088, 1089, 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]);
+ }
+}
+
+/*
+ * Destination flush against its guard page (for (dst_off + len) % 16 == 0;
+ * within 15 bytes of it otherwise), so that over-writes fault rather than
+ * relying on the canaries. Lengths around every block size and switch-over.
+ */
+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, 1039, 1040, 1041, 1055, 1056, 1057, 1087, 1088, 1089,
+ 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++) {
+ 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]),
+ copy_place_at_end(copy_dst_area,
+ dst_off, lens[l]),
+ lens[l], COPY_GUARD, 0);
+ }
+}
+
+/* len == 0: return 0 and touch nothing. */
+static void test_csum_copy_zero_len(struct kunit *test)
+{
+ u8 *dst = copy_dst_area + COPY_GUARD;
+
+ copy_fill_random();
+ memset(copy_dst_area, COPY_CANARY, 2 * COPY_GUARD);
+ CHECK_EQ(csum_partial_copy_nocheck(copy_src_area, dst, 0), 0);
+ CHECK_EQ(first_non_canary(copy_dst_area, 2 * COPY_GUARD), -1);
+}
+
+/*
+ * Interrupt context: csum_partial_copy_nocheck() is called from task,
+ * softirq and hardirq context concurrently. Each context has its own
+ * destination (so concurrent copies do not interfere) and the calls cycle
+ * through a few source messages of different lengths, including ones that
+ * an arch's SIMD path would normally handle so that the fallback path is
+ * reached from hardirq context and the nested SIMD case from softirq.
+ */
+#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 = (__force u16)csum_fold(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);
+}
+
+/*
+ * Throughput of csum_partial_copy_nocheck() versus the pre-existing generic
+ * form, memcpy() followed by csum_partial(), for aligned and for misaligned
+ * buffers. Only run if CONFIG_CHECKSUM_KUNIT_BENCHMARK is set.
+ */
+static void test_csum_copy_benchmark(struct kunit *test)
+{
+ static const int lens[] = {
+ 16, 40, 64, 128, 256, 512, 1000, 1024, 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; see 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, t_fused),
+ div64_u64((u64)len * num_iters * 1000, t_split));
+ }
+ }
+}
+
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_around_1024),
+ 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);
|
| 3/2 | 2026/09/28 20:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17125 output: 35 thoughts: 880 Thoughts: **Patch Evaluation: Architecture Mismatch and Test Code** Okay, so I've got this git patch here, and the target architecture for fuzzing is amd64. Let's dig in. It's touching `arch/arm64` stuff - checksum implementations and related files, plus `lib/Kconfig.debug` and `lib/tests/checksum_kunit.c`. Wait a minute, hold up! The target is amd64, but this patch is primarily focused on arm64 architecture files. That's a red flag immediately. Let's refer to the negative criteria. The instructions specify returning `WorthFuzzing=false` if *all* the modified code falls into certain categories. The last bullet point there is key: "Code paths exclusive to architectures other than the target architecture". Bingo! The vast majority of this patch is specific to `arm64`, and we're aiming for `amd64`. Okay, what about that `checksum_kunit.c` file? Is that *potentially* relevant? Is a KUnit test a valid target for syzkaller? Syzkaller primarily focuses on syscall fuzzing. KUnit tests, are self-tests, effectively unit tests. They're typically run at boot or through debugfs/module loading, but they are *test cases* and not the production kernel code. So, according to the negative criteria, KUnit tests are "non-kernel and non-executable changes" because they are self-tests. The instructions cover that too. Just to be absolutely certain, I'll quickly check if `checksum_kunit.c` is even testing anything that's relevant to amd64, such as `csum_partial_copy_nocheck`, just to be certain. And for the sake of completeness, I should also see if any related code changed in the `amd64` checksum implementation. Given the `arm64` focus, I doubt that's the case. It's unlikely that the arm64 changes are relevant to the amd64 implementation. Still, let's verify on `amd64`. |
| 4/2 | 2026/09/28 20:55 | tool | grepper |
0mArgs:{
"Expression": "csum_partial_copy_nocheck",
"PathPrefix": "arch/x86"
}Results: {
"Output": "arch/x86/include/asm/checksum_32.h=30=asmlinkage __wsum csum_partial_copy_generic(const void *src, void *dst, int len);\n--\narch/x86/include/asm/checksum_32.h-38- */\narch/x86/include/asm/checksum_32.h:39:static inline __wsum csum_partial_copy_nocheck(const void *src, void *dst, int len)\narch/x86/include/asm/checksum_32.h-40-{\n--\narch/x86/include/asm/checksum_64.h=140=extern __wsum csum_and_copy_to_user(const void *src, void __user *dst, int len);\narch/x86/include/asm/checksum_64.h:141:extern __wsum csum_partial_copy_nocheck(const void *src, void *dst, int len);\narch/x86/include/asm/checksum_64.h-142-\n--\narch/x86/lib/csum-wrappers_64.c=44=csum_and_copy_to_user(const void *src, void __user *dst, int len)\n--\narch/x86/lib/csum-wrappers_64.c-56-/**\narch/x86/lib/csum-wrappers_64.c:57: * csum_partial_copy_nocheck - Copy and checksum.\narch/x86/lib/csum-wrappers_64.c-58- * @src: source address\n--\narch/x86/lib/csum-wrappers_64.c=64=__wsum\narch/x86/lib/csum-wrappers_64.c:65:csum_partial_copy_nocheck(const void *src, void *dst, int len)\narch/x86/lib/csum-wrappers_64.c-66-{\n--\narch/x86/lib/csum-wrappers_64.c-68-}\narch/x86/lib/csum-wrappers_64.c:69:EXPORT_SYMBOL(csum_partial_copy_nocheck);\narch/x86/lib/csum-wrappers_64.c-70-\n"
} |
| 5/2 | 2026/09/28 20:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19096 output: 144 thoughts: 2299 Thoughts: **Analysis of the Patch for Fuzzing Suitability** Okay, let's break this down. This patch modifies several files, and my job is to determine if it's worth fuzzing on the target architecture, amd64. First, I need to understand the changes at a granular level. The meat of the changes is centered around `arch/arm64/`, specifically in checksum calculations and related code: `arch/arm64/include/asm/checksum.h`, `arch/arm64/lib/Makefile`, `arch/arm64/lib/csum-copy-neon.c`, `arch/arm64/lib/csum-copy.c`, `arch/arm64/lib/csum-copy.h`, and `arch/arm64/lib/csum.c`. These are all clearly ARM64-specific implementations. The additional changes are in `lib/Kconfig.debug`, introducing `CONFIG_CHECKSUM_KUNIT_BENCHMARK`, and `lib/tests/checksum_kunit.c`, which adds unit tests and benchmarks for `csum_partial_copy_nocheck()`. Now, the crucial question: are these changes reachable and relevant for fuzzing on amd64? The triage guidelines are clear. The primary gate is reachability. Let's look at the negative criteria. Anything that is "exclusive to architectures other than the target architecture" is a red flag. The ARM64 code falls squarely into that category. On amd64, this code is *never* compiled, *never* executed. It's simply unreachable. But what about the KUnit tests? The guidelines say to ignore "User-space directories, self-tests, samples, or scripts... that do not affect the compiled kernel image (vmlinux) or kernel modules". KUnit tests are self-tests or unit tests, and the guidelines state they should be ignored, although, `lib/tests/checksum_kunit.c` can be built into vmlinux or modules if enabled. But Syzkaller, which is the fuzzer of concern, operates on system calls, ioctls, and network packets, which are not triggered by KUnit tests. These tests run once, with fixed inputs. The KUnit code, even if compiled in, isn't an attack surface for syzkaller. Furthermore, the core functionality of `csum_partial_copy_nocheck` *has not been modified* on amd64 by this patch. Re-reading the guidelines, I can definitively say that the changes in `arch/arm64/` are irrelevant to amd64, and the KUnit tests are not an attack surface. Therefore, all modified code is *unreachable* or falls into a category explicitly excluded from fuzzing consideration on amd64. There's no kernel logic reachable through syscalls or I/O. So, I'm confident in setting `WorthFuzzing` to `false`. Since nothing is worth fuzzing, there's no need to enable any specific configurations or focus on particular symbols. The lists for `EnableConfigs` and `FocusSymbols` should be empty (or null). The reasoning is: 1. Target architecture: amd64 2. The primary logic is arm64 specific, not reachable. 3. Unit tests/benchmarks do not provide an attack surface for the syscall-based fuzzer. 4. All modifications are either architecture-specific or part of internal testing, so fuzzing is not worthwhile. Let's call `set-results` now. |
| 6/2 | 2026/09/28 20:55 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch introduces an arm64-specific fused copy and checksum implementation (NEON and scalar variants of csum_partial_copy_nocheck) under arch/arm64/, which is structurally unreachable on the target architecture (amd64). The only generic changes are additions to the KUnit checksum test suite and benchmark in lib/tests/checksum_kunit.c and lib/Kconfig.debug, which are standalone unit tests rather than reachable production kernel code. Consequently, there are no reachable functional changes on amd64 to fuzz.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces an arm64-specific fused copy and checksum implementation (NEON and scalar variants of csum_partial_copy_nocheck) under arch/arm64/, which is structurally unreachable on the target architecture (amd64). The only generic changes are additions to the KUnit checksum test suite and benchmark in lib/tests/checksum_kunit.c and lib/Kconfig.debug, which are standalone unit tests rather than reachable production kernel code. Consequently, there are no reachable functional changes on amd64 to fuzz.",
"WorthFuzzing": false
} |
| 7/1 | 2026/09/28 20:55 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|