// SPDX-License-Identifier: GPL-2.0 /* * CUBELinux: the `cube(2)` syscall — the kernel's coordinate interface. * * The store's operations live in Rust (drivers/cube/cubelinux_store.rs) and are already * proven: they append to a write-ahead log durably, replay it, fold it into the image, and * survive a torn tail. What was missing was a way for a program to *call* them. This file is * that way and nothing else. * * Why C for the entry point: syscalls are defined by `SYSCALL_DEFINE*`, which is a C macro * that registers the function in the syscall table with the right calling convention. Rust in * this kernel cannot define one, so the entry point, the user copies and the argument * validation are here, and the Rust side sees only kernel memory and validated numbers. That * split is deliberate: everything that touches a userspace pointer is in one place, and * everything that touches the store's bytes is in the other. */ #include #include #include #include #include /* * Implemented in Rust. The coordinate is passed as its parts rather than as a struct, so the * format knowledge on the Rust side stays in the module that owns it — including the Morton * encoding, which must produce exactly the key a userspace reader decodes. */ int cubelinux_kernel_put(const __u8 *space, __u64 x, __u64 y, __u64 z, const void *value, size_t len); ssize_t cubelinux_kernel_get(const __u8 *space, __u64 x, __u64 y, __u64 z, void *buf, size_t len); int cubelinux_kernel_del(const __u8 *space, __u64 x, __u64 y, __u64 z); int cubelinux_kernel_sync(void); /* No value may be larger than this in one call. A coordinate store is not a bulk-file path; * a caller with more than this to store has more than one record to store. */ #define CUBE_MAX_VALUE (16u * 1024u * 1024u) SYSCALL_DEFINE2(cube, unsigned int, op, void __user *, uargs) { struct cube_args args; void *buf = NULL; long ret = 0; if (copy_from_user(&args, uargs, sizeof(args))) return -EFAULT; /* * The size is the caller's, and it must be the one this kernel implements: a caller * built against a later block would otherwise have fields silently ignored. */ if (args.size != sizeof(struct cube_args)) return -EINVAL; switch (op) { case CUBE_OP_PUT: case CUBE_OP_GET: break; case CUBE_OP_DEL: case CUBE_OP_SYNC: break; default: return -EINVAL; } if (op == CUBE_OP_PUT || op == CUBE_OP_GET) { if (args.len > CUBE_MAX_VALUE) return -E2BIG; if (args.len > 0) { buf = kvmalloc(args.len, GFP_KERNEL); if (!buf) return -ENOMEM; } } switch (op) { case CUBE_OP_PUT: if (args.len > 0 && copy_from_user(buf, (void __user *)args.value, args.len)) { ret = -EFAULT; break; } ret = cubelinux_kernel_put(args.coord.space, args.coord.x, args.coord.y, args.coord.z, buf, args.len); break; case CUBE_OP_GET: { ssize_t got; got = cubelinux_kernel_get(args.coord.space, args.coord.x, args.coord.y, args.coord.z, buf, args.len); if (got < 0) { ret = got; break; } if ((u64)got > args.len) { /* * Too small. Tell the caller how much it needs, so a read is two * calls at worst and never a guess. */ args.len = (u64)got; if (copy_to_user(uargs, &args, sizeof(args))) ret = -EFAULT; else ret = -ERANGE; break; } if (got > 0 && copy_to_user((void __user *)args.value, buf, got)) { ret = -EFAULT; break; } args.len = (u64)got; if (copy_to_user(uargs, &args, sizeof(args))) ret = -EFAULT; break; } case CUBE_OP_DEL: ret = cubelinux_kernel_del(args.coord.space, args.coord.x, args.coord.y, args.coord.z); break; case CUBE_OP_SYNC: ret = cubelinux_kernel_sync(); break; } kvfree(buf); return ret; }