commit - e7410e29eb092d28a616a7e1bf805d39c154d987
commit + 6d3224a4064b7c0db0800d004d6e15f50bd8fb8b
blob - 6f743a8441c62c158d86795fa21f5e40f32d2398
blob + 8ef5c9e8ccc856d9de0a9a6b04c16b3a2fc33323
--- AI_CONTEXT.md
+++ AI_CONTEXT.md
|-------------------------------|----------------------------------|------------------------------------------------|
| Compile, selected arch | `make ARCH=<arch> legacy` | Successful compile and link |
| Runtime sysroot compile/link | `make ARCH=<arch> test-sysroot` | Freestanding hello objects and static ELF link |
-| Static analysis/style | `make lint` | Tools ran on a non-empty tracked-file list |
+| Static analysis/style | `make lint` | Tools ran on a non-empty source-file list |
| x86_64 UEFI boot | `make run-uefi` | Required service and shell markers |
| x86_64 legacy boot | `make run-legacy` | Required service and shell markers |
+| x86_64 console input | `make ARCH=x86_64 test-console` | Ordered UEFI and legacy command results |
| ARM64 direct boot | `make ARCH=arm64 run-arm64` | Required common boot markers |
| Cross-arch smoke | `scripts/test_sanity.sh` | Init, namesvc, VFS, and ext2 markers |
| Ext2 and poll regression | `scripts/test_ext2_poll.sh` | Mount, stat, read, poll, and ppoll markers |
though normal guests may keep running for input. Use a build or guest exit
path that can terminate cleanly when running this harness.
- `scripts/run_boot_tests.sh` uses fixed OVMF paths and `/tmp/boot_<n>.log`.
-- `scripts/lint.sh` gets files with `git ls-files`. This checkout currently has
- `.got` but no `.git`, so a successful lint exit may have inspected no source
- files. Verify logs and file counts.
+- `scripts/test-console-input.sh` boots both x86_64 paths through QEMU serial
+ pipes. It runs `hello`, `bench_portal_pingpong`, then `hello`, and requires
+ two hello results with one benchmark result in that order. Its logs are
+ `build/test-logs/console-input-{uefi,legacy}.log`.
+- `scripts/lint.sh` discovers C sources directly under `kernel/`, `servers/`,
+ and `user/`, excluding generated embedded-image headers. It does not depend
+ on Git or GoT metadata. Verify the logs under `build/test-logs/` when a tool
+ reports a failure or is unavailable.
Never report a skipped architecture, absent tool, timeout, or empty lint file
list as a pass.
Available guest benchmarks are:
-- `bench_syscall`: 100,000 `getpid()` calls.
-- `bench_fs_read`: 1,000 open/read/close cycles with configurable
- `BENCH_FS_PATH`.
+- `bench_syscall`: five batches of one million calls each for the null,
+ `getpid()`, and `getuid()` syscalls, reporting each sample and the median.
+- `bench_fs_read`: creates and validates a 64 KiB `/fstest`, then performs ten
+ complete 4 KiB-buffered reads through VFS, ext2, blockd, and device 2.
+- `bench_block_read`: five 1 MiB sequential reads from a block device in 4 KiB
+ requests, reporting checksums and median raw throughput. Device 2 is the
+ default; pass a device number as its first argument to select another.
- `bench_portal_pingpong`: 10,000 64-byte round trips; documentation says its
server-spawn integration is incomplete.
+The 2026-08-31 x86_64 UEFI baseline and optimized measurements used QEMU TCG,
+q35, 2 GiB RAM, two configured vCPUs with Lenix in single-CPU mode, release
+builds, and the initrd-promoted ramdisk rootfs. Under that configuration:
+
+| Benchmark | Baseline | Optimized |
+|---------------------------|-------------------|-------------------|
+| Null syscall median | 1,280 ns/call | 1,010 ns/call |
+| Null syscall throughput | 781,250 calls/sec | 990,099 calls/sec |
+| VFS/ext2 read throughput | 14,188 bytes/sec | 39,056 bytes/sec |
+| Raw device 2 read median | Not measured | 76,987 bytes/sec |
+
Do not use expectation percentages in `user/bench/README.md` as measured
results. Record raw timing, iteration count, debug flags, SMP mode, QEMU flags,
host or hardware, and filesystem/backend before comparing runs.
blob - 8cbed6759e558db8c894ae3f26f3718a3b539ee1
blob + 33f50d03188d4506e2d3a99e52c16a7be206124b
--- GNUmakefile
+++ GNUmakefile
$(BUILD)/user/apps/bench_syscall.elf \
$(BUILD)/user/apps/real_read.elf \
$(BUILD)/user/apps/bench_fs_read.elf \
+ $(BUILD)/user/apps/bench_block_read.elf \
$(BUILD)/user/apps/bench_portal_pingpong.elf
PACKAGE_MUSL ?= 1
INCLUDE_MUSL_HEADERS ?= 1
endif
# --------------------------- Rules ----------------------------------
-.PHONY: all legacy efi esp iso run-uefi run-legacy run run-arm64 initrd clean dirs lint package-runtime test-sysroot musl
+.PHONY: all legacy efi esp iso run-uefi run-legacy run run-arm64 initrd clean dirs lint package-runtime test-sysroot test-console musl
musl: $(MUSL_LIBC)
# ---- compile C for legacy kernel (ELF) ----
$(ARCH_DIR)/%.o: %.c | dirs
@mkdir -p $(dir $@)
- $(CC) $(CFLAGS) -Ikernel -Ikernel/include -c $< -o $@
+ $(CC) $(CFLAGS) -MMD -MP -MF $@.d -Ikernel -Ikernel/include \
+ -c $< -o $@
# ---- GAS .S for legacy ----
$(ARCH_DIR)/%.o: %.S | dirs
@mkdir -p $(dir $@)
- $(CC) $(ASFLAGS) -x assembler-with-cpp -c $< -o $@
+ $(CC) $(ASFLAGS) -MMD -MP -MF $@.d -x assembler-with-cpp \
+ -c $< -o $@
# ---- NASM .asm for legacy (x86_64 only) ----
ifeq ($(ARCH),x86_64)
$(USER_BUILD_DIR)/%.o: %.c | dirs
@mkdir -p $(dir $@)
- $(CC) $(USER_CFLAGS) -c $< -o $@
+ $(CC) $(USER_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@
$(USER_BUILD_DIR)/%.o: %.S | dirs
@mkdir -p $(dir $@)
- $(CC) $(USER_ASFLAGS) -x assembler-with-cpp -c $< -o $@
+ $(CC) $(USER_ASFLAGS) -MMD -MP -MF $@.d -x assembler-with-cpp \
+ -c $< -o $@
$(USER_RUNTIME_ARCHIVE): $(USER_RUNTIME_OBJS) $(USER_RUNTIME_CRT0) | dirs
@mkdir -p $(dir $@)
apps: $(USER_RUNTIME_ARCHIVE)
$(MAKE) -f $(USER_APPS_MAKEFILE) BUILD_DIR=$(BUILD) ARCH=$(ARCH) \
+ CC="$(CC)" LD="$(LD)" \
+ USER_CFLAGS="$(filter-out -Werror,$(USER_CFLAGS))" \
+ USER_LDFLAGS="$(USER_LDFLAGS)" \
RUNTIME_ARCHIVE=$(USER_RUNTIME_ARCHIVE) apps
install-apps: $(USER_RUNTIME_ARCHIVE)
$(MAKE) -f $(USER_APPS_MAKEFILE) BUILD_DIR=$(BUILD) ARCH=$(ARCH) \
+ CC="$(CC)" LD="$(LD)" \
+ USER_CFLAGS="$(filter-out -Werror,$(USER_CFLAGS))" \
+ USER_LDFLAGS="$(USER_LDFLAGS)" \
RUNTIME_ARCHIVE=$(USER_RUNTIME_ARCHIVE) install
package-apps: install-apps $(ROOTFS_IMG)
$(UEFI_CC) -ffreestanding -fno-builtin -fno-stack-protector -fno-omit-frame-pointer \
-Wall -Wextra -Werror -O2 \
-Ikernel -Ikernel/include -Ikernel/uefi -DUEFI_BUILD \
- -c $< -o $@
+ -MMD -MP -MF $@.d -c $< -o $@
# ---- Link UEFI PE/COFF application ----
efi: $(ARCH_DIR)/lenix.efi
@LOG=boot-qemu-x86_64-legacy.log; rm -f "$$LOG"; \
./scripts/run-qemu.sh "$$LOG"
+test-console: iso esp
+ ./scripts/test-console-input.sh
+
lint:
./scripts/lint.sh
run-legacy:
@echo "Legacy GRUB flow is only available for ARCH=x86_64" && exit 1
+test-console:
+ @echo "Console input testing requires ARCH=x86_64" && exit 1
+
endif
ifeq ($(ARCH),arm64)
@echo "run-debug-arm64 is only meaningful for ARCH=arm64" && exit 1
endif
+-include $(addsuffix .d,$(LEGACY_OBJS) $(UEFI_OBJS) $(USER_OBJS))
+
clean:
rm -rf $(BUILD)
rm -f kernel/user/*_image_*.h
blob - a36a18a56d0e1aeed20e94dfef47b33528d74068
blob + ccd28697393ea48c3e8bf1738a75814316c69501
--- README.md
+++ README.md
| `make iso` | x86_64 GRUB/Multiboot2 ISO |
| `make initrd` | Initial user-mode service archive |
| `make build/rootfs.ext2` | ext2 root filesystem image |
+| `make test-console` | UEFI and legacy serial input test |
| `ARCH=arm64 make` | arm64 kernel for QEMU's `virt` machine |
`ARCH` defaults to `x86_64`. Set `PACKAGE_MUSL=0` to omit musl packaging, or
```bash
./scripts/test_sanity.sh
+make test-console
make lint
```
The smoke test builds and boots the supported QEMU paths, then checks for
-required boot markers. A QEMU timeout alone does not count as a pass. Set
-`SANITY_TIMEOUT=<seconds>` to change its default timeout.
+required boot markers. `make test-console` boots both x86_64 paths and checks
+that `hello`, `bench_portal_pingpong`, and a second `hello` execute in order.
+Its logs are written under `build/test-logs/`. A QEMU timeout alone does not
+count as a pass. Set `SANITY_TIMEOUT=<seconds>` to change the smoke test's
+default timeout.
Focused test scripts and logs are under `scripts/` and `build/test-logs/`.
blob - bc69f8c2d60c1508f5e3361e85b1d6eb3b6baad9
blob + 2c12cffcab4db40b18b214fed5fa2db5a0750f0a
--- changelog.md
+++ changelog.md
# Changelog
+# 2026-09-01
+
+## Ext2 allocation corruption
+
+- Corrected the group descriptor table offset used by ext2 block and inode
+ allocation on 4 KiB filesystems.
+- Corrected group-relative block bitmap indices and one-based inode bitmap
+ indices in allocation and free paths. Creating `/fstest` no longer
+ overwrites existing application inodes or data blocks.
+- Verified `bench_fs_read` followed by `bench_syscall` under legacy and UEFI
+ QEMU. The filesystem benchmark validated 65,536 bytes, and the syscall
+ benchmark loaded and completed without ELF errors.
+
# 2026-08-31
+## Console input regression
+
+- Moved COM1 IRQ4 activation after x86_64 IDT and exception setup so the UART
+ vector is not replaced by the default interrupt handler.
+- Added an atomic RX ring between IRQ4 and the console task. Hardware polling
+ runs with interrupts masked, keeping the console line discipline as the only
+ consumer without restoring the duplicate raw-stdin path.
+- Made the scheduler leave `hlt` through `sched_yield()` so an interrupt can
+ dispatch work that became runnable while the CPU was idle.
+- Corrected the console mailbox lock probe so a failed acquisition cannot
+ release a lock held by another task.
+- Added `make test-console`, which boots UEFI and legacy QEMU paths and verifies
+ `hello`, `bench_portal_pingpong`, then `hello` execute once and in order.
+
+## Syscall, IPC, and storage performance
+
+- Replaced syscall fast-table initialization and the custom-syscall linear
+ search with one compile-time table bounded by `SYS_MAX`. Resolved existing
+ syscall-number collisions and added `scripts/check-syscall-abi.sh` to compare
+ the kernel and runtime definitions.
+- Moved x86_64 syscall stack, register, return, and exec scratch state into
+ GS-relative per-CPU storage. BSP and AP setup now install the correct GS base,
+ and ARM64 exec returns restore the saved user trap frame.
+- Reduced kernel service-request stack use by sharing one bounded scratch
+ buffer and storing the caller's response pointer in each pending slot. The
+ response copy and timeout cleanup are synchronized to prevent a late response
+ from writing through a released slot.
+- Added push-capable block backend registration. Blockd sends queued requests
+ directly to ramdiskd and virtio-blk, validates completion tokens and payload
+ lengths, and retains the older fetch protocol as a fallback.
+- Made VFS the single read-ahead owner, tracked logical and backend offsets
+ separately, and synchronized the backend before writes or uncached reads.
+ Disabled the overlapping ext2 read-ahead cache.
+- Added `bench_block_read`, expanded `bench_syscall` to five one-million-call
+ batches for three syscall paths, and made `bench_fs_read` validate all 64 KiB
+ before timing ten complete reads.
+- Added compiler dependency files for kernel, runtime, UEFI, and application
+ objects. Root application sub-makes now receive the selected compiler,
+ linker, target flags, and sysroot flags.
+- Made `scripts/lint.sh` independent of Git metadata, split kernel and userland
+ include paths, run the syscall ABI check, and fail on analyzer or whitespace
+ errors instead of accepting an empty or warning-only run.
+- On x86_64 UEFI QEMU TCG, null syscall median latency changed from 1,280 to
+ 1,010 ns, and VFS/ext2 read throughput changed from 14,188 to 39,056
+ bytes/sec. The new raw block benchmark measured 76,987 bytes/sec. Both UEFI
+ and legacy images reached the shell after rebuilding.
+- The affected ARM64 kernel and VFS objects compile with `-Werror`. The full
+ ARM64 userland build still stops in the existing floating-point formatter
+ because it is compiled with `-mgeneral-regs-only`.
+- `clang-tidy` completed on the build-wired source set. The corrected lint gate
+ stops on existing `cppcheck` findings; its style phase also identifies
+ trailing whitespace in eight existing files.
+
## Ext2 indirect write performance
- Restored 4 KiB ext2 write batching for aligned and partial-block operations, replacing eight 512-byte service requests with one batched request.
blob - cc36766f16fd1446c452367cb0332f5d3b7cc2d5
blob + 35b589ac2c2ce4fd5d5a2f38a9474b1a57a37996
--- docs/ipc.md
+++ docs/ipc.md
`ipc_service_handle_response()` to:
- Find the pending request entry matching `(portal, token)`
- Locate the corresponding client slot in the pending registry
- - Copy the response data into the client slot
- - Signal the client as ready
+ - Copy the response into the waiting caller's bounded response buffer
+ - Publish the ready state and wake the waiting task
- Clean up the registry entry
+ The registry lock remains held through the response copy and ready-state
+ publication so a timeout cannot release the caller's buffer concurrently.
+
### Request/response flow
```
- Tokens are 32-bit and wrap around (with 0 reserved as invalid)
- Pending registry has 256 slots, supporting up to 256 concurrent requests
-- Each request blocks in the kernel, spinning with yield() until response arrives
-- Response data is limited to 512 bytes per request
+- Each client has four pending slots. A slot stores the caller's response
+ pointer and capacity instead of an additional 8 KiB response array.
+- Requests yield briefly, then sleep in one-tick intervals until a response or
+ the five-second timeout. Response handling wakes the waiting task.
+- Request and response payloads are limited to
+ `IPC_MAILBOX_MAX_PAYLOAD`, currently 8 KiB. A response is bounded by the
+ caller-provided capacity.
- Responses include a status code allowing services to report errors
- The implementation is self-contained and requires no changes to the portal layer
blob - 638c087d239f59d1f07264b1713fbbe5ea782a77
blob + 4d795893a1f19bd6516e04ced0e064e6ba83fd50
--- docs/subsystems/filesystems-vfs.md
+++ docs/subsystems/filesystems-vfs.md
- Manage mount points, resolve paths to backends, and cache vnode metadata.
- Translate POSIX-style file ops into IPC to backend filesystems.
- Provide backend implementations: tmpfs (memory) and ext2 (block-backed).
+- Read ahead sequential regular-file data while preserving the backend file
+ descriptor offset across cache hits, seeks, writes, and duplicated handles.
## Key Data Structures
- `struct vfs_mount`, `struct vfs_vnode` (servers/fs/vfs/vfs_internal.h).
- ext2 dispatch and block I/O path (servers/fs/ext2/main.c).
- Syscalls package requests through kernel IPC shims (kernel/ipc/fs_service.c, vfs_service.c).
+## Read Path
+
+VFS owns the active sequential read-ahead cache. Each unshared regular-file
+handle can hold 8 KiB and tracks both the user-visible logical offset and the
+backend filesystem descriptor offset. A backend seek is sent only when those
+offsets differ. `lseek()`, writes, and invalid cache state clear the cache, and
+plain reads resynchronize the backend cursor before bypassing read-ahead.
+
+Ext2's second read-ahead layer is disabled. Keeping one cache owner avoids
+duplicated 8 KiB fetches and makes backend cursor movement visible to VFS.
+Ext2 still batches aligned block transfers through blockd.
+
## Interactions
- Namesvc registers `vfs`, `fs.ext2`, `fs.tmpfs`; clients resolve via service_resolve().
- Ext2 relies on blockd/backends; VFS relies on namesvc to find backends.
## Configuration
- Initial mounts set in VFS (root via tmpfs/ext2 as configured).
-- Vnode cache sizing and mount table limits defined in VFS constants.
+- Vnode cache sizing, mount table limits, and `VFS_READAHEAD_SIZE` are defined
+ in `servers/fs/vfs/vfs_internal.h`.
## TODOs
- Add more filesystems, write/perms enforcement, caching policies, devfs/procfs, and mount flags (ro/rw) handling.
blob - 7bbba797e69957b043805cecab555da5131ce350
blob + b33614847fd8f48ea084151945069beba2787b4c
--- docs/subsystems/kernel-core.md
+++ docs/subsystems/kernel-core.md
- `struct sched_task` — task state/run-queue links (kernel/sched/task.c).
- `struct vm_space`, `struct vm_mapping` — address spaces/mappings (kernel/mm/aspace.c, vm.c).
- `struct bootinfo` — firmware/loader metadata (kernel/boot/bootinfo.c).
+- `struct x86_per_cpu_state` holds the current task, kernel stack, CPU identity,
+ syscall entry scratch values, and pending exec return state for each x86_64
+ CPU.
## Important Code Paths
- `kmain()` (kernel/kmain.c): boot entry, initializes subsystems, launches servers.
- `sched_prepare_task()`, `sched_switch()` (kernel/sched/task.c): task create/switch.
- Trap print/panic paths (kernel/trap/print.c, kernel/panic.c); timer tick (kernel/sched/clock.c).
+- x86_64 syscall entry uses GS-relative per-CPU state instead of global scratch
+ variables or an APIC lookup. BSP and AP setup assign GS before the CPU can
+ enter user mode.
+- `syscall_dispatch()` indexes a compile-time handler table bounded by
+ `SYS_MAX`. `scripts/check-syscall-abi.sh` verifies matching, unique syscall
+ numbers in the kernel and runtime headers.
+- ARM64 syscall return handles exec by restoring the saved architecture trap
+ frame before returning to EL0.
## Interactions
- Supplies mailboxes to IPC layer; calls ELF loader for embedded/initrd images.
blob - d71f846faae67e1138879756de7c4df075f78d12
blob + 2384d8eedc0f49257d90024ba43217e21cbdaae4
--- docs/subsystems/storage-block.md
+++ docs/subsystems/storage-block.md
# Storage / Block
## Overview
-User-mode block stack with a registry daemon and backends (ramdisk, virtio-blk). Kernel forwards requests and records traces; blockd brokers geometry and dispatch to backends.
+User-mode block stack with a registry daemon and ramdisk or virtio-blk
+backends. The kernel forwards requests and records traces. Blockd brokers
+geometry, queues requests, and dispatches them to the selected backend.
## Key Responsibilities
- Track block devices and geometry in blockd; expose a backend portal.
- Accept registrations from ramdiskd/virtio-blk and route I/O requests.
+- Push queued work to backends that register a receive portal.
+- Keep the older backend fetch protocol as a compatibility fallback.
- Trace recent block operations for diagnostics.
## Key Data Structures
- Block IPC request/response structs (kernel/ipc/block_service.c, block_backend_service.c).
## Important Code Paths
-- Backend register/serve loop in blockd; forwards to ramdiskd/virtio-blk portals.
+- Backend registration includes `IPC_BLOCK_BACKEND_REGISTER_F_PUSH` and a
+ receive portal. Blockd sends `IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST` directly
+ to that portal and receives `IPC_BLOCK_BACKEND_OPCODE_IO_RESPONSE` on its own
+ service portal.
+- Blockd validates the device, token, transfer length, and read/write payload
+ shape before completing the original block-service request. One request can
+ be active per device, with additional requests held in a bounded queue.
+- A backend that does not advertise push support continues to fetch work and
+ acknowledge completion through the service request protocol.
- Virtio-blk: PCI discovery, MMIO mapping, DMA buffers, virtqueue service (servers/block/virtio-blk/main.c).
- Ramdiskd: maps loader rootfs and serves requests synchronously (servers/block/ramdiskd/main.c).
- Kernel block syscalls forward to blockd/backends (kernel/ipc/block_service.c).
- QEMU drive flags and loader ramdisk determine available devices.
## TODOs
-- Add flush/barriers/error propagation, more drivers, and performance tuning for high IOPS paths.
+- Add flush and barrier semantics, more drivers, and batched or shared-buffer
+ transfers. The current protocol carries at most 4 KiB inline and schedules
+ every request through the kernel, blockd, and backend.
blob - fd864fd5db0d1040cd41f019d66c8b64d9db877b
blob + 1b4c549921bd31732d5f135c45277ddb64a0d566
--- kernel/arch/arm64/syscall.c
+++ kernel/arch/arm64/syscall.c
REG(frame, 5));/* x5 */
if ((ret.flags & SYSCALL_RESULT_FLAG_EXIT) != 0)
syscall_exit_to_kernel(ret.value);
+ if ((ret.flags & SYSCALL_RESULT_FLAG_EXECVE) != 0) {
+ const struct arch_trap_frame *saved;
+ struct sched_task *task;
+
+ task = sched_current_task();
+ saved = sched_task_user_frame(task);
+ if (saved == NULL)
+ syscall_exit_to_kernel(127);
+ for (size_t i = 0; i < 31; i++)
+ frame->regs[i] = saved->x[i];
+ frame->sp_el0 = saved->sp_el0;
+ frame->elr_el1 = saved->elr_el1;
+ frame->spsr_el1 = saved->spsr_el1;
+ return;
+ }
REG(frame, 0) = ret.value;
}
void
blob - 1a95b5497b901bbb68f74cc91691ff1c0eb907f5
blob + 44fef356b005fc31cf7578a526cf0a25cbcb0e3b
--- kernel/arch/x86_64/ap_entry.S
+++ kernel/arch/x86_64/ap_entry.S
movq (%rbx), %rax /* stack top */
movq %rax, %rsp
movq %rax, %rdi
+ movl 8(%rbx), %esi /* logical CPU id */
call x86_ap_stage1
movl 8(%rbx), %edi /* logical CPU id */
movl 12(%rbx), %esi /* APIC id */
blob - 1fbf107d2fa9658cbad6714514469191d22061f4
blob + 9995413b5d9fef9e0289eb0d2734f6e62d486bd8
--- kernel/arch/x86_64/init.c
+++ kernel/arch/x86_64/init.c
#include "arch/arch.h"
#include "log/serial.h"
#include "arch/tss.h"
+#include "arch/x86_64/per_cpu.h"
+#include "arch/x86_64/serial.h"
#include "arch/x86_64/syscall.h"
#include "arch/x86_64/pic.h"
#include "arch/x86_64/cpufeatures.h"
uintptr_t stack_top;
__asm__ __volatile__("mov %%rsp, %0" : "=r"(stack_top));
- x86_syscall_stack_top = stack_top;
+ x86_per_cpu_set_kernel_stack(0, stack_top);
+ x86_per_cpu_gs_base_set(0);
x86_protection_init(stack_top);
x86_64_trap_init();
x86_exception_init();
+ x86_serial_irq_enable();
x86_syscall_init();
}
blob - 3886cf9661c1f94ff039003cf6984e58efe3a7a9
blob + d5ce279a2139cd645676436f09183240884e89c7
--- kernel/arch/x86_64/isr.c
+++ kernel/arch/x86_64/isr.c
#include "arch/x86_64/idt.h"
#include "arch/tss.h"
+#include "arch/x86_64/per_cpu.h"
#include "arch/x86_64/pic.h"
#include "log/printk.h"
#include "log/serial.h"
static void x86_log_page_fault(uint64_t);
static void x86_log_gpf(uint64_t);
static inline uint64_t x86_read_cr2(void);
-extern uint64_t x86_syscall_last_rcx;
static void x86_dump_user_stack(uint64_t rsp);
static void x86_dump_kernel_stack(uint64_t rsp);
x86_fault_page(struct interrupt_frame *frame, uint64_t error_code)
{
bool user_mode = (frame->cs & 0x3) == 0x3;
+ struct x86_per_cpu_state *per_cpu = x86_per_cpu_current();
printk("[trap] page fault\n");
trap_print_hex64(" CR2=", x86_read_cr2());
trap_print_hex64(" error_code=", error_code);
x86_log_page_fault(error_code);
- trap_print_hex64(" last_sysret_rcx=", x86_syscall_last_rcx);
+ trap_print_hex64(" last_sysret_rcx=",
+ per_cpu != NULL ? per_cpu->syscall_last_rcx : 0);
printk(" task=");
printk(sched_current_task_name());
printk("\n");
blob - 7a7753ef959a0f5e17824004d9d09f2400a4a473
blob + 9a2c19b1b1864819af6a87da8ddd5337ba3a910c
--- kernel/arch/x86_64/per_cpu.c
+++ kernel/arch/x86_64/per_cpu.c
/* Per-CPU state array - aligned to cache line (64 bytes) */
struct x86_per_cpu_state x86_per_cpu_states[16] __attribute__((aligned(64)));
-/* Current per-CPU state pointer (set via GS.base MSR) */
-static struct x86_per_cpu_state *x86_current_per_cpu_ptr = NULL;
-
/*
* Initialize per-CPU state for a given CPU.
* Called during boot for each CPU (primary + APs).
per_cpu = &x86_per_cpu_states[cpu_id];
/* Initialize per-CPU state */
+ per_cpu->self = per_cpu;
per_cpu->cpu_state = state;
per_cpu->current_task = NULL; /* Will be set by scheduler */
per_cpu->logical_cpu_id = cpu_id;
per_cpu->apic_id = 0; /* Will be set by caller */
per_cpu->kernel_stack_top = stack_top;
per_cpu->tss_ptr = 0; /* Will be set by caller */
+ per_cpu->syscall_user_sp = 0;
+ per_cpu->syscall_regs_ptr = 0;
+ per_cpu->syscall_saved_rdi = 0;
+ per_cpu->syscall_last_rcx = 0;
+ per_cpu->syscall_execve_entry = 0;
+ per_cpu->syscall_execve_stack = 0;
printk("[x86] CPU ");
printk_dec(cpu_id);
}
per_cpu_addr = (uint64_t)&x86_per_cpu_states[cpu_id];
- x86_current_per_cpu_ptr = &x86_per_cpu_states[cpu_id];
/*
- * Set IA32_KERNEL_GS_BASE (0xc0000102) to per-CPU state address.
- * This MSR is used by SYSCALL/SYSRET and can be accessed from kernel.
+ * Set IA32_GS_BASE (0xc0000101) to the per-CPU state address.
* We don't use SWAPGS in Lenix because kernel stays in GS throughout.
*/
- wrmsr(0xc0000102, per_cpu_addr);
+ wrmsr(0xc0000101, per_cpu_addr);
printk("[x86] CPU ");
printk_dec(cpu_id);
struct x86_per_cpu_state *
x86_get_per_cpu_state(void)
{
- return x86_current_per_cpu_ptr;
+ struct x86_per_cpu_state *per_cpu;
+
+ __asm__ __volatile__("movq %%gs:0, %0" : "=r"(per_cpu));
+ return per_cpu;
}
/*
struct sched_task *
x86_get_current_task(void)
{
- struct x86_per_cpu_state *per_cpu = x86_current_per_cpu_ptr;
- if (per_cpu == NULL)
- return NULL;
- return per_cpu->current_task;
+ struct sched_task *task;
+
+ __asm__ __volatile__("movq %%gs:16, %0" : "=r"(task));
+ return task;
}
/*
uint32_t
x86_get_logical_cpu_id(void)
{
- struct x86_per_cpu_state *per_cpu = x86_current_per_cpu_ptr;
- if (per_cpu == NULL)
- return 0;
- return per_cpu->logical_cpu_id;
+ uint32_t cpu_id;
+
+ __asm__ __volatile__("movl %%gs:24, %0" : "=r"(cpu_id));
+ return cpu_id;
}
/*
return;
x86_per_cpu_states[cpu_id].cpu_state = state;
}
+
+void
+x86_per_cpu_set_kernel_stack(uint32_t cpu_id, uint64_t stack_top)
+{
+ if (cpu_id >= 16)
+ return;
+ x86_per_cpu_states[cpu_id].kernel_stack_top = stack_top;
+}
blob - 2619127e3a7c99b3535a12be99e865ab51a35afd
blob + 75f7e9547f49ba6e632934c1e5ad6b43899ff786
--- kernel/arch/x86_64/per_cpu.h
+++ kernel/arch/x86_64/per_cpu.h
* - Scales with call frequency (10K syscalls/sec = 1.1-2.1M cycles saved)
*/
+#define X86_PER_CPU_OFFSET_SELF 0
+#define X86_PER_CPU_OFFSET_CPU_STATE 8
+#define X86_PER_CPU_OFFSET_CURRENT_TASK 16
+#define X86_PER_CPU_OFFSET_LOGICAL_CPU 24
+#define X86_PER_CPU_OFFSET_KERNEL_STACK 32
+#define X86_PER_CPU_OFFSET_TSS 40
+#define X86_PER_CPU_OFFSET_USER_SP 48
+#define X86_PER_CPU_OFFSET_REGS_PTR 56
+#define X86_PER_CPU_OFFSET_SAVED_RDI 64
+#define X86_PER_CPU_OFFSET_LAST_RCX 72
+#define X86_PER_CPU_OFFSET_EXECVE_ENTRY 80
+#define X86_PER_CPU_OFFSET_EXECVE_STACK 88
+
+#ifndef __ASSEMBLER__
+
+#include <stddef.h>
#include <stdint.h>
struct sched_task;
struct sched_cpu_state;
struct x86_per_cpu_state {
+ struct x86_per_cpu_state *self;
/* Scheduler state - updated on context switch */
struct sched_cpu_state *cpu_state; /* Per-CPU scheduler state */
struct sched_task *current_task; /* Currently executing task */
uint64_t kernel_stack_top; /* Top of kernel stack for this CPU */
uint64_t tss_ptr; /* TSS address for this CPU */
- /* IPC state - cached for fast path */
- uint64_t _reserved[5]; /* Reserved for future per-CPU buffers */
-
- /* Padding to cache line boundary (64 bytes)
- * Structure size: 64 bytes exactly (8+8+4+4+8+8+40 = 80, so we need -16 padding)
- * Adjust based on actual sizeof(struct x86_per_cpu_state)
- */
- uint64_t _padding[2]; /* Flexible padding to reach 64-byte alignment */
+ /* Syscall entry scratch state. */
+ uint64_t syscall_user_sp;
+ uint64_t syscall_regs_ptr;
+ uint64_t syscall_saved_rdi;
+ uint64_t syscall_last_rcx;
+ uint64_t syscall_execve_entry;
+ uint64_t syscall_execve_stack;
+ uint64_t _padding[4];
} __attribute__((aligned(64)));
/* Per-CPU state array - one per CPU */
extern struct x86_per_cpu_state x86_per_cpu_states[16]; /* MAX_CPUS = 16 typical */
-/* Accessor macros for GS-relative addressing */
-#define X86_PER_CPU_OFFSET_CURRENT_TASK offsetof(struct x86_per_cpu_state, current_task)
-#define X86_PER_CPU_OFFSET_CPU_STATE offsetof(struct x86_per_cpu_state, cpu_state)
-#define X86_PER_CPU_OFFSET_LOGICAL_CPU offsetof(struct x86_per_cpu_state, logical_cpu_id)
-#define X86_PER_CPU_OFFSET_KERNEL_STACK offsetof(struct x86_per_cpu_state, kernel_stack_top)
+_Static_assert(offsetof(struct x86_per_cpu_state, self) ==
+ X86_PER_CPU_OFFSET_SELF, "per-CPU self offset");
+_Static_assert(offsetof(struct x86_per_cpu_state, syscall_execve_stack) ==
+ X86_PER_CPU_OFFSET_EXECVE_STACK, "per-CPU syscall offset");
/* Initialize per-CPU state for each CPU */
void x86_per_cpu_init(uint32_t cpu_id, struct sched_cpu_state *state, uint64_t stack_top);
uint32_t x86_get_logical_cpu_id(void);
void x86_per_cpu_set_current_task(uint32_t cpu_id, struct sched_task *task);
void x86_per_cpu_set_cpu_state(uint32_t cpu_id, struct sched_cpu_state *state);
+void x86_per_cpu_set_kernel_stack(uint32_t cpu_id, uint64_t stack_top);
/* Get current CPU's per-CPU state (fast path for kernel code) */
static inline struct x86_per_cpu_state *
{
return x86_get_logical_cpu_id();
}
+
+#endif
blob - 2dbbeecb2b6794f1744db62b0631b3d4f748493e
blob + 8e63e4daecd8c9bd87eda3792cfb96d31df708dc
--- kernel/arch/x86_64/serial.c
+++ kernel/arch/x86_64/serial.c
/* Share the prototype definitions used by both legacy and UEFI paths. */
#include <stdint.h>
+#include "arch/arch.h"
+#include "arch/x86_64/serial.h"
#include "log/serial.h"
#include "log/printk.h"
#ifndef UEFI_BUILD
#include "console/line.h"
-#include "sched/task.h"
#endif
#if SERIAL_DEBUG_RX && !defined(UEFI_BUILD)
#define SERIAL_WITH_IRQ 1
#define COM1_IRQ 4
#define COM1_VECTOR (32 + COM1_IRQ)
+#define SERIAL_RX_BUFSZ 256
+
+static char serial_rx_buf[SERIAL_RX_BUFSZ];
+static unsigned int serial_rx_head;
+static unsigned int serial_rx_tail;
+static unsigned int serial_irq_count;
+static int serial_irq_enabled;
#endif
#define COM1 0x3F8
#if SERIAL_WITH_IRQ
static void
-serial_receive_char(char c)
+serial_buffer_push(char c)
{
-#if SERIAL_DEBUG_RX
- static int serial_rx_debug;
-#endif
+ unsigned int head;
+ unsigned int next;
+ unsigned int tail;
-#if SERIAL_DEBUG_RX && !defined(UEFI_BUILD)
- if (serial_rx_debug < 8) {
- const char hex[] = "0123456789abcdef";
- char buf[3];
- unsigned char uc = (unsigned char)c;
- buf[0] = hex[(uc >> 4) & 0xf];
- buf[1] = hex[uc & 0xf];
- buf[2] = '\0';
- printk("[serial] RX char=0x");
- printk(buf);
- if (uc >= 0x20 && uc <= 0x7e) {
- char out[4] = { ' ', '(', (char)uc, ')'};
- printk(out);
- }
- printk("\n");
- serial_rx_debug++;
- }
-#endif
-#ifndef UEFI_BUILD
- console_line_rx(c);
-#endif
+ head = __atomic_load_n(&serial_rx_head, __ATOMIC_RELAXED);
+ tail = __atomic_load_n(&serial_rx_tail, __ATOMIC_ACQUIRE);
+ next = (head + 1U) % SERIAL_RX_BUFSZ;
+ if (next == tail)
+ return;
+ serial_rx_buf[head] = c;
+ __atomic_store_n(&serial_rx_head, next, __ATOMIC_RELEASE);
}
+
+static int
+serial_buffer_pop(char *c)
+{
+ unsigned int head;
+ unsigned int tail;
+
+ tail = __atomic_load_n(&serial_rx_tail, __ATOMIC_RELAXED);
+ head = __atomic_load_n(&serial_rx_head, __ATOMIC_ACQUIRE);
+ if (tail == head)
+ return 0;
+ *c = serial_rx_buf[tail];
+ __atomic_store_n(&serial_rx_tail,
+ (tail + 1U) % SERIAL_RX_BUFSZ, __ATOMIC_RELEASE);
+ return 1;
+}
#endif
/*
serial_outb(COM1 + 4, 0x0B); /* Enable IRQs, assert RTS/DSR. */
(void)serial_inb(COM1 + 5); /* Read LSR to acknowledge status. */
(void)serial_inb(COM1 + 0); /* Dummy read RBR to clear it. */
+#if !SERIAL_WITH_IRQ
+#ifndef UEFI_BUILD
+ printk("[serial_init] POLLING mode (no IRQs)\n");
+#endif
+#endif
+}
+
#if SERIAL_WITH_IRQ
- serial_outb(COM1 + 1, 0x01); /* Enable received-data interrupts. */
+void
+x86_serial_irq_enable(void)
+{
+ if (serial_irq_enabled)
+ return;
+ serial_init();
x86_idt_set_gate(COM1_VECTOR, x86_serial_isr);
pic_unmask_irq(COM1_IRQ);
-#ifndef UEFI_BUILD
+ serial_outb(COM1 + 1, 0x01); /* Enable received-data interrupts. */
+ serial_irq_enabled = 1;
printk("[serial_init] IRQs ENABLED: COM1_IRQ=4 COM1_VECTOR=36\n");
-#endif
-#else
-#ifndef UEFI_BUILD
- printk("[serial_init] POLLING mode (no IRQs)\n");
-#endif
-#endif
}
+#endif
/*
* serial_ready - report whether the TX holding register is empty.
void
serial_irq(void)
{
- static uint32_t irq_count = 0;
- /* Log FIRST call unconditionally to confirm IRQ handler is invoked */
- if (irq_count == 0) {
- printk("[serial_irq] FIRST CALL - IRQ handler active\n");
- }
- if ((++irq_count % 10) == 0) {
- printk("[serial_irq] called #");
- char buf[16];
- uint32_t val = irq_count / 10;
- int idx = 0;
- if (val == 0) {
- buf[idx++] = '0';
- } else {
- char tmp[16];
- int t = 0;
- while (val > 0) {
- tmp[t++] = '0' + (val % 10);
- val /= 10;
- }
- while (t > 0)
- buf[idx++] = tmp[--t];
- }
- buf[idx] = '\0';
- printk(buf);
- printk("*10\n");
- }
+ __atomic_add_fetch(&serial_irq_count, 1U, __ATOMIC_RELAXED);
while ((serial_inb(COM1 + 2) & 0x01) == 0) {
unsigned char lsr = serial_inb(COM1 + 5);
if ((lsr & 0x01) == 0)
break;
char ch = (char)serial_inb(COM1 + 0);
- /* Log every character received via IRQ */
- {
- const char hex[] = "0123456789abcdef";
- printk("[serial_irq_rx] ch=0x");
- char buf[3];
- unsigned char uc = (unsigned char)ch;
- buf[0] = hex[(uc >> 4) & 0xf];
- buf[1] = hex[uc & 0xf];
- buf[2] = '\0';
- printk(buf);
- if (uc >= 0x20 && uc <= 0x7e) {
- printk(" '");
- printk(&ch);
- printk("'");
- }
- printk("\n");
- }
- serial_receive_char(ch);
+ serial_buffer_push(ch);
}
}
#endif
unsigned char lsr;
static uint32_t poll_call_count = 0;
+ static int serial_irq_logged;
+ uint64_t flags;
+ char ch;
/* Log FIRST poll call to confirm function is being invoked */
if (poll_call_count == 0) {
printk(")\n");
}
poll_call_count++;
-
+
/* Log periodic polling to confirm serial_poll_rx is being called after shell prompt */
#ifdef LENIX_DEBUG
if (poll_call_count % 100000 == 0 && poll_call_count > 0) {
}
#endif
- /* Poll hardware FIFO for new characters */
+ /* Poll with interrupts masked so the task and ISR cannot both produce. */
+ flags = arch_irq_save();
for (;;) {
lsr = serial_inb(COM1 + 5);
if ((lsr & 0x01) == 0)
break;
- char ch = (char)serial_inb(COM1 + 0);
+ ch = (char)serial_inb(COM1 + 0);
+ serial_buffer_push(ch);
+ }
+ arch_irq_restore(flags);
+
+ if (!serial_irq_logged &&
+ __atomic_load_n(&serial_irq_count, __ATOMIC_RELAXED) != 0) {
+ printk("[serial_irq] RX path active\n");
+ serial_irq_logged = 1;
+ }
+ while (serial_buffer_pop(&ch)) {
#if SERIAL_DEBUG_RX
if (serial_poll_debug < 16) {
serial_debug_log_char(ch);
serial_poll_debug++;
}
#endif
- serial_receive_char(ch);
+ console_line_rx(ch);
}
}
#else
blob - /dev/null
blob + 83269adda3446a47513f604522f782406029e98c (mode 644)
--- /dev/null
+++ kernel/arch/x86_64/serial.h
+/* SPDX-License-Identifier: ISC */
+#pragma once
+
+void x86_serial_irq_enable(void);
blob - 38e7bbfc78a8bb640290e50fa70c5bd321043256
blob + 3a731852dba8a64c53e733bbe9c18442db941846
--- kernel/arch/x86_64/signal.c
+++ kernel/arch/x86_64/signal.c
#include "sys/syscall.h"
#include "sys/wait.h"
-extern uint64_t x86_syscall_user_sp;
-struct x86_syscall_regs *x86_syscall_regs_ptr;
-
static inline uint32_t
signal_pick(uint32_t mask)
{
{
struct sched_task *task = sched_current_task();
struct signal_state *state = sched_task_signal_state(task);
- struct vm_space *space = sched_current_space();
+ struct vm_space *space;
struct signal_action act;
struct sigframe frame;
uint32_t pending;
return 0;
}
}
+ space = sched_current_space();
+ if (space == NULL)
+ return 0;
new_sp = user_sp;
new_sp -= sizeof(sigreturn_stub);
stub_addr = new_sp;
blob - 5162a5dec83a4fac158b60b0d1a908e8c126afef
blob + d9569e4e271d6e0dde1ffe0d3c467d71cd4636a1
--- kernel/arch/x86_64/signal.h
+++ kernel/arch/x86_64/signal.h
uint64_t r15;
};
-extern struct x86_syscall_regs *x86_syscall_regs_ptr;
-
#include "sys/sigframe.h"
uint64_t x86_signal_prepare(struct x86_syscall_regs *regs,
blob - dc7c078dcc9ef9f2d54e3ed821c153b961d21cda
blob + 0cf5ebe000e5ed67686d3cd5eecf2ae3019ec577
--- kernel/arch/x86_64/smp.c
+++ kernel/arch/x86_64/smp.c
#include "arch/x86_64/idt.h"
#include "arch/x86_64/mm.h"
#include "arch/x86_64/io.h"
+#include "arch/x86_64/per_cpu.h"
#include "boot/bootinfo.h"
#include "mm/vm.h"
#include "mm/vm_layout.h"
}
void
-x86_ap_stage1(uintptr_t stack_top)
+x86_ap_stage1(uintptr_t stack_top, uint32_t cpu_id)
{
+ x86_per_cpu_set_kernel_stack(cpu_id, stack_top);
+ x86_per_cpu_gs_base_set(cpu_id);
x86_protection_init(stack_top);
arch_set_kernel_stack(stack_top);
}
blob - 80bb3a82dbf40a694b64bb336cbd14dcebe0f8db
blob + 61bc932187f01e1ad3371d9dd9b9abefbad78994
--- kernel/arch/x86_64/syscall.c
+++ kernel/arch/x86_64/syscall.c
#define RFLAGS_TF (1ULL << 8)
extern void x86_syscall_entry(void);
-extern void syscall_init_fastpath(void);
-uint64_t x86_syscall_stack_top;
-uint64_t x86_syscall_user_sp;
-uint64_t x86_syscall_last_rcx;
-uint64_t x86_syscall_saved_rdi;
-
static inline uint64_t
rdmsr(uint32_t msr)
{
efer |= (EFER_SCE | EFER_NXE);
wrmsr(MSR_EFER, efer);
- /* Initialize fastpath dispatch table for O(1) syscall lookup */
- syscall_init_fastpath();
-
printk("* x86_64: SYSCALL path armed (STAR/LSTAR/SFMASK)\n");
}
blob - 2deb81b102091005639779298cac2781b40427eb
blob + a29e983cc24abb8737ea57bf0cea0a120a4a95a7
--- kernel/arch/x86_64/syscall.h
+++ kernel/arch/x86_64/syscall.h
#include <stdint.h>
void x86_syscall_init(void);
-
-extern uint64_t x86_syscall_stack_top;
-extern uint64_t x86_syscall_user_sp;
blob - abe3af5a0f656ee6192308b2f00b4c89b7a2c443
blob + e1a224e9c83bc98af95e66ff9acb1aae34c6ff17
--- kernel/arch/x86_64/syscall_entry.S
+++ kernel/arch/x86_64/syscall_entry.S
/* SPDX-License-Identifier: ISC */
+#include "per_cpu.h"
+
.text
.globl x86_syscall_entry
.extern syscall_dispatch
.extern syscall_exit_to_kernel
.extern sched_task_store_user_sp
.extern sched_task_load_user_sp
- .extern x86_syscall_stack_top
- .extern x86_syscall_user_sp
- .extern x86_syscall_last_rcx
- .extern x86_syscall_saved_rdi
.extern arch_trap_capture_syscall
.extern x86_signal_prepare
- .extern x86_syscall_regs_ptr
- .extern x86_syscall_execve_entry
- .extern x86_syscall_execve_stack
.equ SYSCALL_FLAG_EXIT, 1
#define OFF_R15 112
x86_syscall_entry:
- mov %rdi, x86_syscall_saved_rdi(%rip)
+ movq %rdi, %gs:X86_PER_CPU_OFFSET_SAVED_RDI
mov %rsp, %rdi
- mov %rdi, x86_syscall_user_sp(%rip)
- mov x86_syscall_stack_top(%rip), %rsp
+ movq %rdi, %gs:X86_PER_CPU_OFFSET_USER_SP
+ movq %gs:X86_PER_CPU_OFFSET_KERNEL_STACK, %rsp
test %rsp, %rsp
jne .Lstack_ready
.Lhalt:
jmp .Lhalt
.Lstack_ready:
cld
- mov x86_syscall_saved_rdi(%rip), %rdi
+ movq %gs:X86_PER_CPU_OFFSET_SAVED_RDI, %rdi
pushq %r15
pushq %r14
pushq %r13
pushq %rcx
pushq %rax
- mov %rsp, x86_syscall_regs_ptr(%rip)
+ movq %rsp, %gs:X86_PER_CPU_OFFSET_REGS_PTR
- mov x86_syscall_user_sp(%rip), %rdi
+ movq %gs:X86_PER_CPU_OFFSET_USER_SP, %rdi
call sched_task_store_user_sp
mov %rsp, %rdi
- mov x86_syscall_user_sp(%rip), %rsi
+ movq %gs:X86_PER_CPU_OFFSET_USER_SP, %rsi
call arch_trap_capture_syscall
mov OFF_RAX(%rsp), %rdi /* syscall number */
/* Load new entry point and stack from global variables.
* The new entry point needs to replace RCX on the stack (at OFF_RCX offset)
* since RCX will be popped later and used as the return address for sysretq */
- movq x86_syscall_execve_entry(%rip), %r14
+ movq %gs:X86_PER_CPU_OFFSET_EXECVE_ENTRY, %r14
movq %r14, OFF_RCX(%rsp) /* Replace RCX on stack with entry point */
- movq x86_syscall_execve_stack(%rip), %r14
- movq %r14, x86_syscall_user_sp(%rip)
+ movq %gs:X86_PER_CPU_OFFSET_EXECVE_STACK, %r14
+ movq %r14, %gs:X86_PER_CPU_OFFSET_USER_SP
/* Clear the global variables for next syscall */
- movq $0, x86_syscall_execve_entry(%rip)
- movq $0, x86_syscall_execve_stack(%rip)
+ movq $0, %gs:X86_PER_CPU_OFFSET_EXECVE_ENTRY
+ movq $0, %gs:X86_PER_CPU_OFFSET_EXECVE_STACK
jmp .Lafter_signal
.Lno_execve:
mov %rax, %r14
call sched_task_load_user_sp
- mov %rax, x86_syscall_user_sp(%rip)
+ movq %rax, %gs:X86_PER_CPU_OFFSET_USER_SP
mov %r14, %rax
.Lafter_signal:
mov %rax, %r15 /* save syscall return value */
lea OFF_RAX(%rsp), %rdi
- mov x86_syscall_user_sp(%rip), %rsi
+ movq %gs:X86_PER_CPU_OFFSET_USER_SP, %rsi
call x86_signal_prepare
test %rax, %rax
je .Lnosignal
- mov %rax, x86_syscall_user_sp(%rip)
+ movq %rax, %gs:X86_PER_CPU_OFFSET_USER_SP
.Lnosignal:
mov %r15, %rax /* restore syscall return value */
popq %r14
popq %r15
- mov x86_syscall_user_sp(%rip), %rsp
- mov %rcx, x86_syscall_last_rcx(%rip)
+ movq %gs:X86_PER_CPU_OFFSET_USER_SP, %rsp
+ movq %rcx, %gs:X86_PER_CPU_OFFSET_LAST_RCX
sysretq
.Lexit_kernel:
blob - 36cb2f2bfc279c4258dfdb62dd5302102eac1f24
blob + 560fb3ad0da56c01445a740418e4feb1980e5f02
--- kernel/arch/x86_64/usermode.c
+++ kernel/arch/x86_64/usermode.c
#include "arch/arch.h"
#include "arch/tss.h"
+#include "arch/x86_64/per_cpu.h"
#include "arch/x86_64/syscall.h"
#include "arch/trap_frame.h"
void
arch_set_kernel_stack(uintptr_t stack_top)
{
+ struct x86_per_cpu_state *per_cpu;
+
x86_set_kernel_stack(stack_top);
- x86_syscall_stack_top = stack_top;
+ per_cpu = x86_per_cpu_current();
+ if (per_cpu != NULL)
+ per_cpu->kernel_stack_top = stack_top;
}
static inline void
blob - 5acc73906b7b64559273d029b872ebd0c26cff02
blob + 539ff96378239c33c338f8a2d27a449b5ac3e851
--- kernel/console/service.c
+++ kernel/console/service.c
* or kmem_alloc(). This prevents system-wide deadlocks. */
struct ipc_mailbox *mbox = ipc_portal_target_mailbox(console_tty_portal);
if (mbox != NULL) {
- /* Try to acquire lock with a quick check - if we can't get it immediately,
- * the mailbox is likely contended and we should skip this send. */
- uint32_t lock_val = __atomic_exchange_n(&mbox->lock, 1U, __ATOMIC_ACQUIRE);
- if (lock_val != 0) {
- /* Lock was held - release our failed acquisition and skip send */
- __atomic_store_n(&mbox->lock, 0U, __ATOMIC_RELEASE);
+ uint32_t expected = 0;
+
+ /* Never release a mailbox lock owned by another task. */
+ if (!__atomic_compare_exchange_n(&mbox->lock, &expected, 1U,
+ 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
return false;
}
/* Lock acquired - check depth */
blob - e0f3f450c0c4ff44732013e3789f1ecd59495f32
blob + 5e9d8b3af65d6843b7c4b763417e6fee1df15eda
--- kernel/include/ipc/block_backend.h
+++ kernel/include/ipc/block_backend.h
#define IPC_BLOCK_BACKEND_F_WRITE (1U << 0)
#define IPC_BLOCK_BACKEND_F_FLUSH (1U << 1)
+#define IPC_BLOCK_BACKEND_REGISTER_F_PUSH (1U << 0)
#define IPC_BLOCK_BACKEND_MAX_DATA 4096U
uint32_t opcode;
uint32_t token;
uint32_t flags;
- uint32_t reserved;
+ uint32_t portal;
uint32_t block_size;
uint64_t block_count;
};
blob - 36960d13c6879980f36c465591c29a32c2355623
blob + 8449767b4865dd0048e4092d2b075bda28c5fdd9
--- kernel/include/ipc/service_client.h
+++ kernel/include/ipc/service_client.h
struct ipc_service_pending {
bool in_use;
uint32_t token;
- uint8_t response_buf[IPC_SERVICE_MAX_RESPONSE];
+ uint8_t *response_buf;
+ size_t response_capacity;
size_t response_len;
bool ready;
};
* 1. Allocates a token and pending slot
* 2. Sends request with token via portal
* 3. Yields until response is ready
- * 4. Copies response to caller's buffer
+ * 4. Publishes the response in the caller's buffer
*
* spin_limit_override (iterations): optional iteration budget. If zero, a
* default budget (~10M iterations with yields) is used.
blob - d156644f12b502b06ce41d2b240449a57c42319e
blob + c14a5305d78fb8963274f2721ea229c25bfcb4eb
--- kernel/include/sync/spinlock.h
+++ kernel/include/sync/spinlock.h
#include <stdbool.h>
#include <stdint.h>
+#include "arch/cpu.h"
+
/*
* Simple spinlock implementation for SMP safety.
* Uses atomic operations to provide mutual exclusion across CPUs.
if (__atomic_compare_exchange_n(&lock->locked, &expected, 1,
0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
return;
- /* Pause hint to reduce contention */
- __asm__ __volatile__("pause" ::: "memory");
+ /* Reduce contention while preserving the architecture hint. */
+ arch_cpu_relax();
}
}
blob - c37453822f5a5f3cce53e81a9b7914a143c943c5
blob + c52c28f2fcac9762dbcf03815c0b3086b0d31cf7
--- kernel/include/sys/syscall.h
+++ kernel/include/sys/syscall.h
#define SYS_nanosleep 574U
#define SYS_getdents 575U
#define SYS_fstatat 576U
-#define SYS_getcwd 590U
-#define SYS_chdir 591U
+#define SYS_getcwd 619U
+#define SYS_chdir 620U
#define SYS_fchdir 592U
#define SYS_getppid 593U
#define SYS_unlinkat 594U
#define SYS_ftruncate 595U
#define SYS_null 600U /* Null syscall for performance measurement (does nothing) */
-#define SYS_pty_get_previous 601U
+#define SYS_pty_get_previous 621U
#define SYS_srv_stage_start 602U
#define SYS_srv_stage_commit 603U
#define SYS_srv_stage_abort 604U
#define SYS_getrlimit 616U
#define SYS_setrlimit 617U
#define SYS_getrusage 618U
+#define SYS_MAX 621U
#define AT_FDCWD (-100)
#define AT_SYMLINK_NOFOLLOW 0x100
blob - ce9b1dad913836bd010d9af81b42f1247e9b613a
blob + 1a8d6d939db2e3ae14ad098d49f950fea2cf64a1
--- kernel/ipc/service_client.c
+++ kernel/ipc/service_client.c
uint32_t token; /* Request token */
struct sched_task *waiter; /* Task waiting on the response */
struct ipc_service_pending *pending_slot; /* Pointer to pending slot in client */
- volatile uint8_t prepared; /* Set when client is prepared to block */
};
static spinlock_t pending_requests_lock = SPINLOCK_INIT;
res = &pending_requests[i];
/* Mark as in_use under lock */
res->in_use = true;
- __atomic_store_n((uint8_t *)&res->prepared, 0,
- __ATOMIC_RELAXED);
break;
}
}
return NULL;
}
+static void
+service_pending_release(struct ipc_service_pending *slot)
+{
+ slot->in_use = false;
+ slot->response_buf = NULL;
+ slot->response_capacity = 0;
+ slot->response_len = 0;
+ slot->ready = false;
+}
+
int
ipc_service_client_init(struct ipc_service_client *client,
ipc_portal_handle_t portal)
client->next_token = 1;
for (size_t i = 0; i < IPC_SERVICE_MAX_PENDING; i++) {
client->slots[i].in_use = false;
+ client->slots[i].response_buf = NULL;
+ client->slots[i].response_capacity = 0;
client->slots[i].ready = false;
client->slots[i].response_len = 0;
}
struct ipc_service_pending *slot;
uint8_t tmp_req[IPC_MAILBOX_MAX_PAYLOAD];
uint64_t spins = 0;
+ size_t actual_resp_len;
(void)spin_limit_override; /* Override no longer used; fixed threshold */
if (client == NULL || req == NULL || resp == NULL)
return -1;
/* Portal payloads are limited by IPC_MAILBOX_MAX_PAYLOAD; enforce the
* same cap here so we can safely inline the request buffer. */
- if (req_len > sizeof(tmp_req))
+ if (req_len < 2 * sizeof(uint32_t) || req_len > sizeof(tmp_req) ||
+ resp_len > IPC_MAILBOX_MAX_PAYLOAD)
return -1;
/* Allocate a pending slot for this request */
if (slot == NULL)
return -1;
- if (resp_len > sizeof(slot->response_buf)) {
- slot->in_use = false;
- return -1;
- }
-
slot->in_use = true;
slot->ready = false;
slot->token = service_next_token();
+ slot->response_buf = resp;
+ slot->response_capacity = resp_len;
+ slot->response_len = 0;
client->next_token = slot->token;
/* Register this request in the global pending registry */
struct pending_service_request *preq = pending_service_request_alloc();
if (preq == NULL) {
- slot->in_use = false;
+ service_pending_release(slot);
return -1;
}
/* preq->in_use is already set by pending_service_request_alloc() */
/* Send request to service portal */
if (ipc_portal_send(client->server_portal, tmp_req, req_len) != 0) {
- slot->in_use = false;
spinlock_lock(&pending_requests_lock);
preq->in_use = false;
+ preq->waiter = NULL;
spinlock_unlock(&pending_requests_lock);
+ service_pending_release(slot);
return -1;
}
/* Time-based timeout */
if (timeout_ticks > 0 &&
(timer_ticks() - start_ticks) > timeout_ticks) {
- slot->in_use = false;
spinlock_lock(&pending_requests_lock);
- preq->in_use = false;
- preq->waiter = NULL;
+ if (__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE)) {
+ spinlock_unlock(&pending_requests_lock);
+ break;
+ }
+ if (preq->in_use && preq->pending_slot == slot &&
+ preq->token == slot->token) {
+ preq->in_use = false;
+ preq->waiter = NULL;
+ }
spinlock_unlock(&pending_requests_lock);
+ service_pending_release(slot);
return -1;
}
if (spins < 64) {
sched_yield();
} else {
- __atomic_store_n((uint8_t *)&preq->prepared, 1,
- __ATOMIC_RELEASE);
- /* Check ready after setting prepared to avoid lost wakeup */
+ /* Recheck readiness before the bounded sleep. */
if (!__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE))
sched_sleep_ticks(sleep_interval);
- /* Don't clear prepared here - only clear after ready=true */
}
}
- /* Clear prepared after successfully receiving response */
- __atomic_store_n((uint8_t *)&preq->prepared, 0, __ATOMIC_RELAXED);
- /* Copy response to caller's buffer */
- if (slot->response_len > resp_len)
- slot->response_len = resp_len;
- service_memcpy(resp, slot->response_buf, slot->response_len);
-
- slot->in_use = false;
- slot->ready = false;
+ actual_resp_len = slot->response_len;
/* Release the registry entry if it still matches this request */
spinlock_lock(&pending_requests_lock);
if (preq->in_use &&
}
spinlock_unlock(&pending_requests_lock);
if (out_resp_len != NULL)
- *out_resp_len = slot->response_len;
+ *out_resp_len = actual_resp_len;
+ service_pending_release(slot);
return 0;
}
if (slot == NULL)
return -1;
- if (resp_len > sizeof(slot->response_buf))
- resp_len = sizeof(slot->response_buf);
+ if (slot->response_buf == NULL)
+ return -1;
+ if (resp_len > slot->response_capacity)
+ resp_len = slot->response_capacity;
service_memcpy(slot->response_buf, resp, resp_len);
slot->response_len = resp_len;
- slot->ready = true;
+ __atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE);
return 0;
}
return -1;
}
- /* Copy response to the pending slot */
- if (resp_len > sizeof(slot->response_buf))
- resp_len = sizeof(slot->response_buf);
+ /* Copy the response directly into the caller-owned buffer. */
+ if (slot->response_buf == NULL) {
+ spinlock_unlock(&pending_requests_lock);
+ return -1;
+ }
+ if (resp_len > slot->response_capacity)
+ resp_len = slot->response_capacity;
service_memcpy(slot->response_buf, resp, resp_len);
slot->response_len = resp_len;
blob - 1d9880a4b3e03c77666511f6524bb9051fc1a194
blob + 0ffb7b4006139833bbb22886ae764d82a910c44e
--- kernel/sched/task.c
+++ kernel/sched/task.c
static void
sched_idle(void *arg __attribute__((unused)))
{
- for (;;)
+ for (;;) {
arch_idle();
+ sched_yield();
+ }
}
/*
blob - 2ec743dac5e1265ad008330832b3832263c7ade6
blob + f68092340aeb09364057e1fe0b5117c9f8433f8f
--- kernel/sys/policy.c
+++ kernel/sys/policy.c
#define POLICY_MAX_SYSCALL_RULES 32
#define POLICY_MAX_PORTAL_RULES 32
-static struct policy_syscall_rule syscall_rules[POLICY_MAX_SYSCALL_RULES];
+static struct policy_syscall_rule syscall_rules[POLICY_MAX_SYSCALL_RULES] = {
+ {
+ .uid = POLICY_UID_NON_ROOT,
+ .syscall = SYS_execve,
+ .allow = false,
+ .in_use = true,
+ },
+};
static struct policy_portal_rule portal_rules[POLICY_MAX_PORTAL_RULES];
-static bool policy_rules_initialized;
+static size_t syscall_rule_count = 1;
+static size_t portal_rule_count;
-static void policy_rules_init(void);
-
static void
policy_log(const char *action, uint64_t detail, const struct sched_credentials *creds, bool allow)
{
printk("\n");
}
-static void
-policy_rules_init(void)
-{
- if (policy_rules_initialized)
- return;
- policy_rule_add_syscall(POLICY_UID_NON_ROOT, SYS_execve, false);
- policy_rules_initialized = true;
-}
-
bool
policy_rule_add_syscall(uint32_t uid, uint64_t syscall, bool allow)
{
- for (size_t i = 0; i < POLICY_MAX_SYSCALL_RULES; i++) {
- if (!syscall_rules[i].in_use) {
- syscall_rules[i].in_use = true;
- syscall_rules[i].uid = uid;
- syscall_rules[i].syscall = syscall;
- syscall_rules[i].allow = allow;
- return true;
- }
- }
- return false;
+ struct policy_syscall_rule *rule;
+
+ if (syscall_rule_count >= POLICY_MAX_SYSCALL_RULES)
+ return false;
+ rule = &syscall_rules[syscall_rule_count++];
+ rule->in_use = true;
+ rule->uid = uid;
+ rule->syscall = syscall;
+ rule->allow = allow;
+ return true;
}
bool
policy_rule_add_portal(uint32_t uid, uint32_t label, uint32_t required_rights,
bool allow)
{
- for (size_t i = 0; i < POLICY_MAX_PORTAL_RULES; i++) {
- if (!portal_rules[i].in_use) {
- portal_rules[i].in_use = true;
- portal_rules[i].uid = uid;
- portal_rules[i].label = label;
- portal_rules[i].required_rights = required_rights;
- portal_rules[i].allow = allow;
- return true;
- }
- }
- return false;
+ struct policy_portal_rule *rule;
+
+ if (portal_rule_count >= POLICY_MAX_PORTAL_RULES)
+ return false;
+ rule = &portal_rules[portal_rule_count++];
+ rule->in_use = true;
+ rule->uid = uid;
+ rule->label = label;
+ rule->required_rights = required_rights;
+ rule->allow = allow;
+ return true;
}
bool
policy_syscall_allowed(uint64_t num, const uint64_t args[6])
{
- policy_rules_init();
const struct sched_credentials *creds =
- sched_task_credentials(sched_current_task());
+ sched_task_credentials(sched_current_task());
+ uint32_t uid = (creds != NULL) ? creds->uid : POLICY_UID_ANY;
bool allow = true;
+
(void)args;
- for (size_t i = 0; i < POLICY_MAX_SYSCALL_RULES; i++) {
+ for (size_t i = 0; i < syscall_rule_count; i++) {
const struct policy_syscall_rule *rule = &syscall_rules[i];
- uint32_t uid = (creds != NULL) ? creds->uid : POLICY_UID_ANY;
- if (!rule->in_use)
- continue;
if (rule->uid != POLICY_UID_ANY) {
if (rule->uid == POLICY_UID_NON_ROOT) {
if (uid == 0)
} else if (rule->uid != uid)
continue;
}
- continue;
if (rule->syscall != UINT64_MAX && rule->syscall != num)
continue;
allow = rule->allow;
policy_portal_send_allowed(ipc_portal_handle_t handle,
uint32_t rights, uint32_t label, size_t len)
{
- policy_rules_init();
const struct sched_credentials *creds =
- sched_task_credentials(sched_current_task());
+ sched_task_credentials(sched_current_task());
+ uint32_t uid = (creds != NULL) ? creds->uid : POLICY_UID_ANY;
bool allow = true;
+
(void)len;
- for (size_t i = 0; i < POLICY_MAX_PORTAL_RULES; i++) {
+ for (size_t i = 0; i < portal_rule_count; i++) {
const struct policy_portal_rule *rule = &portal_rules[i];
- uint32_t uid = (creds != NULL) ? creds->uid : POLICY_UID_ANY;
- if (!rule->in_use)
- continue;
if (rule->uid != POLICY_UID_ANY) {
if (rule->uid == POLICY_UID_NON_ROOT) {
if (uid == 0)
} else if (rule->uid != uid)
continue;
}
- continue;
if (rule->label != POLICY_LABEL_ANY && rule->label != label)
continue;
if (rule->required_rights != 0 &&
blob - 58abc97dbe1f732713866996c62e648b8b9f265e
blob + c8a9923d75a85ba10aa208acf67b85fe0e0f2cef
--- kernel/sys/syscall.c
+++ kernel/sys/syscall.c
#include "console/tty_protocol.h"
#include "mm/vm_layout.h"
#if defined(__x86_64__)
+#include "arch/x86_64/per_cpu.h"
#include "arch/x86_64/syscall.h"
#include "arch/x86_64/signal.h"
#include "arch/x86_64/io.h"
typedef struct syscall_result (*syscall_impl_t)(uint64_t, uint64_t, uint64_t,
uint64_t, uint64_t, uint64_t);
-struct syscall_entry {
- uint64_t num;
- syscall_impl_t fn;
- const char *name;
-};
-
-/* Fastpath dispatch table for syscalls 0-63 (O(1) lookup for common POSIX syscalls) */
-#define SYSCALL_FAST_MAX 64
-static syscall_impl_t syscall_fast_table[SYSCALL_FAST_MAX];
-
#define SYSCALL_IO_CHUNK IPC_FS_INLINE_DATA_MAX
#define SYS_POLL_MAX_FDS 128
#define SYS_MAX_FDS 64
return res;
}
-/* Global storage for execve entry point - used to bypass normal syscall return */
-uint64_t x86_syscall_execve_entry = 0;
-uint64_t x86_syscall_execve_stack = 0;
+static uint64_t
+syscall_frame_entry(const struct arch_trap_frame *frame)
+{
+#if defined(__x86_64__)
+ return frame->rip;
+#elif defined(__aarch64__)
+ return frame->elr_el1;
+#endif
+}
+static uint64_t
+syscall_frame_stack(const struct arch_trap_frame *frame)
+{
+#if defined(__x86_64__)
+ return frame->rsp;
+#elif defined(__aarch64__)
+ return frame->sp_el0;
+#endif
+}
+
+static void
+syscall_frame_set_return(struct arch_trap_frame *frame, uint64_t value)
+{
+#if defined(__x86_64__)
+ frame->rax = value;
+#elif defined(__aarch64__)
+ frame->x[0] = value;
+#endif
+}
+
+static void
+syscall_frame_set_exec(struct arch_trap_frame *frame, uint64_t entry,
+ uint64_t stack)
+{
+#if defined(__x86_64__)
+ frame->rip = entry;
+ frame->rsp = stack;
+ frame->cs = 0x2b;
+ frame->ss = 0x23;
+#elif defined(__aarch64__)
+ frame->elr_el1 = entry;
+ frame->sp_el0 = stack;
+#endif
+ syscall_frame_set_return(frame, 0);
+}
+
#define SYSCALL_PATH_MAX IPC_VFS_MAX_PATH
#define SYSCALL_PATH_COMPONENTS 64
#define SYSCALL_S_IFMT 0170000U
return syscall_make_result(0, 0);
}
-static const struct syscall_entry syscall_table[] = {
- /* Null syscall - first for best branch prediction */
- { SYS_null, sys_null_impl, "null" },
- { SYS_read, sys_read_impl, "read" },
- { SYS_write, sys_write_impl, "write" },
- { SYS_open, sys_open_impl, "open" },
- { SYS_openat, sys_openat_impl, "openat" },
- { SYS_close, sys_close_impl, "close" },
- { SYS_stat, sys_stat_impl, "stat" },
- { SYS_lstat, sys_lstat_impl, "lstat" },
- { SYS_symlinkat, sys_symlinkat_impl, "symlinkat" },
- { SYS_readlinkat, sys_readlinkat_impl, "readlinkat" },
- { SYS_renameat, sys_renameat_impl, "renameat" },
- { SYS_mknodat, sys_mknodat_impl, "mknodat" },
- { SYS_mkfifoat, sys_mkfifoat_impl, "mkfifoat" },
- { SYS_getuid, sys_getuid_impl, "getuid" },
- { SYS_geteuid, sys_geteuid_impl, "geteuid" },
- { SYS_getgid, sys_getgid_impl, "getgid" },
- { SYS_getegid, sys_getegid_impl, "getegid" },
- { SYS_setuid, sys_setuid_impl, "setuid" },
- { SYS_setgid, sys_setgid_impl, "setgid" },
- { SYS_setsid, sys_setsid_impl, "setsid" },
- { SYS_waitid, sys_waitid_impl, "waitid" },
- { SYS_getrlimit, sys_getrlimit_impl, "getrlimit" },
- { SYS_setrlimit, sys_setrlimit_impl, "setrlimit" },
- { SYS_getrusage, sys_getrusage_impl, "getrusage" },
- { SYS_socketpair, sys_socketpair_impl, "socketpair" },
- { SYS_getcwd, sys_getcwd_impl, "getcwd" },
- { SYS_chdir, sys_chdir_impl, "chdir" },
- { SYS_fchdir, sys_fchdir_impl, "fchdir" },
- { SYS_fstat, sys_fstat_impl, "fstat" },
- { SYS_mmap, sys_nosys_impl, "mmap" },
- { SYS_mprotect, sys_nosys_impl, "mprotect" },
- { SYS_munmap, sys_nosys_impl, "munmap" },
- { SYS_brk, sys_nosys_impl, "brk" },
- { SYS_lseek, sys_lseek_impl, "lseek" },
- { SYS_dup, sys_dup_impl, "dup" },
- { SYS_dup3, sys_nosys_impl, "dup3" },
- { SYS_fcntl, sys_fcntl_impl, "fcntl" },
- { SYS_pipe, sys_pipe_impl, "pipe" },
- { SYS_pipe2, sys_nosys_impl, "pipe2" },
- { SYS_poll, sys_poll_impl, "poll" },
- { SYS_sigaction, sys_sigaction_impl, "sigaction" },
- { SYS_sigprocmask, sys_sigprocmask_impl, "sigprocmask" },
- { SYS_sigaltstack, sys_nosys_impl, "sigaltstack" },
- { SYS_sigreturn, sys_sigreturn_impl, "sigreturn" },
- { SYS_raise, sys_raise_impl, "raise" },
- { SYS_waitpid, sys_waitpid_impl, "waitpid" },
- { SYS_exit_group, sys_exit_group_impl, "exit_group" },
- { SYS_clone, sys_clone_impl, "clone" },
- { SYS_set_tid_address, sys_set_tid_address_impl, "set_tid_address" },
- { SYS_futex, sys_futex_impl, "futex" },
- { SYS_spawn, sys_spawn_impl, "spawn" },
- { SYS_console_tty_register, sys_console_tty_register_impl,
- "console_tty_register" },
- { SYS_pty_feed_input, sys_pty_feed_impl, "pty_feed_input" },
- { SYS_console_ttyctl_register, sys_console_ttyctl_register_impl,
- "console_ttyctl_register" },
- { SYS_fork, sys_fork_impl, "fork" },
- { SYS_execve, sys_execve_impl, "execve" },
- { SYS_portal_send, sys_portal_send_impl, "portal_send" },
- { SYS_portal_recv, sys_portal_recv_impl, "portal_recv" },
- { SYS_portal_sender, sys_portal_sender_impl, "portal_sender" },
- { SYS_portal_bootstrap, sys_portal_bootstrap_impl, "portal_bootstrap" },
- { SYS_service_portal, sys_service_portal_impl, "service_portal" },
- { SYS_service_request, sys_service_request_impl, "service_request" },
- { SYS_service_respond, sys_service_respond_impl, "service_respond" },
- { SYS_fs_register, sys_fs_register_impl, "fs_register" },
- { SYS_fs_respond, sys_fs_respond_impl, "fs_respond" },
- { SYS_console_open, sys_console_open_impl, "console_open" },
- { SYS_console_close, sys_console_close_impl, "console_close" },
- { SYS_console_client_pty, sys_console_client_pty_impl, "console_client_pty" },
- { SYS_pty_alloc, sys_pty_alloc_impl, "pty_alloc" },
- { SYS_pty_free, sys_pty_free_impl, "pty_free" },
- { SYS_pty_activate, sys_pty_activate_impl, "pty_activate" },
- { SYS_pty_query, sys_pty_query_impl, "pty_query" },
- { SYS_pty_enum, sys_pty_enum_impl, "pty_enum" },
- { SYS_pty_set_supervisor, sys_pty_supervisor_impl, "pty_set_supervisor" },
- { SYS_pty_set_termios, sys_pty_set_termios_impl, "pty_set_termios" },
- { SYS_pty_get_termios, sys_pty_get_termios_impl, "pty_get_termios" },
- { SYS_pty_send_signal, sys_pty_send_signal_impl, "pty_send_signal" },
- { SYS_pty_get_previous, sys_pty_get_previous_impl, "pty_get_previous" },
- { SYS_srv_stage_start, sys_srv_stage_start_impl, "srv_stage_start" },
- { SYS_srv_stage_commit, sys_srv_stage_commit_impl, "srv_stage_commit" },
- { SYS_srv_stage_abort, sys_srv_stage_abort_impl, "srv_stage_abort" },
- { SYS_vfs_register, sys_vfs_register_impl, "vfs_register" },
- { SYS_vfs_respond, sys_vfs_respond_impl, "vfs_respond" },
- { SYS_wait_services, sys_wait_services_impl, "wait_services" },
- { SYS_wait_block_service, sys_wait_block_service_impl, "wait_block_service" },
- { SYS_wait_device_registered, sys_wait_device_registered_impl, "wait_device_registered" },
- { SYS_portal_revoke, sys_portal_revoke_impl, "portal_revoke" },
- { SYS_mount, sys_mount_impl, "mount" },
- { SYS_umount, sys_umount_impl, "umount" },
- { SYS_block_info, sys_block_info_impl, "block_info" },
- { SYS_block_rw, sys_block_rw_impl, "block_rw" },
- { SYS_blocksvc_register, sys_blocksvc_register_impl, "blocksvc_register" },
- { SYS_blocksvc_request, sys_blocksvc_request_impl, "blocksvc_request" },
- { SYS_blocksvc_respond, sys_blocksvc_respond_impl, "blocksvc_respond" },
- { SYS_exit, sys_exit_impl, "exit" },
- { SYS_getpid, sys_getpid_impl, "getpid" },
- { SYS_hw_io_read, sys_hw_io_read_impl, "hw_io_read" },
- { SYS_hw_io_write, sys_hw_io_write_impl, "hw_io_write" },
- { SYS_hw_mmio_map, sys_hw_mmio_map_impl, "hw_mmio_map" },
- { SYS_hw_irq_subscribe, sys_hw_irq_subscribe_impl, "hw_irq_subscribe" },
- { SYS_hw_irq_poll, sys_hw_irq_poll_impl, "hw_irq_poll" },
- { SYS_pci_service_register, sys_pci_service_register_impl,
- "pci_service_register" },
- { SYS_block_backend_register, sys_block_backend_register_impl,
- "block_backend_register" },
- { SYS_namesvc_register, sys_namesvc_register_impl,
- "namesvc_register" },
- { SYS_rootfs_info, sys_rootfs_info_impl, "rootfs_info" },
- { SYS_dma_alloc, sys_dma_alloc_impl, "dma_alloc" },
- { SYS_dma_free, sys_dma_free_impl, "dma_free" },
- { SYS_console_history, sys_console_history_impl,
- "console_history" },
- { SYS_ioctl, sys_ioctl_impl, "ioctl" },
- { SYS_kill, sys_kill_impl, "kill" },
- { SYS_getpgrp, sys_getpgrp_impl, "getpgrp" },
- { SYS_setpgrp, sys_setpgrp_impl, "setpgrp" },
- { SYS_tcgetpgrp, sys_tcgetpgrp_impl, "tcgetpgrp" },
- { SYS_tcsetpgrp, sys_tcsetpgrp_impl, "tcsetpgrp" },
- { SYS_socket, sys_socket_impl, "socket" },
- { SYS_bind, sys_bind_impl, "bind" },
- { SYS_listen, sys_listen_impl, "listen" },
- { SYS_accept, sys_accept_impl, "accept" },
- { SYS_connect, sys_connect_impl, "connect" },
- { SYS_send, sys_send_impl, "send" },
- { SYS_recv, sys_recv_impl, "recv" },
- { SYS_sendto, sys_sendto_impl, "sendto" },
- { SYS_recvfrom, sys_recvfrom_impl, "recvfrom" },
- { SYS_sock_tcp_server_register, sys_sock_tcp_server_register_impl,
- "sock_tcp_server_register" },
- { SYS_sock_udp_server_register, sys_sock_udp_server_register_impl,
- "sock_udp_server_register" },
- { SYS_clock_gettime, sys_clock_gettime_impl, "clock_gettime" },
- { SYS_gettimeofday, sys_gettimeofday_impl, "gettimeofday" },
- { SYS_nanosleep, sys_nanosleep_impl, "nanosleep" },
- { SYS_getrandom, sys_nosys_impl, "getrandom" },
- { SYS_getdents, sys_getdents_impl, "getdents" },
- { SYS_fstatat, sys_fstatat_impl, "fstatat" },
+static syscall_impl_t const syscall_table[SYS_MAX + 1] = {
+ [SYS_null] = sys_null_impl,
+ [SYS_read] = sys_read_impl,
+ [SYS_write] = sys_write_impl,
+ [SYS_open] = sys_open_impl,
+ [SYS_openat] = sys_openat_impl,
+ [SYS_close] = sys_close_impl,
+ [SYS_stat] = sys_stat_impl,
+ [SYS_lstat] = sys_lstat_impl,
+ [SYS_symlinkat] = sys_symlinkat_impl,
+ [SYS_readlinkat] = sys_readlinkat_impl,
+ [SYS_renameat] = sys_renameat_impl,
+ [SYS_mknodat] = sys_mknodat_impl,
+ [SYS_mkfifoat] = sys_mkfifoat_impl,
+ [SYS_getuid] = sys_getuid_impl,
+ [SYS_geteuid] = sys_geteuid_impl,
+ [SYS_getgid] = sys_getgid_impl,
+ [SYS_getegid] = sys_getegid_impl,
+ [SYS_setuid] = sys_setuid_impl,
+ [SYS_setgid] = sys_setgid_impl,
+ [SYS_setsid] = sys_setsid_impl,
+ [SYS_waitid] = sys_waitid_impl,
+ [SYS_getrlimit] = sys_getrlimit_impl,
+ [SYS_setrlimit] = sys_setrlimit_impl,
+ [SYS_getrusage] = sys_getrusage_impl,
+ [SYS_socketpair] = sys_socketpair_impl,
+ [SYS_getcwd] = sys_getcwd_impl,
+ [SYS_chdir] = sys_chdir_impl,
+ [SYS_fchdir] = sys_fchdir_impl,
+ [SYS_fstat] = sys_fstat_impl,
+ [SYS_mmap] = sys_nosys_impl,
+ [SYS_mprotect] = sys_nosys_impl,
+ [SYS_munmap] = sys_nosys_impl,
+ [SYS_brk] = sys_nosys_impl,
+ [SYS_lseek] = sys_lseek_impl,
+ [SYS_dup] = sys_dup_impl,
+ [SYS_dup3] = sys_nosys_impl,
+ [SYS_fcntl] = sys_fcntl_impl,
+ [SYS_pipe] = sys_pipe_impl,
+ [SYS_pipe2] = sys_nosys_impl,
+ [SYS_poll] = sys_poll_impl,
+ [SYS_sigaction] = sys_sigaction_impl,
+ [SYS_sigprocmask] = sys_sigprocmask_impl,
+ [SYS_sigaltstack] = sys_nosys_impl,
+ [SYS_sigreturn] = sys_sigreturn_impl,
+ [SYS_raise] = sys_raise_impl,
+ [SYS_waitpid] = sys_waitpid_impl,
+ [SYS_exit_group] = sys_exit_group_impl,
+ [SYS_clone] = sys_clone_impl,
+ [SYS_set_tid_address] = sys_set_tid_address_impl,
+ [SYS_futex] = sys_futex_impl,
+ [SYS_spawn] = sys_spawn_impl,
+ [SYS_console_tty_register] = sys_console_tty_register_impl,
+ [SYS_pty_feed_input] = sys_pty_feed_impl,
+ [SYS_console_ttyctl_register] = sys_console_ttyctl_register_impl,
+ [SYS_fork] = sys_fork_impl,
+ [SYS_execve] = sys_execve_impl,
+ [SYS_portal_send] = sys_portal_send_impl,
+ [SYS_portal_recv] = sys_portal_recv_impl,
+ [SYS_portal_sender] = sys_portal_sender_impl,
+ [SYS_portal_bootstrap] = sys_portal_bootstrap_impl,
+ [SYS_service_portal] = sys_service_portal_impl,
+ [SYS_service_request] = sys_service_request_impl,
+ [SYS_service_respond] = sys_service_respond_impl,
+ [SYS_fs_register] = sys_fs_register_impl,
+ [SYS_fs_respond] = sys_fs_respond_impl,
+ [SYS_console_open] = sys_console_open_impl,
+ [SYS_console_close] = sys_console_close_impl,
+ [SYS_console_client_pty] = sys_console_client_pty_impl,
+ [SYS_pty_alloc] = sys_pty_alloc_impl,
+ [SYS_pty_free] = sys_pty_free_impl,
+ [SYS_pty_activate] = sys_pty_activate_impl,
+ [SYS_pty_query] = sys_pty_query_impl,
+ [SYS_pty_enum] = sys_pty_enum_impl,
+ [SYS_pty_set_supervisor] = sys_pty_supervisor_impl,
+ [SYS_pty_set_termios] = sys_pty_set_termios_impl,
+ [SYS_pty_get_termios] = sys_pty_get_termios_impl,
+ [SYS_pty_send_signal] = sys_pty_send_signal_impl,
+ [SYS_pty_get_previous] = sys_pty_get_previous_impl,
+ [SYS_srv_stage_start] = sys_srv_stage_start_impl,
+ [SYS_srv_stage_commit] = sys_srv_stage_commit_impl,
+ [SYS_srv_stage_abort] = sys_srv_stage_abort_impl,
+ [SYS_vfs_register] = sys_vfs_register_impl,
+ [SYS_vfs_respond] = sys_vfs_respond_impl,
+ [SYS_wait_services] = sys_wait_services_impl,
+ [SYS_wait_block_service] = sys_wait_block_service_impl,
+ [SYS_wait_device_registered] = sys_wait_device_registered_impl,
+ [SYS_portal_revoke] = sys_portal_revoke_impl,
+ [SYS_mount] = sys_mount_impl,
+ [SYS_umount] = sys_umount_impl,
+ [SYS_block_info] = sys_block_info_impl,
+ [SYS_block_rw] = sys_block_rw_impl,
+ [SYS_blocksvc_register] = sys_blocksvc_register_impl,
+ [SYS_blocksvc_request] = sys_blocksvc_request_impl,
+ [SYS_blocksvc_respond] = sys_blocksvc_respond_impl,
+ [SYS_exit] = sys_exit_impl,
+ [SYS_getpid] = sys_getpid_impl,
+ [SYS_hw_io_read] = sys_hw_io_read_impl,
+ [SYS_hw_io_write] = sys_hw_io_write_impl,
+ [SYS_hw_mmio_map] = sys_hw_mmio_map_impl,
+ [SYS_hw_irq_subscribe] = sys_hw_irq_subscribe_impl,
+ [SYS_hw_irq_poll] = sys_hw_irq_poll_impl,
+ [SYS_pci_service_register] = sys_pci_service_register_impl,
+ [SYS_block_backend_register] = sys_block_backend_register_impl,
+ [SYS_namesvc_register] = sys_namesvc_register_impl,
+ [SYS_rootfs_info] = sys_rootfs_info_impl,
+ [SYS_dma_alloc] = sys_dma_alloc_impl,
+ [SYS_dma_free] = sys_dma_free_impl,
+ [SYS_console_history] = sys_console_history_impl,
+ [SYS_ioctl] = sys_ioctl_impl,
+ [SYS_kill] = sys_kill_impl,
+ [SYS_getpgrp] = sys_getpgrp_impl,
+ [SYS_setpgrp] = sys_setpgrp_impl,
+ [SYS_tcgetpgrp] = sys_tcgetpgrp_impl,
+ [SYS_tcsetpgrp] = sys_tcsetpgrp_impl,
+ [SYS_socket] = sys_socket_impl,
+ [SYS_bind] = sys_bind_impl,
+ [SYS_listen] = sys_listen_impl,
+ [SYS_accept] = sys_accept_impl,
+ [SYS_connect] = sys_connect_impl,
+ [SYS_send] = sys_send_impl,
+ [SYS_recv] = sys_recv_impl,
+ [SYS_sendto] = sys_sendto_impl,
+ [SYS_recvfrom] = sys_recvfrom_impl,
+ [SYS_sock_tcp_server_register] = sys_sock_tcp_server_register_impl,
+ [SYS_sock_udp_server_register] = sys_sock_udp_server_register_impl,
+ [SYS_clock_gettime] = sys_clock_gettime_impl,
+ [SYS_gettimeofday] = sys_gettimeofday_impl,
+ [SYS_nanosleep] = sys_nanosleep_impl,
+ [SYS_getrandom] = sys_nosys_impl,
+ [SYS_getdents] = sys_getdents_impl,
+ [SYS_fstatat] = sys_fstatat_impl,
};
-/* Initialize fastpath dispatch table for O(1) syscall lookup */
-void
-syscall_init_fastpath(void)
-{
- /* Zero-initialize fastpath table (NULL = no handler) */
- for (size_t i = 0; i < SYSCALL_FAST_MAX; i++)
- syscall_fast_table[i] = NULL;
-
- /* Populate fastpath table with syscalls 0-63 */
- for (size_t i = 0; i < (sizeof(syscall_table) / sizeof(syscall_table[0])); i++) {
- uint64_t num = syscall_table[i].num;
- if (num < SYSCALL_FAST_MAX)
- syscall_fast_table[num] = syscall_table[i].fn;
- }
-}
-
static struct syscall_result
sys_read_impl(uint64_t fd, uint64_t buf_addr, uint64_t len,
uint64_t arg3 __attribute__((unused)),
(uint32_t)frame.mask, NULL);
sched_task_user_frame_set(task, &frame.frame);
#if defined(__x86_64__)
- if (x86_signal_restore(x86_syscall_regs_ptr, &frame) != 0)
+ struct x86_per_cpu_state *per_cpu = x86_per_cpu_current();
+ struct x86_syscall_regs *regs;
+
+ if (per_cpu == NULL)
return syscall_make_result(-SYSCALL_EFAULT, 0);
+ regs = (struct x86_syscall_regs *)(uintptr_t)
+ per_cpu->syscall_regs_ptr;
+ if (x86_signal_restore(regs, &frame) != 0)
+ return syscall_make_result(-SYSCALL_EFAULT, 0);
sched_task_store_user_sp(frame.frame.rsp);
return syscall_make_result(frame.frame.rax, 0);
#elif defined(__aarch64__)
/* Create child task */
child = sched_spawn_user("child", child_space,
- parent_frame->rip, parent_frame->rsp, 0);
+ syscall_frame_entry(parent_frame),
+ syscall_frame_stack(parent_frame), 0);
if (child == NULL) {
rc = -SYSCALL_ENOMEM;
goto out_free_child_space;
/* Set child's initial state to match parent's registers */
struct arch_trap_frame child_frame = *parent_frame;
- child_frame.rax = 0; /* Child returns 0 from fork() */
+ syscall_frame_set_return(&child_frame, 0);
sched_task_user_frame_set(child, &child_frame);
/* Inherit stdio portal from parent */
if (current_frame != NULL) {
new_frame = *current_frame; /* Copy current register state */
}
- new_frame.rip = prog.entry;
- new_frame.rsp = child_sp;
- new_frame.rax = 0; /* Return value for successful execve */
- new_frame.cs = 0x2b; /* User code segment */
- new_frame.ss = 0x23; /* User data segment */
+ syscall_frame_set_exec(&new_frame, prog.entry, child_sp);
sched_task_user_frame_set(task, &new_frame);
/* Activate the new address space */
/* Store execve entry point and stack for special syscall return handling.
* The syscall_entry code will use these to jump directly to the new entry
* point instead of returning to the instruction after the syscall */
- x86_syscall_execve_entry = prog.entry;
- x86_syscall_execve_stack = child_sp;
+#if defined(__x86_64__)
+ {
+ struct x86_per_cpu_state *per_cpu = x86_per_cpu_current();
+ if (per_cpu == NULL) {
+ rc = -SYSCALL_EFAULT;
+ goto out_free_file;
+ }
+ per_cpu->syscall_execve_entry = prog.entry;
+ per_cpu->syscall_execve_stack = child_sp;
+ }
+#endif
+
rc = 0;
out_free_file:
ipc_portal_handle_t portal = (ipc_portal_handle_t)portal_handle;
size_t actual_resp_len = 0;
struct ipc_service_client client;
- uint8_t stack_req_buf[2048];
- uint8_t stack_resp_buf[2048];
- uint8_t *req_buf = stack_req_buf;
- uint8_t *resp_buf = stack_resp_buf;
- bool req_alloc = false;
- bool resp_alloc = false;
+ uint8_t scratch[IPC_MAILBOX_MAX_PAYLOAD];
/* Allow responses large enough for VFS getmounts payloads */
if (portal == IPC_PORTAL_INVALID_HANDLE)
if (resp_len == 0 || resp_len > 8192)
return syscall_make_result(-SYSCALL_EINVAL, 0);
- if (req_len > sizeof(stack_req_buf)) {
- req_buf = kmem_alloc(req_len);
- if (req_buf == NULL)
- return syscall_make_result(-SYSCALL_ENOMEM, 0);
- req_alloc = true;
- }
- if (resp_len > sizeof(stack_resp_buf)) {
- resp_buf = kmem_alloc(resp_len);
- if (resp_buf == NULL) {
- if (req_alloc)
- kmem_free(req_buf);
- return syscall_make_result(-SYSCALL_ENOMEM, 0);
- }
- resp_alloc = true;
- }
-
/* Copy request from userspace */
- if (syscall_copy_from_user(req_buf, req_addr, req_len) != 0) {
+ if (syscall_copy_from_user(scratch, req_addr, req_len) != 0) {
printk("[sys_service_request] copy_from_user failed portal=0x");
{
const char hex[] = "0123456789abcdef";
printk(" resp_len=");
printk_dec((uint64_t)resp_len);
printk("\n");
- if (req_alloc)
- kmem_free(req_buf);
- if (resp_alloc)
- kmem_free(resp_buf);
return syscall_make_result(-SYSCALL_EFAULT, 0);
}
printk(" resp_len=");
printk_dec((uint64_t)resp_len);
printk("\n");
- if (req_alloc)
- kmem_free(req_buf);
- if (resp_alloc)
- kmem_free(resp_buf);
return syscall_make_result(-SYSCALL_EINVAL, 0);
}
/* Issue request and wait for response */
- if (ipc_service_request_issue(&client, req_buf, req_len,
- resp_buf, resp_len, &actual_resp_len) != 0) {
+ if (ipc_service_request_issue(&client, scratch, req_len,
+ scratch, resp_len, &actual_resp_len) != 0) {
/* Rate-limit error messages to prevent flooding serial output */
static uint32_t error_log_count = 0;
static uint64_t last_error_log_ticks = 0;
} else {
error_log_count++;
}
- if (req_alloc)
- kmem_free(req_buf);
- if (resp_alloc)
- kmem_free(resp_buf);
return syscall_make_result(-SYSCALL_EIO, 0);
}
if (actual_resp_len > resp_len)
actual_resp_len = resp_len;
if (actual_resp_len > 0 &&
- syscall_copy_to_user(resp_addr, resp_buf, actual_resp_len) != 0) {
+ syscall_copy_to_user(resp_addr, scratch, actual_resp_len) != 0) {
printk("[sys_service_request] copy_to_user failed portal=0x");
{
const char hex[] = "0123456789abcdef";
printk(" resp_len=");
printk_dec((uint64_t)resp_len);
printk("\n");
- if (req_alloc)
- kmem_free(req_buf);
- if (resp_alloc)
- kmem_free(resp_buf);
return syscall_make_result(-SYSCALL_EFAULT, 0);
}
- if (req_alloc)
- kmem_free(req_buf);
- if (resp_alloc)
- kmem_free(resp_buf);
return syscall_make_result(0, 0);
}
uint64_t arg3, uint64_t arg4, uint64_t arg5)
{
const uint64_t args[6] = { arg0, arg1, arg2, arg3, arg4, arg5 };
+ syscall_impl_t fn;
+
#ifdef LENIX_DEBUG
audit_log_syscall(num, args);
#endif
if (!policy_syscall_allowed(num, args))
- goto deny;
+ return syscall_make_result(-SYSCALL_EPERM, 0);
- /* Fastpath: O(1) dispatch for syscalls 0-63 (common POSIX syscalls) */
- if (num < SYSCALL_FAST_MAX) {
- syscall_impl_t fn = syscall_fast_table[num];
- if (fn != NULL) {
- struct syscall_result res = fn(arg0, arg1, arg2, arg3, arg4, arg5);
-#if defined(__x86_64__)
- x86_syscall_user_sp = sched_task_load_user_sp();
-#endif
- return res;
- }
- }
-
- /* Slowpath: linear search for syscalls >= 64 (custom Lenix syscalls) */
- for (size_t i = 0; i < (sizeof(syscall_table) / sizeof(syscall_table[0]));
- i++) {
- if (syscall_table[i].num != num)
- continue;
+ fn = (num <= SYS_MAX) ? syscall_table[num] : NULL;
+ if (fn != NULL) {
#ifdef LENIX_DEBUG
if (num == SYS_console_ttyctl_register)
printk("[diag] dispatch: ttyctl_register sysnum hit\n");
#endif
- struct syscall_result res =
- syscall_table[i].fn(arg0, arg1, arg2, arg3, arg4, arg5);
-#if defined(__x86_64__)
- x86_syscall_user_sp = sched_task_load_user_sp();
-#endif
- return res;
+ return fn(arg0, arg1, arg2, arg3, arg4, arg5);
}
#if defined(DEBUG_ALL)
}
DEBUG_SYSCALL_LOG("\n");
#endif
-#if defined(__x86_64__)
- x86_syscall_user_sp = sched_task_load_user_sp();
-#endif
return syscall_make_result(-SYSCALL_ENOSYS, 0);
-deny:
-#if defined(__x86_64__)
- x86_syscall_user_sp = sched_task_load_user_sp();
-#endif
- return syscall_make_result(-SYSCALL_EPERM, 0);
}
static int
blob - /dev/null
blob + 4033ef51d3b0b3dd3ae9688621056109cfa731b8 (mode 755)
--- /dev/null
+++ scripts/check-syscall-abi.sh
+#!/usr/bin/env bash
+# Lenix - Developed by lex0de (lex0de@tuta.com)
+# lenix/scripts/check-syscall-abi.sh
+
+set -euo pipefail
+
+ROOT=$(cd "$(dirname "$0")/.." && pwd)
+KERNEL_HEADER="$ROOT/kernel/include/sys/syscall.h"
+RUNTIME_HEADER="$ROOT/user/runtime/include/sys/syscall.h"
+TMP_DIR=$(mktemp -d)
+
+cleanup()
+{
+ rm -rf "$TMP_DIR"
+}
+
+extract_syscalls()
+{
+ local header=$1
+ local output=$2
+
+ awk '
+ $1 == "#define" && $2 ~ /^SYS_/ && $2 != "SYS_MAX" &&
+ $3 ~ /^[0-9]+U$/ {
+ value = $3
+ sub(/U$/, "", value)
+ print $2, value
+ }
+ ' "$header" | sort -u >"$output"
+}
+
+check_duplicates()
+{
+ local input=$1
+ local label=$2
+ local duplicates
+
+ duplicates=$(awk '
+ {
+ if (owner[$2] != "")
+ print $2 ": " owner[$2] ", " $1
+ else
+ owner[$2] = $1
+ }
+ ' "$input")
+ if [[ -n "$duplicates" ]]; then
+ echo "[syscall-abi] duplicate numbers in $label:" >&2
+ echo "$duplicates" >&2
+ return 1
+ fi
+}
+
+trap cleanup EXIT
+
+extract_syscalls "$KERNEL_HEADER" "$TMP_DIR/kernel"
+extract_syscalls "$RUNTIME_HEADER" "$TMP_DIR/runtime"
+
+check_duplicates "$TMP_DIR/kernel" "$KERNEL_HEADER"
+check_duplicates "$TMP_DIR/runtime" "$RUNTIME_HEADER"
+
+if ! diff -u "$TMP_DIR/kernel" "$TMP_DIR/runtime"; then
+ echo "[syscall-abi] kernel and runtime syscall definitions differ" >&2
+ exit 1
+fi
+
+echo "[syscall-abi] syscall definitions match and are unique"
blob - 0a26e12b0dda5277790590f2b090e3ecb7fd4106
blob + 56259e824630770bb5e6fb5e1d432e000c87ab6e
--- scripts/lint.sh
+++ scripts/lint.sh
#!/usr/bin/env bash
+# Lenix - Developed by lex0de (lex0de@tuta.com)
+# lenix/scripts/lint.sh
+
# Static analysis/style gate: clang-tidy, cppcheck (when installed), and
# whitespace checks to enforce style(9) expectations.
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/.." && pwd)
-ALL_FILES=$(git -C "$ROOT" ls-files '*.c' '*.h' | grep -E '^(kernel|user)/' || true)
-CFILES=$(git -C "$ROOT" ls-files '*.c' | grep -E '^(kernel|user)/' || true)
-INCLUDE_FLAGS=(
- -Ikernel
- -Ikernel/include
- -Iuser/runtime/include
+cd "$ROOT"
+mapfile -t ALL_FILES < <(find kernel servers user -type f \
+ \( -name '*.c' -o -name '*.h' \) \
+ ! -path 'kernel/user/*_image_*.h' | sort)
+mapfile -t KERNEL_CFILES < <(find kernel -type f -name '*.c' | sort)
+mapfile -t USER_CFILES < <(find servers user -type f -name '*.c' \
+ ! -path 'user/runtime/src/ipc_service.c' | sort)
+CFILES=("${KERNEL_CFILES[@]}" "${USER_CFILES[@]}")
+if [[ ${#ALL_FILES[@]} -eq 0 || ${#CFILES[@]} -eq 0 ]]; then
+ echo "[lint] no source files found" >&2
+ exit 1
+fi
+KERNEL_INCLUDE_FLAGS=(
+ -Ikernel
+ -Ikernel/include
)
+USER_INCLUDE_FLAGS=(
+ -Iuser/runtime/include
+ -Ikernel/include
+)
CLANG_TIDY_INCLUDES=(
- -include stddef.h
- -include stdint.h
+ -include stddef.h
+ -include stdint.h
)
LOG_DIR="$ROOT/build/test-logs"
mkdir -p "$LOG_DIR"
CLANG_TIDY_LOG="$LOG_DIR/lint-clang-tidy.log"
CPPCHECK_LOG="$LOG_DIR/lint-cppcheck.log"
STYLE_LOG="$LOG_DIR/lint-style.log"
+TMP_DIR=$(mktemp -d)
+
+cleanup()
+{
+ rm -rf "$TMP_DIR"
+}
+
+trap cleanup EXIT
: >"$CLANG_TIDY_LOG"
: >"$CPPCHECK_LOG"
: >"$STYLE_LOG"
-run_clang_tidy() {
- local clang_tidy=${CLANG_TIDY:-clang-tidy}
- if ! command -v "$clang_tidy" >/dev/null 2>&1; then
- echo "[lint] clang-tidy not found; skipping" | tee -a "$CLANG_TIDY_LOG"
- return 0
- fi
- echo "[lint] Running clang-tidy" | tee -a "$CLANG_TIDY_LOG"
- local rc=0
- while IFS= read -r file; do
- [[ -z "$file" ]] && continue
- if [[ $file == kernel/arch/arm64/* ]]; then
- echo "[lint] skipping clang-tidy for $file (arm64 inline asm unsupported)" >>"$CLANG_TIDY_LOG"
- continue
- fi
- "$clang_tidy" "$ROOT/$file" --quiet -- -std=gnu11 "${CLANG_TIDY_INCLUDES[@]}" "${INCLUDE_FLAGS[@]}" >temp.tidy 2>&1 || rc=$?
- if [[ $rc -ne 0 ]]; then
- echo "[lint] clang-tidy issues in $file" | tee -a "$CLANG_TIDY_LOG"
- cat temp.tidy >>"$CLANG_TIDY_LOG"
- rm -f temp.tidy
- return $rc
- fi
- rm -f temp.tidy
- done <<<"$CFILES"
- echo "[lint] clang-tidy passed" | tee -a "$CLANG_TIDY_LOG"
+run_clang_tidy()
+{
+ local clang_tidy=${CLANG_TIDY:-clang-tidy}
+ local file
+ local rc
+ local -a include_flags
+
+ if ! command -v "$clang_tidy" >/dev/null 2>&1; then
+ echo "[lint] clang-tidy not found; skipping" | tee -a "$CLANG_TIDY_LOG"
+ return 0
+ fi
+ echo "[lint] Running clang-tidy" | tee -a "$CLANG_TIDY_LOG"
+ for file in "${CFILES[@]}"; do
+ if [[ $file == kernel/arch/arm64/* ]]; then
+ echo "[lint] skipping clang-tidy for $file (arm64 inline asm unsupported)" >>"$CLANG_TIDY_LOG"
+ continue
+ fi
+ if [[ $file == kernel/* ]]; then
+ include_flags=("${KERNEL_INCLUDE_FLAGS[@]}")
+ else
+ include_flags=("${USER_INCLUDE_FLAGS[@]}")
+ fi
+ rc=0
+ "$clang_tidy" "$ROOT/$file" --quiet -- -std=gnu11 \
+ "${CLANG_TIDY_INCLUDES[@]}" "${include_flags[@]}" \
+ >"$TMP_DIR/clang-tidy" 2>&1 || rc=$?
+ if [[ $rc -ne 0 ]]; then
+ echo "[lint] clang-tidy issues in $file" | tee -a "$CLANG_TIDY_LOG"
+ cat "$TMP_DIR/clang-tidy" >>"$CLANG_TIDY_LOG"
+ return "$rc"
+ fi
+ done
+ echo "[lint] clang-tidy passed" | tee -a "$CLANG_TIDY_LOG"
}
-run_cppcheck() {
- local cppcheck=${CPPCHECK:-cppcheck}
- if ! command -v "$cppcheck" >/dev/null 2>&1; then
- echo "[lint] cppcheck not found; skipping" | tee -a "$CPPCHECK_LOG"
- return 0
- fi
- echo "[lint] Running cppcheck" | tee -a "$CPPCHECK_LOG"
- "$cppcheck" --enable=warning,performance,portability --std=gnu11 --inline-suppr \
- "${INCLUDE_FLAGS[@]}" --template='[{file}:{line}] {severity}: {message}' \
- $CFILES >>"$CPPCHECK_LOG" 2>&1 || {
- echo "[lint] cppcheck reported issues" | tee -a "$CPPCHECK_LOG"
- exit 1
- }
- echo "[lint] cppcheck finished" | tee -a "$CPPCHECK_LOG"
+run_cppcheck()
+{
+ local cppcheck=${CPPCHECK:-cppcheck}
+
+ if ! command -v "$cppcheck" >/dev/null 2>&1; then
+ echo "[lint] cppcheck not found; skipping" | tee -a "$CPPCHECK_LOG"
+ return 0
+ fi
+ echo "[lint] Running cppcheck" | tee -a "$CPPCHECK_LOG"
+ "$cppcheck" --enable=warning,performance,portability --std=gnu11 \
+ --error-exitcode=1 --inline-suppr "${KERNEL_INCLUDE_FLAGS[@]}" \
+ --template='[{file}:{line}] {severity}: {message}' \
+ "${KERNEL_CFILES[@]}" >>"$CPPCHECK_LOG" 2>&1 || {
+ echo "[lint] cppcheck reported kernel issues" | tee -a "$CPPCHECK_LOG"
+ return 1
+ }
+ "$cppcheck" --enable=warning,performance,portability --std=gnu11 \
+ --error-exitcode=1 --inline-suppr "${USER_INCLUDE_FLAGS[@]}" \
+ --template='[{file}:{line}] {severity}: {message}' \
+ "${USER_CFILES[@]}" >>"$CPPCHECK_LOG" 2>&1 || {
+ echo "[lint] cppcheck reported userland issues" | tee -a "$CPPCHECK_LOG"
+ return 1
+ }
+ echo "[lint] cppcheck finished" | tee -a "$CPPCHECK_LOG"
}
-run_style_checks() {
- echo "[lint] Running style checks" | tee -a "$STYLE_LOG"
- local fail=0
- while IFS= read -r file; do
- [[ -z "$file" ]] && continue
- if grep -n $'\s+$' "$ROOT/$file" >>"$STYLE_LOG"; then
- echo "[lint] trailing whitespace in $file" | tee -a "$STYLE_LOG"
- fail=1
- fi
- done <<<"$ALL_FILES"
- if [[ $fail -ne 0 ]]; then
- echo "[lint] style issues detected; see $STYLE_LOG" | tee -a "$STYLE_LOG"
- return 0
- fi
- echo "[lint] style checks passed" | tee -a "$STYLE_LOG"
+run_style_checks()
+{
+ local fail=0
+ local file
+
+ echo "[lint] Running style checks" | tee -a "$STYLE_LOG"
+ for file in "${ALL_FILES[@]}"; do
+ if grep -nE '[[:blank:]]+$' "$ROOT/$file" >>"$STYLE_LOG"; then
+ echo "[lint] trailing whitespace in $file" | tee -a "$STYLE_LOG"
+ fail=1
+ fi
+ done
+ if [[ $fail -ne 0 ]]; then
+ echo "[lint] style issues detected; see $STYLE_LOG" | tee -a "$STYLE_LOG"
+ return 1
+ fi
+ echo "[lint] style checks passed" | tee -a "$STYLE_LOG"
}
+"$ROOT/scripts/check-syscall-abi.sh"
run_clang_tidy
run_cppcheck
run_style_checks
blob - /dev/null
blob + 368da96ea195c9e4f481ad06c12381c93696e49a (mode 755)
--- /dev/null
+++ scripts/test-console-input.sh
+#!/usr/bin/env bash
+# Lenix - Developed by lex0de (lex0de@tuta.com)
+# lenix/scripts/test-console-input.sh
+
+set -euo pipefail
+
+ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
+LOG_DIR="$ROOT/build/test-logs"
+OVMF_CODE=${OVMF_CODE:-/usr/share/ovmf/x64/OVMF_CODE.4m.fd}
+OVMF_VARS=${OVMF_VARS:-/usr/share/ovmf/x64/OVMF_VARS.4m.fd}
+TEST_TIMEOUT=${CONSOLE_TEST_TIMEOUT:-45}
+TEMP_DIR=$(mktemp -d /tmp/lenix-console-input.XXXXXX)
+QEMU_PID=
+READER_PID=
+SERIAL_BASE=
+
+cleanup_case()
+{
+ if [[ -n "$QEMU_PID" ]] && kill -0 "$QEMU_PID" 2>/dev/null; then
+ kill "$QEMU_PID" 2>/dev/null || true
+ wait "$QEMU_PID" 2>/dev/null || true
+ fi
+ if [[ -n "$READER_PID" ]] && kill -0 "$READER_PID" 2>/dev/null; then
+ kill "$READER_PID" 2>/dev/null || true
+ wait "$READER_PID" 2>/dev/null || true
+ fi
+ exec 3>&- 2>/dev/null || true
+ exec 4>&- 2>/dev/null || true
+ QEMU_PID=
+ READER_PID=
+}
+
+cleanup_all()
+{
+ cleanup_case
+ rm -f "$TEMP_DIR/legacy.in" "$TEMP_DIR/legacy.out" \
+ "$TEMP_DIR/uefi.in" "$TEMP_DIR/uefi.out" \
+ "$TEMP_DIR/OVMF_VARS.fd"
+ rmdir "$TEMP_DIR" 2>/dev/null || true
+}
+
+fail()
+{
+ echo "[console-test] ERROR: $1" >&2
+ exit 1
+}
+
+wait_for_count()
+{
+ local count
+ local expected=$3
+ local log=$1
+ local pattern=$2
+ local steps=$((TEST_TIMEOUT * 4))
+
+ for ((count = 0; count < steps; count++)); do
+ local found
+ found=$(grep -aFc -- "$pattern" "$log" || true)
+ if ((found >= expected)); then
+ return 0
+ fi
+ if ! kill -0 "$QEMU_PID" 2>/dev/null; then
+ return 1
+ fi
+ sleep 0.25
+ done
+ return 1
+}
+
+wait_for_pattern()
+{
+ local count
+ local log=$1
+ local pattern=$2
+ local steps=$((TEST_TIMEOUT * 4))
+
+ for ((count = 0; count < steps; count++)); do
+ if grep -aFq -- "$pattern" "$log"; then
+ return 0
+ fi
+ if ! kill -0 "$QEMU_PID" 2>/dev/null; then
+ return 1
+ fi
+ sleep 0.25
+ done
+ return 1
+}
+
+validate_log()
+{
+ local bench_count
+ local bench_line
+ local label=$1
+ local log=$2
+ local -a hello_lines
+
+ mapfile -t hello_lines < <(grep -anF \
+ "hello from Lenix userland" "$log" || true)
+ bench_count=$(grep -aFc \
+ "[bench_portal_pingpong] Starting portal round-trip benchmark" \
+ "$log" || true)
+ bench_line=$(grep -anF \
+ "[bench_portal_pingpong] Starting portal round-trip benchmark" \
+ "$log" | head -n 1 | cut -d: -f1)
+
+ [[ ${#hello_lines[@]} -eq 2 ]] ||
+ fail "$label produced ${#hello_lines[@]} hello results, expected 2"
+ [[ $bench_count -eq 1 ]] ||
+ fail "$label ran bench_portal_pingpong $bench_count times, expected 1"
+ [[ ${hello_lines[0]%%:*} -lt $bench_line ]] ||
+ fail "$label ran the benchmark before the first hello"
+ [[ $bench_line -lt ${hello_lines[1]%%:*} ]] ||
+ fail "$label ran the second hello before the benchmark"
+ if grep -aEiq '\[trap\].*fault|panic' "$log"; then
+ fail "$label reported a kernel fault or panic"
+ fi
+}
+
+run_case()
+{
+ local label=$1
+ local log="$LOG_DIR/console-input-$label.log"
+ local qemu_log="$LOG_DIR/console-input-$label.qemu.log"
+ local -a qemu_args
+
+ SERIAL_BASE="$TEMP_DIR/$label"
+ rm -f "$SERIAL_BASE.in" "$SERIAL_BASE.out" "$log" "$qemu_log"
+ mkfifo "$SERIAL_BASE.in" "$SERIAL_BASE.out"
+ exec 3<>"$SERIAL_BASE.in"
+ exec 4<>"$SERIAL_BASE.out"
+ tee "$log" <&4 >/dev/null &
+ READER_PID=$!
+
+ if [[ $label == uefi ]]; then
+ cp "$OVMF_VARS" "$TEMP_DIR/OVMF_VARS.fd"
+ qemu_args=(
+ -machine q35
+ -m 2048M
+ -smp 2
+ -drive "if=pflash,format=raw,readonly=on,file=$OVMF_CODE"
+ -drive "if=pflash,format=raw,file=$TEMP_DIR/OVMF_VARS.fd"
+ -drive "file=fat:rw:$ROOT/build,format=raw"
+ )
+ else
+ qemu_args=(
+ -cdrom "$ROOT/build/x86_64/lenix.iso"
+ -m 2048M
+ -smp 2
+ )
+ fi
+ qemu_args+=(
+ -drive "if=none,id=virtio-rootfs,file=$ROOT/build/rootfs.ext2,format=raw"
+ -device "virtio-blk-pci,drive=virtio-rootfs"
+ -serial "pipe:$SERIAL_BASE"
+ -monitor none
+ -display none
+ -no-reboot
+ -no-shutdown
+ )
+
+ echo "[console-test] Booting $label"
+ TMPDIR="$ROOT/build/tmp" qemu-system-x86_64 "${qemu_args[@]}" \
+ >"$qemu_log" 2>&1 &
+ QEMU_PID=$!
+
+ wait_for_pattern "$log" "[sh] entering main loop" ||
+ fail "$label did not reach the shell; see $log and $qemu_log"
+ printf 'hello\r' >&3
+ wait_for_count "$log" "hello from Lenix userland" 1 ||
+ fail "$label did not execute the first hello; see $log"
+ printf 'bench_portal_pingpong\r' >&3
+ wait_for_pattern "$log" "[bench_portal_pingpong] Per-RTT:" ||
+ fail "$label did not execute bench_portal_pingpong; see $log"
+ printf 'hello\r' >&3
+ wait_for_count "$log" "hello from Lenix userland" 2 ||
+ fail "$label did not execute the second hello; see $log"
+
+ validate_log "$label" "$log"
+ cleanup_case
+ echo "[console-test] $label passed"
+}
+
+trap cleanup_all EXIT
+trap 'exit 130' INT
+trap 'exit 143' TERM
+
+command -v qemu-system-x86_64 >/dev/null 2>&1 ||
+ fail "qemu-system-x86_64 is not installed"
+[[ -f "$OVMF_CODE" ]] || fail "OVMF code image not found: $OVMF_CODE"
+[[ -f "$OVMF_VARS" ]] || fail "OVMF variables image not found: $OVMF_VARS"
+[[ -f "$ROOT/build/x86_64/lenix.iso" ]] || fail "legacy ISO is missing"
+[[ -f "$ROOT/build/EFI/BOOT/BOOTX64.EFI" ]] || fail "UEFI image is missing"
+[[ -f "$ROOT/build/rootfs.ext2" ]] || fail "rootfs image is missing"
+
+mkdir -p "$LOG_DIR" "$ROOT/build/tmp"
+cd "$ROOT"
+
+run_case uefi
+run_case legacy
+
+echo "[console-test] UEFI and legacy input tests passed"
blob - 46fe4c3c6a2aa7d8cb694842a67c89ae09aadfce
blob + 58f20ae8c1b17ddd18da49f62d8b11496e5a41e5
--- servers/block/blockd/main.c
+++ servers/block/blockd/main.c
uint32_t device_id;
uint32_t block_size;
uint64_t block_count;
+ portal_handle_t backend_portal;
+ bool push_enabled;
bool request_pending;
bool waiting_completion;
+ uint32_t completion_token;
uint64_t waiting_since_ms; /* timestamp when waiting started */
struct ipc_block_service_request pending_req;
struct ipc_block_service_request queue[8];
static bool blockd_backend_queue_pop(struct blockd_backend_device *slot,
struct ipc_block_service_request *req_out);
static void blockd_backend_promote_queued(struct blockd_backend_device *slot);
+static bool blockd_backend_push_request(struct blockd_backend_device *slot);
+static void blockd_backend_fail_requests(struct blockd_backend_device *slot,
+ int32_t status);
static void blockd_check_device_timeouts(void);
static uint64_t blockd_now_ms(void);
blockd_next_backend_id = 2;
for (size_t i = 0; i < BLOCKD_BACKEND_MAX_DEVICES; i++) {
blockd_backend_devices[i].in_use = false;
+ blockd_backend_devices[i].backend_portal =
+ IPC_PORTAL_INVALID_HANDLE;
+ blockd_backend_devices[i].push_enabled = false;
blockd_backend_devices[i].request_pending = false;
blockd_backend_devices[i].waiting_completion = false;
+ blockd_backend_devices[i].completion_token = 0;
blockd_backend_queue_reset(&blockd_backend_devices[i]);
}
}
slot->request_pending = true;
}
+static void
+blockd_backend_fail_requests(struct blockd_backend_device *slot,
+ int32_t status)
+{
+ struct ipc_block_service_request req;
+ struct ipc_block_service_response resp;
+
+ if (slot == NULL)
+ return;
+ if (slot->request_pending || slot->waiting_completion) {
+ memset(&resp, 0, sizeof(resp));
+ resp.token = slot->pending_req.token;
+ resp.status = status;
+ block_service_respond(&resp);
+ }
+ while (blockd_backend_queue_pop(slot, &req)) {
+ memset(&resp, 0, sizeof(resp));
+ resp.token = req.token;
+ resp.status = status;
+ block_service_respond(&resp);
+ }
+ slot->request_pending = false;
+ slot->waiting_completion = false;
+ slot->completion_token = 0;
+}
+
static bool
+blockd_backend_push_request(struct blockd_backend_device *slot)
+{
+ struct ipc_block_backend_io_request work;
+ const struct ipc_block_service_request *pending;
+
+ if (slot == NULL || !slot->push_enabled || !slot->request_pending ||
+ slot->waiting_completion)
+ return true;
+ if (slot->backend_portal == IPC_PORTAL_INVALID_HANDLE)
+ return false;
+ pending = &slot->pending_req;
+ memset(&work, 0, sizeof(work));
+ work.opcode = IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST;
+ work.token = pending->token;
+ work.status = IPC_BLOCK_BACKEND_STATUS_OK;
+ work.device_id = slot->device_id;
+ work.flags = pending->flags;
+ work.lba = pending->lba;
+ work.blocks = pending->blocks;
+ work.data_len = pending->data_len;
+ if (work.data_len > sizeof(work.data))
+ work.data_len = sizeof(work.data);
+ if ((pending->flags & IPC_BLOCK_SERVICE_F_WRITE) != 0 &&
+ work.data_len > 0)
+ memcpy(work.data, pending->data, work.data_len);
+ if (portal_send(slot->backend_portal, &work, sizeof(work)) != 0)
+ return false;
+ slot->request_pending = false;
+ slot->waiting_completion = true;
+ slot->completion_token = pending->token;
+ slot->waiting_since_ms = blockd_now_ms();
+ return true;
+}
+
+static bool
blockd_backend_queue_request(struct blockd_backend_device *slot,
const struct ipc_block_service_request *req)
{
req->device, req->token, (unsigned long long)req->lba,
req->blocks, req->flags);
#endif
+ if (!blockd_backend_push_request(slot)) {
+ blockd_backend_fail_requests(slot,
+ IPC_BLOCK_BACKEND_STATUS_ERROR);
+ slot->in_use = false;
+ }
return true;
}
if (blockd_backend_queue_push(slot, req)) {
blockd_log("[blockd] backend register request");
blockd_log_hex64("[blockd] block_count=0x", req->block_count);
blockd_log_hex64("[blockd] block_size=0x", req->block_size);
+ if (req->block_size == 0 || req->block_count == 0 ||
+ ((req->flags & IPC_BLOCK_BACKEND_REGISTER_F_PUSH) != 0 &&
+ req->portal == IPC_PORTAL_INVALID_HANDLE)) {
+ resp.status = IPC_BLOCK_BACKEND_STATUS_INVALID;
+ goto respond;
+ }
slot = blockd_backend_alloc_slot();
if (slot == NULL) {
resp.status = IPC_BLOCK_BACKEND_STATUS_NO_DEVICE;
} else {
slot->in_use = true;
slot->device_id = blockd_next_backend_id++;
+ slot->backend_portal = (portal_handle_t)req->portal;
+ slot->push_enabled =
+ (req->flags & IPC_BLOCK_BACKEND_REGISTER_F_PUSH) != 0;
slot->request_pending = false;
slot->waiting_completion = false;
+ slot->completion_token = 0;
blockd_backend_queue_reset(slot);
slot->block_size = req->block_size;
slot->block_count = req->block_count;
register_log_count++;
}
}
+respond:
if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE)
ipc_service_respond(blockd_backend_portal, resp.token,
&resp, sizeof(resp));
resp.status = IPC_BLOCK_BACKEND_STATUS_ERROR;
slot = blockd_backend_find(req->device_id);
if (slot != NULL) {
+ blockd_backend_fail_requests(slot,
+ IPC_BLOCK_BACKEND_STATUS_NO_DEVICE);
slot->in_use = false;
- slot->request_pending = false;
- slot->waiting_completion = false;
+ slot->backend_portal = IPC_PORTAL_INVALID_HANDLE;
+ slot->push_enabled = false;
blockd_backend_queue_reset(slot);
resp.status = IPC_BLOCK_BACKEND_STATUS_OK;
}
memcpy(resp.data, pending->data, resp.data_len);
slot->request_pending = false;
slot->waiting_completion = true;
+ slot->completion_token = resp.token;
slot->waiting_since_ms = blockd_now_ms();
#ifdef LENIX_DEBUG
blockd_log_int("[blockd] backend fetch token=",
{
struct blockd_backend_device *slot;
struct ipc_block_service_response out;
+ uint64_t expected;
+ bool write;
if (resp_pkt == NULL)
return;
return;
if (!slot->waiting_completion)
return;
+ if (resp_pkt->token != slot->completion_token) {
+ BLOCKD_TRACE("[blockd] response token mismatch dev=%u got=%u expected=%u\n",
+ slot->device_id, resp_pkt->token, slot->completion_token);
+ return;
+ }
BLOCKD_TRACE("[blockd] backend resp token=%u dev=%u status=%d bytes=%u data_len=%u\n",
resp_pkt->token, resp_pkt->device_id, resp_pkt->status,
resp_pkt->bytes_transferred, resp_pkt->data_len);
memset(&out, 0, sizeof(out));
out.token = slot->pending_req.token;
out.status = resp_pkt->status;
- out.bytes_transferred = resp_pkt->bytes_transferred;
- out.data_len = 0;
- if (out.status == IPC_BLOCK_BACKEND_STATUS_OK) {
- uint64_t expect = (uint64_t)slot->pending_req.blocks * 512ULL;
- if (out.bytes_transferred != expect) {
- BLOCKD_TRACE("[blockd] WARN: short backend resp dev=%u token=%u bytes=%u expect=%llu\n",
- slot->device_id, slot->pending_req.token,
- out.bytes_transferred,
- (unsigned long long)expect);
- }
- }
- if ((slot->pending_req.flags & IPC_BLOCK_SERVICE_F_WRITE) == 0 &&
+ out.bytes_transferred = resp_pkt->bytes_transferred;
+ expected = (uint64_t)slot->pending_req.blocks * slot->block_size;
+ write = (slot->pending_req.flags & IPC_BLOCK_SERVICE_F_WRITE) != 0;
+ if (out.status == IPC_BLOCK_BACKEND_STATUS_OK &&
+ (expected == 0 || expected > IPC_BLOCK_SERVICE_MAX_DATA ||
+ out.bytes_transferred != expected ||
+ resp_pkt->data_len > sizeof(resp_pkt->data) ||
+ (!write && resp_pkt->data_len != expected) ||
+ (write && resp_pkt->data_len != 0))) {
+ out.status = IPC_BLOCK_BACKEND_STATUS_INVALID;
+ out.bytes_transferred = 0;
+ }
+ if (out.status == IPC_BLOCK_BACKEND_STATUS_OK && !write &&
resp_pkt->data_len > 0) {
- uint32_t copy = resp_pkt->data_len;
- if (copy > IPC_BLOCK_SERVICE_MAX_DATA)
- copy = IPC_BLOCK_SERVICE_MAX_DATA;
- memcpy(out.data, resp_pkt->data, copy);
- out.data_len = copy;
+ memcpy(out.data, resp_pkt->data, resp_pkt->data_len);
+ out.data_len = resp_pkt->data_len;
}
slot->waiting_completion = false;
+ slot->completion_token = 0;
#ifdef LENIX_DEBUG
blockd_log_int("[blockd] backend response token=",
(int)slot->pending_req.token);
#endif
block_service_respond(&out);
blockd_backend_promote_queued(slot);
- if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE) {
+ if (slot->push_enabled && !blockd_backend_push_request(slot)) {
+ blockd_backend_fail_requests(slot,
+ IPC_BLOCK_BACKEND_STATUS_ERROR);
+ slot->in_use = false;
+ } else if (!slot->push_enabled &&
+ blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE) {
struct ipc_block_backend_io_response ack;
memset(&ack, 0, sizeof(ack));
ack.opcode = IPC_BLOCK_BACKEND_OPCODE_IO_RESPONSE;
uint64_t wait_ms = now - slot->waiting_since_ms;
if (wait_ms > BLOCKD_BACKEND_TIMEOUT_MS) {
- /* Backend timed out - clear stuck state and fail the request */
BLOCKD_TRACE("[blockd] TIMEOUT dev=%u waited=%llu ms, clearing stuck state\n",
slot->device_id, (unsigned long long)wait_ms);
-
- struct ipc_block_service_response out;
- memset(&out, 0, sizeof(out));
- out.token = slot->pending_req.token;
- out.status = -110; /* ETIMEDOUT */
- out.bytes_transferred = 0;
- out.data_len = 0;
- slot->waiting_completion = false;
- block_service_respond(&out);
-
- /* Promote any queued requests */
- blockd_backend_promote_queued(slot);
+ blockd_backend_fail_requests(slot, -110);
+ if (slot->push_enabled) {
+ slot->in_use = false;
+ slot->backend_portal = IPC_PORTAL_INVALID_HANDLE;
+ slot->push_enabled = false;
+ } else {
+ blockd_backend_promote_queued(slot);
+ }
} else if (wait_ms > 2000) {
/* Log warning if waiting more than 2s */
BLOCKD_TRACE("[blockd] WAITING dev=%u for %llu ms token=%u\n",
blob - dd1a43a66b91b17e300b153ed564aeaae063c7c8
blob + e6f5efe9bd5114e9f539fc8b928fe6387a24f92a
--- servers/block/ramdiskd/main.c
+++ servers/block/ramdiskd/main.c
ramdisk_stats.requests_completed++;
}
+static int
+ramdisk_push_loop(portal_handle_t push_portal,
+ portal_handle_t blockd_portal, uint32_t device_id)
+{
+ struct ipc_block_backend_io_request work;
+ struct ipc_block_backend_io_response response;
+ struct pollfd pfd;
+ ssize_t received;
+ while (!ramdisk_terminate) {
+ pfd.fd = (int)push_portal;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+ if (poll(&pfd, 1, 100) <= 0)
+ continue;
+ if ((pfd.revents & POLLIN) == 0)
+ continue;
+ memset(&work, 0, sizeof(work));
+ received = portal_recv(&work, sizeof(work));
+ if (received != (ssize_t)sizeof(work) ||
+ work.opcode != IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST ||
+ work.device_id != device_id)
+ continue;
+ memset(&response, 0, sizeof(response));
+ response.token = work.token;
+ ramdisk_handle_request(&work, &response);
+ if (portal_send(blockd_portal, &response, sizeof(response)) != 0)
+ return -1;
+ }
+ return 0;
+}
+
+
int
main(void)
{
portal_handle_t portal;
+ portal_handle_t push_portal;
struct ipc_service_client client;
struct ipc_block_backend_register reg_req = {0};
struct ipc_block_backend_register_response reg_resp = {0};
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_sigterm;
sigaction(SIGTERM, &sa, NULL);
+ push_portal = portal_get_bootstrap();
/* Wait for block-backend service */
DLOG("[ramdiskd] waiting for blockd backend portal (namesvc)...");
log_line("[ramdiskd] block backend portal ready");
#endif
portal_handle_t namesvc = service_resolve("namesvc");
- if (namesvc == IPC_PORTAL_INVALID_HANDLE) {
+ if (namesvc == IPC_PORTAL_INVALID_HANDLE ||
+ push_portal == IPC_PORTAL_INVALID_HANDLE) {
DLOG("[ramdiskd] namesvc unavailable; skipping registration");
} else {
- int ns_rc = namesvc_register(namesvc, "ramdiskd", portal,
+ int ns_rc = namesvc_register(namesvc, "ramdiskd", push_portal,
IPC_PORTAL_RIGHT_SEND,
staged ? IPC_NAMESVC_FLAG_STAGED : 0);
if (ns_rc != 0) {
#endif
DLOG("[ramdiskd] sending REGISTER request...");
reg_req.opcode = IPC_BLOCK_BACKEND_OPCODE_REGISTER;
+ if (push_portal != IPC_PORTAL_INVALID_HANDLE) {
+ reg_req.flags = IPC_BLOCK_BACKEND_REGISTER_F_PUSH;
+ reg_req.portal = push_portal;
+ }
reg_req.block_size = RAMDISK_BLOCK_SIZE;
reg_req.block_count = ramdisk_block_count;
log_line("[ramdiskd] registered backend device");
log_hex32("[ramdiskd] device_id=", (uint32_t)device_id);
#endif
+ memset(&ramdisk_stats, 0, sizeof(ramdisk_stats));
+ if ((reg_req.flags & IPC_BLOCK_BACKEND_REGISTER_F_PUSH) != 0)
+ return ramdisk_push_loop(push_portal, portal, device_id);
memset(&fetch, 0, sizeof(fetch));
memset(&work, 0, sizeof(work));
memset(&response, 0, sizeof(response));
memset(&ack, 0, sizeof(ack));
- memset(&ramdisk_stats, 0, sizeof(ramdisk_stats));
fetch.opcode = IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST;
fetch.device_id = device_id;
fetch.token = 1;
blob - e016b6bd71e754ac8b92f98ae6faa2b0081a4edc
blob + 179ba5422a010242f76425ac6d4ce63f89df6bf1
--- servers/block/virtio-blk/main.c
+++ servers/block/virtio-blk/main.c
static int virtio_blk_transfer(struct virtio_blk_driver *drv, bool write,
uint64_t lba, void *buf, uint32_t blocks);
static int virtio_backend_register(struct virtio_blk_driver *drv,
- struct ipc_service_client *client, uint32_t *device_id_out);
+ struct ipc_service_client *client,
+ portal_handle_t push_portal, uint32_t *device_id_out);
static void virtio_backend_loop(struct virtio_blk_driver *drv,
- struct ipc_service_client *client, uint32_t device_id);
+ struct ipc_service_client *client,
+ portal_handle_t push_portal,
+ portal_handle_t blockd_portal, uint32_t device_id);
static void virtio_lock_acquire(void);
static void virtio_lock_release(void);
static bool virtio_log_verbose_enabled(void);
static int
virtio_backend_register(struct virtio_blk_driver *drv,
- struct ipc_service_client *client, uint32_t *device_id_out)
+ struct ipc_service_client *client, portal_handle_t push_portal,
+ uint32_t *device_id_out)
{
struct ipc_block_backend_register req;
struct ipc_block_backend_register_response resp;
memset(&req, 0, sizeof(req));
memset(&resp, 0, sizeof(resp));
req.opcode = IPC_BLOCK_BACKEND_OPCODE_REGISTER;
+ if (push_portal != IPC_PORTAL_INVALID_HANDLE) {
+ req.flags = IPC_BLOCK_BACKEND_REGISTER_F_PUSH;
+ req.portal = push_portal;
+ }
req.block_size = drv->block_size;
req.block_count = drv->capacity;
if (req.block_size == 0 || req.block_count == 0)
static void
virtio_backend_loop(struct virtio_blk_driver *drv,
- struct ipc_service_client *client, uint32_t device_id)
+ struct ipc_service_client *client, portal_handle_t push_portal,
+ portal_handle_t blockd_portal, uint32_t device_id)
{
static struct ipc_block_backend_io_request fetch;
static struct ipc_block_backend_io_request work;
static struct ipc_block_backend_io_response response;
static struct ipc_block_backend_io_response ack;
+ struct pollfd pfd;
+ ssize_t received;
int busy_spins = 0;
static int busy_log_count;
log_line(LOG_PREFIX "SIGTERM received, exiting backend loop");
return;
}
- memset(&fetch, 0, sizeof(fetch));
- fetch.opcode = IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST;
- fetch.device_id = device_id;
- if (ipc_service_request_issue(client, &fetch, sizeof(fetch),
- &work, sizeof(work)) != 0)
- continue;
- if (work.status == IPC_BLOCK_BACKEND_STATUS_BUSY ||
- work.status == IPC_BLOCK_BACKEND_STATUS_NOT_FOUND ||
- work.status == IPC_BLOCK_BACKEND_STATUS_IDLE) {
- /* No work yet; back off briefly (longer for IDLE) */
- busy_spins++;
- if (busy_spins > 10000) {
- if (virtio_log_verbose_enabled() &&
- busy_log_count < 4) {
- log_hex32(LOG_PREFIX "fetch busy status ",
- (uint32_t)work.status);
- busy_log_count++;
+ if (push_portal != IPC_PORTAL_INVALID_HANDLE) {
+ pfd.fd = (int)push_portal;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+ if (poll(&pfd, 1, 100) <= 0 ||
+ (pfd.revents & POLLIN) == 0)
+ continue;
+ memset(&work, 0, sizeof(work));
+ received = portal_recv(&work, sizeof(work));
+ if (received != (ssize_t)sizeof(work) ||
+ work.opcode != IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST ||
+ work.device_id != device_id)
+ continue;
+ } else {
+ memset(&fetch, 0, sizeof(fetch));
+ fetch.opcode = IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST;
+ fetch.device_id = device_id;
+ if (ipc_service_request_issue(client, &fetch, sizeof(fetch),
+ &work, sizeof(work)) != 0)
+ continue;
+ if (work.status == IPC_BLOCK_BACKEND_STATUS_BUSY ||
+ work.status == IPC_BLOCK_BACKEND_STATUS_NOT_FOUND ||
+ work.status == IPC_BLOCK_BACKEND_STATUS_IDLE) {
+ busy_spins++;
+ if (busy_spins > 10000) {
+ if (virtio_log_verbose_enabled() &&
+ busy_log_count < 4) {
+ log_hex32(LOG_PREFIX "fetch busy status ",
+ (uint32_t)work.status);
+ busy_log_count++;
+ }
+ busy_spins = 0;
}
- busy_spins = 0;
+ if (work.status == IPC_BLOCK_BACKEND_STATUS_IDLE)
+ poll(NULL, 0, 10);
+ else
+ poll(NULL, 0, 1);
+ continue;
}
- /* Use moderate backoff for IDLE (no work pending) */
- if (work.status == IPC_BLOCK_BACKEND_STATUS_IDLE)
- poll(NULL, 0, 10);
- else
- poll(NULL, 0, 1);
- continue;
+ busy_spins = 0;
+ if (work.status != IPC_BLOCK_BACKEND_STATUS_OK) {
+ log_hex32(LOG_PREFIX "fetch status ", work.status);
+ continue;
+ }
}
- busy_spins = 0;
- if (work.status != IPC_BLOCK_BACKEND_STATUS_OK) {
- log_hex32(LOG_PREFIX "fetch status ", work.status);
- continue;
- }
memset(&response, 0, sizeof(response));
response.opcode = IPC_BLOCK_BACKEND_OPCODE_IO_RESPONSE;
response.token = work.token;
log_hex32(LOG_PREFIX "blocks ", blocks);
log_hex32(LOG_PREFIX "token ", response.token);
}
- if (ipc_service_request_issue(client, &response, sizeof(response),
- &ack, sizeof(ack)) != 0)
+ if (push_portal != IPC_PORTAL_INVALID_HANDLE) {
+ if (portal_send(blockd_portal, &response,
+ sizeof(response)) != 0)
+ return;
+ } else if (ipc_service_request_issue(client, &response,
+ sizeof(response), &ack, sizeof(ack)) != 0) {
continue;
+ }
}
}
{
struct ipc_service_client backend_client;
portal_handle_t backend_portal;
+ portal_handle_t push_portal;
static portal_handle_t cached_backend = IPC_PORTAL_INVALID_HANDLE;
bool staged = srv_should_stage();
uint32_t gen = 0;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = virtio_handle_sigterm;
sigaction(SIGTERM, &sa, NULL);
+ push_portal = portal_get_bootstrap();
virtio_log_verbose = false;
#ifdef LENIX_DEBUG
log_line(LOG_PREFIX "failed to init backend client - skipping virtio-blk");
return 0;
}
- if (virtio_backend_register(&virtio_driver, &backend_client,
+ if (virtio_backend_register(&virtio_driver, &backend_client, push_portal,
&backend_device_id) != 0) {
log_line(LOG_PREFIX "backend registration failed - skipping virtio-blk");
return 0;
log_hex32(LOG_PREFIX "backend device id ", backend_device_id);
{
portal_handle_t namesvc = service_resolve("namesvc");
- if (namesvc != IPC_PORTAL_INVALID_HANDLE)
- (void)namesvc_register(namesvc, "virtio-blk", backend_portal,
+ if (namesvc != IPC_PORTAL_INVALID_HANDLE &&
+ push_portal != IPC_PORTAL_INVALID_HANDLE)
+ (void)namesvc_register(namesvc, "virtio-blk", push_portal,
IPC_PORTAL_RIGHT_SEND,
staged ? IPC_NAMESVC_FLAG_STAGED : 0);
}
return 0;
}
log_line(LOG_PREFIX "initialization complete, entering event loop");
- virtio_backend_loop(&virtio_driver, &backend_client, backend_device_id);
+ virtio_backend_loop(&virtio_driver, &backend_client, push_portal,
+ backend_portal, backend_device_id);
return 0;
}
blob - 42cc40383a9fb3a8cefc75783f0b6323379f73ea
blob + 0a6dee99c3b6771d6ca929b05651c132912a5564
--- servers/fs/ext2/main.c
+++ servers/fs/ext2/main.c
#define EXT2_SYMLINK_MAX_FOLLOW 8
/* Read-ahead cache configuration */
-#define EXT2_READAHEAD_ENABLE 1
+#define EXT2_READAHEAD_ENABLE 0
#define EXT2_READAHEAD_BLOCKS 8 /* Fetch 8 blocks (8KB for 1024-byte blocks) on cache miss */
#define EXT2_ENOENT (-2)
/* Forward declarations */
static int ext2_is_valid_block_no(uint32_t block);
+static uint64_t
+ext2_group_desc_offset(void)
+{
+ uint32_t block;
+
+ block = (ext2_ctx.block_size == 1024) ? 2U : 1U;
+ return (uint64_t)block * ext2_ctx.block_size;
+}
+
static int __attribute__((unused))
ext2_allocate_block(uint32_t *allocated_block_no)
{
static uint8_t bitmap_block_buf[EXT2_MAX_BLOCK_SIZE];
uint32_t bitmap_block_no;
uint32_t block_no;
+ uint32_t first_data_block_in_group;
+ uint32_t group_first_block;
+ uint32_t group_block_limit;
+ uint32_t inode_table_block;
+ uint32_t inode_table_blocks;
+ uint32_t inode_table_end;
+ uint32_t bitmap_bit;
+ uint64_t gd_offset;
size_t byte_offset;
size_t bit_offset;
int found = 0;
return -1;
}
- /* Hardcoded to group 0 for now - read group descriptor */
- /* Group descriptor table starts right after superblock */
- uint32_t gd_offset = 1024 + ext2_ctx.block_size;
+ /* Hardcoded to group 0 for now - read group descriptor. */
+ gd_offset = ext2_group_desc_offset();
#ifdef LENIX_DEBUG
- printf("[ext2] allocate_block: reading group descriptor from offset %u\n", gd_offset);
+ printf("[ext2] allocate_block: reading group descriptor from offset %llu\n",
+ (unsigned long long)gd_offset);
#endif
if (ext2_read_bytes(gd_offset, &group_desc, sizeof(struct ext2_group_desc)) != 0) {
printf("[ext2] allocate_block: read group descriptor failed\n");
/* Calculate first usable data block in this group */
/* For group 0, we need to skip: superblock, group descriptors,
* block bitmap, inode bitmap, and inode table */
- uint32_t first_data_block_in_group = 0;
-
/* Hardcoded to group 0 for now */
- uint32_t inode_table_block = group_desc.bg_inode_table;
+ inode_table_block = group_desc.bg_inode_table;
/* Handle invalid inode table location (same logic as read_inode) */
if (inode_table_block == 0 || inode_table_block > 1024)
inode_table_block = 36;
/* Inode table starts at bg_inode_table, calculate where it ends */
- uint32_t inode_table_blocks = (ext2_ctx.inodes_per_group * ext2_ctx.inode_size + ext2_ctx.block_size - 1) / ext2_ctx.block_size;
- uint32_t inode_table_end = inode_table_block + inode_table_blocks;
+ inode_table_blocks = (ext2_ctx.inodes_per_group * ext2_ctx.inode_size +
+ ext2_ctx.block_size - 1) / ext2_ctx.block_size;
+ inode_table_end = inode_table_block + inode_table_blocks;
/* Data blocks start after the inode table */
first_data_block_in_group = inode_table_end;
inode_table_block, inode_table_blocks, first_data_block_in_group);
#endif
- /* Scan bitmap for first free block (bit = 0) */
- for (uint32_t i = first_data_block_in_group; i < ext2_ctx.blocks_per_group; i++) {
- byte_offset = i / 8;
- bit_offset = i % 8;
+ group_first_block = ext2_ctx.first_data_block;
+ if (ext2_ctx.blocks_per_group > UINT32_MAX - group_first_block)
+ return -1;
+ group_block_limit = group_first_block + ext2_ctx.blocks_per_group;
+ if (group_block_limit > ext2_ctx.super.s_blocks_count)
+ group_block_limit = ext2_ctx.super.s_blocks_count;
+ if (first_data_block_in_group < group_first_block)
+ first_data_block_in_group = group_first_block;
+ /* Scan the group-0 bitmap for the first free data block. */
+ for (block_no = first_data_block_in_group;
+ block_no < group_block_limit; block_no++) {
+ bitmap_bit = block_no - group_first_block;
+ if (bitmap_bit >= ext2_ctx.block_size * 8U)
+ break;
+ byte_offset = bitmap_bit / 8;
+ bit_offset = bitmap_bit % 8;
+
/* Check if bit is free (0 = free) */
- if ((bitmap_block_buf[byte_offset] & (1 << bit_offset)) == 0) {
- /* Found free block */
- block_no = i; /* Block number within group */
+ if ((bitmap_block_buf[byte_offset] & (1U << bit_offset)) == 0) {
found = 1;
break;
}
printf("[ext2] allocate_block: found free block %u in group\n", block_no);
#endif
/* Mark block as used in bitmap (set bit to 1) */
- byte_offset = block_no / 8;
- bit_offset = block_no % 8;
- bitmap_block_buf[byte_offset] |= (1 << bit_offset);
+ bitmap_bit = block_no - group_first_block;
+ byte_offset = bitmap_bit / 8;
+ bit_offset = bitmap_bit % 8;
+ bitmap_block_buf[byte_offset] |= (1U << bit_offset);
/* Write bitmap back to disk */
if (ext2_write_block(bitmap_block_no, bitmap_block_buf) != 0) {
struct ext2_group_desc group_desc;
static uint8_t bitmap_block_buf[EXT2_MAX_BLOCK_SIZE];
uint32_t bitmap_block_no;
+ uint32_t bitmap_bit;
+ uint32_t group_first_block;
+ uint64_t gd_offset;
size_t byte_offset;
size_t bit_offset;
if (!ext2_is_valid_block_no(block_no))
return -1;
- /* Hardcoded to group 0 for now - read group descriptor */
- /* Group descriptor table starts right after superblock */
- uint32_t gd_offset = 1024 + ext2_ctx.block_size;
+ group_first_block = ext2_ctx.first_data_block;
+ if (block_no < group_first_block)
+ return -1;
+ bitmap_bit = block_no - group_first_block;
+ if (bitmap_bit >= ext2_ctx.blocks_per_group ||
+ bitmap_bit >= ext2_ctx.block_size * 8U)
+ return -1;
+
+ /* Hardcoded to group 0 for now - read group descriptor. */
+ gd_offset = ext2_group_desc_offset();
if (ext2_read_bytes(gd_offset, &group_desc, sizeof(struct ext2_group_desc)) != 0)
return -1;
return -1;
/* Mark block as free in bitmap (clear bit to 0) */
- byte_offset = block_no / 8;
- bit_offset = block_no % 8;
- bitmap_block_buf[byte_offset] &= ~(1 << bit_offset);
+ byte_offset = bitmap_bit / 8;
+ bit_offset = bitmap_bit % 8;
+ bitmap_block_buf[byte_offset] &= ~(1U << bit_offset);
/* Write bitmap back to disk */
if (ext2_write_block(bitmap_block_no, bitmap_block_buf) != 0)
struct ext2_group_desc group_desc;
uint8_t bitmap_block_buf[EXT2_MAX_BLOCK_SIZE];
uint32_t bitmap_block_no;
+ uint32_t bitmap_bit;
+ uint32_t first_inode;
uint32_t inode_no;
+ uint64_t gd_offset;
size_t byte_offset, bit_offset;
int found = 0;
if (allocated_inode_no == NULL)
return -1;
- /* Hardcoded to group 0 - read group descriptor */
- if (ext2_read_bytes(ext2_ctx.first_data_block * 512 + 32, &group_desc, 32) != 0)
+ /* Hardcoded to group 0 - read group descriptor. */
+ gd_offset = ext2_group_desc_offset();
+ if (ext2_read_bytes(gd_offset, &group_desc, sizeof(group_desc)) != 0)
return -1;
/* Get inode bitmap block number from group descriptor */
ext2_ctx.inodes_per_group);
#endif
- for (uint32_t i = 11; i < ext2_ctx.inodes_per_group; i++) {
- byte_offset = i / 8;
- bit_offset = i % 8;
+ first_inode = ext2_ctx.super.s_first_ino;
+ if (first_inode == 0)
+ first_inode = 11;
+ for (inode_no = first_inode;
+ inode_no <= ext2_ctx.inodes_per_group; inode_no++) {
+ bitmap_bit = inode_no - 1;
+ if (bitmap_bit >= ext2_ctx.block_size * 8U)
+ break;
+ byte_offset = bitmap_bit / 8;
+ bit_offset = bitmap_bit % 8;
/* Check if bit is free (0 = free) */
- if ((bitmap_block_buf[byte_offset] & (1 << bit_offset)) == 0) {
- /* Found free inode */
- inode_no = i;
+ if ((bitmap_block_buf[byte_offset] & (1U << bit_offset)) == 0) {
found = 1;
#ifdef LENIX_DEBUG
printf("[ext2] allocate_inode: allocated inode %u\n", inode_no);
}
/* Mark inode as used in bitmap (set bit to 1) */
- byte_offset = inode_no / 8;
- bit_offset = inode_no % 8;
- bitmap_block_buf[byte_offset] |= (1 << bit_offset);
+ bitmap_bit = inode_no - 1;
+ byte_offset = bitmap_bit / 8;
+ bit_offset = bitmap_bit % 8;
+ bitmap_block_buf[byte_offset] |= (1U << bit_offset);
/* Write bitmap back to disk */
if (ext2_write_block(bitmap_block_no, bitmap_block_buf) != 0)
group_desc.bg_free_inodes_count--;
/* Write updated group descriptor back */
- if (ext2_write_bytes(ext2_ctx.first_data_block * 512 + 32, &group_desc, 32) != 0)
+ if (ext2_write_bytes(gd_offset, &group_desc, sizeof(group_desc)) != 0)
return -1;
/* Update superblock free inode count */
struct ext2_group_desc group_desc;
uint8_t bitmap_block_buf[EXT2_MAX_BLOCK_SIZE];
uint32_t bitmap_block_no;
+ uint32_t bitmap_bit;
+ uint64_t gd_offset;
size_t byte_offset, bit_offset;
- if (inode_no == 0)
+ if (inode_no == 0 || inode_no > ext2_ctx.inodes_per_group)
return -1;
- /* Hardcoded to group 0 - read group descriptor */
- if (ext2_read_bytes(ext2_ctx.first_data_block * 512 + 32, &group_desc, 32) != 0)
+ /* Hardcoded to group 0 - read group descriptor. */
+ gd_offset = ext2_group_desc_offset();
+ if (ext2_read_bytes(gd_offset, &group_desc, sizeof(group_desc)) != 0)
return -1;
/* Get inode bitmap block number */
return -1;
/* Mark inode as free in bitmap (clear bit to 0) */
- byte_offset = inode_no / 8;
- bit_offset = inode_no % 8;
- bitmap_block_buf[byte_offset] &= ~(1 << bit_offset);
+ bitmap_bit = inode_no - 1;
+ if (bitmap_bit >= ext2_ctx.block_size * 8U)
+ return -1;
+ byte_offset = bitmap_bit / 8;
+ bit_offset = bitmap_bit % 8;
+ bitmap_block_buf[byte_offset] &= ~(1U << bit_offset);
/* Write bitmap back to disk */
if (ext2_write_block(bitmap_block_no, bitmap_block_buf) != 0)
group_desc.bg_free_inodes_count++;
/* Write updated group descriptor back */
- if (ext2_write_bytes(ext2_ctx.first_data_block * 512 + 32, &group_desc, 32) != 0)
+ if (ext2_write_bytes(gd_offset, &group_desc, sizeof(group_desc)) != 0)
return -1;
/* Update superblock free inode count */
blob - 14148c7225887861770d8f0479b579e062e3c919
blob + 75cc22a91f0f82517da9efc8b56391fe699e673c
--- servers/fs/vfs/main.c
+++ servers/fs/vfs/main.c
handle->path[0] = '\0';
#if VFS_READAHEAD_ENABLE
handle->logical_offset = 0;
+ handle->backend_offset = 0;
handle->cache_file_offset = -1;
handle->cache_valid = 0;
#endif
handle_table[i].flags = 0;
#if VFS_READAHEAD_ENABLE
handle_table[i].logical_offset = 0;
+ handle_table[i].backend_offset = 0;
handle_table[i].cache_file_offset = -1;
handle_table[i].cache_valid = 0;
#endif
goto done;
}
- /* Seek FS fd to logical position before reading */
- #ifdef LENIX_DEBUG
- printf("[vfs] seeking to pos=%lld\n", (long long)pos);
- #endif
- memset(&fs_request_buffer, 0, sizeof(fs_request_buffer));
- fs_request_buffer.opcode = IPC_FS_REQ_SEEK;
- fs_request_buffer.body.seek.fd = handle->fs_fd;
- fs_request_buffer.body.seek.offset = pos;
- fs_request_buffer.body.seek.whence = SEEK_SET;
-
- if (ipc_service_request_issue(&fs_client_buffer, &fs_request_buffer,
- sizeof(fs_request_buffer), &fs_response_buffer,
- sizeof(fs_response_buffer)) != 0 ||
- fs_response_buffer.opcode != IPC_FS_RESP_SEEK) {
+ /* Synchronize the backend only after cached data changed position. */
+ if (handle->backend_offset != pos) {
#ifdef LENIX_DEBUG
- printf("[vfs] seek failed: ipc_err or wrong opcode\n");
+ printf("[vfs] seeking to pos=%lld\n", (long long)pos);
#endif
- if (copied == 0) {
- resp.body.read.status = VFS_EIO;
- resp.body.read.data_len = 0;
- vfs_send_response(&resp);
- return;
+ memset(&fs_request_buffer, 0, sizeof(fs_request_buffer));
+ fs_request_buffer.opcode = IPC_FS_REQ_SEEK;
+ fs_request_buffer.body.seek.fd = handle->fs_fd;
+ fs_request_buffer.body.seek.offset = pos;
+ fs_request_buffer.body.seek.whence = SEEK_SET;
+
+ if (ipc_service_request_issue(&fs_client_buffer,
+ &fs_request_buffer, sizeof(fs_request_buffer),
+ &fs_response_buffer,
+ sizeof(fs_response_buffer)) != 0 ||
+ fs_response_buffer.opcode != IPC_FS_RESP_SEEK ||
+ fs_response_buffer.body.seek.status != 0) {
+ #ifdef LENIX_DEBUG
+ printf("[vfs] seek failed\n");
+ #endif
+ if (copied == 0) {
+ resp.body.read.status = VFS_EIO;
+ resp.body.read.data_len = 0;
+ vfs_send_response(&resp);
+ return;
+ }
+ goto done;
}
- goto done;
+ handle->backend_offset =
+ fs_response_buffer.body.seek.new_offset;
}
- #ifdef LENIX_DEBUG
- printf("[vfs] seek status=%d new_offset=%lld\n",
- fs_response_buffer.body.seek.status,
- (long long)fs_response_buffer.body.seek.new_offset);
- #endif
/* Fetch read-ahead data (up to VFS_READAHEAD_SIZE bytes total) */
size_t total_fetched = 0;
if (ipc_service_request_issue(&fs_client_buffer, &fs_request_buffer,
sizeof(fs_request_buffer), &fs_response_buffer,
sizeof(fs_response_buffer)) != 0 ||
- fs_response_buffer.opcode != IPC_FS_RESP_READ) {
+ fs_response_buffer.opcode != IPC_FS_RESP_READ ||
+ fs_response_buffer.body.read.status < 0) {
break;
}
size_t fetched = fs_response_buffer.body.read.data_len;
+ if (fetched > chunk)
+ fetched = chunk;
if (fetched == 0)
break; /* EOF */
memcpy(handle->cache + total_fetched, fs_response_buffer.body.read.data, fetched);
total_fetched += fetched;
+ handle->backend_offset += (int64_t)fetched;
if (fetched < chunk)
break; /* EOF or short read */
vfs_send_response(&resp);
return;
}
+ if (!handle->is_device && handle->backend_offset != pos) {
+ memset(&fs_request_buffer, 0, sizeof(fs_request_buffer));
+ memset(&fs_response_buffer, 0, sizeof(fs_response_buffer));
+ fs_request_buffer.opcode = IPC_FS_REQ_SEEK;
+ fs_request_buffer.body.seek.fd = handle->fs_fd;
+ fs_request_buffer.body.seek.offset = pos;
+ fs_request_buffer.body.seek.whence = SEEK_SET;
+ if (ipc_service_request_issue(&fs_client_buffer,
+ &fs_request_buffer, sizeof(fs_request_buffer),
+ &fs_response_buffer, sizeof(fs_response_buffer)) != 0 ||
+ fs_response_buffer.opcode != IPC_FS_RESP_SEEK ||
+ fs_response_buffer.body.seek.status != 0) {
+ resp.body.read.status = VFS_EIO;
+ resp.body.read.data_len = 0;
+ vfs_send_response(&resp);
+ return;
+ }
+ handle->backend_offset =
+ fs_response_buffer.body.seek.new_offset;
+ }
memset(&fs_request_buffer, 0, sizeof(fs_request_buffer));
+ memset(&fs_response_buffer, 0, sizeof(fs_response_buffer));
fs_request_buffer.opcode = IPC_FS_REQ_READ;
fs_request_buffer.body.read.fd = handle->fs_fd;
fs_request_buffer.body.read.length = (uint32_t)requested_len;
if (ipc_service_request_issue(&fs_client_buffer, &fs_request_buffer,
sizeof(fs_request_buffer), &fs_response_buffer,
sizeof(fs_response_buffer)) == 0 &&
- fs_response_buffer.opcode == IPC_FS_RESP_READ) {
+ fs_response_buffer.opcode == IPC_FS_RESP_READ &&
+ fs_response_buffer.body.read.status >= 0) {
copied = fs_response_buffer.body.read.data_len;
+ if (copied > requested_len)
+ copied = requested_len;
if (copied > 0) {
memcpy(resp.body.read.data, fs_response_buffer.body.read.data, copied);
pos += copied;
+ handle->backend_offset += (int64_t)copied;
}
}
}
return;
}
+#if VFS_READAHEAD_ENABLE
+ if (!handle->is_device &&
+ handle->backend_offset != handle->logical_offset) {
+ memset(&fs_request_buffer, 0, sizeof(fs_request_buffer));
+ memset(&fs_response_buffer, 0, sizeof(fs_response_buffer));
+ fs_request_buffer.opcode = IPC_FS_REQ_SEEK;
+ fs_request_buffer.body.seek.fd = handle->fs_fd;
+ fs_request_buffer.body.seek.offset = handle->logical_offset;
+ fs_request_buffer.body.seek.whence = SEEK_SET;
+ if (ipc_service_request_issue(&fs_client_buffer, &fs_request_buffer,
+ sizeof(fs_request_buffer), &fs_response_buffer,
+ sizeof(fs_response_buffer)) != 0 ||
+ fs_response_buffer.opcode != IPC_FS_RESP_SEEK ||
+ fs_response_buffer.body.seek.status != 0) {
+ resp.body.write.status = VFS_EIO;
+ resp.body.write.bytes_written = 0;
+ vfs_send_response(&resp);
+ return;
+ }
+ handle->backend_offset = fs_response_buffer.body.seek.new_offset;
+ }
+#endif
+
memset(&fs_request_buffer, 0, sizeof(fs_request_buffer));
memset(&fs_response_buffer, 0, sizeof(fs_response_buffer));
fs_request_buffer.opcode = IPC_FS_REQ_WRITE;
resp.body.write.status = fs_response_buffer.body.write.status;
resp.body.write.bytes_written =
fs_response_buffer.body.write.bytes_written;
+#if VFS_READAHEAD_ENABLE
+ handle->logical_offset += resp.body.write.bytes_written;
+ handle->backend_offset += resp.body.write.bytes_written;
+ handle->cache_valid = 0;
+ handle->cache_file_offset = -1;
+#endif
vfs_send_response(&resp);
}
static void
/* Update logical offset and invalidate cache */
if (fs_response_buffer.body.seek.status == 0) {
handle->logical_offset = fs_response_buffer.body.seek.new_offset;
+ handle->backend_offset = fs_response_buffer.body.seek.new_offset;
}
handle->cache_valid = 0;
handle->cache_file_offset = -1;
blob - 6f79491df66c2506b7c5ff8903e0f23e2013d0fe
blob + b9be4769786e986b1e739294a294fca099637c30
--- servers/fs/vfs/vfs_internal.h
+++ servers/fs/vfs/vfs_internal.h
#if VFS_READAHEAD_ENABLE
/* Read-ahead cache for sequential read optimization */
int64_t logical_offset; /* Logical file offset (what user sees) */
+ int64_t backend_offset; /* Current backend file descriptor offset */
int64_t cache_file_offset; /* File offset where cache[0] starts */
size_t cache_valid; /* Valid bytes in cache */
uint8_t cache[VFS_READAHEAD_SIZE]; /* Inline cache buffer */
blob - fd1d5d3b9ec10dea64a62257e135fdb8d5da7ef7
blob + 6aa5471e3b4fbd050e150cb8fce790a24eeab0a0
--- user/apps/Makefile
+++ user/apps/Makefile
# SPDX-License-Identifier: ISC
-APPS := hello sh ls devtest ioctltest killtest termtest socktest filetest forktest pipetest exectest dirtest cachetest bench_syscall real_read bench_fs_read bench_portal_pingpong
+APPS := hello sh ls devtest ioctltest killtest termtest socktest filetest forktest pipetest exectest dirtest cachetest bench_syscall real_read bench_fs_read bench_block_read bench_portal_pingpong
APP_SRCS_hello := hello/hello.c
APP_OBJS_hello := $(APP_SRCS_hello:%.c=$(BUILD_DIR)/user/apps/%.o)
APP_OBJS_bench_fs_read := $(BUILD_DIR)/user/apps/bench_fs_read.o
APP_ELF_bench_fs_read := $(BUILD_DIR)/user/apps/bench_fs_read.elf
APP_BIN_bench_fs_read := disk/ext2root/bin/bench_fs_read
+APP_SRCS_bench_block_read := ../bench/bench_block_read.c
+APP_OBJS_bench_block_read := $(BUILD_DIR)/user/apps/bench_block_read.o
+APP_ELF_bench_block_read := $(BUILD_DIR)/user/apps/bench_block_read.elf
+APP_BIN_bench_block_read := disk/ext2root/bin/bench_block_read
APP_SRCS_bench_portal_pingpong := ../bench/bench_portal_pingpong.c
APP_OBJS_bench_portal_pingpong := $(BUILD_DIR)/user/apps/bench_portal_pingpong.o
APP_ELF_bench_portal_pingpong := $(BUILD_DIR)/user/apps/bench_portal_pingpong.elf
all: apps
-apps: $(APP_ELF_hello) $(APP_ELF_sh) $(APP_ELF_ls) $(APP_ELF_devtest) $(APP_ELF_ioctltest) $(APP_ELF_killtest) $(APP_ELF_termtest) $(APP_ELF_socktest) $(APP_ELF_filetest) $(APP_ELF_forktest) $(APP_ELF_pipetest) $(APP_ELF_exectest) $(APP_ELF_dirtest) $(APP_ELF_cachetest) $(APP_ELF_bench_syscall) $(APP_ELF_real_read) $(APP_ELF_bench_fs_read) $(APP_ELF_bench_portal_pingpong)
+apps: $(APP_ELF_hello) $(APP_ELF_sh) $(APP_ELF_ls) $(APP_ELF_devtest) $(APP_ELF_ioctltest) $(APP_ELF_killtest) $(APP_ELF_termtest) $(APP_ELF_socktest) $(APP_ELF_filetest) $(APP_ELF_forktest) $(APP_ELF_pipetest) $(APP_ELF_exectest) $(APP_ELF_dirtest) $(APP_ELF_cachetest) $(APP_ELF_bench_syscall) $(APP_ELF_real_read) $(APP_ELF_bench_fs_read) $(APP_ELF_bench_block_read) $(APP_ELF_bench_portal_pingpong)
install: install-apps
-install-apps: $(APP_ELF_hello) $(APP_ELF_sh) $(APP_ELF_ls) $(APP_ELF_devtest) $(APP_ELF_ioctltest) $(APP_ELF_killtest) $(APP_ELF_termtest) $(APP_ELF_socktest) $(APP_ELF_filetest) $(APP_ELF_forktest) $(APP_ELF_pipetest) $(APP_ELF_exectest) $(APP_ELF_dirtest) $(APP_ELF_cachetest) $(APP_ELF_bench_syscall) $(APP_ELF_real_read) $(APP_ELF_bench_fs_read) $(APP_ELF_bench_portal_pingpong)
+install-apps: $(APP_ELF_hello) $(APP_ELF_sh) $(APP_ELF_ls) $(APP_ELF_devtest) $(APP_ELF_ioctltest) $(APP_ELF_killtest) $(APP_ELF_termtest) $(APP_ELF_socktest) $(APP_ELF_filetest) $(APP_ELF_forktest) $(APP_ELF_pipetest) $(APP_ELF_exectest) $(APP_ELF_dirtest) $(APP_ELF_cachetest) $(APP_ELF_bench_syscall) $(APP_ELF_real_read) $(APP_ELF_bench_fs_read) $(APP_ELF_bench_block_read) $(APP_ELF_bench_portal_pingpong)
@mkdir -p disk/ext2root/bin
cp $(APP_ELF_hello) $(APP_BIN_hello)
cp $(APP_ELF_sh) $(APP_BIN_sh)
cp $(APP_ELF_bench_syscall) $(APP_BIN_bench_syscall)
cp $(APP_ELF_real_read) $(APP_BIN_real_read)
cp $(APP_ELF_bench_fs_read) $(APP_BIN_bench_fs_read)
+ cp $(APP_ELF_bench_block_read) $(APP_BIN_bench_block_read)
cp $(APP_ELF_bench_portal_pingpong) $(APP_BIN_bench_portal_pingpong)
$(BUILD_DIR)/user/apps/%.o: user/apps/%.c
@mkdir -p $(dir $@)
- $(CC) $(USER_CFLAGS) -c $< -o $@
+ $(CC) $(USER_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@
$(BUILD_DIR)/user/apps/bench_%.o: user/bench/bench_%.c
@mkdir -p $(dir $@)
- $(CC) $(USER_CFLAGS) -c $< -o $@
+ $(CC) $(USER_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@
$(BUILD_DIR)/user/apps/%.o: user/bench/%.c
@mkdir -p $(dir $@)
- $(CC) $(USER_CFLAGS) -c $< -o $@
+ $(CC) $(USER_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@
$(APP_ELF_hello): $(APP_OBJS_hello)
@mkdir -p $(dir $@)
$(LD) $(USER_LDFLAGS) -o $@ $(APP_OBJS_bench_fs_read) $(RUNTIME_OBJS_GLOB); \
fi
+$(APP_ELF_bench_block_read): $(APP_OBJS_bench_block_read)
+ @mkdir -p $(dir $@)
+ @if [ -f $(CRT0_OBJ) ] && [ -f $(RUNTIME_ARCHIVE) ]; then \
+ $(LD) $(USER_LDFLAGS) -o $@ $(CRT0_OBJ) $(APP_OBJS_bench_block_read) $(RUNTIME_ARCHIVE); \
+ elif [ -f $(RUNTIME_ARCHIVE) ]; then \
+ $(LD) $(USER_LDFLAGS) -o $@ $(APP_OBJS_bench_block_read) $(RUNTIME_ARCHIVE); \
+ else \
+ $(LD) $(USER_LDFLAGS) -o $@ $(APP_OBJS_bench_block_read) $(RUNTIME_OBJS_GLOB); \
+ fi
+
$(APP_ELF_bench_portal_pingpong): $(APP_OBJS_bench_portal_pingpong)
@mkdir -p $(dir $@)
@if [ -f $(CRT0_OBJ) ] && [ -f $(RUNTIME_ARCHIVE) ]; then \
$(LD) $(USER_LDFLAGS) -o $@ $(APP_OBJS_bench_portal_pingpong) $(RUNTIME_OBJS_GLOB); \
fi
+-include $(foreach app,$(APPS),$(addsuffix .d,$(APP_OBJS_$(app))))
+
clean:
rm -rf $(BUILD_DIR)/user/apps
blob - 36faa8be80a45dd12b53f2a5de610daec03b9d23
blob + c0f7581e71ada08b4349c8168d7ece7fec2b0520
--- user/bench/README.md
+++ user/bench/README.md
-# Lenix Microkernel Performance Benchmarks
+# Lenix Benchmarks
-This directory contains a suite of microbenchmarks for measuring the impact of IPC and scheduler optimizations in the Lenix microkernel.
+These programs measure syscall, portal, block, and filesystem paths inside a
+running Lenix guest. They are small diagnostic tools, not a general benchmark
+suite. Compare results only when the build, QEMU command, debug settings, CPU
+mode, storage backend, and iteration counts are the same.
+## Table of Contents
+
+- [Benchmarks](#benchmarks)
+- [Build](#build)
+- [Run](#run)
+- [Compare Results](#compare-results)
+
## Benchmarks
-### bench_syscall
-**Purpose:** Measure raw syscall overhead
+### `bench_syscall`
-**What it does:**
-- Executes `getpid()` in a tight loop (100,000 iterations)
-- Measures total elapsed time and per-call cost
-- Provides baseline for syscall latency
+Runs five batches of one million calls for each of these syscalls:
-**Expected output:**
-```
-[bench_syscall] Starting syscall overhead benchmark
-[bench_syscall] Iterations: 100000
-[bench_syscall] Total time: XXXXX ns
-[bench_syscall] Per-syscall: XXX ns
-[bench_syscall] Throughput: XXXXXXX syscalls/sec
-```
+- `null_syscall()` for entry, dispatch, and return overhead
+- `getpid()` for a task lookup
+- `getuid()` for a credential lookup
-**Interpretation:**
-- Lower ns/syscall = better performance
-- Baseline for comparison with optimized IPC
+The program warms each path first, prints every batch, and reports the median
+latency. Lower nanoseconds per call and higher calls per second are better.
-### bench_fs_read
-**Purpose:** Measure filesystem read performance
+### `bench_block_read`
-**What it does:**
-- Creates a test file (10 KB)
-- Performs open/read/close cycles (1,000 iterations)
-- Measures total time and throughput
+Reads 1 MiB from a raw block device in 4 KiB requests for five batches. It
+checksums every batch and fails if the data changes between runs. The default
+device is 2; pass another device number as the first argument when needed.
-**Expected output:**
-```
-[bench_fs_read] Starting filesystem read benchmark
-[bench_fs_read] Iterations: 1000
-[bench_fs_read] Total time: XXXXX ns
-[bench_fs_read] Per-iteration: XXX ns
-[bench_fs_read] Total data read: XXXXXXX bytes
-[bench_fs_read] Throughput: XXXXXXXX bytes/sec
-```
+This test bypasses VFS and ext2, so it helps separate block-service overhead
+from filesystem overhead.
-**Interpretation:**
-- Higher throughput = better performance
-- Demonstrates batch processing efficiency
-- Lower per-iteration time = better caching/locality
+### `bench_fs_read`
-### bench_portal_pingpong
-**Purpose:** Measure portal round-trip latency
+Creates `/fstest`, writes and validates 64 KiB, then reads the full file ten
+times using a 4 KiB buffer. Only the repeated read phase is included in the
+throughput result. The active path is:
-**What it does:**
-- Sends request message to server
-- Server echoes back response
-- Measures round-trip time (10,000 iterations)
-- Demonstrates inline payload efficiency
-
-**Note:** Requires server spawning infrastructure (not yet fully integrated)
-
-**Expected output:**
+```text
+application -> syscall -> VFS -> ext2 -> blockd -> block device
```
-[bench_portal_pingpong] Starting portal round-trip benchmark
-[bench_portal_pingpong] Iterations: 10000
-[bench_portal_pingpong] Message size: 64 bytes
-[bench_portal_pingpong] Total time: XXXXX ns
-[bench_portal_pingpong] Per-RTT: XXX ns
-```
-**Interpretation:**
-- Lower ns/RTT = better performance
-- Shows impact of inline payloads
-- Demonstrates batch processing efficiency
+The benchmark fails on a short write, short read, unexpected EOF, or content
+mismatch.
-## Building and Installing
+### `bench_portal_pingpong`
-The benchmarks are now **fully integrated into the main Lenix build system** and are built alongside other userland applications.
+Measures 10,000 portal round trips with a 64-byte message. Its peer startup is
+not fully integrated, so treat it as development scaffolding until the guest
+can launch both sides reliably.
-### Integrated Build (Recommended)
-The benchmarks are automatically built and installed when you build the system:
+## Build
-```bash
-$ make install-apps # Builds and installs all userland apps including benchmarks
-$ # Benchmarks are now in disk/ext2root/bin/bench_*
-```
+The root build includes all benchmarks in the user application set:
-The benchmarks are also included in the disk image when you run:
-```bash
-$ make package-apps # Creates rootfs with all apps (including benchmarks)
+```sh
+make ARCH=x86_64 install-apps
+make ARCH=x86_64 iso esp
```
-## Running the Benchmarks
+The installed binaries are placed under `disk/ext2root/bin/` before the ext2
+image is generated.
-### Method 1: From shell within Lenix
-Once the system is booted, invoke the benchmarks directly:
+## Run
-```bash
-$ bench_syscall
-$ bench_fs_read
-$ bench_portal_pingpong # Framework (not yet fully integrated)
+Boot Lenix and run a benchmark from the shell:
+
+```text
+# bench_syscall
+# bench_block_read
+# bench_block_read 1
+# bench_fs_read
```
-### Method 2: As part of automated tests
-The benchmarks can be added to boot tests or startup scripts for automatic execution during system initialization (requires configuration in system init).
+Use `bench_block_read` before `bench_fs_read` when investigating storage. If the
+raw block result is slow, the bottleneck is below the filesystem. If raw block
+I/O is healthy but the filesystem result is slow, inspect VFS and ext2 request
+counts, cache behavior, and seek traffic.
-## Performance Expectations
+## Compare Results
-After implementing the optimizations:
+Record these details with every result:
-- **Inline Payloads**: 10-20% reduction in small message latency
-- **Batch Processing**: 15-40% improvement in server throughput
-- **CPU Locality**: 20-30% reduction in context switches during benchmark runs
+- commit ID and architecture
+- release or debug build
+- QEMU machine, accelerator, RAM, and vCPU arguments
+- Lenix SMP state
+- rootfs source and block device number
+- complete benchmark output
-## Before/After Comparison
+Run a correctness check before accepting a faster result. `bench_block_read`
+must report matching checksums, and `bench_fs_read` must report a successful
+64 KiB validation and the expected 655,360 timed bytes.
-To measure the impact of optimizations:
-
-1. **Build baseline**: Without optimizations
- ```
- $ make clean && make
- $ # Run benchmarks and record results
- ```
-
-2. **Build optimized**: With all optimizations enabled
- ```
- $ make clean && make
- $ # Run same benchmarks
- ```
-
-3. **Compare results**: Calculate improvement percentages
-
-## Tuning Parameters
-
-The following constants can be adjusted in the source code:
-
-### bench_syscall
-- `ITERATIONS` - Number of syscall invocations (default: 100,000)
-
-### bench_fs_read
-- `ITERATIONS` - Number of open/read/close cycles (default: 1,000)
-- `BUFFER_SIZE` - Read buffer size (default: 4,096 bytes)
-- `BENCH_FS_PATH` (env) - Optional override for the test file path. If unset,
- the benchmark tries `./benchfile`, then `/benchfile`, and finally known
- read-only fallbacks like `/dev/zero` and `/bin/sh` when creation is not
- permitted (streaming devices are capped to a small read size per iteration).
-
-### bench_portal_pingpong
-- `ITERATIONS` - Number of round trips (default: 10,000)
-- `MESSAGE_SIZE` - Request/response size (default: 64 bytes)
-
-## Design Notes
-
-### Syscall Benchmark
-- Uses `getpid()` as a trivial syscall
-- No side effects that would affect timing
-- Single-threaded for consistent results
-
-### Filesystem Benchmark
-- Uses `/tmp/benchfile` for reads
-- Creates fresh file each run
-- Reads entire file per iteration
-- Demonstrates IPC efficiency in context of real I/O
-
-### Portal Benchmark
-- Simulates client-server interaction
-- Measures full round-trip latency
-- 64-byte messages test inline payload path
-- Larger messages (>128B) would test external buffers
-
-## Contributing
-
-To add new benchmarks:
-1. Create `bench_NAME.c` in this directory
-2. Implement `main()` returning 0 on success
-3. Use standard Lenix APIs (unistd.h, time.h, stdio.h)
-4. Add output prefixed with `[bench_NAME]`
-5. Document in this README
-
-## References
-
-- Parent document: `PERFORMANCE_OPTIMIZATION_LOG.md`
-- Performance plan: `lenix_microkernel_perf_plan.md`
+The current measured reference configuration and results are recorded in the
+Performance Context section of `AI_CONTEXT.md`.
blob - 6a0a754703653f6c7041f7affb80153972eb6cc4
blob + 13a2a8c0f74b893bc50890a9078354edb3e3d7fc
--- user/bench/bench_fs_read.c
+++ user/bench/bench_fs_read.c
FS_SUMMARY("[bench_fs_read] Starting filesystem read benchmark\n");
FS_SUMMARY("[bench_fs_read] Iterations: %d\n", FS_ITERATIONS);
FS_SUMMARY("[bench_fs_read] Buffer size: %d bytes\n", FS_BUFSIZE);
+ FS_SUMMARY("[bench_fs_read] Backend: VFS -> ext2 -> blockd -> device 2\n");
/* Phase 1: create and populate the test file (untimed) */
FS_LOG("[bench_fs_read] Trying test file: %s\n", FS_TEST_PATH);
#if FS_BENCH_DIAG
FS_SUMMARY("[bench_fs_read] === DIAGNOSTIC BREAKDOWN ===\n");
+ uint64_t phase_ns = total_open_ns + total_read_ns + total_close_ns;
/* Use integer percentage (x10 for one decimal place) to avoid floating point */
- uint64_t open_pct = (bench_ns > 0) ? (1000ULL * total_open_ns / bench_ns) : 0;
- uint64_t read_pct = (bench_ns > 0) ? (1000ULL * total_read_ns / bench_ns) : 0;
- uint64_t close_pct = (bench_ns > 0) ? (1000ULL * total_close_ns / bench_ns) : 0;
+ uint64_t open_pct = (phase_ns > 0) ?
+ (1000ULL * total_open_ns / phase_ns) : 0;
+ uint64_t read_pct = (phase_ns > 0) ?
+ (1000ULL * total_read_ns / phase_ns) : 0;
+ uint64_t close_pct = (phase_ns > 0) ?
+ (1000ULL * total_close_ns / phase_ns) : 0;
FS_SUMMARY("[bench_fs_read] Total open(): %llu ns (%llu.%llu%%)\n",
(unsigned long long)total_open_ns,
(unsigned long long)(open_pct / 10),
(unsigned long long)total_close_ns,
(unsigned long long)(close_pct / 10),
(unsigned long long)(close_pct % 10));
- FS_SUMMARY("[bench_fs_read] Avg open() per iter: %llu ns (%llu ms)\n",
- (unsigned long long)(total_open_ns / FS_ITERATIONS),
- (unsigned long long)(total_open_ns / FS_ITERATIONS / 1000000));
+ FS_SUMMARY("[bench_fs_read] Open phase: %llu ns (%llu ms)\n",
+ (unsigned long long)total_open_ns,
+ (unsigned long long)(total_open_ns / 1000000));
FS_SUMMARY("[bench_fs_read] Avg read() per iter: %llu ns (%llu ms)\n",
(unsigned long long)(total_read_ns / FS_ITERATIONS),
(unsigned long long)(total_read_ns / FS_ITERATIONS / 1000000));
- FS_SUMMARY("[bench_fs_read] Avg close() per iter: %llu ns (%llu ms)\n",
- (unsigned long long)(total_close_ns / FS_ITERATIONS),
- (unsigned long long)(total_close_ns / FS_ITERATIONS / 1000000));
+ FS_SUMMARY("[bench_fs_read] Close phase: %llu ns (%llu ms)\n",
+ (unsigned long long)total_close_ns,
+ (unsigned long long)(total_close_ns / 1000000));
#endif
return 0;
blob - /dev/null
blob + a0663fef5ac4e8c683d9d76ecc9f6e6773e9362a (mode 644)
--- /dev/null
+++ user/bench/bench_block_read.c
+/* SPDX-License-Identifier: ISC */
+/* Lenix - Developed by lex0de (lex0de@tuta.com) */
+/* lenix/user/bench/bench_block_read.c */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <lenix/libc.h>
+#include <lenix/stdio.h>
+#include <lenix/time.h>
+#include <lenix/block.h>
+
+#define BENCH_BATCHES 5
+#define BENCH_BYTES (1024U * 1024U)
+#define BENCH_DEFAULT_DEVICE 2U
+#define BENCH_IO_SIZE 4096U
+
+static uint8_t io_buffer[BENCH_IO_SIZE];
+
+static uint64_t
+bench_checksum(const uint8_t *buffer, size_t length, uint64_t checksum)
+{
+ for (size_t i = 0; i < length; i++)
+ checksum = (checksum ^ buffer[i]) * 1099511628211ULL;
+ return checksum;
+}
+
+static uint64_t
+bench_elapsed_ns(const struct timespec *start, const struct timespec *end)
+{
+ return (uint64_t)(end->tv_sec - start->tv_sec) * 1000000000ULL +
+ (uint64_t)(end->tv_nsec - start->tv_nsec);
+}
+
+static void
+bench_sort(uint64_t samples[BENCH_BATCHES])
+{
+ for (size_t i = 1; i < BENCH_BATCHES; i++) {
+ uint64_t value = samples[i];
+ size_t pos = i;
+
+ while (pos > 0 && samples[pos - 1] > value) {
+ samples[pos] = samples[pos - 1];
+ pos--;
+ }
+ samples[pos] = value;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct ipc_block_info info;
+ uint64_t checksums[BENCH_BATCHES];
+ uint64_t samples[BENCH_BATCHES];
+ uint64_t blocks_per_batch;
+ uint32_t blocks_per_io;
+ uint32_t device = BENCH_DEFAULT_DEVICE;
+
+ if (argc > 1) {
+ int parsed = atoi(argv[1]);
+
+ if (parsed < 0 || (uint32_t)parsed >= IPC_BLOCK_MAX_DEVICES) {
+ puts("[bench_block_read] ERROR: invalid device");
+ return 1;
+ }
+ device = (uint32_t)parsed;
+ }
+ if (block_get_info(device, &info) != 0) {
+ puts("[bench_block_read] ERROR: block_get_info failed");
+ return 1;
+ }
+ if (info.block_size == 0 || info.block_size > BENCH_IO_SIZE ||
+ BENCH_IO_SIZE % info.block_size != 0) {
+ puts("[bench_block_read] ERROR: unsupported block size");
+ return 1;
+ }
+ blocks_per_io = BENCH_IO_SIZE / info.block_size;
+ blocks_per_batch = BENCH_BYTES / info.block_size;
+ if (blocks_per_batch == 0 || info.block_count < blocks_per_batch) {
+ puts("[bench_block_read] ERROR: device is too small");
+ return 1;
+ }
+
+ printf("[bench_block_read] Device: %u\n", device);
+ printf("[bench_block_read] Block size: %u bytes\n", info.block_size);
+ printf("[bench_block_read] Bytes per batch: %u\n", BENCH_BYTES);
+ for (size_t batch = 0; batch < BENCH_BATCHES; batch++) {
+ struct timespec start;
+ struct timespec end;
+ uint64_t checksum = 1469598103934665603ULL;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) {
+ puts("[bench_block_read] ERROR: benchmark timer failed");
+ return 1;
+ }
+ for (uint64_t lba = 0; lba < blocks_per_batch;
+ lba += blocks_per_io) {
+ ssize_t received;
+
+ received = block_read(device, lba, io_buffer,
+ blocks_per_io);
+ if (received != (ssize_t)sizeof(io_buffer)) {
+ printf("[bench_block_read] ERROR: read failed at LBA "
+ "%llu\n", (unsigned long long)lba);
+ return 1;
+ }
+ checksum = bench_checksum(io_buffer, sizeof(io_buffer),
+ checksum);
+ }
+ if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) {
+ puts("[bench_block_read] ERROR: benchmark timer failed");
+ return 1;
+ }
+ samples[batch] = bench_elapsed_ns(&start, &end);
+ checksums[batch] = checksum;
+ printf("[bench_block_read] Batch %zu: %llu ns, %llu "
+ "bytes/sec, checksum=%llu\n", batch + 1,
+ (unsigned long long)samples[batch],
+ (unsigned long long)(BENCH_BYTES * 1000000000ULL /
+ samples[batch]), (unsigned long long)checksum);
+ }
+ for (size_t batch = 1; batch < BENCH_BATCHES; batch++) {
+ if (checksums[batch] != checksums[0]) {
+ puts("[bench_block_read] ERROR: checksum mismatch");
+ return 1;
+ }
+ }
+ bench_sort(samples);
+ printf("[bench_block_read] Median throughput: %llu bytes/sec\n",
+ (unsigned long long)(BENCH_BYTES * 1000000000ULL /
+ samples[BENCH_BATCHES / 2]));
+ return 0;
+}
blob - 109184356b181a1709846caa73074c995200add3
blob + 2717c5d96798d6d6e3d116890125374b23874f26
--- user/bench/bench_syscall.c
+++ user/bench/bench_syscall.c
/* SPDX-License-Identifier: ISC */
-/*
- * Benchmark: Raw syscall overhead
- * Measures the cost of a trivial syscall in a tight loop
- */
+/* Benchmark raw null, identity, and credential syscall overhead. */
-#include <lenix/unistd.h>
-#include <lenix/time.h>
-#include <lenix/stdio.h>
+#include <stddef.h>
#include <stdint.h>
-#define ITERATIONS 1000000
-#define WARMUP_ITERATIONS 1000
+#include <lenix/stdio.h>
+#include <lenix/time.h>
+#include <lenix/unistd.h>
-/* Minimal syscall wrapper - prevents compiler optimization */
+#define BENCH_BATCHES 5
+#define BENCH_ITERATIONS 1000000
+#define BENCH_WARMUP 1000
+
+typedef long (*bench_call_t)(void);
+
static long __attribute__((noinline))
-lenix_bench_null_syscall(void)
+bench_getpid(void)
{
+ return getpid();
+}
+
+static long __attribute__((noinline))
+bench_getuid(void)
+{
+ return (long)getuid();
+}
+
+static long __attribute__((noinline))
+bench_null(void)
+{
return null_syscall();
}
+static uint64_t
+bench_elapsed_ns(const struct timespec *start, const struct timespec *end)
+{
+ return (uint64_t)(end->tv_sec - start->tv_sec) * 1000000000ULL +
+ (uint64_t)(end->tv_nsec - start->tv_nsec);
+}
+
+static void
+bench_sort(uint64_t samples[BENCH_BATCHES])
+{
+ for (size_t i = 1; i < BENCH_BATCHES; i++) {
+ uint64_t value = samples[i];
+ size_t pos = i;
+
+ while (pos > 0 && samples[pos - 1] > value) {
+ samples[pos] = samples[pos - 1];
+ pos--;
+ }
+ samples[pos] = value;
+ }
+}
+
+static int
+bench_run(const char *name, bench_call_t call, uint64_t *median_out)
+{
+ struct timespec start;
+ struct timespec end;
+ uint64_t samples[BENCH_BATCHES];
+ volatile long sink = 0;
+
+ for (int i = 0; i < BENCH_WARMUP; i++)
+ sink = call();
+ for (size_t batch = 0; batch < BENCH_BATCHES; batch++) {
+ if (clock_gettime(CLOCK_MONOTONIC, &start) != 0)
+ return -1;
+ for (int i = 0; i < BENCH_ITERATIONS; i++)
+ sink = call();
+ if (clock_gettime(CLOCK_MONOTONIC, &end) != 0)
+ return -1;
+ samples[batch] = bench_elapsed_ns(&start, &end) /
+ BENCH_ITERATIONS;
+ printf("[bench_syscall] %s batch %zu: %llu ns/call\n", name,
+ batch + 1, (unsigned long long)samples[batch]);
+ }
+ bench_sort(samples);
+ *median_out = samples[BENCH_BATCHES / 2];
+ printf("[bench_syscall] %s median: %llu ns/call\n", name,
+ (unsigned long long)*median_out);
+ (void)sink;
+ return 0;
+}
+
int
main(void)
{
- struct timespec start, end;
- long ns_per_call;
- uint64_t elapsed_ns, throughput;
- volatile long sink = 0; /* Prevents compiler optimization */
- #ifdef LENIX_DEBUG
- puts("[bench_syscall] Starting syscall overhead benchmark");
- printf("[bench_syscall] Iterations: %d\n", ITERATIONS);
- #endif
- /* Warm up - reduce cold-start cache effects */
- for (int i = 0; i < WARMUP_ITERATIONS; i++) {
- sink = lenix_bench_null_syscall();
- }
+ uint64_t null_median;
+ uint64_t getpid_median;
+ uint64_t getuid_median;
- /* Get starting time */
- if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) {
- puts("[bench_syscall] ERROR: clock_gettime failed");
+ printf("[bench_syscall] Batches: %d\n", BENCH_BATCHES);
+ printf("[bench_syscall] Iterations per batch: %d\n",
+ BENCH_ITERATIONS);
+ if (bench_run("null", bench_null, &null_median) != 0 ||
+ bench_run("getpid", bench_getpid, &getpid_median) != 0 ||
+ bench_run("getuid", bench_getuid, &getuid_median) != 0) {
+ puts("[bench_syscall] ERROR: benchmark timer failed");
return 1;
}
-
- /* Tight loop calling minimal syscall - timer NOT inside loop */
- for (int i = 0; i < ITERATIONS; i++) {
- sink = lenix_bench_null_syscall();
- }
-
- /* Get ending time */
- if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) {
- puts("[bench_syscall] ERROR: clock_gettime failed");
- return 1;
- }
-
- /* Calculate elapsed time in nanoseconds */
- elapsed_ns = (end.tv_sec - start.tv_sec) * 1000000000ULL +
- (end.tv_nsec - start.tv_nsec);
- if (elapsed_ns == 0) {
- puts("[bench_syscall] ERROR: elapsed time is zero");
- return 1;
- }
-
- ns_per_call = (long)(elapsed_ns / ITERATIONS);
-
- /* Calculate throughput: iterations per second (64-bit to avoid overflow) */
- throughput = (ITERATIONS * 1000000000ULL) / elapsed_ns;
-
- printf("[bench_syscall] Total time: %llu ns\n",
- (unsigned long long)elapsed_ns);
- printf("[bench_syscall] Per-syscall: %ld ns\n", ns_per_call);
+ printf("[bench_syscall] Per-syscall: %llu ns\n",
+ (unsigned long long)null_median);
printf("[bench_syscall] Throughput: %llu syscalls/sec\n",
- (unsigned long long)throughput);
-
- /* Use sink to prevent optimization */
- (void)sink;
-
+ (unsigned long long)(1000000000ULL / null_median));
return 0;
}
blob - d8adb60bda2a7e0ba52bfdb3c8882fcea05feb3d
blob + b58643a9dd143e93bbbfeba77932f4ca855fbf3d
--- user/runtime/include/sys/syscall.h
+++ user/runtime/include/sys/syscall.h
#define SYS_nanosleep 574U
#define SYS_getdents 575U
#define SYS_fstatat 576U
-#define SYS_getcwd 590U
-#define SYS_chdir 591U
+#define SYS_getcwd 619U
+#define SYS_chdir 620U
#define SYS_fchdir 592U
#define SYS_getppid 593U
#define SYS_unlinkat 594U
#define SYS_ftruncate 595U
#define SYS_lstat 596U
#define SYS_null 600U /* Null syscall for performance measurement (does nothing) */
-#define SYS_pty_get_previous 601U
+#define SYS_pty_get_previous 621U
#define SYS_srv_stage_start 602U
#define SYS_srv_stage_commit 603U
#define SYS_srv_stage_abort 604U
#define SYS_getrlimit 616U
#define SYS_setrlimit 617U
#define SYS_getrusage 618U
+#define SYS_MAX 621U
struct syscall_result {
int64_t value;