ajhahn.de
← FlashOS
plain text 207 lines
/*
 * 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 <constant>` 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 */