[PATCH v2 01/14] smb: common: compress: add memory access helpers to compress.h
Enzo Matsumiya <[email protected]> Mon, 20 Jul 2026 16:49:12 -0300
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <[email protected]> |
Add memory access helpers + some other common definitions to be used by SMB2 compression (both server and client, and all algorithms). Changes: - update affected call paths in lz77.c and compress.c - update server/compress.* and client/compress.* to include compress.h only Signed-off-by: Enzo Matsumiya <[email protected]> --- fs/smb/client/compress.c | 3 +- fs/smb/client/compress.h | 11 --- fs/smb/common/compress/compress.c | 9 +- fs/smb/common/compress/compress.h | 126 ++++++++++++++++++++++++++++ fs/smb/common/compress/lz77.c | 134 +++++++----------------------- fs/smb/server/compress.c | 3 - 6 files changed, 156 insertions(+), 130 deletions(-) diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c index 8f0860970741..5591a8d85037 100644 --- a/fs/smb/client/compress.c +++ b/fs/smb/client/compress.c @@ -18,11 +18,10 @@ #include <linux/sort.h> #include "cifsglob.h" -#include "../common/smb2pdu.h" #include "cifsproto.h" #include "smb2proto.h" -#include "../common/compress/lz77.h" +#include "../common/compress/compress.h" #include "compress.h" /* diff --git a/fs/smb/client/compress.h b/fs/smb/client/compress.h index e08e6d339d21..fc8c8d502e45 100644 --- a/fs/smb/client/compress.h +++ b/fs/smb/client/compress.h @@ -17,24 +17,14 @@ #include <linux/uio.h> #include <linux/kernel.h> -#include "../common/smb2pdu.h" -#include "../common/compress/compress.h" #include "cifsglob.h" -/* sizeof(smb2_compression_hdr) - sizeof(OriginalPayloadSize) */ -#define SMB_COMPRESS_HDR_LEN 16 -/* sizeof(smb2_compression_payload_hdr) - sizeof(OriginalPayloadSize) */ -#define SMB_COMPRESS_PAYLOAD_HDR_LEN 8 -#define SMB_COMPRESS_MIN_LEN PAGE_SIZE - #ifdef CONFIG_CIFS_COMPRESSION typedef int (*compress_send_fn)(struct TCP_Server_Info *, int, struct smb_rqst *); - int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_send_fn send_fn); bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq); - #else /* !CONFIG_CIFS_COMPRESSION */ static inline int smb_compress(void *unused1, void *unused2, void *unused3) { @@ -45,6 +35,5 @@ static inline bool should_compress(void *unused1, void *unused2) { return false; } - #endif /* !CONFIG_CIFS_COMPRESSION */ #endif /* _SMB_COMPRESS_H */ diff --git a/fs/smb/common/compress/compress.c b/fs/smb/common/compress/compress.c index b07a317597a4..088e0d0d2792 100644 --- a/fs/smb/common/compress/compress.c +++ b/fs/smb/common/compress/compress.c @@ -7,15 +7,8 @@ #include <linux/module.h> #include <linux/overflow.h> #include <linux/string.h> -#include <linux/unaligned.h> #include "compress.h" -#include "lz77.h" - -#define SMB2_COMPRESSION_CHAINED_HDR_LEN \ - offsetof(struct smb2_compression_hdr, CompressionAlgorithm) -#define SMB2_COMPRESSION_PAYLOAD_BASE_LEN \ - (sizeof(struct smb2_compression_payload_hdr) - sizeof(__le32)) /* * A NONE payload carries bytes verbatim. Keep both cursors and remaining @@ -75,7 +68,7 @@ static int smb_decompress_lz77_payload(const u8 **src, u32 *slen, u8 **dst, if (len < sizeof(__le32) || len > *slen) return -EINVAL; - orig_size = get_unaligned_le32(*src); + orig_size = mem_read32(*src); if (orig_size > *dlen) return -EINVAL; diff --git a/fs/smb/common/compress/compress.h b/fs/smb/common/compress/compress.h index 7ace3bf4b664..24b558167fe7 100644 --- a/fs/smb/common/compress/compress.h +++ b/fs/smb/common/compress/compress.h @@ -1,11 +1,137 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (C) 2026 Namjae Jeon <[email protected]> + * Copyright (C) 2026, SUSE LLC, author: Enzo Matsumiya <[email protected]> + * + * Common helpers and definitions for SMB2 compression/decompression. + * + * Note that, as helpers, most assume their args were previously checked by callers. */ #ifndef _COMMON_SMB_COMPRESS_H #define _COMMON_SMB_COMPRESS_H +#include <linux/count_zeros.h> +#include <linux/string.h> +#include <linux/sizes.h> +#include <linux/slab.h> #include "../smb2pdu.h" +#include "lz77.h" + +#define SMB2_COMPRESSION_CHAINED_HDR_LEN \ + offsetof(struct smb2_compression_hdr, CompressionAlgorithm) +#define SMB2_COMPRESSION_PAYLOAD_BASE_LEN \ + (sizeof(struct smb2_compression_payload_hdr) - sizeof(__le32)) +#define SMB_COMPRESS_MIN_LEN PAGE_SIZE + +/* + * Memory ops helpers. + */ +#undef MEM_UNALIGNED_READ +#undef MEM_UNALIGNED_WRITE + +/* Read prefetch */ +#define MEM_PREFETCH(ptr) __builtin_prefetch((ptr), 0, 3) + +/* + * x86 safely handles unaligned reads by pointer deref. + * Use the unaligned helpers for other archs. + */ +#ifdef CONFIG_X86 +# define MEM_UNALIGNED_READ(ptr, t) (*(const t *)(ptr)) +# define MEM_UNALIGNED_WRITE(ptr, v, t) (*(t *)(ptr) = (t)(v)) +#else +# include <linux/unaligned.h> +# define MEM_UNALIGNED_READ(ptr, t) get_unaligned((const t *)(ptr)) +# define MEM_UNALIGNED_WRITE(ptr, v, t) put_unaligned((v), (t *)(ptr)) +#endif /* !CONFIG_X86 */ + +#define mem_read8(ptr) MEM_UNALIGNED_READ(ptr, u8) +#define mem_read16(ptr) MEM_UNALIGNED_READ(ptr, u16) +#define mem_read32(ptr) MEM_UNALIGNED_READ(ptr, u32) +#define mem_read64(ptr) MEM_UNALIGNED_READ(ptr, u64) +#define mem_write8(ptr, v) MEM_UNALIGNED_WRITE(ptr, v, u8) +#define mem_write16(ptr, v) MEM_UNALIGNED_WRITE(ptr, v, u16) +#define mem_write32(ptr, v) MEM_UNALIGNED_WRITE(ptr, v, u32) +/* mem_write64() not implemented -- not used anywhere yet */ + +/** + * SMB_COMPRESS_RSTEP_SIZE: Number of bytes to read from input buffer for hashing and initial + * match check (default 4 bytes). + * SMB_COMPRESS_MSTEP_SIZE: Number of bytes to extend-compare a found match (default 8 bytes). + */ +#define SMB_COMPRESS_RSTEP_SIZE sizeof(u32) +#define SMB_COMPRESS_MSTEP_SIZE sizeof(u64) + +/** + * mem_match_len() - Fast (batch) count matching bytes on a linear buffer. + * @start: start of buffer + * @head: current position on buffer + * @end: end of buffer + * + * Compare 8 bytes of @start and @head until a mismatch is found or @head reaches @end. + * + * Requirements: + * - no args can be NULL (must be asserted by caller) + * - all args must point to the same allocated memory (must be asserted by caller) + * - @start < @head + 8 <= end (asserted here) + * + * Return: number of matching bytes (0 if last requirement fails) + */ +static __always_inline size_t mem_match_len(const void *start, const void *head, const void *end) +{ + const void *cur = head; + + if (unlikely(start >= head || head + SMB_COMPRESS_MSTEP_SIZE > end)) + return 0; + + do { + const u64 diff = mem_read64(head) ^ mem_read64(start); + + if (!diff) { + head += SMB_COMPRESS_MSTEP_SIZE; + start += SMB_COMPRESS_MSTEP_SIZE; + + continue; + } + + /* This computes the number of common bytes in @diff. */ + head += count_trailing_zeros(diff) >> 3; + + return (head - cur); + } while (likely(head + SMB_COMPRESS_MSTEP_SIZE <= end)); + + /* Fallback to byte-by-byte comparison for last bytes (< SMB_COMPRESS_MSTEP_SIZE). */ + while (head < end && mem_read8(start) == mem_read8(head)) { + head++; + start++; + } + + return (head - cur); +} + +/* + * Hashing parameters. + * Same for all algorithms. + * + * XXX: these are fixed for now, might make them tunables in the future. + */ + +/** + * SMB_COMPRESS_HASH_LOG: ilog2 hash size (recommended to be 13 - 18, default 15). + * SMB_COMPRESS_HASH_SIZE: Hashtable size (default is 32k (1 << SMB_COMPRESS_HASH_LOG))). + */ +#define SMB_COMPRESS_HASH_LOG 15 +#define SMB_COMPRESS_HASH_SIZE BIT(SMB_COMPRESS_HASH_LOG) + +static __always_inline u32 smb_compress_hash(const u32 v) +{ + return ((v ^ 0x9E3779B9U) * 0x85EBCA6BU) >> (32 - SMB_COMPRESS_HASH_LOG); +} + +static __always_inline u32 smb_compress_hash_ptr(const void *ptr) +{ + return smb_compress_hash(mem_read32(ptr)); +} /* * SMB3_COMPRESS_NONE is valid only in chained payload headers. It is never diff --git a/fs/smb/common/compress/lz77.c b/fs/smb/common/compress/lz77.c index 9216d973d876..e32e2f3040d8 100644 --- a/fs/smb/common/compress/lz77.c +++ b/fs/smb/common/compress/lz77.c @@ -15,19 +15,12 @@ #include <linux/module.h> #include <linux/overflow.h> -#include "lz77.h" +#include "compress.h" /* * Compression parameters. * * LZ77_MATCH_MAX_DIST: Farthest back a match can be from current position (can be 1 - 8K). - * LZ77_HASH_LOG: - * LZ77_HASH_SIZE: ilog2 hash size (recommended to be 13 - 18, default 15 (hash size - * 32k)). - * LZ77_RSTEP_SIZE: Number of bytes to read from input buffer for hashing and initial - * match check (default 4 bytes, this effectivelly makes this the min - * match len). - * LZ77_MSTEP_SIZE: Number of bytes to extend-compare a found match (default 8 bytes). * LZ77_SKIP_TRIGGER: ilog2 value for adaptive skipping, i.e. to progressively skip input * bytes when we can't find matches. Default is 4. * Higher values (>0) will decrease compression time, but will result @@ -35,75 +28,10 @@ * compression ratio (more matches found), but will increase time. */ #define LZ77_MATCH_MAX_DIST SZ_8K -#define LZ77_HASH_LOG 15 -#define LZ77_HASH_SIZE BIT(LZ77_HASH_LOG) -#define LZ77_RSTEP_SIZE sizeof(u32) -#define LZ77_MSTEP_SIZE sizeof(u64) #define LZ77_SKIP_TRIGGER 4 -#define LZ77_PREFETCH(ptr) __builtin_prefetch((ptr), 0, 3) #define LZ77_FLAG_MAX 32 -static __always_inline u8 lz77_read8(const u8 *ptr) -{ - return get_unaligned(ptr); -} - -static __always_inline u32 lz77_read32(const u32 *ptr) -{ - return get_unaligned(ptr); -} - -static __always_inline u64 lz77_read64(const u64 *ptr) -{ - return get_unaligned(ptr); -} - -static __always_inline void lz77_write8(u8 *ptr, u8 v) -{ - put_unaligned(v, ptr); -} - -static __always_inline void lz77_write16(u16 *ptr, u16 v) -{ - put_unaligned_le16(v, ptr); -} - -static __always_inline void lz77_write32(u32 *ptr, u32 v) -{ - put_unaligned_le32(v, ptr); -} - -static __always_inline u32 lz77_match_len(const void *match, const void *cur, const void *end) -{ - const void *start = cur; - - /* Safe for a do/while because otherwise we wouldn't reach here from the main loop. */ - do { - const u64 diff = lz77_read64(cur) ^ lz77_read64(match); - - if (!diff) { - cur += LZ77_MSTEP_SIZE; - match += LZ77_MSTEP_SIZE; - - continue; - } - - /* This computes the number of common bytes in @diff. */ - cur += count_trailing_zeros(diff) >> 3; - - return (cur - start); - } while (likely(cur + LZ77_MSTEP_SIZE <= end)); - - /* Fallback to byte-by-byte comparison for last <8 bytes. */ - while (cur < end && lz77_read8(cur) == lz77_read8(match)) { - cur++; - match++; - } - - return (cur - start); -} - /** * lz77_encode_match() - Match encoding. * @dst: compressed buffer @@ -124,24 +52,24 @@ static __always_inline void *lz77_encode_match(void *dst, void **nib, u16 dist, dist <<= 3; if (len < 7) { - lz77_write16(dst, dist + len); + mem_write16(dst, dist + len); return dst + sizeof(u16); } dist |= 7; - lz77_write16(dst, dist); + mem_write16(dst, dist); dst += sizeof(u16); len -= 7; if (!*nib) { - lz77_write8(dst, umin(len, 15)); + mem_write8(dst, umin(len, 15)); *nib = dst; dst++; } else { u8 *b = *nib; - lz77_write8(b, *b | umin(len, 15) << 4); + mem_write8(b, *b | umin(len, 15) << 4); *nib = NULL; } @@ -150,23 +78,23 @@ static __always_inline void *lz77_encode_match(void *dst, void **nib, u16 dist, len -= 15; if (len < 255) { - lz77_write8(dst, len); + mem_write8(dst, len); return dst + 1; } - lz77_write8(dst, 0xff); + mem_write8(dst, 0xff); dst++; len += 7 + 15; if (len <= 0xffff) { - lz77_write16(dst, len); + mem_write16(dst, len); return dst + sizeof(u16); } - lz77_write16(dst, 0); + mem_write16(dst, 0); dst += sizeof(u16); - lz77_write32(dst, len); + mem_write32(dst, len); return dst + sizeof(u32); } @@ -204,7 +132,7 @@ static __always_inline void *lz77_encode_literals(const void *start, const void *f <<= len; *fc += len; if (*fc == LZ77_FLAG_MAX) { - lz77_write32(*fp, *f); + mem_write32(*fp, *f); *fc = 0; *fp = dst; dst += sizeof(u32); @@ -214,13 +142,7 @@ static __always_inline void *lz77_encode_literals(const void *start, const void return dst; } -static __always_inline u32 lz77_hash(const u32 v) -{ - return ((v ^ 0x9E3779B9) * 0x85EBCA6B) >> (32 - LZ77_HASH_LOG); -} - -noinline int smb_lz77_compress(const void *src, const u32 slen, - void *dst, u32 *dlen) +noinline int smb_lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen) { const void *srcp, *rlim, *end, *anchor; u32 *htable, hash, flag_count = 0; @@ -234,25 +156,25 @@ noinline int smb_lz77_compress(const void *src, const u32 slen, srcp = src; anchor = src; end = srcp + slen; /* absolute end */ - rlim = end - LZ77_MSTEP_SIZE; /* read limit (for lz77_match_len()) */ + rlim = end - SMB_COMPRESS_MSTEP_SIZE; /* read limit (for mem_match_len()) */ dstp = dst; flag_pos = dstp; dstp += sizeof(u32); nib = NULL; - htable = kvcalloc(LZ77_HASH_SIZE, sizeof(*htable), GFP_KERNEL); + htable = kvcalloc(SMB_COMPRESS_HASH_SIZE, sizeof(*htable), GFP_KERNEL); if (!htable) return -ENOMEM; - LZ77_PREFETCH(srcp + LZ77_RSTEP_SIZE); + MEM_PREFETCH(srcp + SMB_COMPRESS_RSTEP_SIZE); /* * Adjust @srcp so we don't get a false positive match on first iteration. * Then prepare hash for first loop iteration (don't advance @srcp again). */ - hash = lz77_hash(lz77_read32(srcp++)); + hash = smb_compress_hash_ptr(srcp++); htable[hash] = 0; - hash = lz77_hash(lz77_read32(srcp)); + hash = smb_compress_hash_ptr(srcp); /* * Main loop. @@ -285,11 +207,11 @@ noinline int smb_lz77_compress(const void *src, const u32 slen, if (unlikely(next > rlim)) goto out; - hash = lz77_hash(lz77_read32(next)); + hash = smb_compress_hash_ptr(next); match = src + htable[cur_hash]; htable[cur_hash] = srcp - src; } while (likely(match + LZ77_MATCH_MAX_DIST < srcp) || - lz77_read32(match) != lz77_read32(srcp)); + mem_read32(match) != mem_read32(srcp)); /* * Match found. Warm/cold path; begin parsing @srcp and writing to @dstp: @@ -302,17 +224,17 @@ noinline int smb_lz77_compress(const void *src, const u32 slen, * redundantly compute it again in lz77_match_len() than to adjust pointers/len. */ dstp = lz77_encode_literals(anchor, srcp, dstp, &flag, &flag_count, &flag_pos); - len = lz77_match_len(match, srcp, end); + len = mem_match_len(match, srcp, end); dstp = lz77_encode_match(dstp, &nib, srcp - match, len); srcp += len; anchor = srcp; - LZ77_PREFETCH(srcp); + MEM_PREFETCH(srcp); flag = (flag << 1) | 1; flag_count++; if (flag_count == LZ77_FLAG_MAX) { - lz77_write32(flag_pos, flag); + mem_write32(flag_pos, flag); flag_count = 0; flag_pos = dstp; dstp += sizeof(u32); @@ -322,7 +244,7 @@ noinline int smb_lz77_compress(const void *src, const u32 slen, break; /* Prepare for next loop. */ - hash = lz77_hash(lz77_read32(srcp)); + hash = smb_compress_hash_ptr(srcp); } while (srcp < end); out: dstp = lz77_encode_literals(anchor, end, dstp, &flag, &flag_count, &flag_pos); @@ -330,7 +252,7 @@ noinline int smb_lz77_compress(const void *src, const u32 slen, flag_count = LZ77_FLAG_MAX - flag_count; flag <<= flag_count; flag |= (1UL << flag_count) - 1; - lz77_write32(flag_pos, flag); + mem_write32(flag_pos, flag); *dlen = dstp - dst; kvfree(htable); @@ -376,7 +298,7 @@ static int lz77_decode_match_len(const u8 **src, const u8 *end, u16 token, if (end - *src < 2) return -EINVAL; - w = get_unaligned_le16(*src); + w = mem_read16(*src); *src += 2; if (w) { *len = w + 3; @@ -385,7 +307,7 @@ static int lz77_decode_match_len(const u8 **src, const u8 *end, u16 token, if (end - *src < 4) return -EINVAL; - long_len = get_unaligned_le32(*src); + long_len = mem_read32(*src); *src += 4; if (check_add_overflow(long_len, 3, len)) return -EINVAL; @@ -413,7 +335,7 @@ int smb_lz77_decompress(const void *src, const u32 slen, void *dst, if (!flag_count) { if (send - sp < 4) return -EINVAL; - flags = get_unaligned_le32(sp); + flags = mem_read32(sp); sp += 4; flag_count = 32; } @@ -433,7 +355,7 @@ int smb_lz77_decompress(const void *src, const u32 slen, void *dst, if (send - sp < 2) return -EINVAL; - token = get_unaligned_le16(sp); + token = mem_read16(sp); sp += 2; dist = (token >> 3) + 1; diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c index f8cf515b9c30..8c910f996e04 100644 --- a/fs/smb/server/compress.c +++ b/fs/smb/server/compress.c @@ -10,9 +10,6 @@ #include "compress.h" #include "smb_common.h" -#include "../common/compress/lz77.h" - -#define SMB_COMPRESS_MIN_LEN PAGE_SIZE /** * ksmbd_decompress_request() - replace a compressed request with its SMB2 PDU -- 2.54.0