// SPDX-License-Identifier: GPL-2.0-or-later // The zstd decoder for SCFF chunks (include/fs/scff.h). // // One static decompression context in a fixed workspace, so nothing here // allocates, and one caller at a time: kernel/dev/ramdisk.c holds its chunk // lock across every call. // // **The decoder runs on a stack of its own.** A kernel stack is 8 KB, and a // chunk is decompressed at the bottom of a read -- syscall, namei, SchFS, // bread, ramdiskrw -- with most of it already used. zstd's sequence decoder // keeps several KB of state in locals, which Linux gives it on a 16 KB stack. // Here that would run off the end of the process's stack into whatever is // below it. So the call is made on scff_stack, which the chunk lock makes // this caller's alone. An interrupt taken meanwhile lands on it too, which // is fine: it is a kernel stack like any other, and a big one. // // This file sees zstd's headers and none of the kernel's, which keeps the two // sets of type definitions from meeting. #define ZSTD_STATIC_LINKING_ONLY #include "zstd.h" #include "scff_zstd.h" static unsigned char scff_work[256 * 1024] __attribute__((aligned(64))); static unsigned char scff_stack[64 * 1024] __attribute__((aligned(16))); static ZSTD_DCtx *dctx; // call_on_stack(fn, arg, top): fn(arg) with %rsp at top, then back. // %rbp keeps the old stack pointer across the call; it is callee-saved. long call_on_stack(long (*fn)(void *), void *arg, void *top); __asm__( ".text\n" ".globl call_on_stack\n" "call_on_stack:\n" " pushq %rbp\n" " movq %rsp, %rbp\n" " movq %rdx, %rsp\n" " movq %rdi, %rax\n" " movq %rsi, %rdi\n" " callq *%rax\n" " movq %rbp, %rsp\n" " popq %rbp\n" " retq\n"); struct job { void *dst; unsigned long cap; const void *src; unsigned long n; }; static long run(void *arg) { struct job *j = arg; size_t r = ZSTD_decompressDCtx(dctx, j->dst, j->cap, j->src, j->n); return ZSTD_isError(r) ? -1 : (long)r; } int scff_zstd_init(void) { if (dctx) return 0; if (ZSTD_estimateDCtxSize() > sizeof(scff_work)) return -1; dctx = ZSTD_initStaticDCtx(scff_work, sizeof(scff_work)); return dctx ? 0 : -1; } long scff_zstd_decompress(void *dst, unsigned long cap, const void *src, unsigned long n) { struct job j = { dst, cap, src, n }; if (!dctx) return -1; return call_on_stack(run, &j, scff_stack + sizeof(scff_stack)); }