commit 11244300a5210268239c9b0962a5886da0a15d0a from: lex0de date: Mon Aug 31 16:35:25 2026 UTC Fix x86 boot and toolchain setup commit - bb549fa703c9691eb1437b83266b9853a7c4c1f2 commit + 11244300a5210268239c9b0962a5886da0a15d0a blob - /dev/null blob + 6f743a8441c62c158d86795fa21f5e40f32d2398 (mode 644) --- /dev/null +++ AI_CONTEXT.md @@ -0,0 +1,556 @@ +# Lenix AI Context + +This file is the operational context for Lenix development, administration, +and testing. It reflects the repository tree inspected on 2026-08-31. Verify +volatile facts against current code and build output before relying on them. + +## Identity and Goals + +Lenix is a work-in-progress microkernel and Unix-like operating system written +primarily in freestanding C and assembly. The design targets POSIX.1-2017 and +UNIX V7 behavior, with later POSIX.1-2024 expansion. Its long-term goals include +capability-oriented IPC, user-mode system services and drivers, asynchronous +I/O, security policy and audit, SMP, and distributed service routing. + +Naming in the tree is transitional: + +- Lenix is the project and kernel name. +- Mercuron is the target userland/toolchain OS name. +- Kernel target triple: `x86_64-unknown-lenix`. +- Userland target triple: `x86_64-unknown-mercuron`. + +Do not rename either ABI or triple as incidental cleanup. + +## Reliability of Project Information + +Use the authority order in `AGENTS.md`. The most important conflicts found in +the current documentation are: + +- `README.md` warns that parts of the documentation are outdated. +- `docs/boot.md` describes an early kernel that halts after `kmain`; current + `kernel/kmain.c` initializes services, init, timer, and scheduler paths. +- `docs/architecture/overview.md` shows all packaged servers launching, but + current `kernel/kmain.c` comments out PCI and virtio-blk launch calls and has + no virtio-net launch call. +- `README.md` and older architecture text discuss embedded server fallback; + current packaging stages servers in rootfs/initrd, while the exact loader + fallback behavior must be checked in each `kernel/user/*_server.c` path. +- `docs/posix-gap.md` says pthreads/futex-style primitives are absent, while + newer `TODO.md` items and current runtime/kernel files contain clone, TLS, + futex, and thread scaffolding. The uncompleted musl smoke test means full + pthread compatibility is not established. +- `roadmap.md` and `docs/arm64-update.md` contain completed and proposed states + that no longer match every Make target. Treat their unchecked work as plans. +- `docs/lenix_analysis.md` and `docs/scheduler_analysis.md` are detailed + historical analyses. Reproduce their findings before using them as current + root causes. +- LLVM phase documents include old absolute paths and manual build steps. + Prefer current port scripts and `GNUmakefile` integration. + +## Current Risk Baseline + +The repository itself identifies these major risks: + +- Intermittent boot and IPC/VFS timing failures. +- Intermittent hangs. +- ARM64 lag and incomplete userland/package parity. +- SMP instability. SMP is disabled unless `DEBUG_SMP=1` defines + `LENIX_DEBUG_SMP`. +- Incomplete POSIX filesystem semantics, metadata, symlinks, `*at()` behavior, + permissions, timers, synchronization, utilities, and conformance CI. +- Incomplete distributed IPC, security policy enforcement, device namespace, + network integration, and kernel slimming. + +Resolved `TODO.audit/` records document important invariants already added: +portal allocation synchronization, IPC packet sizing, portal ownership and +rights checks, path parent-reference rejection, W^X kernel mappings, bounded +portal allocations, centralized syscall audit hooks, PTY activation checks, +console portal validation, queue-drop counters, serial-init idempotence, and +runtime ABI stack alignment. Verify these invariants remain intact when touching +their code. + +## Architecture + +### Kernel Responsibilities + +The kernel retains mechanisms required for: + +- Multiboot/UEFI/DT boot information and early console. +- Physical and virtual memory, address spaces, user mappings, DMA mediation, + and W^X enforcement. +- Per-task and per-CPU scheduling, timers, traps, signals, and context switch + state. +- Mailboxes, capability-style portals, request/response matching, and remote + transport placeholders. +- Syscall validation and mediation. +- Low-level console/PTY mechanisms, audit/policy scaffolding, diagnostics, and + server launch glue. + +The kernel must not absorb filesystem policy, name lookup, block registries, +socket personalities, or ordinary application logic. + +### User-Mode Services + +| Service | Source | Namesvc name | Role | +|--------------|---------------------------------|--------------|-------------------------------------------| +| namesvc | `servers/namesvc/main.c` | `namesvc` | Capability-bearing service directory | +| vfs | `servers/fs/vfs/` | `vfs` | Mounts, paths, handles, backend routing | +| ext2 | `servers/fs/ext2/main.c` | `fs.ext2` | Ext2 filesystem backend | +| tmpfs | `servers/fs/tmpfs/main.c` | `fs.tmpfs` | Memory filesystem backend | +| blockd | `servers/block/blockd/main.c` | `blockd` | Block registry and request broker | +| ramdiskd | `servers/block/ramdiskd/main.c` | `ramdiskd` | Loader/initrd rootfs block backend | +| virtio-blk | `servers/block/virtio-blk/` | `virtio-blk` | PCI/DMA virtio block backend | +| pci | `servers/drivers/pci/main.c` | `pci` | User-mode PCI enumeration service | +| netd | `servers/net/netd/main.c` | `netd` | Network coordinator and packet queues | +| sock.tcp | `servers/net/sock.tcp/main.c` | `sock.tcp` | TCP socket personality | +| sock.udp | `servers/net/sock.udp/main.c` | `sock.udp` | UDP socket personality | +| virtio-net | `servers/net/virtio-net/main.c` | not verified | Virtio network device service | +| ttyd | `servers/console/ttyd/main.c` | `ttyd` | User-mode terminal handling | +| ptyctl | `servers/console/ptyctl/main.c` | `ptyctl` | PTY supervision and foreground switching | + +`tty.console` is resolved by init as the console-facing service name. Confirm +its registration path before changing console startup. + +### Verified Startup Wiring + +Current `kernel/kmain.c` prepares and launches this sequence after core memory, +scheduler, registry, and console setup: + +1. namesvc +2. blockd +3. ramdiskd +4. fs.ext2 +5. fs.tmpfs +6. vfs +7. netd +8. sock.udp +9. sock.tcp +10. ttyd +11. ptyctl +12. init + +PCI and virtio-blk launch blocks are commented out. The standalone demo launch +is also commented out. All three binaries can still be built and packaged, so +do not equate package contents with boot-time execution. + +Init waits for namesvc, VFS, blockd, and ext2, mounts `/` through VFS, opens the +console when available, and uses `posix_spawnp()` to start `/bin/sh`. + +### IPC Model + +- Every scheduler task owns a mailbox. +- Portals wrap mailbox endpoints with opaque handles, owner/target state, + rights, labels, CPU hints, and remote-routing metadata. +- User tasks use portal syscalls and service request/response helpers. +- `service_resolve()` is the required path for named services. +- Kernel code only bootstraps namesvc and mediates IPC. It must not become a + general name registry. +- IPC messages are bounded. Exact request and response structure size and + semantic fields must be validated at both kernel and server boundaries. +- Pending service requests use portal/token matching. Current documentation + describes a 256-slot registry, 32-bit tokens with zero invalid, and response + payloads bounded to 512 bytes; verify these constants before changing limits. +- Remote node IDs and transport hooks are scaffolding. Off-machine routing is + not complete. + +### Filesystem and Block Flow + +The intended path is: + +```text +application + -> syscall and per-task file descriptor table + -> kernel VFS IPC shim + -> vfs mount/path/handle routing + -> fs.ext2 or fs.tmpfs + -> blockd for block-backed I/O + -> ramdiskd or virtio-blk + -> loader image or device +``` + +VFS supports mount routing and core open, close, read, write, lseek, stat, +fstat, dup, fcntl, poll, and pipe message shapes. Do not assume complete POSIX +semantics. Vnode LRU, symlinks, per-process namespaces, union/NFS support, file +descriptor passing, and VFS statistics remain documented gaps. + +The source root filesystem tree is `disk/ext2root/`. Packaging produces a +16 MiB ext2 image, stages server and app binaries, and writes a SHA-256 manifest +under `/usr/libexec/lenix/manifest.sha256`. + +### Console and Administration + +The physical serial console is mediated by kernel console/PTY code and user-mode +`ttyd`/`ptyctl`. PTY ownership and foreground activation are security +boundaries. + +Verified documented console commands: + +| Command | Purpose | +|--------------------------|----------------------------------------------| +| `help` | List kernel console commands | +| `status` | Show basic live status | +| `ticks` | Show timer heartbeat counters | +| `cpustats` | Show per-CPU scheduler state | +| `perf` | Show scheduler, IPC, and FS counters | +| `blocktrace` | Dump the last 32 block operations | +| `blocktrace clear` | Reset the block trace before reproduction | +| `diag status` | Show diagnostic toggles | +| `diag audit on|off` | Toggle syscall and IPC audit logging | +| `diag policy on|off` | Toggle policy diagnostics | +| `diag telemetry on|off` | Toggle periodic task telemetry | +| `ptys` | Show PTY state | + +Ctrl+N foreground PTY cycling is documented. Confirm current terminal mode and +ownership before treating keyboard input as lost. A guest waiting for input is +not necessarily hung. + +ARM64 debug runs accept boot parameters such as `audit=on`, `policy=on`, +`telemetry=on`, and `deadlock_detect=on`. Current x86_64 QEMU targets do not +pass `-append`; use bootloader configuration if parameters must be tested there. + +## Architectures and Boot Paths + +### x86_64 + +x86_64 is the reference and only architecture with current UEFI ESP and GRUB +ISO targets. + +- UEFI: OVMF loads `build/EFI/BOOT/BOOTX64.EFI`, which loads + `EFI/BOOT/LENIX.ELF`, rootfs, and initrd artifacts. +- Legacy: GRUB loads `build/x86_64/kernel.elf`, `rootfs.img`, and + `initrd.cpio` from `build/x86_64/lenix.iso`. +- QEMU reference: q35, 2 GiB RAM, 2 vCPUs, serial stdio, virtio-blk attachment. +- Physical reference: Dell OptiPlex 7020 using legacy/CSM and COM1. Its recorded + AP bring-up is unstable, so SMP remained disabled for that hardware capture. + +### arm64 + +ARM64 uses QEMU `virt`, Cortex-A72, PL011 at `0x09000000`, DTB in `x0`, and a +direct `-kernel build/arm64/kernel.elf` boot. The current `run-arm64` target does +not attach initrd or rootfs. Plans for per-architecture rootfs/initrd, cross +toolchain selection, and userland parity are not completed. + +### SMP + +SMP mechanisms exist for LAPIC/ACPI/MP-table x86_64 and PSCI/GIC arm64 paths, +per-CPU run queues, barriers, IPIs, and portal CPU hints. Current `kmain.c` +enables AP boot only for builds with `DEBUG_SMP=1`. Treat SMP as an explicit +test mode, not the production default. + +## Toolchain + +The pinned compiler is LLVM/Clang 21.1.8. The integrated Mercuron toolchain is +expected at `/opt/llvm-mercuron`, with target sysroot at +`third-party/build/sysroot`. + +```sh +export TOOLCHAIN_ROOT=/opt/llvm-mercuron +export MERCURON_SYSROOT=$PWD/third-party/build/sysroot +export HOST_TRIPLE=x86_64-unknown-mercuron +export KERNEL_TRIPLE=x86_64-unknown-lenix +export PATH="$TOOLCHAIN_ROOT/bin:$MERCURON_SYSROOT/usr/bin:$PATH" +``` + +Important separation: + +- Kernel: freestanding, target `x86_64-unknown-lenix`, no sysroot or musl. +- User runtime, apps, and servers: target `x86_64-unknown-mercuron`, optionally + using `MERCURON_SYSROOT`. +- Host generators: host compiler and interpreter, never the target compiler. + +Current port versions documented by the tree are LLVM 21.1.8, musl 1.2.5, +zlib 1.3.1, and LibreSSL 4.2.1. Port status is not established solely by a +present build script or an old phase checkbox. Verify the installed sysroot and +run the relevant compile/link test. + +On Ubuntu 26.04 amd64, users run `sudo ./setup.sh` to install host dependencies, +then `sudo ./build-toolchain.sh` to build and install `/opt/llvm-mercuron`. +Agents must not run either privileged command. + +## Build Commands + +The top-level `Makefile` delegates to `GNUmakefile`. Use `make` on GNU systems +or `gmake` where the system `make` is not GNU make. + +### Standard x86_64 Build + +```sh +make clean +make ARCH=x86_64 -j4 +make ARCH=x86_64 package-runtime +make ARCH=x86_64 package-apps +make ARCH=x86_64 initrd +make ARCH=x86_64 esp +make ARCH=x86_64 efi +make ARCH=x86_64 iso +``` + +`./rebuild.sh` runs the integrated toolchain form of this sequence. +`./rebuild-debug.sh` adds debug, vnode-cache, and exit diagnostics. +`./rebuild-smp.sh` and `./rebuild-debug-smp.sh` enable SMP diagnostics. +TFTP variants copy final artifacts to `/srv/tftp`; do not run them unless that +external write is explicitly requested. + +### Integrated Mercuron Toolchain + +```sh +make ARCH=x86_64 USE_MERCURON_TOOLCHAIN=1 \ + TOOLCHAIN_ROOT=/opt/llvm-mercuron \ + MERCURON_SYSROOT="$PWD/third-party/build/sysroot" +``` + +### ARM64 Kernel + +```sh +make ARCH=arm64 legacy +make ARCH=arm64 run-arm64 +``` + +This validates the direct kernel path only. It does not prove packaged ARM64 +userland parity. + +### Runtime, musl, and Images + +```sh +make ARCH=x86_64 package-runtime +make ARCH=x86_64 test-sysroot +make musl USE_MERCURON_TOOLCHAIN=1 \ + MERCURON_SYSROOT="$PWD/third-party/build/sysroot" \ + TOOLCHAIN_ROOT=/opt/llvm-mercuron +make ARCH=x86_64 initrd +make ARCH=x86_64 package-apps +``` + +Set `PACKAGE_MUSL=0` to omit musl. Set `INCLUDE_MUSL_HEADERS=0` to stage its +loader/libraries without headers. + +## Build Artifacts + +| Artifact | Meaning | +|---------------------------------------|------------------------------------------| +| `build/x86_64/kernel.elf` | x86_64 legacy kernel ELF | +| `build/x86_64/lenix.efi` | x86_64 UEFI application | +| `build/x86_64/lenix.iso` | GRUB boot ISO | +| `build/arm64/kernel.elf` | ARM64 direct-boot kernel | +| `build/rootfs.ext2` | Shared packaged ext2 root image | +| `build/initrd.cpio` | Shared newc initrd | +| `build/EFI/BOOT/BOOTX64.EFI` | Staged UEFI boot application | +| `build/EFI/BOOT/LENIX.ELF` | Staged UEFI kernel ELF | +| `build/EFI/BOOT/ROOTFS.IMG` | Staged UEFI root image | +| `build/EFI/BOOT/INITRD.CPIO` | Staged UEFI initrd | +| `build/rootfs.manifest` | Staged rootfs SHA-256 | +| `build/rootfs.iso.manifest` | ISO rootfs SHA-256 | +| `build/sysroot//` | Packaged minimal runtime sysroot | +| `third-party/build/sysroot/` | Canonical ports and musl target sysroot | + +The current rootfs and initrd paths are shared outside `build/`, so mixed +architecture builds can clobber packaging inputs. The per-arch packaging layout +in `docs/arm64-update.md` is still a plan. + +## Boot and Run Commands + +```sh +make ARCH=x86_64 run-uefi +make ARCH=x86_64 run-legacy +make ARCH=arm64 run-arm64 +make ARCH=arm64 run-debug-arm64 \ + PARAMS="audit=on policy=on telemetry=on deadlock_detect=on" +``` + +Expected top-level logs are `boot-qemu-x86_64-uefi.log` and +`boot-qemu-x86_64-legacy.log`. Harness logs belong under `build/test-logs/` or +`build/ext2-harness/`. + +## Test Matrix + +| Scope | Command | Required evidence | +|-------------------------------|----------------------------------|------------------------------------------------| +| Compile, selected arch | `make ARCH= legacy` | Successful compile and link | +| Runtime sysroot compile/link | `make ARCH= test-sysroot` | Freestanding hello objects and static ELF link | +| Static analysis/style | `make lint` | Tools ran on a non-empty tracked-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 | +| 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 | +| Repeated ext2 boot smoke | `scripts/ext2_harness.sh` | Same markers for each UEFI and legacy run | +| Repeated UEFI stability | `scripts/run_boot_tests.sh` | `init: shell` in five captured boots | +| Performance | guest `bench_*` programs | Raw results with identical test configuration | + +### Harness Caveats Verified in the Tree + +- `scripts/test_sanity.sh` runs x86_64 legacy and ARM64. Its UEFI function is + commented out. It builds `legacy` but expects an existing x86_64 ISO, and it + treats an ARM64 compile failure as a skip. +- `scripts/test_ext2_poll.sh` invokes `make run-uefi` without an outer timeout. + Its legacy QEMU timeout is accepted only after marker checks. +- `scripts/ext2_harness.sh` treats QEMU timeout status 124 as failure, even + 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_.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. + +Never report a skipped architecture, absent tool, timeout, or empty lint file +list as a pass. + +### Change-to-Test Mapping + +| Changed area | Minimum focused validation | +|-------------------------------|---------------------------------------------------------| +| Kernel common C/header | x86_64 and ARM64 compile; relevant boot smoke | +| x86_64 boot/MM/trap | x86_64 legacy and UEFI build; affected boot path | +| ARM64 arch code | ARM64 compile and direct QEMU boot | +| IPC/syscall ABI | Both arches compile; client/server test; malformed input| +| Scheduler/SMP | Single-core baseline and `DEBUG_SMP=1` run | +| VFS/ext2/block/initrd/rootfs | UEFI and legacy ext2/poll markers | +| Namesvc/server startup | Missing-service failure plus normal init/shell boot | +| Console/PTY/signals | Interactive input, PTY ownership, raw/canonical modes | +| Runtime headers/startup/TLS | `test-sysroot`; affected app and QEMU smoke | +| Toolchain/port | Target triple, compile, link, sysroot install checks | +| Performance path | Correctness tests first, then same-config benchmark | + +## POSIX and musl Status + +Implemented or scaffolded surfaces include core file descriptors, VFS-routed +file calls, directory reads, basic process/spawn/wait and signal/job-control +work, time calls, poll, sockets, PTY/termios, environment functions, clone/TLS, +futex, and exit-group support. This is not a compliance claim. + +High-priority documented gaps include: + +- Relative path and working-directory semantics. +- `openat` and other `*at()` behavior, symlinks, hard links, atomic rename, + permission and ownership checks, sticky bits, and complete metadata. +- Full pthread lifecycle and synchronization semantics, timed primitives, + cancellation, and end-to-end musl validation. +- Timer APIs, full locale/multibyte behavior, sysconf/pathconf, and utilities. +- AF_UNIX, socketpair, ancillary data, advanced network behavior, and complete + device/VFS integration. +- Curated POSIX conformance tests across both architectures. + +Musl policy is static-first. Dynamic linking requires verified `PT_INTERP` +handling, interpreter mapping, auxv completeness, protections, and +`ld-musl-*.so.1`; do not claim it works without an executed test. + +## Security and Audit Context + +The design intends capabilities, per-task credentials, labels, audit records, +policy hooks, and eventually persistent ACL enforcement. Existing scaffolding +is not the complete policy system. + +Preserve these trust boundaries: + +- Firmware and bootloader data to kernel. +- User pointers and syscall values to kernel. +- Portal handles, ownership, rights, and sender identity. +- IPC packet framing and semantic length fields. +- Namesvc registration and capability distribution. +- VFS paths, mount boundaries, backend responses, and filesystem metadata. +- PCI config, BAR size, MMIO ranges, DMA buffers, and virtqueue indices. +- ELF headers, segments, auxv, stack, TLS, and W^X mappings. +- PTY owner, foreground process group, supervisor authority, and raw input. + +Audit logging can expose syscall arguments. Never add secret payloads, raw user +buffers, keys, or sensitive memory contents to logs. + +## Hardware Notes + +QEMU/KVM q35 is the reference x86_64 platform. Its documented setup uses OVMF, +two LAPIC CPUs, IOAPIC, PIT/legacy IRQ paths, serial stdio, 2 GiB RAM, and a +virtio-blk device backed by `build/rootfs.ext2`. + +The Dell OptiPlex 7020 record covers BIOS A04 legacy boot, Q87/Haswell, COM1, +ACPI reserved regions, two logical CPUs with APIC IDs 0 and 2, missing DMAR in +the captured log, and unstable AP bring-up. New physical hardware work should +capture firmware version, boot mode, memory map, ACPI tables, CPU/APIC IDs, PCI +inventory, IOMMU data, QEMU or hardware configuration, and full serial logs. + +## Performance Context + +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_portal_pingpong`: 10,000 64-byte round trips; documentation says its + server-spawn integration is incomplete. + +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. + +## Documentation Index + +Every original Markdown and text document in the tree was considered. Use the +focused source before modifying its subsystem. + +### Project State and Direction + +- `README.md`: vision, dependencies, builds, boot paths, subsystems, diagnostics. +- `TODO.md`: detailed completed and outstanding feature work. +- `TODO-ports.md`: phased toolchain and ports work. +- `roadmap.md`: architecture milestones and parity tables, partly historical. +- `changelog.md`: dated implementation and optimization history. +- `LICENSE`: current ISC license and repository attribution. + +### Architecture and Subsystems + +- `docs/architecture/overview.md`: full system topology and typical flows. +- `docs/subsystems/kernel-core.md`: kernel core boundaries. +- `docs/subsystems/boot-loader.md`: boot and loader responsibilities. +- `docs/subsystems/ipc-portals.md`: mailbox and portal responsibilities. +- `docs/subsystems/nameservice.md`: discovery responsibilities. +- `docs/subsystems/filesystems-vfs.md`: VFS and filesystem responsibilities. +- `docs/subsystems/storage-block.md`: block service responsibilities. +- `docs/subsystems/networking.md`: network service responsibilities. +- `docs/subsystems/console-pty.md`: console and PTY responsibilities. +- `docs/subsystems/process-posix.md`: syscall/runtime responsibilities. +- `docs/subsystems/security-audit.md`: audit and policy scaffolding. + +### Focused Design and Analysis + +- `docs/boot.md`: early boot-path description; historically stale at `kmain`. +- `docs/ipc.md`: portal and request/response design. +- `docs/namesvc.md`: namesvc protocol and client model. +- `docs/vfs.md`: VFS structures, protocol, flow, status, and future work. +- `docs/musl-readiness.md`: syscall/ABI, TLS/futex, and static-first plan. +- `docs/posix-gap.md`: high-level coverage snapshot; verify against current code. +- `docs/posix-roadmap.md`: POSIX compliance phases. +- `docs/arm64-update.md`: proposed ARM64 packaging and boot parity. +- `docs/lenix_analysis.md`: historical whole-tree analysis and proposals. +- `docs/scheduler_analysis.md`: historical scheduler/startup root-cause analysis. +- `docs/inspiration.md`: clean-room architectural references and direction. + +### Hardware + +- `docs/hardware-qemu-x86_64.txt`: reference QEMU configuration and logs. +- `docs/hardware-Dell7020.txt`: physical Dell bring-up inventory. + +### Toolchain and Ports + +- `docs/toolchain.md`: LLVM 21.1.8 pin and bootstrap instructions. +- `docs/toolchain-using.md`: integrated kernel/userland toolchain use. +- `docs/toolchain-ports.md`: ports layout, patching, builds, and troubleshooting. +- `third-party/build/README.md`: target sysroot environment. +- `third-party/ports/llvm/BUILD_FIX_NOTES.md`: historical LLVM CMake-source fix. +- `third-party/ports/llvm/MERCURON_SUPPORT.md`: required LLVM target support. +- `third-party/ports/llvm/PHASE1_BUILD_GUIDE.md`: historical manual build guide. + +### Security Audit Records + +- `TODO.audit/kernel-core.md`: W^X, IPC allocation, and syscall audit fixes. +- `TODO.audit/UEFI-boot.md`: W^X, serial init, and Boot Services checks. +- `TODO.audit/IPC.md`: portal race, packet validation, registration ownership, + and backend response validation. +- `TODO.audit/FS-VFS.md`: path traversal, flags, and registration ownership. +- `TODO.audit/console-pty-tty.md`: console gating, PTY authorization, raw input. +- `TODO.audit/pty_supervisor.md`: PTY ownership and queue watchdog fixes. +- `TODO.audit/user-runtime.md`: architecture ABI and runtime safety fixes. + +### Testing and Fixtures + +- `user/bench/README.md`: benchmark behavior, build, run, and interpretation. +- `disk/ext2root/hello.txt`: rootfs fixture consumed by ext2 boot tests, not + architecture documentation. blob - 1afbf3d8b959e3ac9acb64c0114e713eb8bdd530 blob + a36a18a56d0e1aeed20e94dfef47b33528d74068 --- README.md +++ README.md @@ -1,394 +1,200 @@ -# lenix +# Lenix -A WIP POSIX-compliant microkernel and Unix-like operating system +Lenix is a freestanding C microkernel with a Unix-like userland. The project +targets POSIX.1-2017 / UNIX V7 semantics, with x86_64 QEMU as the reference +development platform and an arm64 port in progress. -## Vision +Lenix is under active development. It is useful for kernel, IPC, filesystem, +driver, runtime, and operating-system research, but it is not ready for +production use. -Lenix targets a secure, efficient, enterprise-grade footprint that can scale from single-board computers to virtualized deployments (QEMU/KVM, OpenBSD VMM, and Bhyve). The initial bring-up focuses on x86_64 and arm64/aarch64 with planned RISC-V64 support once the core subsystems mature. Symmetric multiprocessing and distributed capabilities are in scope from the outset so the system can span multi-core boards and clustered environments. +## Contents -The kernel follows a microkernel architecture inspired by Minix, GNU/Hurd, Mach,as well as OpenBSD, IRIX, OpenVMS, and Plan 9 - emphasizing clear privilege boundaries, message-passing services, and network-transparent interfaces. +- [Quick start](#quick-start) +- [Host setup](#host-setup) +- [Toolchain](#toolchain) +- [Building](#building) +- [Running in QEMU](#running-in-qemu) +- [Testing](#testing) +- [Architecture](#architecture) +- [Repository layout](#repository-layout) +- [Documentation](#documentation) +- [Known limitations](#known-limitations) -Because IPC is the lifeblood of any microkernel, Lenix will grow a first-class message-passing framework that lets user-mode servers exchange capabilities, share memory safely, and marshal POSIX-style requests without collapsing isolation guarantees. Every subsystem we add (drivers, filesystems, network stacks) is expected to live behind that IPC surface so scaling out to distributed deployments remains straightforward. +## Quick start -We will have real asynchronous I/O supported directly by the kernel. We also want a comprehensive, fine-grained security model involving a database of subjects, actions, and objects, giving administrators fine control over who may do what to what. In short, ACLs built into the OS from the ground up. We want to also automatically log all events, providing an audit trail for security incidents. We should also support clustering to complement IPC; support for failover and load balancing should be built into the OS. +Ubuntu 26.04 LTS on amd64 is the supported build and QEMU test host. From the +repository root: -The operating system should never crash (Hah!). +```bash +sudo ./setup.sh +sudo ./build-toolchain.sh +./rebuild.sh +make run-uefi +``` -Standards compliance is a first-class goal: the project aims to conform to the Single UNIX Specification UNIXV7 - POSIX.1-2017 (IEEE Std 1003.1-2017) minimum as capabilities come online, looking towards POSIX.1-2024 expansion. +`setup.sh` installs the host packages. `build-toolchain.sh` downloads the +pinned LLVM 21.1.8 source, builds the Mercuron cross-toolchain, and installs it +under `/opt/llvm-mercuron`. -We will begin with tarfs+tmpfs support, then ext2, then UFS2. ZFS would be desired as the project matures. +The LLVM build is large and can take some time. Run it once per pinned +toolchain version, not before every Lenix build. -We will use musl or another modern, POSIX-complient libc implementation (or, preferably, our own implementation). +## Host setup -Some of the following documentation may be outdated - check changelog.md and roadmap.md for a better idea of the current state of the microkernel. +Run the host setup script as root on a clean Ubuntu 26.04 amd64 installation: -## Known major issues and blockers +```bash +sudo ./setup.sh +``` -- IPC/VFS timing (or something else!) can cause intermittent boot problems. -- Intermittent hangs -- arm64 support is lagging behind -- SMP needs work +The script installs the compilers, build tools, GRUB utilities, OVMF firmware, +and QEMU packages used by the x86_64 and arm64 build paths. It also checks the +host release, architecture, required commands, and Clang version. +The setup script is intentionally limited to Ubuntu 26.04 amd64. Package names +and firmware paths differ on other operating systems. + ## Toolchain -Current status: we build an LLVM/clang cross-compiler in `/opt/llvm-mercuron` targeting `x86_64-unknown-mercuron`. See `third-party/ports/llvm`. +Lenix pins LLVM/Clang 21.1.8. The project toolchain uses the +`x86_64-unknown-mercuron` target and installs to `/opt/llvm-mercuron`: -- **Clang/LLVM:** 21.1.5 (see `docs/toolchain.md`) - -The `Makefile` assumes the `clang` binary on your PATH is version 21.1.5: - ```bash -$ clang --version -clang version 21.1.5 +sudo ./build-toolchain.sh +/opt/llvm-mercuron/bin/clang --version ``` -If the version differs, install LLVM 21, export `CC=/path/to/clang-21`, and rebuild. +The root-level script is the normal entry point. The port implementation and +version metadata live in `third-party/ports/llvm/`. -## Dependencies +The source archive is cached under `third-party/build/distfiles/`. Build files +remain under `third-party/build/build-work/` and are not part of the source +tree. -Fully tested on Arch Linux +See [docs/toolchain.md](docs/toolchain.md) and +[docs/toolchain-ports.md](docs/toolchain-ports.md) for the toolchain layout and +port details. -### Arch Linux - -```` -sudo pacman -Syu --needed base-devel git \ - clang lld lldb llvm \ - qemu-system-x86 qemu-system-aarch64 gdb nasm \ - xorriso mtools dosfstools grub ovmf \ - cmake ninja meson bear ccache python -```` - -### Debian - -```` -sudo pacman -Syu --needed base-devel git \ - clang lld lldb llvm \ - qemu-system-x86 qemu-system-aarch64 gdb nasm \ - xorriso mtools dosfstools grub ovmf \ - cmake ninja meson bear ccache python -```` - -### OpenBSD - -```` -doas pkg_add gmake nasm qemu ccache cmake ninja meson pkgconf \ - python3 clang-tools-extra cppcheck -```` - -- VMM firmware: vmm-firmware - -### FreeBSD - -```` -sudo pkg install -y gmake nasm qemu-nox11 ccache cmake ninja meson pkgconf \ - python llvm-devel clang-tidy cppcheck uefi-edk2-bhyve -```` - -- Bhyve firmware: uefi-edk2-bhye - ## Building -`./rebuild-debug.sh` or `./rebuild.sh` +Use the repository scripts for a complete x86_64 build: -Rebuild images with musl staged: `gmake rootfs` and/or `gmake initrd`. Set PACKAGE_MUSL=0 to skip entirely or INCLUDE_MUSL_HEADERS=0 to drop headers while keeping the loader/libs. +```bash +./rebuild.sh +``` +For a debug build: -`ARCH` selects the target architecture (default `x86_64`). For example: - ```bash -# x86_64 legacy + UEFI artifacts: -make clean && make ARCH=x86_64 -j4 && make package-runtime && make package-apps && make initrd && make efi && make iso - -# ARM64 (QEMU virt) legacy kernel: -ARCH=arm64 make legacy +./rebuild-debug.sh ``` -## Booting -**UEFI (default)** +Both scripts use `/opt/llvm-mercuron`, rebuild the runtime and applications, +create the initrd and ext2 root filesystem, then produce UEFI and legacy boot +artifacts. + +The main make targets can also be run directly: + +| Target | Result | +|--------------------------|-----------------------------------------| +| `make` | x86_64 UEFI and legacy kernel artifacts | +| `make efi` | x86_64 UEFI loader | +| `make iso` | x86_64 GRUB/Multiboot2 ISO | +| `make initrd` | Initial user-mode service archive | +| `make build/rootfs.ext2` | ext2 root filesystem image | +| `ARCH=arm64 make` | arm64 kernel for QEMU's `virt` machine | + +`ARCH` defaults to `x86_64`. Set `PACKAGE_MUSL=0` to omit musl packaging, or +`INCLUDE_MUSL_HEADERS=0` to package its loader and libraries without headers. + +## Running in QEMU + +Run the x86_64 UEFI path: + ```bash make run-uefi ``` -**Legacy (Multiboot2/GRUB)** +Run the x86_64 legacy GRUB path: -``` +```bash make run-legacy +``` -# or: +Run the arm64 kernel on QEMU `virt`: -qemu-system-x86_64 -cdrom build/x86_64/lenix.iso -m 2048M -smp 2 -serial stdio -display none -drive if=none,id=virtio-rootfs,file="build/rootfs.ext2",format=raw -device virtio-blk-pci,drive=virtio-rootfs -```` +```bash +ARCH=arm64 make run-arm64 +``` -**ARM64 (QEMU virt)** +QEMU uses the terminal for the serial console. Stop it with `Ctrl+C`. -```` -ARCH=arm64 make run-arm64 # spawns a 2-core virt machine -```` - ## Testing -A cross-arch smoke test is available to ensure the kernel, scheduler, syscall -path, and IPC scaffolding still boot to the expected point. It builds both -architectures, runs them under QEMU, and now asserts that the scheduler reaches -the fs-server launch point *and* that the telemetry task logs a heartbeat (so we -know timer ticks are flowing and user-mode threads are alive) - needs updating: +Run the cross-architecture smoke test and source checks from the repository +root: -```` +```bash ./scripts/test_sanity.sh -make lint # clang-tidy/cppcheck/style gate -```` +make lint +``` -Set `SANITY_TIMEOUT=` to adjust how long each QEMU run is allowed to -execute before the harness kills it (default: 25s). +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=` to change its default timeout. -> **Note:** ARM64 bring-up targets QEMU's `virt` machine. Install `qemu-system-aarch64` -> (or ensure your QEMU package includes ARM64 targets) so `ARCH=arm64` builds can run locally. +Focused test scripts and logs are under `scripts/` and `build/test-logs/`. -## Initrd packaging +## Architecture -Build the initial initrd bundle (cpio/newc) with the current user-mode servers: +Lenix keeps mechanisms in the kernel and policy in user-mode services. The +kernel provides boot, memory management, scheduling, traps, IPC, syscall +mediation, capabilities, and the hardware access needed by services. -```` -make initrd # writes build/initrd.cpio -```` +User-mode servers provide service discovery, VFS and filesystems, block +devices, networking, PCI, TTY, and PTY management. Applications resolve these +services through namesvc instead of using hard-coded portal handles. -Set PACKAGE_MUSL=0 to skip musl packaging entirely or INCLUDE_MUSL_HEADERS=0 to drop headers while keeping the loader/libs. +The normal filesystem path is: -Contents include init, namesvc, vfs, fs.ext2, blockd, ramdiskd, virtio-blk, pci, ttyd, ptyctl, and demo. +```text +application -> syscall -> kernel mediation -> VFS -> filesystem server + -> blockd -> block backend +``` -The UEFI loader will automatically map `EFI/BOOT/INITRD.CPIO` if present. Running `make esp` or `make run-uefi` copies `build/initrd.cpio` into place; the legacy/GRUB ISO now carries `/boot/initrd.cpio` (see `boot/grub/grub.cfg`). Kernel user daemons prefer binaries from the initrd and fall back to the embedded images if missing. +## Repository layout +| Path | Purpose | +|----------------------|------------------------------------------| +| `kernel/` | Microkernel and architecture code | +| `servers/` | User-mode services and drivers | +| `user/runtime/` | Freestanding runtime and public headers | +| `user/apps/` | User applications and test programs | +| `user/bench/` | Microbenchmarks | +| `boot/` | x86_64 GRUB and boot assets | +| `disk/ext2root/` | Source tree for the ext2 root image | +| `scripts/` | Packaging, QEMU, lint, and test helpers | +| `third-party/ports/` | Toolchain and userland ports | +| `docs/` | Architecture and subsystem documentation | +## Documentation +- [AI_CONTEXT.md](AI_CONTEXT.md) contains the verified development, + administration, and test command matrix. +- [Architecture overview](docs/architecture/overview.md) describes the system + boundaries. +- [Boot](docs/boot.md), [IPC](docs/ipc.md), [VFS](docs/vfs.md), and + [namesvc](docs/namesvc.md) cover the main subsystems. +- [POSIX gaps](docs/posix-gap.md) records incomplete interfaces and semantics. +- [roadmap.md](roadmap.md), [TODO.md](TODO.md), and + [changelog.md](changelog.md) track planned and completed work. -## SMP status +## Known limitations -### SMP CURRENTLY DISABLED BY DEFAULT -Lenix now boots as a true multi-core kernel on both supported architectures. The -x86_64 path enumerates CPUs through legacy MP tables when available and falls -back to ACPI MADT parsing (via the UEFI system table or a BIOS scan) so modern -machines no longer strand secondary cores. arm64 keeps using PSCI `CPU_ON` -requests but now wires SGIs for both reschedule and generic IPIs, ensuring the -per-CPU scheduler queues can be nudged from any core. The shared scheduler owns -one run queue per CPU, automatically balances runnable tasks across them, and -requests remote reschedules via SMP IPIs whenever userland enqueues work on a -different core. - -To keep IPC reliable under contention, mailboxes now guard their head/tail -pointers with lightweight spin locks, so concurrent portal deliveries from -multiple CPUs no longer corrupt queues or trip the diagnostic watchdog. If SMP -firmware data is missing, the kernel logs the condition and continues in -single-core mode rather than panicking. - -## IPC scaffolding - -Every scheduler task now owns a per-task mailbox (`kernel/ipc/mailbox.c`) so kernel services and staged user processes can pass small control messages without sharing stacks. The scheduler allocates these queues during task creation, exposes them through `sched_task_mailbox()`, and drains them on teardown so messages never leak across processes. See `docs/ipc.md` for an overview of the mailbox lifecycle and how it fits into the Minix/Hurd-inspired IPC roadmap. - -Capability-style portals (`kernel/ipc/portal.c`) sit on top of those mailboxes. Kernel services mint handles that point at a target task’s mailbox plus a rights mask, then hand the handle to trusted clients. Calls such as `ipc_portal_send()` validate the capability before copying data into the destination queue, laying the groundwork for user-visible IPC APIs and distributed message routers. - -Portals can also record a preferred CPU so NUMA-aware services (filesystems, network stacks, etc.) can keep requests close to the cores that host their state. When a hint is present the scheduler nudges that CPU via an SMP IPI so work migrates toward the desired locality without busy waiting. Mailboxes/portals also expose remote placeholders (node IDs + transport stubs) so future distributed transports can route a message over the network instead of delivering it locally. - -User-mode tasks now resolve services via the nameserver: `service_resolve("vfs"/"fs.ext2"/"blockd"/"tty.console")` talks to namesvc (distributed via `SYS_service_portal(LENIX_SERVICE_NAMESVC)`) and fails fast if discovery breaks, so daemons never fall back to kernel-managed IDs or bootstrap handles. This keeps the privilege boundary clear: the kernel hands out the namesvc capability, and userland consumes every other service via the IPC API. - -## Hardware bring-up - -QEMU/KVM remains the reference environment, but we now track real hardware -logs (starting with a Dell OptiPlex 7020) to make sure ACPI, firmware memory -maps, LAPIC discovery, and serial paths behave the same on physical silicon. -See `docs/hardware.md` plus `boot-optiplex7020.log`/`docs/hardware-Dell7020.txt` -for the current dataset and the compatibility checklist used to evaluate new -x86_64 machines. - -Both the UEFI and legacy boot paths now rely on an external ext2 root filesystem -image instead of embedding 4 MiB of disk data inside the kernel. The build -creates `build/rootfs.ext2` from the contents of `disk/ext2root/`, then: - -- copies it into the FAT ESP as `EFI/BOOT/ROOTFS.IMG` (consumed by the UEFI - loader), and -- bundles it into the Multiboot2 ISO as `/boot/rootfs.img` (loaded via - `module2 … rootfs` in `boot/grub/grub.cfg`). - -Whenever you change files under `disk/ext2root/`, rebuild the image with -`make ARCH= DEBUG=1 build/rootfs.ext2` (or just rerun your usual build, -which depends on the image). Boot logs will then reflect the updated contents, -and the ext2 fs-server will see a consistent rootfs across QEMU and physical -hardware. -`scripts/stage-rootfs.sh` is run automatically by both the ESP builder and -`scripts/mkiso.sh` so the FAT layout (`build/EFI/BOOT/ROOTFS.IMG`) and the GRUB -module (`/boot/rootfs.img`) always reuse the exact bits from `build/rootfs.ext2`. -The helper drops `build/rootfs.manifest` / `build/rootfs.iso.manifest` with a -SHA-256 sum of the staged image, making it trivial to confirm both boot paths -loaded the same payload or to restage manually while debugging (`./scripts/stage-rootfs.sh build/rootfs.ext2 build/EFI/BOOT/ROOTFS.IMG`). -Kernel boot logs now include `* x86_64_acpi:` sections that dump MADT/FADT/DMAR -content (LAPIC/IOAPIC entries, SCI overrides, VT-d ranges), so drop every new -hardware capture into `boot-.log` and keep the matching inventory in -`docs/hardware-.txt` for future audits. - -### Block devices & virtio-blk - -Two block devices are present today: - -- **Device 0** – the loader-staged ramdisk built from `disk/ext2root/`. This - exists so early bring-up still works on machines without virtio. -- **Device 1** – a paravirtualized virtio-blk disk backed by the exact same - `build/rootfs.ext2` image. The user-mode `servers/block/virtio-blk/` daemon - now owns this path entirely: it queries the PCI service for virtio-blk, - maps the BARs via the hardware syscalls, allocates DMA via the new - `SYS_dma_alloc`, spins up legacy virtqueues, and registers itself with - blockd through the block-backend portal. Both `make run-uefi` and - `scripts/run-qemu.sh` still attach the disk with - `-device virtio-blk-pci,drive=virtio-rootfs`. - -`servers/block/blockd` tracks every backend device that registers with it and -exposes their geometry via the block-backend portal. The ext2 server walks those -IDs via `block_get_info()` (which now proxies to blockd) and automatically picks -the largest device with 512-byte sectors (so the virtio disk wins when present). -If you want the ramdisk instead, either detach the virtio device or comment out -the QEMU drive flag. - -The virtio-blk driver has now been fully evicted to user space. The -`servers/block/virtio-blk/` daemon launches at boot, queries the PCI service -for the virtio block function, inspects each BAR, maps the MMIO window via the -hardware-access syscalls, allocates queue/data buffers with the DMA syscall -pair, and services every request that blockd forwards through the backend IPC -channel. - -To complete the split we introduced a dedicated block-backend IPC service: -`blockd` now registers its bootstrap portal via `block_backend_register()` and -device daemons resolve `"blockd"` via namesvc to register their geometry and -service I/O requests directly from user space. -`servers/block/ramdiskd/` and `servers/block/virtio-blk/` are the first backend -clients: ramdiskd registers a 4 MiB loader-backed disk, mirrors the contents -after mapping the loader’s rootfs with `hw_mmio_map()`, and services every -request synchronously, while virtio-blk drives the PCI device using its DMA -window and virtqueue logic. The kernel’s `SYS_blocksvc_request` path now simply -forwards requests to blockd/backends, keeping the block registry purely in -userland. - -`kernel/block/core.c` is now a stub (kept only for early boot bookkeeping) so -the kernel no longer registers or serves block devices itself; every disk now -appears only when a user-mode backend daemon registers it with blockd. - -Client code queries blockd’s registry directly: `block_get_info()` talks to the -backend portal so enumerators like ext2 can discover user-mode devices (IDs >1) -without touching kernel tables. - -## Built-in demo & ELF loader - -Until storage and a real userland loader exist, `kmain` boots a baked-in ELF -image that lives under `kernel/user/demo_image_*.h`. The image is generated -from the tiny assembly stubs in `user/demo_*.S` via the freestanding linker -script `user/demo_link.ld`, then embedded directly into the kernel binary. - -At runtime the shared loader (`kernel/elf/loader.c`) validates the ELF header, -walks every PT_LOAD segment inside the user window, allocates pages via the -vm_space API, and seeds a guarded stack with `argc/argv/environ` stubs. The -demo launcher (`kernel/user/demo.c`) binds that vm_space to a scheduler task -and calls `arch_enter_usermode()` so syscalls can exercise the new dispatcher. - -## User-mode tmpfs server - -The first non-trivial user service ships alongside the demo. A second embedded -ELF (see `kernel/user/fs_image_*.h`) is staged and launched by -`kernel/user/fs_server.c`, which grants the task a bootstrap portal so it can -register with the kernel via `fs_register_service()`. Once registered, the -tmpfs loop blocks on `portal_recv()` and responds to open/read/close requests by -looking up the hard-coded file table in `servers/fs/tmpfs/main.c`. - -Because everything runs in user space, IPC happens entirely through the -mailbox/portal path: the kernel forwards requests, the tmpfs task replies with -`fs_send_response()`, and the scheduler treats both sides like any other thread. -When you run `make run-legacy` or `ARCH=arm64 make run-arm64`, the fs-server -prints `[tmpfs] service registered` and then goes idle until the demo process -issues its first `open(2)`. - -## User-mode console daemon - -The physical console is now mediated by a tiny TTY daemon (`servers/console/ttyd/main.c`) -instead of the kernel. The console service forwards every completed keyboard -line to the daemon over a private portal (`console_tty_register()`), and the -daemon pushes those bytes into the active PTY via the new `pty_feed()` syscall. -This keeps stdin canonical mode and session ownership in user space while -preserving the existing `/dev/console` semantics for legacy tasks that still -call `console_open()`. - -PTY 0 = Console (kernel commands: help, status, ptys, ticks, cpustats, perf, diag) -PTY 1 = fs-server -PTY 2 = ttyd -PTY 3 = ptyctl supervisor -PTY 4 = user-demo (echo demo) - -## Repository layout: user apps vs. servers - -Lenix keeps a strict split between “regular” user programs and long-running -microkernel servers: - -- `user/` contains the freestanding runtime (`user/runtime/`) plus sample - binaries such as the demo echo task (`user/demo/…`). These are the programs - an administrator would treat like normal POSIX apps. -- `servers/` hosts every privileged user-mode service, organized by category: - `servers/block/…` for storage drivers (`blockd`, future virtio devices), - `servers/fs/…` for filesystems (tmpfs, ext2, vfs), `servers/console/…` - for terminal daemons (`ttyd`, `ptyctl`), and `servers/drivers/…` for - hardware-facing daemons such as the PCI enumerator. Each server is built into the - kernel image as an embedded ELF (`kernel/user/*_image_*.h`) and launched via - the corresponding `kernel/user/*_server.c` helper so the kernel never mixes - server code with in-tree applications. - -This layout keeps the microkernel boundary clear: production services evolve -under `servers/`, while `user/` remains a clean staging ground for ordinary -programs and the shared runtime. - -After the demo prints “Type a line and it will be echoed back.” it enters a -userspace read loop. Since the QEMU run targets pipe the guest serial console to -your terminal, you can type characters and hit Enter to watch the user-mode -echo path exercise `read()`/`write()` via the syscall table. If you do nothing, -the VM simply waits for input—there is no kernel hang in that state. - -## Runtime diagnostics - -The interrupt-driven console accepts a couple of built-in commands so you can -inspect the live system without a debugger: - -- `ticks` dumps the global jiffies/heartbeat counters exposed by the timer - layer. -- `cpustats` prints per-CPU scheduler telemetry (ticks, switches, run-queue - depth, current task). -- `perf` emits the Stage 6a performance counters (scheduler ticks/context - switches, portal traffic, IPC byte totals, filesystem proxy activity, and the - peak mailbox depth) so regressions are easy to spot while iterating on Stage 7. -- `blocktrace` dumps the last 32 block portal requests (device, op, LBA, blocks, - bytes transferred, and jiffy duration) so you can diff UEFI vs Multiboot runs - or capture the sequence that triggered a storage bug. Run `blocktrace clear` - before reproducing an issue to capture a clean log for comparison. -- A kernel-owned console service logs when it starts and responds to basic - commands (`help`, `status`) even while user-mode subsystems spin up, so - you're never left staring at “Waiting for subsystems…”. -- `/dev/console` is now a portal-backed device. Call `console_open()` from - `` to grab a portal handle that can be `portal_recv()`'d for - processed lines and `portal_send()`'d to print text without relying on `printk`. -- `diag` toggles runtime diagnostics: `diag status` prints the current state, - `diag audit on|off`, `diag policy on|off`, and `diag telemetry on|off` control - the verbose audit/policy logs and the periodic `[task:…] beat/jiffies` output. -- `pty_alloc()`/`pty_free()`/`pty_activate()`/`pty_query()` (see ``) - let shells grab dedicated pseudo-terminals via the new kernel PTY mux. Every - PTY exposes a portal for I/O, while `pty_activate()` switches the physical - console to the desired PTY without rebooting. -- `pty_get_config()`/`pty_set_config()` adjust a PTY’s lightweight termios - struct (canonical vs raw input, echo state, placeholder `rows/cols` window - size), and `pty_send_signal()` injects stub `[signal N]` notifications to - keep future SIGINT/job-control plumbing in sync. -- `stat()`/`fstat()`/`lseek()`/`dup()`/`fcntl(F_DUPFD|F_GETFL)` now route entirely through the tmpfs server: the kernel - packages requests (`IPC_FS_REQ_STAT/FSTAT/SEEK/DUP/FCNTL`), tmpfs updates handle - offsets and metadata, and the user runtime exposes the results via - ``/`lseek(2)`/`dup(2)` so POSIX file queries never touch kernel filesystems. -- User-mode tasks write to stdout/stderr through per-task console portals - instead of the kernel calling `serial_write()` on their behalf, which moves - the console stack one step closer to a user-space TTY service. -- `Ctrl+N` talks to the new `ptyctl` supervisor task, which tracks every PTY via - the `pty_enum()` syscall and cycles the foreground session (or accepts typed - commands like `list`, `next`, `activate ` from its own PTY when you switch - to it manually). - -All commands run at interrupt level, so they are safe to invoke while the demo -waits for console input. +- x86_64 QEMU is the reference path; arm64 packaging and validation lag behind. +- SMP is disabled by default and is not considered stable. +- Boot and IPC timing can still cause intermittent hangs. +- POSIX coverage, networking, device semantics, and dynamic musl support are + incomplete. +- Distributed IPC and clustering remain planned work. blob - f188a244a26e5e4b33743e9a8e3c2dc7dda3fb51 blob + 345b414e121360deb8d2863df854cf784d8336f7 --- changelog.md +++ changelog.md @@ -1,5 +1,18 @@ # Changelog +# 2026-08-31 + +## Ubuntu 26.04 toolchain setup and x86_64 boot reliability + +- Added Ubuntu 26.04 build and QEMU environment setup, plus a root-level helper for building and installing the Mercuron LLVM toolchain from `third-party/ports/llvm`. +- Pinned the LLVM port to Clang 21.1.8 and updated the toolchain documentation and build guidance for `/opt/llvm-mercuron`. +- Reworked the README setup and build instructions so the host setup, cross-toolchain build, Lenix build, and QEMU launch order are explicit. +- Added repository-specific AI development, administration, build, and test context. +- Fixed x86_64 timer and IPI interrupt returns overwriting the restored `%rax` register before `iretq`, which caused intermittent user-mode faults during service startup. +- Made scheduler block, wake, and timed-sleep transitions atomic with interrupt state, preventing lost wakeups and invalid task-state transitions. +- Updated blockd to poll one representative portal because portals owned by one task share the same mailbox, preventing duplicate readiness handling. +- Verified the Clang 21.1.8 Mercuron build, six x86_64 UEFI boots to the shell, and one legacy boot to the shell. The SMP test build completes, but the existing AP trampoline startup still blocks SMP boot before userland. + # 2025-12-09 Yield storm over! blob - /dev/null blob + 7c77e08b910bd4c8160f2f81c01c1c214011aa5d (mode 755) --- /dev/null +++ build-toolchain.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# Lenix - Developed by lex0de (lex0de@tuta.com) +# lenix/build-toolchain.sh +# SPDX-License-Identifier: ISC + +set -euo pipefail + +PROJECT_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +readonly PROJECT_ROOT +readonly PORT_SCRIPT="${PROJECT_ROOT}/third-party/ports/llvm/build.sh" +readonly DISTINFO="${PROJECT_ROOT}/third-party/ports/llvm/distinfo" + +# shellcheck source=third-party/ports/llvm/distinfo +source "$DISTINFO" + +readonly LLVM_VERSION +readonly PREFIX='/opt/llvm-mercuron' +readonly LOCK_FILE="/run/lock/lenix-llvm-${LLVM_VERSION}.lock" + +readonly -a REQUIRED_COMMANDS=( + clang-21 + clang++-21 + cmake + flock + ninja + nproc + patch + runuser + sha256sum + tar + wget +) + + +build_toolchain() +{ + local builder_user="${SUDO_USER:-root}" + + if [[ "$builder_user" == 'root' ]]; then + BOOTSTRAP_CC='clang-21' \ + BOOTSTRAP_CXX='clang++-21' \ + "$PORT_SCRIPT" build + return + fi + + if ! id "$builder_user" >/dev/null 2>&1; then + echo "error: invoking user does not exist: $builder_user" >&2 + return 1 + fi + + runuser -u "$builder_user" -- env \ + BOOTSTRAP_CC='clang-21' \ + BOOTSTRAP_CXX='clang++-21' \ + "$PORT_SCRIPT" build +} + + +install_toolchain() +{ + BOOTSTRAP_CC='clang-21' \ + BOOTSTRAP_CXX='clang++-21' \ + "$PORT_SCRIPT" install +} + + +is_toolchain_installed() +{ + local clang_version + + if [[ ! -x "$PREFIX/bin/clang" ]]; then + return 1 + fi + + clang_version=$("$PREFIX/bin/clang" --version | sed -nE \ + 's/^clang version ([^ ]+).*/\1/p' | head -n 1) + [[ "$clang_version" == "$LLVM_VERSION" ]] +} + + +validate_bootstrap_clang() +{ + local clang_version + + clang_version=$(clang-21 --version | sed -nE \ + 's/^(Ubuntu )?clang version ([^ ]+).*/\2/p' | head -n 1) + if [[ "$clang_version" != "$LLVM_VERSION" ]]; then + echo "error: Clang $LLVM_VERSION is required to bootstrap the toolchain" >&2 + echo "error: clang-21 reports ${clang_version:-an unknown version}" >&2 + return 1 + fi +} + + +validate_commands() +{ + local command_name + local failed=0 + + for command_name in "${REQUIRED_COMMANDS[@]}"; do + if ! command -v "$command_name" >/dev/null 2>&1; then + echo "error: required command is missing: $command_name" >&2 + failed=1 + fi + done + + if (( failed != 0 )); then + echo 'error: run setup.sh before building the toolchain' >&2 + return 1 + fi +} + + +validate_install() +{ + local clang_version + + if [[ ! -x "$PREFIX/bin/clang" ]]; then + echo "error: installed compiler is missing: $PREFIX/bin/clang" >&2 + return 1 + fi + + clang_version=$("$PREFIX/bin/clang" --version | sed -nE \ + 's/^clang version ([^ ]+).*/\1/p' | head -n 1) + if [[ "$clang_version" != "$LLVM_VERSION" ]]; then + echo "error: installed Clang version is ${clang_version:-unknown}" >&2 + return 1 + fi +} + + +validate_invocation() +{ + if (( EUID != 0 )); then + echo 'error: run this script as root to install under /opt' >&2 + return 1 + fi + + if [[ ! -x "$PORT_SCRIPT" ]]; then + echo "error: LLVM port script is missing or not executable: $PORT_SCRIPT" >&2 + return 1 + fi +} + + +main() +{ + if (( $# != 0 )); then + echo 'usage: build-toolchain.sh' >&2 + return 1 + fi + + validate_invocation + validate_commands + validate_bootstrap_clang + + if is_toolchain_installed; then + echo "Lenix LLVM/Clang $LLVM_VERSION is already installed under $PREFIX." + return + fi + + exec 9>"$LOCK_FILE" + if ! flock -n 9; then + echo 'error: another Lenix LLVM toolchain build is running' >&2 + return 1 + fi + + build_toolchain + install_toolchain + validate_install + + echo "Lenix LLVM/Clang $LLVM_VERSION is installed under $PREFIX." +} + + +main "$@" blob - 76a261d664173932879c9c2f262439044a009660 blob + de29998cd87f5abafce69aa4b79454451ec36c8c --- docs/toolchain-ports.md +++ docs/toolchain-ports.md @@ -96,7 +96,7 @@ Ports respect these environment variables: ## Make integration and image staging - `gmake musl USE_MERCURON_TOOLCHAIN=1 MERCURON_SYSROOT=$PWD/third-party/build/sysroot TOOLCHAIN_ROOT=/opt/llvm-mercuron` invokes `third-party/ports/musl/build.sh` through the top-level `GNUmakefile`, honoring the same sysroot and triple defaults. -- `make rootfs` and `make initrd` depend on the musl build (unless `PACKAGE_MUSL=0`) and copy `/lib/ld-musl-*.so.1`, `/usr/lib/*`, and `/usr/include/*` from the sysroot into the generated ext2 root image and cpio initrd so dynamically linked userland binaries have their loader, libc, and headers available. +- `make build/rootfs.ext2` and `make initrd` depend on the musl build (unless `PACKAGE_MUSL=0`) and copy `/lib/ld-musl-*.so.1`, `/usr/lib/*`, and `/usr/include/*` from the sysroot into the generated ext2 root image and cpio initrd so dynamically linked userland binaries have their loader, libc, and headers available. - Toggle staging knobs: `PACKAGE_MUSL=0` to skip musl entirely, or `INCLUDE_MUSL_HEADERS=0` to omit headers while keeping the loader and libraries in the images. ## LLVM/Clang Toolchain @@ -105,13 +105,13 @@ The most critical component is the LLVM/Clang cross-co ### Location - **Build Script**: `third-party/ports/llvm/build.sh` -- **Version**: 21.1.5 +- **Version**: 21.1.8 - **Target Triple**: x86_64-unknown-mercuron - **Install Prefix**: /opt/llvm-mercuron (on build system) ### Mercuron Support Patches -LLVM 21.1.5 required three patches to recognize and support Mercuron as a target OS: +The LLVM port carries three patches to recognize and support Mercuron as a target OS. The build stops if any patch fails. #### Patch 0001: Add Mercuron OS Type **File**: `0001-add-mercuron-os-type.patch` @@ -122,7 +122,6 @@ Adds Mercuron to LLVM's OS type enumeration and parser - Modifies: `llvm/lib/TargetParser/Triple.cpp` - Adds: `Mercuron` enum value to `Triple::OSType` - Adds: `.StartsWith("mercuron", Triple::Mercuron)` to OS parser -- Status: Applied successfully with fuzzy matching (offset -11 lines) **Purpose**: Allows LLVM/Clang to recognize "mercuron" as a valid OS type in target triples. @@ -134,7 +133,6 @@ Adds convenience method for checking Mercuron targets: - Modifies: `llvm/include/llvm/TargetParser/Triple.h` - Adds: `bool isOSMercuron() const { return getOS() == Triple::Mercuron; }` - Location: After `isOSLinux()` method -- Status: Applied successfully with fuzzy matching (offset -1 line) **Purpose**: Provides a clean API for code to test if target is Mercuron with `TargetTriple.isOSMercuron()`. @@ -148,21 +146,18 @@ Adds Mercuron handling in Clang's driver toolchain con - Adds: Architecture-specific library directory configuration - x86_64: Uses `X86_64LibDirs` - x86: Uses `X86LibDirs` -- Status: Applied successfully with offset +7 lines **Purpose**: Allows Clang's driver to properly configure library paths and toolchain options for Mercuron targets. ### Building LLVM/Clang ```bash -# Simple build -./third-party/ports/llvm/build.sh +# Build and install with the root-level wrapper +sudo ./build-toolchain.sh -# With custom sysroot -MERCURON_SYSROOT=/custom/path ./third-party/ports/llvm/build.sh - -# Build time: 2-4 hours depending on hardware -# Disk space required: 20-30 GB +# Run the port phases separately when debugging +third-party/ports/llvm/build.sh build +sudo third-party/ports/llvm/build.sh install ``` ### Build Output @@ -282,7 +277,7 @@ All ports are built using the system's native compiler - **Host**: x86_64-linux-gnu (or similar) - **Target**: x86_64-unknown-mercuron -- **Build**: LLVM/Clang 21.1.5 +- **Build**: LLVM/Clang 21.1.8 ## Usage Examples @@ -291,8 +286,8 @@ All ports are built using the system's native compiler ```bash cd /path/to/lenix -# Build all ports in order -./third-party/ports/llvm/build.sh +# Build and install the pinned LLVM toolchain, then build the target ports +sudo ./build-toolchain.sh ./third-party/ports/zlib/build.sh ./third-party/ports/libressl/build.sh ``` @@ -350,11 +345,11 @@ rm -rf third-party/build **Problem**: "The source directory does not appear to contain CMakeLists.txt" -**Solution**: Ensure the tar extraction uses correct path. LLVM tarball has CMakeLists.txt at `llvm-project-21.1.5.src/llvm/CMakeLists.txt`. The build script uses `--strip-components=1` and points CMake to `../llvm`. +**Solution**: Ensure the tar extraction uses the correct path. The LLVM tarball has CMakeLists.txt at `llvm-project-21.1.8.src/llvm/CMakeLists.txt`. The build script uses `--strip-components=1` and passes the extracted `llvm/` directory to CMake. **Problem**: Patches fail to apply -**Solution**: Check that LLVM version matches 21.1.5. Patches are specific to this version and line numbers may differ in other versions. +**Solution**: Check that LLVM version matches 21.1.8. Patches are specific to this version and may not apply to other releases. ### Compiler Errors During Build @@ -390,7 +385,7 @@ Current versions in use: | Component | Version | Target Triple | |-----------|---------|----------------| -| LLVM/Clang | 21.1.5 | x86_64-unknown-mercuron | +| LLVM/Clang | 21.1.8 | x86_64-unknown-mercuron | | LibreSSL | 4.2.1 | x86_64-unknown-mercuron | | zlib | 1.3.1 | x86_64-unknown-mercuron | blob - 2a80d9c6cadd1382e3d9781e4fbc1e5364b5438f blob + 1a58d5b32c14eed2236eb06752c3f1e1752a8dc9 --- docs/toolchain.md +++ docs/toolchain.md @@ -1,24 +1,58 @@ -# Toolchain Pinning +# Toolchain -Lenix tracks a fixed host compiler so developers, CI, and future self-hosting efforts all build the same bits. +Lenix pins LLVM/Clang 21.1.8 for host builds and for the Mercuron cross-toolchain. -| Component | Version | Notes | -|-----------|---------|-------| -| Clang/LLVM | 21.1.5 | `clang --version` should report 21.1.5. If your distro ships a different revision, install the upstream LLVM 21 toolchain and point `CC`/`CXX` at it. | +| Component | Version | Path | +|------------------|---------|--------------------------------| +| Bootstrap Clang | 21.1.8 | `clang-21` and `clang++-21` | +| Mercuron LLVM | 21.1.8 | `/opt/llvm-mercuron` | +| Mercuron target | N/A | `x86_64-unknown-mercuron` | +| Target sysroot | N/A | `third-party/build/sysroot/` | -## Verifying your environment +## Bootstrap +On Ubuntu 26.04 amd64, install and verify the host dependencies first: + ```bash -$ clang --version -clang version 21.1.5 -Target: x86_64-pc-linux-gnu +sudo ./setup.sh +clang-21 --version ``` -If the version mismatch persists, export explicit compiler paths, e.g.: +The reported version must be 21.1.8. +## Build and install + +Run the root-level wrapper from the repository root: + ```bash -export CC=/opt/llvm-21/bin/clang -export CXX=/opt/llvm-21/bin/clang++ +sudo ./build-toolchain.sh ``` -Future toolchain updates will be added to this document before landing in `main`. +The wrapper compiles LLVM as the user who invoked it through `sudo`, then uses +root access only for the install phase under `/opt`. It downloads the pinned +source archive, checks its SHA-256 digest, applies any port patches, builds +Clang, LLD, and LLDB, and installs the result under `/opt/llvm-mercuron`. + +The implementation is in `third-party/ports/llvm/build.sh`. Its actions can be +run separately when debugging the port: + +```bash +third-party/ports/llvm/build.sh build +sudo third-party/ports/llvm/build.sh install +``` + +## Verify + +```bash +/opt/llvm-mercuron/bin/clang --version +/opt/llvm-mercuron/bin/clang -dumpmachine +``` + +The first command must report Clang 21.1.8. The second must report +`x86_64-unknown-mercuron`. + +Use the installed toolchain with the normal repository scripts: + +```bash +./rebuild.sh +``` blob - 3d5de6760efcba0043a06925f4cd5563643f88b2 blob + abcafb903106d6fa14171986c4599260413fa477 --- kernel/arch/x86_64/isr_stub.S +++ kernel/arch/x86_64/isr_stub.S @@ -33,26 +33,24 @@ x86_timer_isr: call x86_timer_isr_handler lea 120(%rsp), %rdi call x86_timer_log_return_frame - popq %r15 - popq %r14 - popq %r13 - popq %r12 - popq %r11 - popq %r10 - popq %r9 - popq %r8 - popq %rdi - popq %rsi - popq %rbp - popq %rbx - popq %rdx - popq %rcx - popq %rax - movq 16(%rsp), %rax - andq $~0x4000, %rax - movq %rax, 16(%rsp) - movq 8(%rsp), %rax + andq $~0x4000, 136(%rsp) + movq 128(%rsp), %rax movq %rax, x86_last_iret_cs(%rip) + popq %r15 + popq %r14 + popq %r13 + popq %r12 + popq %r11 + popq %r10 + popq %r9 + popq %r8 + popq %rdi + popq %rsi + popq %rbp + popq %rbx + popq %rdx + popq %rcx + popq %rax iretq x86_ipi_resched_isr: @@ -73,6 +71,7 @@ x86_ipi_resched_isr: pushq %r15 mov $0, %edi call x86_ipi_isr_dispatch + andq $~0x4000, 136(%rsp) popq %r15 popq %r14 popq %r13 @@ -88,9 +87,6 @@ x86_ipi_resched_isr: popq %rdx popq %rcx popq %rax - movq 16(%rsp), %rax - andq $~0x4000, %rax - movq %rax, 16(%rsp) iretq x86_ipi_generic_isr: @@ -111,6 +107,7 @@ x86_ipi_generic_isr: pushq %r15 mov $1, %edi call x86_ipi_isr_dispatch + andq $~0x4000, 136(%rsp) popq %r15 popq %r14 popq %r13 @@ -126,7 +123,4 @@ x86_ipi_generic_isr: popq %rdx popq %rcx popq %rax - movq 16(%rsp), %rax - andq $~0x4000, %rax - movq %rax, 16(%rsp) iretq blob - f90ffd9d0af9aa3eada187217c8cbda125cc81f9 blob + 1d9880a4b3e03c77666511f6524bb9051fc1a194 --- kernel/sched/task.c +++ kernel/sched/task.c @@ -734,14 +734,6 @@ sched_block_current(void) if (!scheduler_started) return; - task = sched_current_task(); - if (task != NULL && task->pending_wake) { - task->pending_wake = false; - return; - } - if (task != NULL) - task->block_count++; - perf_counter_inc(PERF_COUNTER_SCHED_BLOCK, 1); flags = arch_irq_save(); state = sched_state_current(); if (state == NULL || !state->idle_ready) { @@ -753,13 +745,20 @@ sched_block_current(void) arch_irq_restore(flags); return; } + if (task->pending_wake) { + task->pending_wake = false; + arch_irq_restore(flags); + return; + } + task->block_count++; + perf_counter_inc(PERF_COUNTER_SCHED_BLOCK, 1); /* Mark task as blocked - it will NOT be requeued */ task->state = SCHED_STATE_BLOCKED; - /* Check pending_wake again after changing state to handle race where - * sched_wake() saw RUNNABLE, set pending_wake=true, but we blocked anyway */ + /* Handle a concurrent wake between the pending check and state change. */ if (task->pending_wake) { task->pending_wake = false; - task->state = SCHED_STATE_RUNNABLE; + /* The task never switched out and remains the running task. */ + task->state = SCHED_STATE_RUNNING; arch_irq_restore(flags); return; } @@ -780,12 +779,13 @@ sched_wake(struct sched_task *task) if (task == NULL) return; - if (task->sleeping) - (void)sched_sleep_cancel(task); - if (task->state != SCHED_STATE_BLOCKED) { + (void)sched_sleep_cancel(task); + if (task->state == SCHED_STATE_RUNNING) { task->pending_wake = true; return; } + if (task->state != SCHED_STATE_BLOCKED) + return; /* Mark as runnable */ task->state = SCHED_STATE_RUNNABLE; @@ -920,15 +920,31 @@ sched_wake_sleepers(uint64_t now) void sched_sleep_ticks(uint64_t ticks) { - struct sched_task *task = sched_current_task(); + uint64_t flags; + struct sched_cpu_state *state; + struct sched_task *task; uint64_t deadline; - if (task == NULL) - return; if (ticks == 0) { sched_yield(); return; } + flags = arch_irq_save(); + state = sched_state_current(); + if (!scheduler_started || state == NULL || !state->idle_ready) { + arch_irq_restore(flags); + return; + } + task = state->current; + if (task == NULL || task->is_idle) { + arch_irq_restore(flags); + return; + } + if (task->pending_wake) { + task->pending_wake = false; + arch_irq_restore(flags); + return; + } deadline = timer_ticks() + ticks; #ifdef LENIX_DEBUG static int sleep_debug_count = 0; @@ -947,8 +963,20 @@ sched_sleep_ticks(uint64_t ticks) #endif spinlock_lock(&sched_sleep_lock); sched_sleep_insert_locked(task, deadline); + task->state = SCHED_STATE_BLOCKED; + if (task->pending_wake) { + task->pending_wake = false; + task->state = SCHED_STATE_RUNNING; + (void)sched_sleep_remove_locked(task); + spinlock_unlock(&sched_sleep_lock); + arch_irq_restore(flags); + return; + } spinlock_unlock(&sched_sleep_lock); - sched_block_current(); + task->block_count++; + perf_counter_inc(PERF_COUNTER_SCHED_BLOCK, 1); + sched_switch(false); + arch_irq_restore(flags); #ifdef LENIX_DEBUG static int wake_debug_count = 0; if (wake_debug_count < 5) { @@ -965,12 +993,15 @@ bool sched_sleep_cancel(struct sched_task *task) { bool was_sleeping; + uint64_t flags; if (task == NULL) return false; + flags = arch_irq_save(); spinlock_lock(&sched_sleep_lock); was_sleeping = sched_sleep_remove_locked(task); spinlock_unlock(&sched_sleep_lock); + arch_irq_restore(flags); return was_sleeping; } blob - 1fffd1edd717e81d6da1bdf42984d615dea368dc blob + 46fe4c3c6a2aa7d8cb694842a67c89ae09aadfce --- servers/block/blockd/main.c +++ servers/block/blockd/main.c @@ -689,85 +689,69 @@ main(int argc, char **argv, char **envp) } /* - * Block on both the client/service portal (bootstrap) and the - * backend portal so responses from ramdiskd/virtio-blk cannot be - * starved by a blocking recv on the other endpoint. + * Portal handles owned by this task share one mailbox. Poll one + * representative handle and receive one message per readiness event. */ - struct pollfd pfds[2]; - nfds_t nfds = 0; + struct pollfd pfd; + portal_handle_t poll_portal = bootstrap; - if (bootstrap != IPC_PORTAL_INVALID_HANDLE) { - pfds[nfds].fd = (int)bootstrap; - pfds[nfds].events = POLLIN; - pfds[nfds].revents = 0; - nfds++; - } - if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE && - blockd_backend_portal != bootstrap) { - pfds[nfds].fd = (int)blockd_backend_portal; - pfds[nfds].events = POLLIN; - pfds[nfds].revents = 0; - nfds++; - } - - if (nfds == 0) { + if (poll_portal == IPC_PORTAL_INVALID_HANDLE) + poll_portal = blockd_backend_portal; + if (poll_portal == IPC_PORTAL_INVALID_HANDLE) { blockd_check_device_timeouts(); poll(NULL, 0, 1); continue; } + pfd.fd = (int)poll_portal; + pfd.events = POLLIN; + pfd.revents = 0; - int ready = poll(pfds, nfds, 100); + int ready = poll(&pfd, 1, 100); if (ready <= 0) { blockd_check_device_timeouts(); continue; } - for (nfds_t i = 0; i < nfds; i++) { - if ((pfds[i].revents & POLLIN) == 0) - continue; - + if ((pfd.revents & POLLIN) != 0) { ssize_t n = portal_recv(portal_buf, sizeof(portal_buf)); - if (n <= 0) - continue; - if ((size_t)n > sizeof(portal_buf)) { + if (n > 0 && (size_t)n > sizeof(portal_buf)) { blockd_log("[blockd] portal_recv overflow; dropping message"); - continue; - } - if ((size_t)n != sizeof(blockd_req)) { + } else if (n > 0 && (size_t)n != sizeof(blockd_req)) { blockd_handle_backend_message(portal_buf, (size_t)n); - continue; - } - memcpy(&blockd_req, portal_buf, sizeof(blockd_req)); - struct blockd_backend_device *backend = blockd_backend_find(blockd_req.device); - BLOCKD_TRACE("[blockd] recv token=%u dev=%u lba=%llu blocks=%u flags=0x%x pending=%s waiting=%s\n", - blockd_req.token, blockd_req.device, - (unsigned long long)blockd_req.lba, - blockd_req.blocks, blockd_req.flags, - (backend != NULL && backend->request_pending) ? "yes" : "no", - (backend != NULL && backend->waiting_completion) ? "yes" : "no"); - if (backend != NULL) { - if (!blockd_backend_queue_request(backend, &blockd_req)) { - BLOCKD_TRACE("[blockd] queue_request rejected dev=%u token=%u pending=%s waiting=%s\n", - backend->device_id, blockd_req.token, - backend->request_pending ? "yes" : "no", - backend->waiting_completion ? "yes" : "no"); + } else if (n > 0) { + memcpy(&blockd_req, portal_buf, sizeof(blockd_req)); + struct blockd_backend_device *backend = + blockd_backend_find(blockd_req.device); + BLOCKD_TRACE("[blockd] recv token=%u dev=%u lba=%llu blocks=%u flags=0x%x pending=%s waiting=%s\n", + blockd_req.token, blockd_req.device, + (unsigned long long)blockd_req.lba, + blockd_req.blocks, blockd_req.flags, + (backend != NULL && backend->request_pending) ? "yes" : "no", + (backend != NULL && backend->waiting_completion) ? "yes" : "no"); + if (backend != NULL) { + if (!blockd_backend_queue_request(backend, &blockd_req)) { + BLOCKD_TRACE("[blockd] queue_request rejected dev=%u token=%u pending=%s waiting=%s\n", + backend->device_id, blockd_req.token, + backend->request_pending ? "yes" : "no", + backend->waiting_completion ? "yes" : "no"); + memset(&blockd_resp, 0, sizeof(blockd_resp)); + blockd_resp.token = blockd_req.token; + blockd_resp.status = -16; /* EBUSY */ + blockd_resp.bytes_transferred = 0; + blockd_resp.data_len = 0; + block_service_respond(&blockd_resp); + } + } else { + BLOCKD_TRACE("[blockd] no backend for dev=%u, responding ENXIO\n", + blockd_req.device); memset(&blockd_resp, 0, sizeof(blockd_resp)); blockd_resp.token = blockd_req.token; - blockd_resp.status = -16; /* EBUSY */ + blockd_resp.status = -6; /* ENXIO */ blockd_resp.bytes_transferred = 0; blockd_resp.data_len = 0; block_service_respond(&blockd_resp); } - continue; } - BLOCKD_TRACE("[blockd] no backend for dev=%u, responding ENXIO\n", - blockd_req.device); - memset(&blockd_resp, 0, sizeof(blockd_resp)); - blockd_resp.token = blockd_req.token; - blockd_resp.status = -6; /* ENXIO */ - blockd_resp.bytes_transferred = 0; - blockd_resp.data_len = 0; - block_service_respond(&blockd_resp); } /* Periodic diagnostic: check for stuck devices */ blob - 456e7a85b67fa5e0173d75de6c4ab51f05415a65 blob + 1bad6f66b5af635bf1c2652833377fde144c8ff6 --- third-party/ports/llvm/MERCURON_SUPPORT.md +++ third-party/ports/llvm/MERCURON_SUPPORT.md @@ -2,7 +2,7 @@ ## Overview -LLVM/Clang 21.1.5 needs configuration to recognize and support the Mercuron OS target: +LLVM/Clang 21.1.8 needs configuration to recognize and support the Mercuron OS target: - Target triple: `x86_64-unknown-mercuron` - Architecture: x86_64 - OS: Mercuron @@ -77,11 +77,11 @@ cmake -G "Ninja" \ ## Patches -If the upstream LLVM/Clang 21.1.5 doesn't fully recognize Mercuron, patches are needed: +If the upstream LLVM/Clang 21.1.8 doesn't fully recognize Mercuron, patches are needed: -- `patches/0001-add-mercuron-os-support.patch` - Add Mercuron OS type -- `patches/0002-clang-driver-mercuron.patch` - Clang driver support -- `patches/0003-mercuron-target-flags.patch` - Target-specific flags +- `patches/0001-add-mercuron-os-type.patch` - Add the Mercuron OS type +- `patches/0002-add-mercuron-triple-helper.patch` - Add `isOSMercuron()` +- `patches/0003-add-mercuron-driver-support.patch` - Add Clang driver support ## Verification blob - 5d532a8d31e279b227c985ba8064ed919ebf8eab blob + e106ad966922daf259bd41de5512006279c93af1 --- third-party/ports/llvm/build.sh +++ third-party/ports/llvm/build.sh @@ -1,101 +1,151 @@ -#!/bin/sh -# LLVM/Clang 21.1.5 build script for Mercuron cross-compiler +#!/usr/bin/env bash +# Lenix - Developed by lex0de (lex0de@tuta.com) +# lenix/third-party/ports/llvm/build.sh +# SPDX-License-Identifier: ISC -set -e +set -euo pipefail -# Determine script directory and project root -SCRIPT_DIR="$(cd "$(dirname "${0}")" 2>/dev/null && pwd)" -if [ -z "$SCRIPT_DIR" ]; then - SCRIPT_DIR="$(dirname "${0}")" -fi -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../../" && pwd)" +readonly ACTION="${1:-all}" +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +readonly SCRIPT_DIR +PROJECT_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd) +readonly PROJECT_ROOT -# Configuration -HOST_TRIPLE="x86_64-unknown-mercuron" -SYSROOT="${MERCURON_SYSROOT:-${PROJECT_ROOT}/third-party/build/sysroot}" -DISTFILES="${DISTFILES:-${PROJECT_ROOT}/third-party/build/distfiles}" -BUILD_WORK="${BUILD_WORK:-${PROJECT_ROOT}/third-party/build/build-work}" -PREFIX="/opt/llvm-mercuron" -VERSION="21.1.5" +# shellcheck source=third-party/ports/llvm/distinfo +source "$SCRIPT_DIR/distinfo" -# Compiler flags -CFLAGS="${CFLAGS:--O2 -pipe -fno-omit-frame-pointer}" -CXXFLAGS="${CXXFLAGS:--O2 -pipe -fno-omit-frame-pointer}" -LDFLAGS="${LDFLAGS:--L${SYSROOT}/usr/lib}" +readonly HOST_TRIPLE='x86_64-unknown-mercuron' +readonly SYSROOT="${MERCURON_SYSROOT:-${PROJECT_ROOT}/third-party/build/sysroot}" +readonly DISTFILES_DIR="${DISTFILES:-${PROJECT_ROOT}/third-party/build/distfiles}" +readonly BUILD_WORK="${BUILD_WORK:-${PROJECT_ROOT}/third-party/build/build-work}" +readonly PREFIX='/opt/llvm-mercuron' +readonly BOOTSTRAP_CC="${BOOTSTRAP_CC:-clang-21}" +readonly BOOTSTRAP_CXX="${BOOTSTRAP_CXX:-clang++-21}" +readonly JOBS="${JOBS:-$(nproc)}" +readonly ARCHIVE="${DISTFILES_DIR}/${LLVM_DISTFILE}" +readonly DOWNLOAD_FILE="${ARCHIVE}.part" +readonly BUILD_DIR="${BUILD_WORK}/llvm-${LLVM_VERSION}" +readonly NINJA_DIR="${BUILD_DIR}/build" +readonly PATCHES_DIR="${SCRIPT_DIR}/patches" +readonly SOURCE_BASE='https://github.com/llvm/llvm-project/releases/download' +readonly SOURCE_URL="${SOURCE_BASE}/llvmorg-${LLVM_VERSION}/${LLVM_DISTFILE}" -echo "=== Building LLVM/Clang ${VERSION} for Mercuron ===" -echo "PREFIX: ${PREFIX}" -echo "SYSROOT: ${SYSROOT}" -echo -# Step 1: Download (if not present) -if [ ! -f "${DISTFILES}/llvm-project-${VERSION}.src.tar.xz" ]; then - echo "Downloading LLVM ${VERSION}..." - cd "${DISTFILES}" - wget -q "https://github.com/llvm/llvm-project/releases/download/llvmorg-${VERSION}/llvm-project-${VERSION}.src.tar.xz" - cd - > /dev/null -fi +apply_patches() +{ + local patch_file -# Step 2: Extract -BUILD_DIR="${BUILD_WORK}/llvm-${VERSION}" -if [ -d "${BUILD_DIR}" ]; then - rm -rf "${BUILD_DIR}" -fi -mkdir -p "${BUILD_DIR}" -cd "${BUILD_DIR}" -tar -xf "${DISTFILES}/llvm-project-${VERSION}.src.tar.xz" --strip-components=1 + for patch_file in "$PATCHES_DIR"/*.patch; do + if [[ ! -f "$patch_file" ]]; then + continue + fi -# Step 3: Apply patches (if any) -PATCHES_DIR="${SCRIPT_DIR}/patches" -if [ -d "${PATCHES_DIR}" ] && [ "$(ls -A ${PATCHES_DIR})" ]; then - echo "Applying patches..." - for patch in ${PATCHES_DIR}/*.patch; do - if [ -f "$patch" ]; then - echo " Applying $(basename $patch)..." - if patch -p1 < "$patch"; then - echo " ✓ $(basename $patch) applied successfully" - else - echo " ⚠ $(basename $patch) failed (continuing without this patch)" - fi - fi - done - echo "Patch application complete (some patches may have been skipped)" -fi + echo "Applying $(basename "$patch_file")..." + patch -d "$BUILD_DIR" -p1 < "$patch_file" + done +} -# Step 3b: Create build directory -mkdir -p build -cd build -# Step 4: Configure with CMake -echo "Configuring LLVM/Clang..." -cmake -G "Ninja" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="${PREFIX}" \ - -DLLVM_ENABLE_PROJECTS="clang;lld;lldb" \ - -DLLVM_DEFAULT_TARGET_TRIPLE="${HOST_TRIPLE}" \ - -DLLVM_TARGETS_TO_BUILD="X86" \ - -DCMAKE_C_COMPILER="clang" \ - -DCMAKE_CXX_COMPILER="clang++" \ - -DCMAKE_C_FLAGS="${CFLAGS}" \ - -DCMAKE_CXX_FLAGS="${CXXFLAGS}" \ - ../llvm +build_llvm() +{ + download_source -# Step 5: Build -echo "Building LLVM/Clang (this may take a long time)..." -ninja -j$(nproc) + echo "Extracting LLVM $LLVM_VERSION..." + rm -rf "$BUILD_DIR" + mkdir -p "$BUILD_DIR" + tar -xf "$ARCHIVE" -C "$BUILD_DIR" --strip-components=1 + apply_patches -# Step 6: Install -echo "Installing LLVM/Clang to ${PREFIX}..." -ninja install + echo "Configuring LLVM $LLVM_VERSION..." + cmake -S "$BUILD_DIR/llvm" -B "$NINJA_DIR" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER="$BOOTSTRAP_CC" \ + -DCMAKE_CXX_COMPILER="$BOOTSTRAP_CXX" \ + -DCMAKE_C_FLAGS='-O2 -pipe -fno-omit-frame-pointer' \ + -DCMAKE_CXX_FLAGS='-O2 -pipe -fno-omit-frame-pointer' \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DLLVM_DEFAULT_TARGET_TRIPLE="$HOST_TRIPLE" \ + -DLLVM_ENABLE_PROJECTS='clang;lld;lldb' \ + -DLLVM_TARGETS_TO_BUILD='X86' -# Step 7: Create symlinks in sysroot -mkdir -p "${SYSROOT}/usr/bin" -for tool in clang clang++ clang-21 clang++-21 lld llvm-config; do - if [ -f "${PREFIX}/bin/${tool}" ]; then - ln -sf "${PREFIX}/bin/${tool}" "${SYSROOT}/usr/bin/${HOST_TRIPLE}-$(basename ${tool})" || true - fi -done + echo "Building LLVM $LLVM_VERSION with $JOBS jobs..." + ninja -C "$NINJA_DIR" -j "$JOBS" +} -echo "✓ LLVM/Clang ${VERSION} build complete!" -echo "PREFIX: ${PREFIX}" -echo "Verify with: ${PREFIX}/bin/clang --version" + +download_source() +{ + mkdir -p "$DISTFILES_DIR" + + if [[ ! -f "$ARCHIVE" ]]; then + echo "Downloading $LLVM_DISTFILE..." + rm -f "$DOWNLOAD_FILE" + wget --output-document="$DOWNLOAD_FILE" "$SOURCE_URL" + mv "$DOWNLOAD_FILE" "$ARCHIVE" + fi + + echo "Verifying $LLVM_DISTFILE..." + printf '%s %s\n' "$LLVM_SHA256" "$ARCHIVE" | sha256sum --check - +} + + +install_llvm() +{ + local tool + + if (( EUID != 0 )); then + echo 'error: the LLVM install action must run as root' >&2 + return 1 + fi + + if [[ ! -f "$NINJA_DIR/build.ninja" ]]; then + echo "error: LLVM $LLVM_VERSION has not been built" >&2 + echo "error: expected $NINJA_DIR/build.ninja" >&2 + return 1 + fi + + echo "Installing LLVM $LLVM_VERSION under $PREFIX..." + ninja -C "$NINJA_DIR" install + + mkdir -p "$SYSROOT/usr/bin" + for tool in clang clang++ clang-21 clang++-21 lld llvm-config; do + if [[ -f "$PREFIX/bin/$tool" ]]; then + ln -sf "$PREFIX/bin/$tool" \ + "$SYSROOT/usr/bin/$HOST_TRIPLE-$tool" + fi + done +} + + +main() +{ + if (( $# > 1 )); then + echo 'usage: build.sh [all|build|install]' >&2 + return 1 + fi + + case "$ACTION" in + all|build|install) + ;; + *) + echo 'usage: build.sh [all|build|install]' >&2 + return 1 + ;; + esac + + case "$ACTION" in + all) + build_llvm + install_llvm + ;; + build) + build_llvm + ;; + install) + install_llvm + ;; + esac +} + + +main "$@" blob - a75b86c604d3e0508b6a12cf2de7f3afd151ce4f blob + 8a65d9a8b77b0fe11cd8ca9dd565a6542e17b219 --- third-party/ports/llvm/distinfo +++ third-party/ports/llvm/distinfo @@ -1,8 +1,7 @@ -# LLVM/Clang 21.1.5 distinfo -# Source: https://github.com/llvm/llvm-project/releases -# -# Download the llvm-project source tarball for version 21.1.5 -# Format: filename version sha256 +# Lenix - Developed by lex0de (lex0de@tuta.com) +# lenix/third-party/ports/llvm/distinfo +# SPDX-License-Identifier: ISC -LLVM_VERSION=21.1.5 -DISTFILES=llvm-project-${LLVM_VERSION}.src.tar.xz +LLVM_VERSION='21.1.8' +LLVM_DISTFILE="llvm-project-${LLVM_VERSION}.src.tar.xz" +LLVM_SHA256='4633a23617fa31a3ea51242586ea7fb1da7140e426bd62fc164261fe036aa142' blob - /dev/null blob + fd2e5438c8bfb7f5ad0d0be98dcd3fe5220addd6 (mode 755) --- /dev/null +++ setup.sh @@ -0,0 +1,248 @@ +#!/usr/bin/env bash +# Lenix - Developed by lex0de (lex0de@tuta.com) +# lenix/setup.sh +# SPDX-License-Identifier: ISC + +set -euo pipefail + +readonly REQUIRED_ARCH='amd64' +readonly REQUIRED_CLANG_VERSION='21.1.8' +readonly REQUIRED_OS='ubuntu' +readonly REQUIRED_VERSION='26.04' + +readonly -a PACKAGES=( + autoconf + automake + bear + bison + build-essential + ca-certificates + ccache + clang + clang-21 + clang-tidy + clang-tidy-21 + cmake + cpio + cppcheck + dosfstools + e2fsprogs + flex + gdb + git + grub-efi-amd64-bin + grub-pc-bin + grub2-common + libcurl4-openssl-dev + libedit-dev + libffi-dev + liblzma-dev + libncurses-dev + libtool + libtool-bin + libxml2-dev + libzstd-dev + lld + lld-21 + llvm + llvm-21 + meson + mtools + nasm + ninja-build + ovmf + patch + pkg-config + python3 + python3-dev + qemu-efi-aarch64 + qemu-system-arm + qemu-system-x86 + qemu-utils + software-properties-common + swig + unzip + wget + xorriso + xz-utils + zip + zlib1g-dev +) + +readonly -a REQUIRED_COMMANDS=( + autoconf + automake + bear + ccache + clang-21 + clang++-21 + clang-tidy + cmake + cpio + cppcheck + git + grub-mkrescue + ld.lld + libtool + llvm-ar + llvm-objcopy + meson + mformat + mke2fs + mkfs.fat + nasm + ninja + patch + pkg-config + python3 + qemu-system-aarch64 + qemu-system-x86_64 + wget + xorriso +) + + +configure_ovmf_paths() +{ + local code_source='/usr/share/OVMF/OVMF_CODE_4M.fd' + local code_target='/usr/share/ovmf/x64/OVMF_CODE.4m.fd' + local vars_source='/usr/share/OVMF/OVMF_VARS_4M.fd' + local vars_target='/usr/share/ovmf/x64/OVMF_VARS.4m.fd' + + install -d -m 0755 '/usr/share/ovmf/x64' + create_compatibility_link "$code_source" "$code_target" + create_compatibility_link "$vars_source" "$vars_target" +} + + +create_compatibility_link() +{ + local source_path="$1" + local target_path="$2" + local resolved_path + + if [[ ! -f "$source_path" ]]; then + echo "error: required OVMF file is missing: $source_path" >&2 + return 1 + fi + + if [[ -e "$target_path" || -L "$target_path" ]]; then + resolved_path=$(readlink -f "$target_path" || true) + if [[ "$resolved_path" == "$source_path" ]]; then + return 0 + fi + + echo "error: refusing to replace existing path: $target_path" >&2 + return 1 + fi + + ln -s "$source_path" "$target_path" +} + + +enable_universe() +{ + if apt-cache show clang-21 >/dev/null 2>&1; then + return 0 + fi + + echo 'Enabling the Ubuntu universe repository...' + add-apt-repository -y universe + apt-get update +} + + +install_packages() +{ + export DEBIAN_FRONTEND='noninteractive' + + echo 'Updating Ubuntu package metadata...' + apt-get update + apt-get install -y --no-install-recommends software-properties-common + enable_universe + + echo 'Installing Lenix build and QEMU test dependencies...' + apt-get install -y --no-install-recommends "${PACKAGES[@]}" +} + + +validate_clang() +{ + local clang_version + + clang_version=$(clang-21 --version | sed -nE \ + 's/^(Ubuntu )?clang version ([^ ]+).*/\2/p' | head -n 1) + if [[ -z "$clang_version" ]]; then + echo 'error: unable to determine the installed Clang version' >&2 + return 1 + fi + + if [[ "$clang_version" != "$REQUIRED_CLANG_VERSION" ]]; then + echo "error: Lenix requires Clang $REQUIRED_CLANG_VERSION; found $clang_version" >&2 + return 1 + fi +} + + +validate_commands() +{ + local command_name + local failed=0 + + for command_name in "${REQUIRED_COMMANDS[@]}"; do + if ! command -v "$command_name" >/dev/null 2>&1; then + echo "error: required command is missing: $command_name" >&2 + failed=1 + fi + done + + if (( failed != 0 )); then + return 1 + fi +} + + +validate_host() +{ + local host_arch + + if [[ ! -r /etc/os-release ]]; then + echo 'error: /etc/os-release is unavailable' >&2 + return 1 + fi + + # shellcheck disable=SC1091 + source /etc/os-release + if [[ "${ID:-}" != "$REQUIRED_OS" || \ + "${VERSION_ID:-}" != "$REQUIRED_VERSION" ]]; then + echo "error: Ubuntu $REQUIRED_VERSION is required" >&2 + return 1 + fi + + host_arch=$(dpkg --print-architecture) + if [[ "$host_arch" != "$REQUIRED_ARCH" ]]; then + echo "error: Ubuntu $REQUIRED_VERSION $REQUIRED_ARCH is required" >&2 + return 1 + fi + + if (( EUID != 0 )); then + echo 'error: run this setup script as root' >&2 + return 1 + fi +} + + +main() +{ + validate_host + install_packages + configure_ovmf_paths + validate_commands + validate_clang + + echo 'Lenix Ubuntu 26.04 build and QEMU test environment is ready.' + echo 'The custom /opt/llvm-mercuron toolchain was not built.' +} + + +main "$@"