| 1 |
# Pactor OS — Raspberry Pi 5 (Cortex A76) Architecture Plan |
| 2 |
|
| 3 |
## Target Hardware |
| 4 |
|
| 5 |
``` |
| 6 |
SoC: BCM2712 (Broadcom) |
| 7 |
CPU: 4x Cortex-A76 (ARMv8.2-A, AArch64) |
| 8 |
RAM: 1GB+ (LPDDR4X, starting at 0x00000000) |
| 9 |
GPU: VideoCore VII (handles boot, HDMI, framebuffer) |
| 10 |
Storage: microSD (via EMMC2 controller) or NVMe (via PCIe) |
| 11 |
UART: PL011 at 0xFE201000 (primary debug console) |
| 12 |
Interrupts: GIC-400 (Generic Interrupt Controller) at 0xFF840000 |
| 13 |
Timer: ARM Generic Timer (CNTPCT_EL0, CNTP_TVAL_EL0) |
| 14 |
Boot: GPU firmware → kernel8.img loaded to 0x80000 |
| 15 |
``` |
| 16 |
|
| 17 |
## Exception Levels (ARM's Ring Model) |
| 18 |
|
| 19 |
``` |
| 20 |
EL3 Secure Monitor — TrustZone (we won't use this) |
| 21 |
EL2 Hypervisor — GPU firmware starts us here, we drop to EL1 |
| 22 |
EL1 Kernel — Pactor kernel runs here (= ring 0) |
| 23 |
EL0 Userspace — Applications run here (= ring 3) |
| 24 |
``` |
| 25 |
|
| 26 |
## Memory Map |
| 27 |
|
| 28 |
``` |
| 29 |
0x00000000 - 0x3BFFFFFF RAM (up to 1GB, minus VC memory) |
| 30 |
0x3C000000 - 0x3FFFFFFF VideoCore memory (GPU firmware) |
| 31 |
0x40000000 - 0xFFFFFFFF Peripheral space (MMIO) |
| 32 |
0xFE000000 GPIO |
| 33 |
0xFE200000 PL011 UART0 |
| 34 |
0xFE201000 PL011 UART1 (used by firmware for BT) |
| 35 |
0xFE215000 PCM (audio) |
| 36 |
0xFE800000 EMMC2 (SD card) |
| 37 |
0xFF840000 GIC-400 (interrupt controller) |
| 38 |
0xFFE00000 Local timer / watchdog |
| 39 |
|
| 40 |
Kernel loads at 0x80000 (512KB into RAM) |
| 41 |
Kernel stack at 0x80000 (grows down from load address) |
| 42 |
Page tables start at 0x40000 (below kernel) |
| 43 |
``` |
| 44 |
|
| 45 |
## Boot Sequence |
| 46 |
|
| 47 |
``` |
| 48 |
1. GPU firmware (start4.elf) loads kernel8.img to 0x80000 |
| 49 |
2. CPU starts in EL2 (Hypervisor) or EL1 (depending on config.txt) |
| 50 |
3. Kernel entry (_start): |
| 51 |
a. Check current EL (read CurrentEL register) |
| 52 |
b. If EL2: configure EL1, drop to EL1 via ERET |
| 53 |
c. Set up exception vector table (VBAR_EL1) |
| 54 |
d. Set up page tables (TTBR1_EL1 for kernel, TTBR0_EL1 for users) |
| 55 |
e. Enable MMU (SCTLR_EL1.M = 1) |
| 56 |
f. Set up GIC-400 (enable interrupts) |
| 57 |
g. Set up ARM Generic Timer (100Hz tick) |
| 58 |
h. Initialize PL011 UART (serial console) |
| 59 |
i. Call kernel_main() |
| 60 |
``` |
| 61 |
|
| 62 |
## Subsystem Mapping |
| 63 |
|
| 64 |
``` |
| 65 |
┌─────────────────────┬──────────────────────┬───────────────────────┐ |
| 66 |
│ Subsystem │ x86-64 (current) │ ARM64 (Pi 5) │ |
| 67 |
├─────────────────────┼──────────────────────┼───────────────────────┤ |
| 68 |
│ Boot │ GRUB2 multiboot2 │ GPU firmware + config │ |
| 69 |
│ CPU mode │ Protected → Long mode│ EL2 → EL1 │ |
| 70 |
│ GDT │ x86 GDT (8-byte ents)│ Not needed (no GDT) │ |
| 71 |
│ IDT │ 256 x 16-byte entries│ VBAR_EL1 vector table│ |
| 72 |
│ Interrupt controller│ 8259 PIC │ GIC-400 │ |
| 73 |
│ Timer │ PIT (8253) 100Hz │ ARM Generic Timer │ |
| 74 |
│ Serial │ 8250 UART (0x3F8) │ PL011 (0xFE201000) │ |
| 75 |
│ Paging │ 4-level (PML4→PT) │ 4-level (TTBR→L3) │ |
| 76 |
│ Syscall │ SYSCALL/SYSRET │ SVC/ERET │ |
| 77 |
│ Ring 0/3 │ CS.RPL │ EL1/EL0 │ |
| 78 |
│ Context switch │ IRETQ │ ERET │ |
| 79 |
│ Userspace entry │ SYSRET │ ERET with SP_EL0 │ |
| 80 |
│ Page size │ 2MB (huge) + 4KB │ 4KB or 2MB (optional)│ |
| 81 |
│ DMA / disk │ ATA PIO │ EMMC2 / SDHOST │ |
| 82 |
│ Display │ VGA text (0xB8000) │ Framebuffer (via VC) │ |
| 83 |
└─────────────────────┴──────────────────────┴───────────────────────┘ |
| 84 |
``` |
| 85 |
|
| 86 |
## File Structure |
| 87 |
|
| 88 |
``` |
| 89 |
pactor-arm64/ |
| 90 |
├── boot/ |
| 91 |
│ ├── config.txt # Pi 5 boot config (kernel=kernel8.img) |
| 92 |
│ └── start.elf # (copied from Pi firmware) |
| 93 |
├── kernel/ |
| 94 |
│ ├── entry.S # AArch64 entry, EL2→EL1, vector table |
| 95 |
│ ├── vectors.S # Exception vector table (sync/irq/fiq/error) |
| 96 |
│ ├── gic.c / gic.h # GIC-400 driver |
| 97 |
│ ├── timer.c / timer.h # ARM Generic Timer |
| 98 |
│ ├── uart.c / uart.h # PL011 UART driver |
| 99 |
│ ├── mmu.c / mmu.h # MMU / page table management |
| 100 |
│ ├── console.c # kprintf, serial output |
| 101 |
│ ├── shell.c # Interactive shell (shared code) |
| 102 |
│ ├── syscall.c # SVC handler (syscall dispatch) |
| 103 |
│ ├── syscall_entry.S # SVC entry/exit (save/restore regs) |
| 104 |
│ ├── elf.c # ELF64 loader (mostly shared) |
| 105 |
│ ├── ext2.c # ext2 filesystem (shared code) |
| 106 |
│ ├── pmm.c # Physical memory manager |
| 107 |
│ └── main.c # kernel_main() |
| 108 |
├── libc/ |
| 109 |
│ ├── start.S # _start entry (EL0, calls main) |
| 110 |
│ ├── libc.h # Userspace API header |
| 111 |
│ └── start.c # printf, malloc, string functions |
| 112 |
├── applications/ |
| 113 |
│ ├── fibonacci.c # Same apps, recompiled for ARM64 |
| 114 |
│ ├── primes.c |
| 115 |
│ └── ... |
| 116 |
├── include/ |
| 117 |
│ └── pactor.h # Common types and declarations |
| 118 |
├── linker.ld # Kernel linker script (load at 0x80000) |
| 119 |
├── userspace.ld # Userspace linker script (load at 0x400000) |
| 120 |
├── Makefile # Build system |
| 121 |
└── mkcard.sh # Create bootable SD card image |
| 122 |
``` |
| 123 |
|
| 124 |
## Key ARM64 Concepts for the Port |
| 125 |
|
| 126 |
### 1. Exception Vector Table (replaces IDT) |
| 127 |
``` |
| 128 |
; VBAR_EL1 points to this table |
| 129 |
; 16 entries, 128 bytes each (0x800 alignment) |
| 130 |
; 4 types × 4 exception sources = 16 entries |
| 131 |
|
| 132 |
vector_table: |
| 133 |
; Current EL with SP0 |
| 134 |
b sync_handler_sp0 ; 0x000 Synchronous |
| 135 |
b irq_handler_sp0 ; 0x080 IRQ |
| 136 |
b fiq_handler_sp0 ; 0x100 FIQ |
| 137 |
b error_handler_sp0 ; 0x180 SError |
| 138 |
|
| 139 |
; Current EL with SPx (kernel mode) |
| 140 |
b sync_handler_spx ; 0x200 Synchronous (syscalls, faults) |
| 141 |
b irq_handler_spx ; 0x280 IRQ (timer, peripherals) |
| 142 |
b fiq_handler_spx ; 0x300 FIQ |
| 143 |
b error_handler_spx ; 0x380 SError |
| 144 |
|
| 145 |
; Lower EL (AArch64, from userspace) |
| 146 |
b sync_handler_el0_64 ; 0x400 SVC instruction |
| 147 |
b irq_handler_el0_64 ; 0x480 IRQ from userspace |
| 148 |
b fiq_handler_el0_64 ; 0x500 FIQ |
| 149 |
b error_handler_el0_64 ; 0x580 SError |
| 150 |
|
| 151 |
; Lower EL (AArch32, unused) |
| 152 |
.space 0x100 ; 0x600-0x700 |
| 153 |
``` |
| 154 |
|
| 155 |
### 2. GIC-400 (replaces 8259 PIC) |
| 156 |
``` |
| 157 |
GICD_BASE = 0xFF841000 ; Distributor |
| 158 |
GICC_BASE = 0xFF842000 ; CPU interface |
| 159 |
|
| 160 |
; Enable distributor |
| 161 |
; Enable CPU interface |
| 162 |
; Configure interrupt routing (IRQ → CPU core 0) |
| 163 |
; Enable specific IRQs (timer = IRQ 27, UART = IRQ 33) |
| 164 |
``` |
| 165 |
|
| 166 |
### 3. ARM Generic Timer (replaces PIT) |
| 167 |
``` |
| 168 |
; Configure timer frequency (read CNTFRQ_EL0, typically 54MHz) |
| 169 |
; Set next tick: CNTP_TVAL_EL0 = freq / 100 (100Hz) |
| 170 |
; Enable timer: CNTP_CTL_EL0 = 1 |
| 171 |
; IRQ fires as PPI 14 (private peripheral interrupt) |
| 172 |
``` |
| 173 |
|
| 174 |
### 4. PL011 UART (replaces 8250 UART) |
| 175 |
``` |
| 176 |
UART0_BASE = 0xFE201000 |
| 177 |
; Different register layout than 8250 |
| 178 |
; IBRD, FBRD for baud rate |
| 179 |
; LCR_H for line control |
| 180 |
; CR for enable TX/RX |
| 181 |
; FR for status (TXFF, RXFE) |
| 182 |
``` |
| 183 |
|
| 184 |
### 5. SVC/ERET (replaces SYSCALL/SYSRET) |
| 185 |
``` |
| 186 |
; Userspace invokes syscall: |
| 187 |
; MOV X8, #syscall_number |
| 188 |
; SVC #0 |
| 189 |
|
| 190 |
; Kernel handler: |
| 191 |
; Save all registers (X0-X30, SP_EL0, SPSR_EL1, ELR_EL1) |
| 192 |
; Dispatch syscall_table[X8] |
| 193 |
; Restore registers |
| 194 |
; ERET ; returns to EL0 |
| 195 |
|
| 196 |
; Entry point (from userspace): |
| 197 |
; ERET with: |
| 198 |
; ELR_EL1 = user PC |
| 199 |
; SP_EL0 = user SP |
| 200 |
; SPSR_EL1 = user flags (EL0, AArch64) |
| 201 |
``` |
| 202 |
|
| 203 |
### 6. Page Tables (replaces x86 page tables) |
| 204 |
``` |
| 205 |
; 4-level translation tables (similar to x86): |
| 206 |
; TTBR1_EL1 → L0 table → L1 table → L2 table → L3 table |
| 207 |
; Each L3 entry maps a 4KB page |
| 208 |
; L2 entries can map 2MB blocks (like x86 huge pages) |
| 209 |
; |
| 210 |
; Attributes: AP[1]=1 for EL0 access (user), AP[1]=0 for EL1 only |
| 211 |
; SH (shareability), AF (access flag), UXN/PXN (execute never) |
| 212 |
``` |
| 213 |
|
| 214 |
## Shared vs Port-Specific Code |
| 215 |
|
| 216 |
``` |
| 217 |
SHARED (copy from x86-64, minimal changes): |
| 218 |
- shell.c (serial I/O abstraction) |
| 219 |
- ext2.c (block device abstraction) |
| 220 |
- elf.c (platform-specific page setup) |
| 221 |
- libc/start.c (printf, malloc, strings) |
| 222 |
- applications/*.c (recompile for ARM64) |
| 223 |
|
| 224 |
PORT-SPECIFIC (rewrite from scratch): |
| 225 |
- entry.S (AArch64 boot, EL2→EL1, MMU setup) |
| 226 |
- vectors.S (exception vector table) |
| 227 |
- gic.c (GIC-400 driver) |
| 228 |
- timer.c (ARM Generic Timer) |
| 229 |
- uart.c (PL011 driver) |
| 230 |
- mmu.c (ARM64 translation tables) |
| 231 |
- syscall_entry.S (SVC handler, save/restore) |
| 232 |
- linker.ld (load at 0x80000) |
| 233 |
- Makefile (aarch64-none-elf toolchain) |
| 234 |
``` |
| 235 |
|
| 236 |
## Build Toolchain |
| 237 |
|
| 238 |
```bash |
| 239 |
# Cross-compiler for ARM64 |
| 240 |
sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu |
| 241 |
|
| 242 |
# Or use LLVM |
| 243 |
clang --target=aarch64-none-elf -mcpu=cortex-a76 |
| 244 |
|
| 245 |
# Build |
| 246 |
make ARCH=arm64 |
| 247 |
|
| 248 |
# Deploy to SD card |
| 249 |
# 1. Format SD card as FAT32 |
| 250 |
# 2. Copy firmware files (start4.elf, fixup4.dat, bootcode.bin) |
| 251 |
# 3. Copy kernel8.img |
| 252 |
# 4. Copy config.txt |
| 253 |
# 5. Insert into Pi 5, power on |
| 254 |
``` |
| 255 |
|
| 256 |
## config.txt (Pi 5 boot configuration) |
| 257 |
|
| 258 |
```ini |
| 259 |
arm_64bit=1 |
| 260 |
kernel=kernel8.img |
| 261 |
disable_commandline_tags=1 |
| 262 |
armstub=armstub8.bin |
| 263 |
enable_uart=1 |
| 264 |
uart_2ndstage=1 |
| 265 |
``` |
| 266 |
|
| 267 |
## Development Timeline |
| 268 |
|
| 269 |
``` |
| 270 |
Phase 1: Bare metal boot (serial output) |
| 271 |
- entry.S: EL2→EL1, enable UART, print "Hello" |
| 272 |
- UART polling driver |
| 273 |
- Build toolchain setup |
| 274 |
→ Test: boot on Pi 5, see "Pactor64" on serial |
| 275 |
|
| 276 |
Phase 2: Interrupts + Timer |
| 277 |
- GIC-400 driver (enable distributor, CPU interface) |
| 278 |
- ARM Generic Timer (100Hz tick) |
| 279 |
- Exception vector table |
| 280 |
- Timer ISR, tick counter |
| 281 |
→ Test: uptime command works |
| 282 |
|
| 283 |
Phase 3: Memory Management |
| 284 |
- Physical memory allocator (parse ATAGS or DTB for memory map) |
| 285 |
- MMU setup (translation tables, identity map) |
| 286 |
- Kernel heap |
| 287 |
→ Test: malloc works |
| 288 |
|
| 289 |
Phase 4: Syscalls + Userspace |
| 290 |
- SVC handler (syscall dispatch) |
| 291 |
- EL0 entry (ERET to userspace) |
| 292 |
- ELF loader |
| 293 |
- hello.elf compiled for ARM64 |
| 294 |
→ Test: hello.elf runs in EL0 |
| 295 |
|
| 296 |
Phase 5: Filesystem + Applications |
| 297 |
- EMMC2 SD card driver |
| 298 |
- ext2 filesystem (shared code) |
| 299 |
- Application bundle (recompiled for ARM64) |
| 300 |
→ Test: execute fibonacci on Pi 5 |
| 301 |
|
| 302 |
Phase 6: Embedded Features |
| 303 |
- GPIO driver (LED, buttons) |
| 304 |
- I2C/SPI drivers (sensors) |
| 305 |
- Framebuffer (HDMI output) |
| 306 |
- Network (Ethernet via PCIe/USB) |
| 307 |
→ Test: real embedded application |
| 308 |
``` |
| 309 |
|
| 310 |
## Estimated Effort |
| 311 |
|
| 312 |
``` |
| 313 |
Phase 1: ~2-3 hours (boot + serial) |
| 314 |
Phase 2: ~2-3 hours (GIC + timer + vectors) |
| 315 |
Phase 3: ~3-4 hours (MMU + memory management) |
| 316 |
Phase 4: ~3-4 hours (syscalls + userspace) |
| 317 |
Phase 5: ~2-3 hours (SD card + filesystem) |
| 318 |
Phase 6: ~4-8 hours (GPIO, I2C, framebuffer, network) |
| 319 |
|
| 320 |
Total: ~16-25 hours of focused work |
| 321 |
``` |
| 322 |
|