Hardware Abstraction Layer

Core HAL trait, CPU, MMU, interrupts, firmware, KASLR, and ELF relocation.

Documentation

Core HAL Trait

The Hardware Abstraction Layer (helix-hal) is the kernel's interface to bare metal. It contains 136 source files totaling approximately 51,300 lines of Rust code, spanning three CPU architectures.

The root HAL trait aggregates four sub-traits into a single interface that the rest of the kernel programs against:

hal/src/lib.rs
rust
pub trait HardwareAbstractionLayer: Send + Sync + 'static {
2
type Cpu: CpuAbstraction;
3
type Mmu: MmuAbstraction;
4
type InterruptController: InterruptController;
5
type Firmware: FirmwareInterface;
6
fn cpu(&self) -> &Self::Cpu;
fn mmu(&self) -> &Self::Mmu;
fn interrupt_controller(&self) -> &Self::InterruptController;
fn firmware(&self) -> &Self::Firmware;
11
fn arch_name(&self) -> &'static str;
fn early_init(&mut self) -> HalResult<()>;
fn init(&mut self) -> HalResult<()>;
fn halt(&self) -> !;
fn reboot(&self) -> !;
fn shutdown(&self) -> !;
18
}
19
3 refs
pub type HalResult<T> = Result<T, HalError>;
Index

The concrete implementation is selected at compile time via #[cfg(target_arch)] — there is zero runtime dispatch for architecture selection. The kernel binary contains code for exactly one architecture.

Trait Hierarchy

HAL Trait Hierarchy5N · 4E
extendsextendsextendsextendsHalImplRoot HAL trait — agg…4CpuHalCPU control1InterruptHalInterrupt controller1MmuHalPage table managemen…1TimerHalTimer services1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

CPU Abstraction

The CpuHal trait provides low-level CPU control:

hal/src/cpu.rs
rust
pub trait CpuAbstraction: Send + Sync {
2
type Context: CpuContext;
3
fn enable_interrupts(&self);
fn disable_interrupts(&self);
fn interrupts_enabled(&self) -> bool;
fn halt_until_interrupt(&self);
8
fn current_cpu_id(&self) -> u32;
fn cpu_count(&self) -> u32;
fn read_register(&self, reg: &str) -> u64;
unsafe fn write_register(&self, reg: &str, value: u64);
13
unsafe fn context_switch(&self, old: &mut Self::Context, new: &Self::Context);
15
}
Index

CPU Context

Saved register state for context switches:

hal/src/cpu.rs
rust
pub trait CpuContext: Clone + Default + Send {
fn instruction_pointer(&self) -> u64;
fn stack_pointer(&self) -> u64;
fn set_instruction_pointer(&mut self, ip: u64);
fn set_stack_pointer(&mut self, sp: u64);
6
}
Index

SMP Startup

Bringing up secondary CPUs (Application Processors) follows a platform-specific protocol:

ArchitectureProtocolMechanism
x86_64INIT-SIPI-SIPIAPIC IPI to wake AP, trampoline at known address
AArch64PSCIARM Power State Coordination Interface calls
RISC-VSBI HSMHart State Management SBI extension

The startup sequence:

  1. BSP discovers CPU topology from ACPI/DeviceTree
  2. BSP allocates per-CPU stacks and data areas
  3. BSP sends startup IPI to each AP
  4. APs execute trampoline code: set up GDT/IDT, enable paging
  5. APs jump to ap_entry() and register with the scheduler
  6. BSP waits for all APs to report ready

MMU and Page Table

The MmuHal trait abstracts memory management:

hal/src/mmu.rs
rust
pub trait MmuAbstraction: Send + Sync {
2
type PageTable: PageTable;
3
type Asid: Copy + Eq;
4
fn create_page_table(&self) -> HalResult<Self::PageTable>;
fn kernel_page_table(&self) -> &Self::PageTable;
fn active_asid(&self) -> Self::Asid;
fn allocate_asid(&self) -> HalResult<Self::Asid>;
unsafe fn switch_page_table(&self, table: &Self::PageTable, asid: Self::Asid);
fn flush_tlb_page(&self, vaddr: VirtAddr);
fn flush_tlb_all(&self);
12
}
13
5 refs
pub trait PageTable {
fn map(&mut self, vaddr: VirtAddr, paddr: PhysAddr,
16
flags: PageFlags, page_size: PageSize) -> HalResult<()>;
fn unmap(&mut self, vaddr: VirtAddr) -> HalResult<PhysAddr>;
fn translate(&self, vaddr: VirtAddr) -> Option<PhysAddr>;
fn update_flags(&mut self, vaddr: VirtAddr, flags: PageFlags) -> HalResult<()>;
20
}
Index

Page Table Formats

ArchitectureFormatLevelsPage SizesAddress Bits
x86_644-levelPML4 → PDPT → PD → PT4 KB, 2 MB, 1 GB48-bit virtual
AArch644-level (4KB granule)L0 → L1 → L2 → L34 KB, 2 MB, 1 GB48-bit virtual
RISC-VSv39 / Sv48 / Sv573/4/5-level4 KB, 2 MB, 1 GB39/48/57-bit

x86_64 Page Table Entry

x86_64 Page Table Entry — Bit Layout13N · 12E
PPresent (bit 0)1R/WRead/Write (bit 1)2U/SUser/Supervisor (bit…2PWTPage Write-Through (…2PCDPage Cache Disable (…2AAccessed (bit 5)2DDirty (bit 6)2PATPage Attribute Table…2GGlobal (bit 8)2AVLAvailable (9:11)2Physical AddressPhysical address (12…2AVLAvailable (52:62)2NXENo-Execute (bit 63)1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

TLB Management

TLB (Translation Lookaside Buffer) flushes are critical for correctness when page tables change:

Operationx86_64AArch64RISC-V
Single page flushinvlpgTLBI VAE1sfence.vma addr
Full TLB flushReload CR3TLBI VMALLE1sfence.vma
Remote TLB flushIPI + invlpgTLBI VAE1IS (Inner Shareable)IPI + sfence.vma

Interrupt Controller

The InterruptHal trait abstracts the platform's interrupt controller:

hal/src/interrupt.rs
rust
7 refs
pub type InterruptVector = u8;
2
pub trait InterruptController: Send + Sync {
fn init(&mut self) -> HalResult<()>;
fn enable(&mut self);
fn disable(&mut self);
fn enable_interrupt(&mut self, vector: InterruptVector) -> HalResult<()>;
fn disable_interrupt(&mut self, vector: InterruptVector) -> HalResult<()>;
fn set_priority(&mut self, vector: InterruptVector, priority: u8) -> HalResult<()>;
fn acknowledge(&mut self, vector: InterruptVector);
fn send_ipi(&mut self, target: IpiTarget, vector: InterruptVector) -> HalResult<()>;
fn pending_interrupt(&self) -> Option<InterruptVector>;
13
}
14
2 refs
pub enum IpiTarget {
16
Current, Cpu(usize), AllOthers, All,
17
}
Index

Platform Implementations

ArchitectureControllerVectorsFeatures
x86_64Legacy PIC (8259)16 IRQsCascaded, edge-triggered
x86_64Local APIC + I/O APIC256 vectorsMSI, per-CPU, priority
AArch64GICv21020 IRQsDistributor + CPU interface
AArch64GICv31020+ IRQsITS, LPI, affinity routing
RISC-VCLINTTimer + softwarePer-hart timer/IPI
RISC-VPLICExternal IRQsPriority, threshold, claim/complete

x86_64 IDT Setup

The Interrupt Descriptor Table maps each vector (0-255) to a handler:

x86_64 IDT Vector Map5N · 4E
int 0x80CPU ExceptionsVectors 0–311Legacy PIC IRQsVectors 32–472APIC IRQs / IPIsVectors 48–2543Syscall (legacy)Vector 1281SpuriousVector 2551
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

Each IDT entry specifies:

  • Handler address (64-bit)
  • Code segment selector
  • IST (Interrupt Stack Table) index for stack switching
  • DPL (Descriptor Privilege Level)
  • Gate type (Interrupt gate — clears IF; Trap gate — preserves IF)

Timer Abstraction

The TimerHal trait provides platform-independent timing:

hal/src/timer.rs
rust
pub trait TimerHal {
2
/// Read the current counter value.
fn read_counter(&self) -> u64;
4
5
/// Return the timer frequency in Hz.
2 refs
fn frequency(&self) -> u64;
7
8
/// Set a one-shot deadline interrupt.
fn set_deadline(&mut self, ticks: u64);
10
11
/// Convert ticks to nanoseconds.
fn ticks_to_ns(&self, ticks: u64) -> u64;
13
14
/// Convert nanoseconds to ticks.
fn ns_to_ticks(&self, ns: u64) -> u64;
16
}
Index

Timer Sources by Architecture

ArchitectureTimerResolutionFeatures
x86_64TSC (Time Stamp Counter)~1 nsPer-core, invariant on modern CPUs
x86_64HPET100 nsSystem-wide, multiple comparators
x86_64APIC TimerVariablePer-core, one-shot or periodic
x86_64PIT (8254)~838 nsLegacy, single channel
AArch64Generic Timer~40 nsSystem counter + per-core virtual timer
RISC-VSBI TimerVariableSBI set_timer extension

The HAL selects the best available timer at boot time. TSC is preferred on x86_64 if the invariant_tsc CPUID flag is present.


Firmware Interface

The HAL provides access to firmware services discovered during boot:

ACPI (x86_64 / AArch64)

hal/src/firmware/acpi.rs
rust
pub struct AcpiInterface {
pub fn rsdp(&self) -> Option<&Rsdp>;
pub fn find_table<T: AcpiTable>(&self, signature: &[u8; 4]) -> Option<&T>;
pub fn madt(&self) -> Option<&Madt>; // APIC topology
pub fn hpet(&self) -> Option<&Hpet>; // HPET table
pub fn fadt(&self) -> Option<&Fadt>; // Fixed ACPI Description
7
}
Index

Device Tree (RISC-V / AArch64)

hal/src/firmware/device_tree.rs
rust
pub struct DeviceTree {
pub fn root(&self) -> DtNode;
pub fn find_node(&self, path: &str) -> Option<DtNode>;
pub fn find_compatible(&self, compatible: &str) -> Vec<DtNode>;
pub fn memory_regions(&self) -> Vec<MemoryRegion>;
pub fn cpu_nodes(&self) -> Vec<DtNode>;
7
}
Index

SBI (RISC-V)

hal/src/firmware/sbi.rs
rust
pub struct SbiInterface {
pub fn console_putchar(c: u8);
pub fn set_timer(deadline: u64);
pub fn hart_start(hartid: usize, start_addr: usize, opaque: usize);
pub fn hart_stop();
pub fn hart_get_status(hartid: usize) -> HartStatus;
7
}
Index

KASLR

Kernel Address Space Layout Randomization randomizes the kernel's load address on every boot, making exploits harder.

Implementation (847 lines)

KASLR — Address Randomization Flow7N · 6E
Boot EntryKernel loaded by boo…1Read EntropyRDRAND/RDSEED or tim…2Generate OffsetRandom 2 MB-aligned …2Calculate New BaseKERNEL_VIRT_BASE + o…2Update Page TablesRemap kernel at new …2Relocate ImageELF relocation proce…2Jump to New KernelContinue at randomiz…1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

Entropy Sources

SourceArchitectureQualityFallback
RDRANDx86_64HighYes (hardware RNG)
RDSEEDx86_64HighYes (true RNG)
Timer jitterAllMediumPrimary fallback
Boot timestampAllLowLast resort

ELF Relocation

The relocation engine (in helix-relocation, 12 files, ~2,000 lines) processes ELF relocations at boot time for PIE (Position-Independent Executable) kernels.

Supported Relocations

TypeArchitectureDescription
R_X86_64_RELATIVEx86_64Base + Addend
R_X86_64_64x86_64Symbol + Addend
R_X86_64_GLOB_DATx86_64Symbol address → GOT
R_AARCH64_RELATIVEAArch64Base + Addend
R_AARCH64_ABS64AArch64Symbol + Addend
R_RISCV_64RISC-VSymbol + Addend
R_RISCV_RELATIVERISC-VBase + Addend

Relocation Process

subsystems/relocation/src/engine.rs
rust
pub struct RelocationEngine {
pub fn new(base: usize, load_offset: isize) -> Self;
pub fn process_rela(&mut self, rela: &[Rela64]) -> Result<usize>;
pub fn process_dynamic(&mut self, dynamic: &[Dyn64]) -> Result<()>;
pub fn validate(&self) -> Result<()>;
6
}
Index

The engine processes all .rela.dyn entries, applying the load offset to each. Validation ensures no relocations point outside the kernel image.


Architecture Backends

Each architecture provides a complete HAL implementation. Here's the file structure:

x86_64 Backend

x86_64 Backend — Source Tree13N · 12E
hal/src/x86_64/x86_64 HAL backend12mod.rsX86_64Hal implementi…1cpu.rsMSR, CR, CPUID, priv…1gdt.rsGlobal Descriptor Ta…1idt.rsInterrupt Descriptor…1pic.rsLegacy 8259 PIC1apic.rsLocal APIC + I/O API…1paging.rs4-level page tables …1tss.rsTask State Segment (…1timer/Timer subsystem1smp.rsAP startup via INIT-…1serial.rsCOM1/COM2 serial por…1boot.rsx86_64-specific earl…1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

AArch64 Backend

AArch64 Backend — Source Tree10N · 9E
hal/src/aarch64/AArch64 HAL backend9mod.rsAArch64Hal implement…1cpu.rsSystem registers, ex…1gic_v2.rsGICv2 distributor + …1gic_v3.rsGICv3 with ITS, LPI …1mmu.rs4-level page tables …1timer.rsGeneric timer (CNTPC…1smp.rsPSCI-based AP startu…1exception.rsException vector tab…1boot.rsAArch64 early boot1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

RISC-V Backend

RISC-V Backend — Source Tree10N · 9E
hal/src/riscv64/RISC-V HAL backend9mod.rsRiscv64Hal implement…1cpu.rsCSR access, privileg…1clint.rsCore Local Interrupt…1plic.rsPlatform-Level Inter…1mmu.rsSv39/Sv48/Sv57 page …1timer.rsSBI timer extension1smp.rsSBI HSM hart managem…1trap.rsTrap handler (sync +…1boot.rsRISC-V early boot1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

Architecture Comparison

Featurex86_64AArch64RISC-V
Source files~60~35~30
Estimated lines~25,000~14,000~12,000
Page table levels4 (PML4)4 (4KB)3-5 (Sv39-57)
Interrupt controllerAPICGICv3PLIC
TimerTSC/HPET/APICGeneric TimerSBI Timer
SMP startupINIT-SIPIPSCISBI HSM
Serial outputCOM1 (0x3F8)PL011 UARTSBI console
KASLR entropyRDRAND/RDSEEDTimer jitterTimer jitter