# Pactor OS — Raspberry Pi 5 (Cortex A76) Architecture Plan ## Target Hardware ``` SoC: BCM2712 (Broadcom) CPU: 4x Cortex-A76 (ARMv8.2-A, AArch64) RAM: 1GB+ (LPDDR4X, starting at 0x00000000) GPU: VideoCore VII (handles boot, HDMI, framebuffer) Storage: microSD (via EMMC2 controller) or NVMe (via PCIe) UART: PL011 at 0xFE201000 (primary debug console) Interrupts: GIC-400 (Generic Interrupt Controller) at 0xFF840000 Timer: ARM Generic Timer (CNTPCT_EL0, CNTP_TVAL_EL0) Boot: GPU firmware → kernel8.img loaded to 0x80000 ``` ## Exception Levels (ARM's Ring Model) ``` EL3 Secure Monitor — TrustZone (we won't use this) EL2 Hypervisor — GPU firmware starts us here, we drop to EL1 EL1 Kernel — Pactor kernel runs here (= ring 0) EL0 Userspace — Applications run here (= ring 3) ``` ## Memory Map ``` 0x00000000 - 0x3BFFFFFF RAM (up to 1GB, minus VC memory) 0x3C000000 - 0x3FFFFFFF VideoCore memory (GPU firmware) 0x40000000 - 0xFFFFFFFF Peripheral space (MMIO) 0xFE000000 GPIO 0xFE200000 PL011 UART0 0xFE201000 PL011 UART1 (used by firmware for BT) 0xFE215000 PCM (audio) 0xFE800000 EMMC2 (SD card) 0xFF840000 GIC-400 (interrupt controller) 0xFFE00000 Local timer / watchdog Kernel loads at 0x80000 (512KB into RAM) Kernel stack at 0x80000 (grows down from load address) Page tables start at 0x40000 (below kernel) ``` ## Boot Sequence ``` 1. GPU firmware (start4.elf) loads kernel8.img to 0x80000 2. CPU starts in EL2 (Hypervisor) or EL1 (depending on config.txt) 3. Kernel entry (_start): a. Check current EL (read CurrentEL register) b. If EL2: configure EL1, drop to EL1 via ERET c. Set up exception vector table (VBAR_EL1) d. Set up page tables (TTBR1_EL1 for kernel, TTBR0_EL1 for users) e. Enable MMU (SCTLR_EL1.M = 1) f. Set up GIC-400 (enable interrupts) g. Set up ARM Generic Timer (100Hz tick) h. Initialize PL011 UART (serial console) i. Call kernel_main() ``` ## Subsystem Mapping ``` ┌─────────────────────┬──────────────────────┬───────────────────────┐ │ Subsystem │ x86-64 (current) │ ARM64 (Pi 5) │ ├─────────────────────┼──────────────────────┼───────────────────────┤ │ Boot │ GRUB2 multiboot2 │ GPU firmware + config │ │ CPU mode │ Protected → Long mode│ EL2 → EL1 │ │ GDT │ x86 GDT (8-byte ents)│ Not needed (no GDT) │ │ IDT │ 256 x 16-byte entries│ VBAR_EL1 vector table│ │ Interrupt controller│ 8259 PIC │ GIC-400 │ │ Timer │ PIT (8253) 100Hz │ ARM Generic Timer │ │ Serial │ 8250 UART (0x3F8) │ PL011 (0xFE201000) │ │ Paging │ 4-level (PML4→PT) │ 4-level (TTBR→L3) │ │ Syscall │ SYSCALL/SYSRET │ SVC/ERET │ │ Ring 0/3 │ CS.RPL │ EL1/EL0 │ │ Context switch │ IRETQ │ ERET │ │ Userspace entry │ SYSRET │ ERET with SP_EL0 │ │ Page size │ 2MB (huge) + 4KB │ 4KB or 2MB (optional)│ │ DMA / disk │ ATA PIO │ EMMC2 / SDHOST │ │ Display │ VGA text (0xB8000) │ Framebuffer (via VC) │ └─────────────────────┴──────────────────────┴───────────────────────┘ ``` ## File Structure ``` pactor-arm64/ ├── boot/ │ ├── config.txt # Pi 5 boot config (kernel=kernel8.img) │ └── start.elf # (copied from Pi firmware) ├── kernel/ │ ├── entry.S # AArch64 entry, EL2→EL1, vector table │ ├── vectors.S # Exception vector table (sync/irq/fiq/error) │ ├── gic.c / gic.h # GIC-400 driver │ ├── timer.c / timer.h # ARM Generic Timer │ ├── uart.c / uart.h # PL011 UART driver │ ├── mmu.c / mmu.h # MMU / page table management │ ├── console.c # kprintf, serial output │ ├── shell.c # Interactive shell (shared code) │ ├── syscall.c # SVC handler (syscall dispatch) │ ├── syscall_entry.S # SVC entry/exit (save/restore regs) │ ├── elf.c # ELF64 loader (mostly shared) │ ├── ext2.c # ext2 filesystem (shared code) │ ├── pmm.c # Physical memory manager │ └── main.c # kernel_main() ├── libc/ │ ├── start.S # _start entry (EL0, calls main) │ ├── libc.h # Userspace API header │ └── start.c # printf, malloc, string functions ├── applications/ │ ├── fibonacci.c # Same apps, recompiled for ARM64 │ ├── primes.c │ └── ... ├── include/ │ └── pactor.h # Common types and declarations ├── linker.ld # Kernel linker script (load at 0x80000) ├── userspace.ld # Userspace linker script (load at 0x400000) ├── Makefile # Build system └── mkcard.sh # Create bootable SD card image ``` ## Key ARM64 Concepts for the Port ### 1. Exception Vector Table (replaces IDT) ``` ; VBAR_EL1 points to this table ; 16 entries, 128 bytes each (0x800 alignment) ; 4 types × 4 exception sources = 16 entries vector_table: ; Current EL with SP0 b sync_handler_sp0 ; 0x000 Synchronous b irq_handler_sp0 ; 0x080 IRQ b fiq_handler_sp0 ; 0x100 FIQ b error_handler_sp0 ; 0x180 SError ; Current EL with SPx (kernel mode) b sync_handler_spx ; 0x200 Synchronous (syscalls, faults) b irq_handler_spx ; 0x280 IRQ (timer, peripherals) b fiq_handler_spx ; 0x300 FIQ b error_handler_spx ; 0x380 SError ; Lower EL (AArch64, from userspace) b sync_handler_el0_64 ; 0x400 SVC instruction b irq_handler_el0_64 ; 0x480 IRQ from userspace b fiq_handler_el0_64 ; 0x500 FIQ b error_handler_el0_64 ; 0x580 SError ; Lower EL (AArch32, unused) .space 0x100 ; 0x600-0x700 ``` ### 2. GIC-400 (replaces 8259 PIC) ``` GICD_BASE = 0xFF841000 ; Distributor GICC_BASE = 0xFF842000 ; CPU interface ; Enable distributor ; Enable CPU interface ; Configure interrupt routing (IRQ → CPU core 0) ; Enable specific IRQs (timer = IRQ 27, UART = IRQ 33) ``` ### 3. ARM Generic Timer (replaces PIT) ``` ; Configure timer frequency (read CNTFRQ_EL0, typically 54MHz) ; Set next tick: CNTP_TVAL_EL0 = freq / 100 (100Hz) ; Enable timer: CNTP_CTL_EL0 = 1 ; IRQ fires as PPI 14 (private peripheral interrupt) ``` ### 4. PL011 UART (replaces 8250 UART) ``` UART0_BASE = 0xFE201000 ; Different register layout than 8250 ; IBRD, FBRD for baud rate ; LCR_H for line control ; CR for enable TX/RX ; FR for status (TXFF, RXFE) ``` ### 5. SVC/ERET (replaces SYSCALL/SYSRET) ``` ; Userspace invokes syscall: ; MOV X8, #syscall_number ; SVC #0 ; Kernel handler: ; Save all registers (X0-X30, SP_EL0, SPSR_EL1, ELR_EL1) ; Dispatch syscall_table[X8] ; Restore registers ; ERET ; returns to EL0 ; Entry point (from userspace): ; ERET with: ; ELR_EL1 = user PC ; SP_EL0 = user SP ; SPSR_EL1 = user flags (EL0, AArch64) ``` ### 6. Page Tables (replaces x86 page tables) ``` ; 4-level translation tables (similar to x86): ; TTBR1_EL1 → L0 table → L1 table → L2 table → L3 table ; Each L3 entry maps a 4KB page ; L2 entries can map 2MB blocks (like x86 huge pages) ; ; Attributes: AP[1]=1 for EL0 access (user), AP[1]=0 for EL1 only ; SH (shareability), AF (access flag), UXN/PXN (execute never) ``` ## Shared vs Port-Specific Code ``` SHARED (copy from x86-64, minimal changes): - shell.c (serial I/O abstraction) - ext2.c (block device abstraction) - elf.c (platform-specific page setup) - libc/start.c (printf, malloc, strings) - applications/*.c (recompile for ARM64) PORT-SPECIFIC (rewrite from scratch): - entry.S (AArch64 boot, EL2→EL1, MMU setup) - vectors.S (exception vector table) - gic.c (GIC-400 driver) - timer.c (ARM Generic Timer) - uart.c (PL011 driver) - mmu.c (ARM64 translation tables) - syscall_entry.S (SVC handler, save/restore) - linker.ld (load at 0x80000) - Makefile (aarch64-none-elf toolchain) ``` ## Build Toolchain ```bash # Cross-compiler for ARM64 sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu # Or use LLVM clang --target=aarch64-none-elf -mcpu=cortex-a76 # Build make ARCH=arm64 # Deploy to SD card # 1. Format SD card as FAT32 # 2. Copy firmware files (start4.elf, fixup4.dat, bootcode.bin) # 3. Copy kernel8.img # 4. Copy config.txt # 5. Insert into Pi 5, power on ``` ## config.txt (Pi 5 boot configuration) ```ini arm_64bit=1 kernel=kernel8.img disable_commandline_tags=1 armstub=armstub8.bin enable_uart=1 uart_2ndstage=1 ``` ## Development Timeline ``` Phase 1: Bare metal boot (serial output) - entry.S: EL2→EL1, enable UART, print "Hello" - UART polling driver - Build toolchain setup → Test: boot on Pi 5, see "Pactor64" on serial Phase 2: Interrupts + Timer - GIC-400 driver (enable distributor, CPU interface) - ARM Generic Timer (100Hz tick) - Exception vector table - Timer ISR, tick counter → Test: uptime command works Phase 3: Memory Management - Physical memory allocator (parse ATAGS or DTB for memory map) - MMU setup (translation tables, identity map) - Kernel heap → Test: malloc works Phase 4: Syscalls + Userspace - SVC handler (syscall dispatch) - EL0 entry (ERET to userspace) - ELF loader - hello.elf compiled for ARM64 → Test: hello.elf runs in EL0 Phase 5: Filesystem + Applications - EMMC2 SD card driver - ext2 filesystem (shared code) - Application bundle (recompiled for ARM64) → Test: execute fibonacci on Pi 5 Phase 6: Embedded Features - GPIO driver (LED, buttons) - I2C/SPI drivers (sensors) - Framebuffer (HDMI output) - Network (Ethernet via PCIe/USB) → Test: real embedded application ``` ## Estimated Effort ``` Phase 1: ~2-3 hours (boot + serial) Phase 2: ~2-3 hours (GIC + timer + vectors) Phase 3: ~3-4 hours (MMU + memory management) Phase 4: ~3-4 hours (syscalls + userspace) Phase 5: ~2-3 hours (SD card + filesystem) Phase 6: ~4-8 hours (GPIO, I2C, framebuffer, network) Total: ~16-25 hours of focused work ```