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:
pub trait HardwareAbstractionLayer : Send + Sync + 'static {
type Cpu : CpuAbstraction ;
type Mmu : MmuAbstraction ;
type InterruptController : InterruptController ;
type Firmware : FirmwareInterface ;
fn cpu ( & self ) -> & Self :: Cpu ;
fn mmu ( & self ) -> & Self :: Mmu ;
fn interrupt_controller ( & self ) -> & Self :: InterruptController ;
fn firmware ( & self ) -> & Self :: Firmware ;
fn arch_name ( & self ) -> & 'static str ;
fn early_init ( & mut self ) -> HalResult < ( ) > ;
fn init ( & mut self ) -> HalResult < ( ) > ;
pub type HalResult < T > = Result < T , HalError > ;
Index HardwareAbstractionLayer cpu mmu interrupt_controller firmware arch_name early_init init halt reboot shutdown HalResult
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.
HAL Trait Hierarchy 5N · 4E extends extends extends extends ▶ HalImpl Root HAL trait — agg… 4 CpuHal CPU control 1 InterruptHal Interrupt controller 1 MmuHal Page table managemen… 1 TimerHal Timer services 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
The CpuHal trait provides low-level CPU control:
pub trait CpuAbstraction : Send + Sync {
type Context : CpuContext ;
fn enable_interrupts ( & self ) ;
fn disable_interrupts ( & self ) ;
fn interrupts_enabled ( & self ) -> bool ;
fn halt_until_interrupt ( & self ) ;
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 ) ;
unsafe fn context_switch ( & self , old : & mut Self :: Context , new : & Self :: Context ) ;
Index CpuAbstraction enable_interrupts disable_interrupts interrupts_enabled halt_until_interrupt current_cpu_id cpu_count read_register write_register context_switch
Saved register state for context switches:
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 ) ;
Index CpuContext instruction_pointer stack_pointer set_instruction_pointer set_stack_pointer
Bringing up secondary CPUs (Application Processors) follows a platform-specific protocol:
Architecture Protocol Mechanism x86_64 INIT-SIPI-SIPI APIC IPI to wake AP, trampoline at known address AArch64 PSCI ARM Power State Coordination Interface calls RISC-V SBI HSM Hart State Management SBI extension
The startup sequence:
BSP discovers CPU topology from ACPI/DeviceTree
BSP allocates per-CPU stacks and data areas
BSP sends startup IPI to each AP
APs execute trampoline code: set up GDT/IDT, enable paging
APs jump to ap_entry() and register with the scheduler
BSP waits for all APs to report ready
The MmuHal trait abstracts memory management:
pub trait MmuAbstraction : Send + Sync {
type PageTable : PageTable ;
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 map ( & mut self , vaddr : VirtAddr , paddr : PhysAddr ,
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 < ( ) > ;
Index MmuAbstraction create_page_table kernel_page_table active_asid allocate_asid switch_page_table flush_tlb_page flush_tlb_all PageTable map unmap translate update_flags
Architecture Format Levels Page Sizes Address Bits x86_64 4-level PML4 → PDPT → PD → PT 4 KB, 2 MB, 1 GB 48-bit virtual AArch64 4-level (4KB granule) L0 → L1 → L2 → L3 4 KB, 2 MB, 1 GB 48-bit virtual RISC-V Sv39 / Sv48 / Sv57 3/4/5-level 4 KB, 2 MB, 1 GB 39/48/57-bit
x86_64 Page Table Entry — Bit Layout 13N · 12E P Present (bit 0) 1 R/W Read/Write (bit 1) 2 U/S User/Supervisor (bit… 2 PWT Page Write-Through (… 2 PCD Page Cache Disable (… 2 A Accessed (bit 5) 2 D Dirty (bit 6) 2 PAT Page Attribute Table… 2 G Global (bit 8) 2 AVL Available (9:11) 2 Physical Address Physical address (12… 2 AVL Available (52:62) 2 NXE No-Execute (bit 63) 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
TLB (Translation Lookaside Buffer) flushes are critical for correctness when page tables change:
Operation x86_64 AArch64 RISC-V Single page flush invlpgTLBI VAE1sfence.vma addrFull TLB flush Reload CR3 TLBI VMALLE1sfence.vmaRemote TLB flush IPI + invlpg TLBI VAE1IS (Inner Shareable)IPI + sfence.vma
The InterruptHal trait abstracts the platform's interrupt controller:
pub type InterruptVector = u8 ;
pub trait InterruptController : Send + Sync {
fn init ( & mut self ) -> HalResult < ( ) > ;
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 > ;
Current , Cpu ( usize ) , AllOthers , All ,
Index InterruptVector InterruptController init enable disable enable_interrupt disable_interrupt set_priority acknowledge send_ipi pending_interrupt IpiTarget
Architecture Controller Vectors Features x86_64 Legacy PIC (8259) 16 IRQs Cascaded, edge-triggered x86_64 Local APIC + I/O APIC 256 vectors MSI, per-CPU, priority AArch64 GICv2 1020 IRQs Distributor + CPU interface AArch64 GICv3 1020+ IRQs ITS, LPI, affinity routing RISC-V CLINT Timer + software Per-hart timer/IPI RISC-V PLIC External IRQs Priority, threshold, claim/complete
The Interrupt Descriptor Table maps each vector (0-255) to a handler:
x86_64 IDT Vector Map 5N · 4E int 0x80 ▶ CPU Exceptions Vectors 0–31 1 Legacy PIC IRQs Vectors 32–47 2 APIC IRQs / IPIs Vectors 48–254 3 Syscall (legacy) Vector 128 1 ■ Spurious Vector 255 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
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)
The TimerHal trait provides platform-independent timing:
/// Read the current counter value.
fn read_counter ( & self ) -> u64 ;
/// Return the timer frequency in Hz.
fn frequency ( & self ) -> u64 ;
/// Set a one-shot deadline interrupt.
fn set_deadline ( & mut self , ticks : u64 ) ;
/// Convert ticks to nanoseconds.
fn ticks_to_ns ( & self , ticks : u64 ) -> u64 ;
/// Convert nanoseconds to ticks.
fn ns_to_ticks ( & self , ns : u64 ) -> u64 ;
Index TimerHal read_counter frequency set_deadline ticks_to_ns ns_to_ticks
Architecture Timer Resolution Features x86_64 TSC (Time Stamp Counter) ~1 ns Per-core, invariant on modern CPUs x86_64 HPET 100 ns System-wide, multiple comparators x86_64 APIC Timer Variable Per-core, one-shot or periodic x86_64 PIT (8254) ~838 ns Legacy, single channel AArch64 Generic Timer ~40 ns System counter + per-core virtual timer RISC-V SBI Timer Variable SBI 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.
The HAL provides access to firmware services discovered during boot:
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
Index AcpiInterface rsdp find_table madt hpet fadt
hal/src/firmware/device_tree.rs
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 > ;
Index DeviceTree root find_node find_compatible memory_regions cpu_nodes
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_get_status ( hartid : usize ) -> HartStatus ;
Index SbiInterface console_putchar set_timer hart_start hart_stop hart_get_status
Kernel Address Space Layout Randomization randomizes the kernel's load address on every boot, making exploits harder.
KASLR — Address Randomization Flow 7N · 6E ▶ Boot Entry Kernel loaded by boo… 1 Read Entropy RDRAND/RDSEED or tim… 2 Generate Offset Random 2 MB-aligned … 2 Calculate New Base KERNEL_VIRT_BASE + o… 2 Update Page Tables Remap kernel at new … 2 Relocate Image ELF relocation proce… 2 ■ Jump to New Kernel Continue at randomiz… 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
Source Architecture Quality Fallback RDRANDx86_64 High Yes (hardware RNG) RDSEEDx86_64 High Yes (true RNG) Timer jitter All Medium Primary fallback Boot timestamp All Low Last resort
The relocation engine (in helix-relocation, 12 files, ~2,000 lines) processes ELF relocations at boot time for PIE (Position-Independent Executable) kernels.
Type Architecture Description R_X86_64_RELATIVEx86_64 Base + Addend R_X86_64_64x86_64 Symbol + Addend R_X86_64_GLOB_DATx86_64 Symbol address → GOT R_AARCH64_RELATIVEAArch64 Base + Addend R_AARCH64_ABS64AArch64 Symbol + Addend R_RISCV_64RISC-V Symbol + Addend R_RISCV_RELATIVERISC-V Base + Addend
subsystems/relocation/src/engine.rs
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 < ( ) > ;
Index RelocationEngine new process_rela process_dynamic validate
The engine processes all .rela.dyn entries, applying the load offset to each. Validation ensures no relocations point outside the kernel image.
Each architecture provides a complete HAL implementation. Here's the file structure:
x86_64 Backend — Source Tree 13N · 12E ▶ hal/src/x86_64/ x86_64 HAL backend 12 mod.rs X86_64Hal implementi… 1 cpu.rs MSR, CR, CPUID, priv… 1 gdt.rs Global Descriptor Ta… 1 idt.rs Interrupt Descriptor… 1 pic.rs Legacy 8259 PIC 1 apic.rs Local APIC + I/O API… 1 paging.rs 4-level page tables … 1 tss.rs Task State Segment (… 1 timer/ Timer subsystem 1 smp.rs AP startup via INIT-… 1 serial.rs COM1/COM2 serial por… 1 boot.rs x86_64-specific earl… 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
AArch64 Backend — Source Tree 10N · 9E ▶ hal/src/aarch64/ AArch64 HAL backend 9 mod.rs AArch64Hal implement… 1 cpu.rs System registers, ex… 1 gic_v2.rs GICv2 distributor + … 1 gic_v3.rs GICv3 with ITS, LPI … 1 mmu.rs 4-level page tables … 1 timer.rs Generic timer (CNTPC… 1 smp.rs PSCI-based AP startu… 1 exception.rs Exception vector tab… 1 boot.rs AArch64 early boot 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
RISC-V Backend — Source Tree 10N · 9E ▶ hal/src/riscv64/ RISC-V HAL backend 9 mod.rs Riscv64Hal implement… 1 cpu.rs CSR access, privileg… 1 clint.rs Core Local Interrupt… 1 plic.rs Platform-Level Inter… 1 mmu.rs Sv39/Sv48/Sv57 page … 1 timer.rs SBI timer extension 1 smp.rs SBI HSM hart managem… 1 trap.rs Trap handler (sync +… 1 boot.rs RISC-V early boot 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
Feature x86_64 AArch64 RISC-V Source files ~60 ~35 ~30 Estimated lines ~25,000 ~14,000 ~12,000 Page table levels 4 (PML4) 4 (4KB) 3-5 (Sv39-57) Interrupt controller APIC GICv3 PLIC Timer TSC/HPET/APIC Generic Timer SBI Timer SMP startup INIT-SIPI PSCI SBI HSM Serial output COM1 (0x3F8) PL011 UART SBI console KASLR entropy RDRAND/RDSEED Timer jitter Timer jitter