001 Notes

Booting, My Confusions and ChatGPT

Booting Before Normal Memory Exists

This note explains early computer boot as a sequence of capabilities being created one by one.

It does not follow the exact Linux boot path. The focus is the machinery people often forget:

  • how execution starts after power-on;
  • what must exist below firmware and microcode;
  • how code runs before DRAM is usable;
  • how XIP works;
  • where microcode fits;
  • how temporary memory becomes real memory;
  • what assembly must do before C is safe;
  • why MTRRs, PAT, caches, and MMIO matter;
  • how the first page tables are built;
  • how a virtual address is forced to resolve to a physical address.

The examples use x86 terminology where useful. For exact architectural rules, use the vendor manuals, especially the Intel SDM: Intel SDM.

1. Boot as Capability Transitions

Early boot is easier to understand as a ladder:

Power applied
    |
    v
CPU can fetch first instruction
    |
    v
Firmware can execute from ROM/flash
    |
    v
Tiny temporary writable storage exists
    |
    v
DRAM is initialized
    |
    v
Assembly prepares a C runtime
    |
    v
Memory types and caches are configured
    |
    v
First page tables create virtual memory
    |
    v
Larger firmware, bootloader, kernel, or OS code can run normally

Important rule:

  • Every step earns one new assumption.
  • Do not use an assumption before the boot path has created it.

Common false assumptions:

  • “The CPU is executing, so RAM must work.”
  • “There is a stack because C code has local variables.”
  • “A virtual address exists because the program was linked with it.”
  • “A physical address is enough, so cacheability does not matter.”
  • “ROM execution means global variables are writable.”

2. Power-On and First Instruction Fetch

At reset:

  • The CPU begins from an architecturally defined reset state.
  • On x86, the first instruction fetch comes from a reset vector near the top of the physical address space.
  • Platform hardware maps that fetch to firmware storage, usually ROM or SPI flash.
  • The first instruction is usually a jump into more complete firmware code.

Conceptually:

CPU reset
   |
   | fetch instruction at reset vector
   v
+-------------------+       platform decode       +------------------+
| CPU fetch address | ---------------------------> | firmware flash   |
| near top of space |                              | ROM/SPI contents |
+-------------------+                              +------------------+

The key point:

  • Instruction fetch can work before DRAM works.
  • Firmware code is not necessarily copied to RAM before it starts.
  • The platform can decode a physical address range as firmware storage.

Reference:

  • Intel documents x86 reset state and instruction execution rules in the SDM: Intel SDM.

3. What Exists Before Firmware Runs?

Firmware is not the first thing that exists. Firmware is the first platform-visible instruction stream that software developers usually see. Below it, the CPU and platform already need enough hardwired behavior to reset, clock, fetch, decode, and begin architectural execution.

Layer view:

power-good / reset / clocks
      |
      v
hardwired reset sequencing and first-fetch control
      |
      v
built-in decode / micro-op / microcode machinery
      |
      v
architectural instruction execution
      |
      v
platform firmware fetched from ROM/flash

Pointwise:

  • Power and reset signals bring the CPU into a defined reset state.
  • Clocking and reset sequencing are below firmware.
  • Address-decode logic must make the reset-vector fetch reach firmware storage.
  • Some instruction fetch, decode, and execution machinery must already work before external firmware code can run.
  • Microcode itself also needs lower-level hardware control and sequencing.
  • Public manuals document the architectural reset state and programmer-visible behavior; exact internal sequencing is usually vendor-private.

Useful distinction:

hardwired/control logic:
  makes reset, first fetch, minimum decode, and low-level sequencing possible

built-in microcode/control store:
  helps implement complex architectural behavior inside the CPU

platform firmware:
  normal ISA-visible code fetched from ROM/flash

The caveat:

  • Microcode is not “BIOS running earlier.”
  • Firmware is software visible at the ISA level.
  • Microcode is internal CPU implementation and update machinery below that level.
  • Even microcode needs some lower-level signal-driven hardware to get control.

Reference:

  • Intel documents the architectural reset state and programmer-visible execution model in the SDM, while internal sequencing remains implementation-specific: Intel SDM.

4. Microcode

Modern x86 CPUs contain internal microcode.

Pointwise:

  • The CPU already has built-in microcode before platform firmware runs.
  • Firmware can load microcode updates early in boot.
  • The OS can also load microcode updates, often very early in kernel startup.
  • Microcode can affect errata behavior, CPUID-visible features, MSRs, and mitigation behavior.
  • Early microcode loading is preferable when later boot code depends on fixed CPU behavior.
  • Late microcode loading is harder because CPUs may already be running normal OS code, interrupts, SMT siblings, and workloads.

Three layers should not be mixed together:

hardwired reset/control logic
        |
        v
built-in CPU microcode and internal control store
        |
        v
firmware instructions fetched from ROM/flash

Pointwise:

  • Hardwired/control logic handles the lowest-level reset, clock, first-fetch, and sequencing requirements.
  • Built-in microcode is part of the CPU’s internal implementation of the architectural machine.
  • Platform firmware is ordinary architectural code: x86 instructions fetched from firmware storage and executed by the CPU.
  • Microcode updates patch or extend the built-in CPU behavior; they are not a replacement for platform firmware.
  • Firmware may deliver a microcode update by executing architectural instructions and writing the documented update interface.

Boot placement:

CPU built-in microcode
        |
        v
Firmware may apply update
        |
        v
Bootloader or kernel starts
        |
        v
OS may apply early update
        |
        v
Late runtime updates require more coordination

References:

5. Execution Without RAM

Before DRAM is initialized:

  • Instruction bytes may come from ROM or flash.
  • Writable memory may not exist yet.
  • A stack may not exist yet.
  • Global variables may not be usable.
  • .bss is not guaranteed to be zero.
  • .data may still be sitting in ROM, not RAM.

Early code may use:

  • CPU registers;
  • CPU or SoC scratch registers;
  • tiny on-chip SRAM, if available;
  • cache-as-RAM;
  • temporary memory created by firmware.

Diagram:

Before DRAM init

Instruction fetch:
  CPU ---> firmware ROM/flash      works

Writable data:
  CPU ---> DRAM                    not safe yet
  CPU ---> stack in DRAM           not safe yet
  CPU ---> globals in DRAM         not safe yet

Practical consequence:

  • Early code is often assembly.
  • If C is used, it must be a restricted subset or must run only after startup code has created the required environment.

References:

  • UEFI PI SEC phase describes temporary memory and cache-as-RAM style behavior: UEFI PI SEC phase.
  • coreboot describes bootblock responsibilities such as cache-as-RAM, stack setup, BSS clearing, and loading the next stage: coreboot architecture.

6. Cache-As-RAM

Cache-As-RAM, often shortened to CAR, is one common bridge between “the CPU can execute instructions” and “DRAM is usable.”

Pointwise:

  • Caches normally accelerate access to backing memory.
  • Before DRAM is initialized, there may be no safe backing memory.
  • CAR configures CPU cache so it can behave like a small temporary SRAM.
  • Firmware can then place an early stack, tiny heap, or small temporary data structures in cache.
  • This makes more C-like early firmware code possible before permanent RAM exists.

Diagram:

before DRAM:

  ROM/flash  -> instruction fetch
  CPU cache  -> temporary stack / heap / small globals
  DRAM       -> not initialized yet

Why no-eviction matters:

normal cache:
  cache miss or eviction may access backing DRAM

cache-as-RAM:
  cache must behave like the backing store
  eviction to uninitialized DRAM must not happen

Typical use:

reset / first firmware code
        |
        v
enable CAR using vendor-specific CPU setup
        |
        v
use CAR for early stack and temporary objects
        |
        v
initialize DRAM
        |
        v
copy or discard CAR-resident state
        |
        v
move stack to DRAM
        |
        v
tear down CAR

Caveats:

  • CAR setup is CPU/vendor-specific.
  • CAR capacity is small compared with DRAM.
  • CAR lifetime is temporary.
  • CAR contents do not automatically survive teardown.
  • Any required state must be copied to permanent RAM before CAR is retired.
  • Code must avoid cache behavior that would evict dirty lines to uninitialized DRAM.
  • CAR may make a stack possible, but it does not make the whole C runtime automatically safe.

References:

  • UEFI PI SEC describes temporary memory and says it can include programming processor cache to behave as a linear memory store in no-eviction mode: UEFI PI SEC phase.
  • coreboot documents Cache-As-RAM as cache used like regular SRAM for heap and stack, and notes that CAR is activated with vendor-specific CPU instructions: coreboot architecture.

7. XIP: Execute In Place

XIP means execute-in-place.

Pointwise:

  • Code runs directly from its storage location.
  • In firmware, that storage is commonly ROM or flash.
  • The instruction stream does not need to be copied into RAM first.
  • XIP solves instruction fetch.
  • XIP does not solve writable data.

Diagram:

XIP firmware image

+-----------------------+       fetch       +-----+
| ROM / flash           | ----------------> | CPU |
|                       |                   +-----+
| .text  executable     |
| rodata readable       |
| .data load image      | -- must copy --> RAM before normal C use
| .bss  no bytes stored | -- must zero --> RAM before normal C use
+-----------------------+

Caveats:

  • ROM is not writable.
  • Flash may be slow.
  • Relocation-sensitive code may not work from arbitrary addresses.
  • Writable global state still needs RAM or temporary storage.
  • .data and .bss must be prepared before normal C code relies on them.

Reference:

8. Bringing Up DRAM

DRAM does not become usable just because power is present.

Firmware may need to:

  • configure the memory controller;
  • read DIMM or platform memory parameters;
  • train timing and links;
  • decide which physical ranges are usable;
  • reserve platform, firmware, MMIO, and device ranges;
  • migrate from temporary memory to permanent RAM.
  • copy or discard any state that currently lives in Cache-As-RAM.

Typical transition:

ROM/XIP execution
      |
      v
temporary memory or cache-as-RAM
      |
      v
DRAM controller initialized
      |
      v
stack moved to DRAM
      |
      v
next stage loaded/copied/decompressed into DRAM
      |
      v
temporary memory retired

Important caveats:

  • A pointer into DRAM is not valid until DRAM is initialized.
  • A stack pointer is valid only if the memory behind it is valid.
  • Temporary memory may disappear after the transition.
  • CAR contents may disappear when CAR is torn down.
  • Any state that must survive has to be copied to permanent RAM.

Reference:

  • coreboot documents the romstage, cache-as-RAM, postcar, and ramstage transition: coreboot architecture.

9. Preparing to Call C

C code assumes a runtime environment.

Cache-As-RAM can make some C possible earlier:

  • A CAR-backed stack can support calls and local variables.
  • A tiny CAR-backed heap may support restricted allocation.
  • Small temporary globals may work if the linker/startup code places them in the temporary memory model intentionally.
  • Permanent C runtime state still needs DRAM migration, .data copying, and .bss clearing.

Before calling ordinary C, assembly startup code usually must:

  • set a valid stack pointer;
  • satisfy ABI alignment requirements;
  • make constants and read-only data addressable;
  • copy .data from its load address to its runtime address;
  • zero .bss;
  • apply relocations if the image is not running where it was linked to run;
  • set up minimal exception or fault handling if the platform requires it;
  • jump or call into the first C function.

Section layout:

Firmware image in ROM/flash                 Runtime layout in RAM

+----------------------+                    +----------------------+
| .text                | execute in place    | optional .text copy   |
| .rodata              | ----------------->  | optional .rodata copy |
| .data initial bytes  | ---- copy --------> | .data writable        |
| no .bss bytes        | ---- zero --------> | .bss zeroed           |
+----------------------+                    | stack                |
                                            | heap later, maybe    |
                                            +----------------------+

VMA and LMA:

  • VMA means virtual memory address, or the address code expects at runtime.
  • LMA means load memory address, or where bytes live in the image.
  • .data often has an LMA in ROM and a VMA in RAM.
  • .bss has a VMA in RAM but usually no stored bytes in the image.

Example invariant:

static int global_counter;

Pointwise:

  • The C language says global_counter starts as zero.
  • Hardware does not zero it automatically.
  • Startup assembly must clear the .bss range.
  • Until that happens, normal C assumptions are false.

Reference:

10. Segmentation, Descriptor Tables, and Early Faults

On x86, firmware may need to move through CPU modes:

  • reset state;
  • real mode;
  • protected mode;
  • long mode.

This can involve:

  • GDT setup;
  • IDT setup;
  • control-register changes;
  • model-specific register writes;
  • paging enablement;
  • carefully ordered jumps.

Fault caveats:

  • Early exception handlers may not exist.
  • A bad descriptor can fault.
  • A bad stack can make the fault handler fail.
  • A bad page table can make instruction fetch fail.
  • Repeated early faults can look like a reset or silent hang.

Reference:

  • Intel documents the mode transition and descriptor rules in the SDM: Intel SDM.

11. Memory Types: MTRR, PAT, Caches, and MMIO

Not every physical address has the same behavior.

Examples:

  • normal DRAM;
  • firmware ROM;
  • MMIO registers;
  • framebuffer memory;
  • PCIe device windows;
  • reserved platform ranges.

Common memory types:

  • WB: write-back, usually normal RAM;
  • UC: uncacheable, often MMIO;
  • WT: write-through;
  • WC: write-combining, often useful for framebuffer-style writes.

Diagram:

Physical address space

+----------------------+  cacheable WB    ordinary DRAM
| RAM                  |
+----------------------+  UC or WP        firmware ROM/flash window
| ROM / flash alias    |
+----------------------+  UC              device registers
| MMIO                 |
+----------------------+  WC or UC        framebuffer
| framebuffer          |
+----------------------+

MTRR:

  • MTRRs describe memory types over physical ranges.
  • Firmware may configure MTRRs early.
  • MTRRs are coarse compared with page-level mappings.

PAT:

  • PAT gives page-level memory type control.
  • PAT interacts with MTRRs.
  • The same physical page must not be mapped with conflicting cacheability.

Caveats:

  • A correct physical address with the wrong memory type can still be wrong.
  • MMIO must not be treated like ordinary cached RAM.
  • A framebuffer may need WC rather than WB.
  • Identity and higher-half aliases must agree on memory type.
  • Memory type aliasing can cause behavior that looks impossible.

References:

12. The First Page Tables

Paging creates virtual memory.

But first:

  • Page tables must exist in physical memory.
  • The CPU must know the physical address of the top-level page table.
  • Page-table entries must contain physical frame addresses.
  • The transition code must remain executable before and after paging is enabled.

Typical first mappings:

  • Identity map:
    • virtual address equals physical address.
    • Useful while switching modes.
  • Higher-half or forced mapping:
    • virtual address differs from physical address.
    • Useful when later code is linked for a high virtual address.
  • MMIO mappings:
    • used for devices, often with UC memory type.

Diagram:

Before paging:

CPU address used by instruction fetch
        |
        v
physical address directly

After paging:

virtual address
        |
        v
page-table walk
        |
        v
physical address

First page-table transition:

physical RAM holds page tables
        |
        v
load page-table root register
        |
        v
enable paging
        |
        v
CPU begins translating instruction fetches through page tables

Caveats:

  • You need RAM, or some writable physical memory, to store the page tables.
  • If paging is enabled and the current instruction address is not mapped, execution fails immediately.
  • If the current stack is not mapped, the next push/call/interrupt may fail.
  • If a page table points to virtual addresses instead of physical addresses, the walk is wrong.

Reference:

  • Intel documents paging structures and control registers in the SDM: Intel SDM.

13. Example: Converting VA to PA

Goal:

  • Force this virtual range:
VA base: 0xffffffff81000000
  • To resolve to this physical range:
PA base: 0x0000000000100000
  • Then this access:
VA: 0xffffffff81001234
  • Should reach:
PA: 0x0000000000101234

Assumptions:

  • x86-64 4-level paging.
  • 48-bit canonical virtual addresses.
  • A 2 MiB page is used for the mapping.
  • The virtual base is 2 MiB aligned.
  • The physical base is 2 MiB aligned.
  • The leaf entry is a page-directory entry with the large-page bit set.

The virtual address is split into indexes:

VA = 0xffffffff81001234

  63                  48 47      39 38      30 29      21 20             0
 +----------------------+----------+----------+----------+---------------+
 | sign extension       | PML4 idx | PDPT idx | PD idx   | 2 MiB offset  |
 +----------------------+----------+----------+----------+---------------+

PML4 index   = 511 = 0x1ff
PDPT index   = 510 = 0x1fe
PD index     =   8 = 0x008
2 MiB offset = 0x1234

The page-table walk:

CR3
 |
 v
PML4 table
  entry[511]  --> PDPT physical address
                    |
                    v
                  PDPT table
                    entry[510] --> PD physical address
                                      |
                                      v
                                    PD table
                                      entry[8] --> PA base 0x00100000
                                                   flags: present, writable,
                                                          executable, PS=1
                                                   page size: 2 MiB

Final address calculation:

physical address = leaf physical base + page offset
physical address = 0x00100000 + 0x1234
physical address = 0x00101234

So:

0xffffffff81001234  --->  0x0000000000101234

The important detail:

  • The page-table entry does not store 0xffffffff81000000.
  • It stores the physical base 0x00100000 plus flags.
  • The virtual address selects which entries to walk.
  • The leaf entry supplies the physical frame.
  • The low offset bits are carried into the final physical address.

14. Same Example With 4 KiB Pages

With 4 KiB pages, there is one more level.

Address split:

VA = 0xffffffff81001234

  63            48 47   39 38   30 29   21 20   12 11        0
 +----------------+-------+-------+-------+-------+-----------+
 | sign extension | PML4  | PDPT  | PD    | PT    | 4K offset |
 +----------------+-------+-------+-------+-------+-----------+

PML4 index = 511 = 0x1ff
PDPT index = 510 = 0x1fe
PD index   =   8 = 0x008
PT index   =   1 = 0x001
4K offset  = 0x234

Walk:

CR3
 |
 v
PML4[511] -> PDPT physical page
              |
              v
            PDPT[510] -> PD physical page
                           |
                           v
                         PD[8] -> PT physical page
                                   |
                                   v
                                 PT[1] -> PA base 0x00101000
                                          flags: present, writable, etc.

Final address:

physical address = 0x00101000 + 0x234
physical address = 0x00101234

Comparison:

2 MiB page:
  leaf base = 0x00100000
  offset    = 0x00001234
  result    = 0x00101234

4 KiB page:
  leaf base = 0x00101000
  offset    = 0x00000234
  result    = 0x00101234

Both mappings can produce the same final physical address. They differ in which page-table level contains the leaf entry and how much memory one leaf entry maps.

Reference:

  • Intel documents 4-level paging, large pages, control registers, and address translation in the SDM: Intel SDM.

15. Pseudocode for Forcing a Mapping

This is conceptual pseudocode, not drop-in firmware code.

map_2m(va_base, pa_base, flags):
    assert va_base is 2 MiB aligned
    assert pa_base is 2 MiB aligned

    pml4_i = bits(va_base, 47, 39)
    pdpt_i = bits(va_base, 38, 30)
    pd_i   = bits(va_base, 29, 21)

    pml4 = physical_address_from_cr3()

    pdpt = ensure_physical_page_table(pml4[pml4_i])
    pd   = ensure_physical_page_table(pdpt[pdpt_i])

    pd[pd_i] = pa_base
             | PRESENT
             | WRITABLE
             | PAGE_SIZE_2M
             | flags

    make_cpu_observe_page_table_update()

Important caveats:

  • ensure_physical_page_table must allocate or use real physical memory.
  • Page-table pointers stored in entries are physical addresses.
  • If modifying active mappings, stale TLB entries must be invalidated.
  • If enabling paging for the first time, the current code path must already be mapped.
  • Cacheability flags must be consistent with MTRR/PAT memory type rules.

16. TLB and Mapping Transition Caveats

Page-table writes are not always enough.

Pointwise:

  • CPUs cache virtual-to-physical translations in TLBs.
  • CPUs may also cache paging-structure information.
  • If a mapping changes while paging is active, old translations may remain.
  • Architecture-defined invalidation is required.
  • On x86, mechanisms include CR3 reloads, INVLPG, and other invalidation instructions depending on CPU features and paging mode.

Transition diagram:

old mapping in TLB
        |
        | page table memory is changed
        v
CPU may still use old translation
        |
        | invalidate / serialize correctly
        v
CPU observes new translation

First enablement caveat:

Before enabling paging:
  instruction pointer names a physical execution path

After enabling paging:
  instruction pointer is interpreted through paging

Therefore:
  the next instruction fetch must be mapped correctly

Reference:

  • Intel documents TLB invalidation, CR3 behavior, INVLPG, and paging serialization rules in the SDM: Intel SDM.

17. Debugging Before There Is a System

Normal logging usually arrives late.

Before that, debugging may use:

  • POST codes;
  • serial output after UART setup;
  • firmware trace buffers after temporary memory exists;
  • JTAG or hardware probes;
  • emulator traces;
  • QEMU logs;
  • GPIO toggles;
  • infinite loops at known milestones.

Capability-aware debugging:

No RAM yet       -> registers, POST codes, hardware trace
UART ready       -> serial bytes
Temp RAM ready   -> tiny trace buffer
DRAM ready       -> larger logs
Paging ready     -> virtual-address-aware diagnostics
OS ready         -> normal logging

Main debugging rule:

  • Do not design the debug method around a capability that has not been created yet.

18. Final Checklist

Keep these invariants in mind:

  • Power-on does not imply DRAM works.
  • Hardwired reset/control logic exists below firmware execution.
  • Microcode itself depends on lower-level hardware sequencing and control.
  • Firmware is ISA-visible code; microcode is internal CPU implementation.
  • Instruction fetch does not imply writable memory exists.
  • XIP does not imply globals are usable.
  • Cache-As-RAM is temporary and vendor-specific.
  • CAR contents must be copied or discarded before teardown.
  • Dirty cache lines must not be allowed to evict to uninitialized DRAM.
  • A stack pointer is valid only if its backing memory is valid.
  • C requires a stack, ABI state, copied .data, and zeroed .bss.
  • Microcode can change behavior that later software treats as architectural.
  • MTRRs and PAT affect whether memory accesses behave like RAM, MMIO, or something else.
  • Page tables require writable physical memory before virtual memory can exist.
  • A page-table walk uses virtual-address bits to find entries.
  • Leaf entries contain physical frame addresses and flags.
  • The page offset is carried into the final physical address.
  • Mapping changes may require TLB invalidation.
  • Enabling paging requires the executing code path to remain mapped.
  • MMIO is not normal RAM.
  • Conflicting cacheability aliases are dangerous.

Source Map

Comments