Developer Guide

This page provides concrete procedures for extending the kernel. Each section lists every file that must be modified, in the order modifications should be applied.

Adding a New System Call

Syscall numbers are sequential. The next available number follows the last entry in the Syscall enum.

Files to Modify

  1. kernite/src/syscall/mod.rs

    1. Check the Syscall enum in kernite/src/syscall/mod.rs for the next available slot, then add a variant with that number.

    2. Add the variant to the TryFrom<u64> implementation.

    3. Add a dispatch arm in syscall_handle_rust() that handles the new number.

  2. lib/trona/uapi/consts/kernel.rs

    1. Add a matching constant (e.g., pub const SYS_MY_NEW_CALL: u64 = N; where N is the slot identified above).

  3. lib/trona/substrate/syscall.rs

    1. Add a raw inline-assembly syscall wrapper function that userspace calls.

  4. docs/spec/syscalls.md

    1. Document the syscall number, arguments, return values, and error codes.

  5. docs/spec/abi.md (if the ABI changes)

    1. Update register conventions if the new syscall uses a non-standard argument layout.

Checklist

  • Enum variant and TryFrom in syscall/mod.rs

  • Dispatch arm in syscall_handle_rust()

  • Matching constant in uapi/consts/kernel.rs

  • Raw wrapper in substrate/syscall.rs

  • Spec documentation updated

  • just build succeeds

  • just run — new syscall reachable and returns expected results

  • just run --smp 2 — verify no SMP regression

Adding a New Capability Invocation

Invoke labels are grouped by object type. Each group has a reserved range (e.g., CNode 0x10-0x18, TCB 0x40-0x4F).

Files to Modify

  1. lib/trona/uapi/consts/kernel.rs

    1. Add the invoke label constant in the appropriate group (e.g., pub const TCB_MY_OP: u64 = 0x50;).

    2. Ensure the label does not collide with existing labels in the same range.

  2. kernite/src/syscall/mod.rs

    1. Add a dispatch arm in handle_invoke() under the matching ObjectType case.

    2. Implement the operation logic (or call a helper function in the object’s module).

  3. lib/trona/substrate/invoke.rs

    1. Add a typed wrapper function that constructs the IPC message and calls sys_invoke().

  4. Protocol file (if the invocation is a server-facing IPC label, not a kernel object operation):

    1. Add the label to the appropriate lib/trona/uapi/protocol/*.rs file.

Label Range Conventions

Object Type Range

CNode

0x10-0x18

Untyped

0x20-0x21

SchedContext

0x30-0x31

TCB

0x40-0x4F (+ 0x80 for introspection extensions outside the saturated block)

VSpace

0x50-0x5F (+ 0x81-0x82 for accounting extensions)

IRQ

0x60-0x64

IoPort

0x70-0x77

MemoryObject

0x90-0x99

VSpace MO mapping

0x97, 0x99, 0x9A (label shared with MO namespace — dispatched by capability object type)

Label 0x97 is a shared dispatch point: when invoked on a MemoryObject cap it dispatches as MO_HAS_PAGE; when invoked on a VSpace cap it dispatches as VSPACE_MAP_MO. Type-based dispatch is the mechanism — the label value alone does not determine the operation.

Adding a New Kernel Object Type

This is a rare, high-impact change.

Files to Modify

  1. kernite/src/cap/object.rs

    1. Add a variant to ObjectType with the next available discriminant.

  2. kernite/src/cap/refcount.rs

    1. Add a destruction case in destroy_object() for the new type.

  3. kernite/src/cap/untyped.rs

    1. Add a size computation case in object_size().

    2. Add an initialization case in init_object().

    3. If the object needs metadata arrays (like Frame, VSpace, Untyped), add array allocation in init_metadata().

  4. lib/trona/uapi/consts/kernel.rs

    1. Add pub const OBJ_MY_TYPE: u64 = N; matching the discriminant.

  5. kernite/src/syscall/mod.rs

    1. Add invoke dispatch cases for the new object’s operations.

Invariants

  • KernelObject header must be the first field (#[repr©]).

  • The object must implement cleanup logic in destroy_object().

  • Retyped objects receive CapRights::ALL, depth 0, badge 0.

Adding a New Userland Program

Subsystem-specific services use dedicated paths. POSIX services go under servers/posix/; Win32 services go under servers/win32/. The userland/<domain>/<name>/ layout below applies to subsystem-agnostic or test programs.

Files to Create

  1. userland/<domain>/<name>/src/main.rs

    1. ![no_std] and ![no_main].

    2. Entry point via trona substrate (_startmain).

  2. userland/<domain>/<name>/arch/x86_64/link.ld and arch/aarch64/link.ld

    1. Linker scripts (copy from an existing program like test_runner).

  3. userland/<domain>/<name>/meson.build

    1. Build definition (copy pattern from userland/tests/test_runner/meson.build).

  4. userland/services/<name>.service

    1. Service descriptor: [Service] (name, binary, type, restart policy) and [Dependencies] (After/Before ordering).

Files to Modify

  1. userland/meson.build

    1. Add subdir('<domain>/<name>').

  2. tools/mkcpio.py

    1. Add the ELF binary and service file entries to the initrd manifest.

Build

After adding all files:

just distclean && just setup && just build

The find command in kernite/meson.build auto-discovers .rs source files only during meson setup. New source files require a full reconfigure.

Adding a New Subsystem Personality Server

A subsystem personality server provides the runtime environment for a specific ABI family (e.g., POSIX, Win32).

  1. Choose the placement path. POSIX personality servers go under servers/posix/. Win32 personality servers go under servers/win32/.

  2. Register the subsystem ID. Add the subsystem ID constant to lib/trona/uapi/consts/kernel.rs (e.g., pub const SUBSYSTEM_MY_PERSONALITY: u64 = N;). The kernel’s procmgr uses this ID to select the personality server for new processes.

  3. Create the service file. Add a .service descriptor under userland/services/ that declares the server’s binary, type, and a [Dependencies] block with After=procmgr.service (and any other personality-specific prerequisites).

  4. Wire into procmgr dispatch. In the procmgr server, add a dispatch arm that routes new-process requests carrying the subsystem ID to this server.

Adding a New Kernel Source File

  1. Create the .rs file in the appropriate kernite/src/ subdirectory.

  2. Add mod my_module; to the parent module’s mod.rs or lib.rs.

  3. Reconfigure: just distclean && just setup && just build.

No Cargo.toml modifications — this project does not use Cargo.

Adding Architecture-Specific Code

Pattern

Architecture-specific code lives under kernite/src/arch/x86_64/ and kernite/src/arch/aarch64/. Generic code consumes it through re-exports in arch/mod.rs.

To add a new architecture-specific function:

  1. Implement in both arch/x86_64/<module>.rs and arch/aarch64/<module>.rs.

  2. Add pub use re-exports in arch/mod.rs under the appropriate #[cfg(target_arch)] blocks.

  3. Generic code calls crate::arch::my_function().

Testing Both Architectures

just setup && just build              # x86_64 (default)
just arch=aarch64 setup && just arch=aarch64 build   # aarch64

Build System Notes

  • All Rust code is compiled via Meson with direct rustc invocation — no Cargo.

  • Rust flags live in meson.build, not .cargo/config.toml.

  • Kernel uses custom JSON target specs (kernite/x86_64-saltyos.json, kernite/aarch64-saltyos.json).

  • Userland uses built-in rustc targets (x86_64-unknown-saltyos, aarch64-unknown-saltyos) from the custom toolchain.

  • Clang is enforced — gcc will not work.