From: Demian Shulhan <demyansh@gmail.com>
To: Catalin Marinas <catalin.marinas@arm.com>, Will Deacon <will@kernel.org>
Cc: Demian Shulhan <demyansh@gmail.com>,
Mark Rutland <mark.rutland@arm.com>,
Eric Biggers <ebiggers@kernel.org>,
Andrew Morton <akpm@linux-foundation.org>,
Marco Elver <elver@google.com>, Ard Biesheuvel <ardb@kernel.org>,
Robin Murphy <robin.murphy@arm.com>,
David Gow <davidgow@google.com>,
Brendan Higgins <brendan.higgins@linux.dev>,
Nathan Chancellor <nathan@kernel.org>,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, kunit-dev@googlegroups.com,
netdev@vger.kernel.org, llvm@lists.linux.dev
Subject: [PATCH 1/2] arm64: csum: Add fused copy and Internet checksum
Date: Sun, 27 Sep 2026 15:17:57 +0200 [thread overview]
Message-ID: <20260927131838.6774-2-demyansh@gmail.com> (raw)
In-Reply-To: <20260927131838.6774-1-demyansh@gmail.com>
arm64 currently falls back to the generic csum_partial_copy_nocheck(),
requiring two passes over the data (memcpy + csum_partial).
Replace it with a single-pass implementation. The implementation
provides two paths handling any src/dst alignment:
1. __csum_partial_copy_scalar(): Uses general-purpose registers,
processing 32 bytes per iteration through four independent ones'
complement accumulators. Used below the NEON threshold or when
may_use_simd() is false.
2. __csum_partial_copy_neon(): Uses UADDLP/UADALP accumulating into
two 64-bit accumulators, processing 64 bytes per iteration.
The NEON threshold is fixed at 1024 bytes (CSUM_COPY_NEON_MIN_LEN).
Boot-time calibration is avoided due to measurement noise and
big.LITTLE asymmetries. 1024 bytes ensures MTU-sized buffers utilize
the NEON path while avoiding kernel_neon_begin() overhead for small
payloads.
All loads remain within [src, src + len) and stores within [dst, dst +
len), requiring no KASAN annotations. To guarantee the returned checksum
matches dst even under concurrent source modification (e.g.,
MSG_ZEROCOPY), overlapping tail bytes are deliberately copied before the
body.
Performance:
1) Ampere Altra (Neoverse-N1, MIDR part 0xd0c), GCP t2a-standard-2,
Ubuntu 24.04, kernel 7.0.0-1011-gcp, gcc 13.3.
Measured in-kernel (minimum over 20000 calls, ns per call):
align len | generic | new | speedup
aligned 64 | 14.7 | 11.0 | 1.33x
aligned 128 | 18.7 | 13.8 | 1.36x
aligned 256 | 27.7 | 19.4 | 1.42x
aligned 512 | 42.9 | 31.5 | 1.36x
aligned 1000 | 72.0 | 54.9 | 1.31x
aligned 1023 | 73.8 | 57.9 | 1.27x
aligned 1024 | 73.0 | 59.8 | 1.21x
aligned 1500 | 102.1 | 73.8 | 1.38x
aligned 2048 | 134.6 | 91.2 | 1.47x
aligned 4096 | 257.1 | 153.4 | 1.67x
src+1/dst+0 1500 | 118.2 | 78.9 | 1.49x
src+1/dst+0 4096 | 300.8 | 164.3 | 1.83x
src+0/dst+1 1500 | 117.6 | 79.7 | 1.47x
src+0/dst+1 4096 | 303.8 | 167.4 | 1.81x
src+15/dst+15 64 | 16.0 | 11.0 | 1.44x
src+15/dst+15 512 | 42.9 | 33.1 | 1.29x
src+15/dst+15 1023 | 71.5 | 61.3 | 1.16x
src+15/dst+15 1024 | 72.5 | 61.9 | 1.17x
src+15/dst+15 1500 | 99.7 | 77.1 | 1.29x
src+15/dst+15 4096 | 255.5 | 156.3 | 1.63x
Scalar vs NEON break-even on the same core (NEON includes
kernel_neon_begin()/end()):
align len | scalar | neon+begin/end
aligned 512 | 29.6 | 40.6
aligned 768 | 42.2 | 48.8
aligned 1024 | 54.9 | 56.6
aligned 1280 | 67.4 | 64.6
2) Apple M-series core (MIDR implementer 0x61).
Userspace harness, fixed CPU affinity, minimum over 3 runs x 9 batches
(ns per call):
align len | generic scalar neon | new | speedup
aligned 20 | 2.8 1.8 - | 1.8 | 1.56x
aligned 40 | 2.8 1.7 - | 1.7 | 1.65x
aligned 64 | 3.0 2.0 - | 2.0 | 1.50x
aligned 128 | 4.6 3.1 2.5 | 3.1 | 1.48x
aligned 256 | 14.8 5.1 3.4 | 5.1 | 2.90x
aligned 512 | 12.8 9.6 5.3 | 9.6 | 1.33x
aligned 1000 | 26.6 20.7 8.2 | 20.7 | 1.29x
aligned 1024 | 24.4 18.9 8.2 | 15.2 | 1.61x
aligned 1500 | 35.2 30.7 13.0 | 20.0 | 1.76x
aligned 2048 | 53.1 41.7 17.3 | 24.3 | 2.19x
aligned 4096 | 104.1 85.6 37.9 | 44.9 | 2.32x
Testing: All sizes and alignments cross-checked against
memcpy()+csum_partial() (0 mismatches). Concurrent-writer test confirms
checksum(dst) == returned sum under source modification (2M iterations,
0 mismatches). Builds clean with gcc 13 and clang 18 at W=1;
checkpatch --strict clean.
Signed-off-by: Demian Shulhan <demyansh@gmail.com>
---
arch/arm64/include/asm/checksum.h | 3 +
arch/arm64/lib/Makefile | 7 +-
arch/arm64/lib/csum-copy-neon.c | 168 ++++++++++++++++++++++++++++++
arch/arm64/lib/csum-copy.c | 108 +++++++++++++++++++
arch/arm64/lib/csum-copy.h | 84 +++++++++++++++
arch/arm64/lib/csum.c | 54 ++++++++++
6 files changed, 423 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/lib/csum-copy-neon.c
create mode 100644 arch/arm64/lib/csum-copy.c
create mode 100644 arch/arm64/lib/csum-copy.h
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/Makefile b/arch/arm64/lib/Makefile
index b33e1ca4a781..5e1024c31f67 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 000000000000..bfeaf2beba39
--- /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 000000000000..d384b6be0abf
--- /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 000000000000..cd4c9238be38
--- /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 2432683e48a6..4f949b7085b5 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)
--
2.43.0
next prev parent reply other threads:[~2026-09-27 13:20 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-27 13:17 [PATCH 0/2] " Demian Shulhan
2026-09-27 13:17 ` Demian Shulhan [this message]
2026-09-27 13:17 ` [PATCH 2/2] lib/tests: checksum: Add KUnit tests for csum_partial_copy_nocheck() Demian Shulhan
2026-09-27 17:44 ` [PATCH 0/2] arm64: csum: Add fused copy and Internet checksum David Laight
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260927131838.6774-2-demyansh@gmail.com \
--to=demyansh@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=ardb@kernel.org \
--cc=brendan.higgins@linux.dev \
--cc=catalin.marinas@arm.com \
--cc=davidgow@google.com \
--cc=ebiggers@kernel.org \
--cc=elver@google.com \
--cc=kunit-dev@googlegroups.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=mark.rutland@arm.com \
--cc=nathan@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=robin.murphy@arm.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®