/* * Board-specific memory layout for QEMU's `-M virt` machine * (AArch64). Picked up via the per-board include path set in * build.zig. * * QEMU virt physical map (the slice we care about): * 0x00000000–0x40000000 device window — PL011, GIC, RTC, * virtio-mmio, fw_cfg, etc. * 0x40000000–0x80000000 RAM (UEFI loads the kernel at * 0x40080000, the Linux arm64 entry * point on this machine). */ #ifndef BOARD_ASM_DEFS_INC #define BOARD_ASM_DEFS_INC /* Skip the first 4 sections (8 MiB at 2 MiB section size) of RAM * so the kernel image and its early data are not stomped by the * stack reserved for core 0. */ #define LOW_MEMORY (0x40000000 + 4 * SECTION_SIZE) /* High-map page count: 1 PGD + 1 PUD + 2 PMDs (one per 1 GiB * virtual chunk — Device + RAM). */ #define HIGH_MAP_PAGES 4 #define HIGH_MAP_TABLE_SIZE (HIGH_MAP_PAGES * PAGE_SIZE) #define ID_MAP_TABLE_SIZE (ID_MAP_PAGES * PAGE_SIZE) #define ID_MAP_SIZE (8 * SECTION_SIZE) #define PUD_ENTRY_MAP_SIZE (1 << PUD_SHIFT) /* Real virt windows. */ #define HIGH_MAP_DEVICE_START (0x0 + LINEAR_MAP_BASE) #define HIGH_MAP_DEVICE_END (0x40000000 + LINEAR_MAP_BASE) #define HIGH_MAP_RAM_START (0x40000000 + LINEAR_MAP_BASE) #define HIGH_MAP_RAM_END (0x80000000 + LINEAR_MAP_BASE) #define DEVICE_START 0x0 #define DEVICE_END 0x40000000 #define RAM_START 0x40000000 #define RAM_END 0x80000000 /* boot.S `.text.boot.literals` declares * .Lhigh_map_first_end ... .Lhigh_map_fourth_end * FIRST_START ... FOURTH_START * via `.quad ` regardless of board (those literals serve * the rpi4b 4-window scheme inside its `map_high_regions` macro). * virt's macro does not consume them, but the assembler still needs * each constant defined — alias them onto the real virt windows. */ #define HIGH_MAP_FIRST_START HIGH_MAP_DEVICE_START #define HIGH_MAP_FIRST_END HIGH_MAP_DEVICE_END #define HIGH_MAP_SECOND_START HIGH_MAP_RAM_START #define HIGH_MAP_SECOND_END HIGH_MAP_RAM_END #define HIGH_MAP_THIRD_START HIGH_MAP_RAM_END #define HIGH_MAP_THIRD_END HIGH_MAP_RAM_END #define HIGH_MAP_FOURTH_START HIGH_MAP_RAM_END #define HIGH_MAP_FOURTH_END HIGH_MAP_RAM_END #define FIRST_START DEVICE_START #define FIRST_END DEVICE_END #define SECOND_START RAM_START #define SECOND_END RAM_END #define THIRD_START RAM_END #define THIRD_END RAM_END #define FOURTH_START RAM_END #define FOURTH_END RAM_END /* QEMU sets CNTFRQ_EL0 itself before kernel entry, so virt does * not need a board-supplied oscillator constant. BOOT_OSC_FREQ is * defined to 0 so any common code that references it (today nothing * does, but the symbol is part of the rpi4b API surface) still * assembles. */ #define BOOT_OSC_FREQ 0 /* QEMU `-M virt -kernel` enters the kernel directly at EL1, so * drop_to_el1 must short-circuit to el1_entry instead of falling * through to the EL3 path that would `msr ELR_EL3` (UNDEFINED at * EL1). LR was set by master's `bl drop_to_el1`, so the el1_entry * `ret` returns to master normally. */ .macro check_el1_already el_reg cmp \el_reg, #(1 << 2) b.eq el1_entry .endm /* virt's identity map covers PA 0x40000000..0x41000000 (16 MiB at * the start of RAM), reachable through PUD entry 1 (VA bits 38..30 * = 1). UEFI/QEMU loads the kernel at PA 0x40080000, so this region * includes both the kernel image and the early stack/BSS — required * because the next instruction after `msr sctlr_el1` (MMU enable) * still fetches at PA via TTBR0 until the jump to the linear-map VA. * `ldr x?, =0x4xxxxxxx` literals all fit in single movz (lsl 16), * so no GAS pool entries are emitted. */ .macro map_identity_regions /* PGD entry 0 → PUD (covers VA 0..512 GiB) */ eor x4, x4, x4 create_table_entry x0, x1, x4, PGD_SHIFT, TD_KERNEL_TABLE_FLAGS, x2, x3 add x0, x0, #PAGE_SIZE add x1, x1, #PAGE_SIZE /* PUD entry 1 → PMD (VA 0x40000000..0x80000000) */ ldr x4, =0x40000000 create_table_entry x0, x1, x4, PUD_SHIFT, TD_KERNEL_TABLE_FLAGS, x2, x3 mov x0, x1 /* Map VA 0x40000000..0x41000000 → PA 0x40000000..0x41000000 */ ldr x2, =0x40000000 ldr x3, =0x41000000 ldr x4, =0x40000000 create_block_map x0, x2, x3, x4, .Ltd_kernel_block_flags, x5 .endm /* virt's LOW_MEMORY (0x40800000) does not fit AArch64's * `mov sp, #imm` or `add Xd, Xn, #imm` immediate fields (those * accept 12-bit immediates with an optional 12-bit shift, max * 0xFFFFFF). Materialise the value through a scratch register; * GAS optimises `ldr =LOW_MEMORY` to a single `movz` because * 0x40800000 fits in a 16-bit-shifted-by-16 movz immediate. */ .macro mov_sp_low_memory tmp ldr \tmp, =LOW_MEMORY mov sp, \tmp .endm .macro add_low_memory dst, src, tmp ldr \tmp, =LOW_MEMORY add \dst, \src, \tmp .endm /* Board-specific high-memory mapping, called from boot.S map_high * after the PGD entry has been installed. See the rpi4b version * for the entry-register contract. virt has only two PUD slots: * PUD 0 -> PMD covering Device (0x00000000..0x40000000) * PUD 1 -> PMD covering RAM (0x40000000..0x80000000) * * The macro emits no GAS literal pool — every operand fits in a * single movz/movk pair so GAS keeps them inline. TD flag labels * (.Ltd_kernel_block_flags / .Ltd_device_block_flags) are already * defined by boot.S `.text.boot.literals` and resolved by the linker. */ .macro map_high_regions /* x4 = address of va we map (pud) */ ldr x4, =LINEAR_MAP_BASE ldr x5, =PUD_ENTRY_MAP_SIZE /* PUD 0 -> Device PMD */ create_table_entry x0, x1, x4, PUD_SHIFT, TD_KERNEL_TABLE_FLAGS, x2, x3 /* PUD 1 -> RAM PMD */ add x1, x1, #PAGE_SIZE add x4, x4, x5 create_table_entry x0, x1, x4, PUD_SHIFT, TD_KERNEL_TABLE_FLAGS, x2, x3 /* Populate Device PMD (0x00000000..0x40000000 -> linear map) */ add x0, x0, #PAGE_SIZE ldr x2, =HIGH_MAP_DEVICE_START ldr x3, =HIGH_MAP_DEVICE_END ldr x4, =DEVICE_START create_block_map x0, x2, x3, x4, .Ltd_device_block_flags, x5 /* Populate RAM PMD (0x40000000..0x80000000 -> linear map) */ add x0, x0, #PAGE_SIZE ldr x2, =HIGH_MAP_RAM_START ldr x3, =HIGH_MAP_RAM_END ldr x4, =RAM_START create_block_map x0, x2, x3, x4, .Ltd_kernel_block_flags, x5 .endm /* Crash-stamp UART macros for entry.S `show_invalid_entry_raw`. * virt redirects them at the QEMU PL011 @ 0x09000000: * DR @ +0x000 — write = TX byte * FR @ +0x018 — bit 5 = TXFF (1 = TX FIFO full); poll until clear * with `tbnz` (inverted vs. Pi's mini-UART LSR). */ .macro err_uart_load_io reg movz \reg, #0x0900, lsl #16 movk \reg, #0xFFFF, lsl #48 .endm .macro err_uart_load_lsr reg movz \reg, #0x0018 movk \reg, #0x0900, lsl #16 movk \reg, #0xFFFF, lsl #48 .endm .macro err_uart_wait_tx_ready scratch, lsr_reg, lbl \lbl\(): ldr \scratch, [\lsr_reg] tbnz \scratch, #5, \lbl\()b .endm /* QEMU `-kernel` and UEFI / GRUB chain hand off the DTB physical * address in x0 per the Linux arm64 boot protocol. Save it before * the first `bl` in boot.S `master` clobbers it; src/board/virt/ * boot_quirks.S declares `dtb_pa` as a `.bss` quad. adrp+:lo12: pair * gives ±4 GiB reach (a plain `adr` would not survive once the .bss * landed far from .text). */ .macro save_dtb_pa src adrp x9, dtb_pa str \src, [x9, #:lo12:dtb_pa] .endm /* Enable FP+SIMD access at EL0/EL1 by setting CPACR_EL1.FPEN * (bits 21:20) to 0b11. virt enters at EL1 directly (QEMU `-kernel`) * or via UEFI handoff, neither of which configures CPACR like * armstub8 does on Pi 4. Zig's std.mem (pulled in by dtb.zig) * lowers to NEON instructions in ReleaseSmall, so without this * write the very first NEON `movi`/`stp q*` faults with EC=0x07 * (Trapped FP/SIMD). Pi's macro is empty — armstub already wrote * the same bits at EL3 and Pi's binary contains zero NEON. */ .macro enable_fp_simd_el1 mrs x0, CPACR_EL1 orr x0, x0, #(3 << 20) msr CPACR_EL1, x0 isb .endm #endif /* BOARD_ASM_DEFS_INC */