001Notes

Booting, My Confusions and ChatGPT

On this page20

A capability-by-capability note on early boot: reset vectors, firmware, microcode, XIP, cache-as-RAM, DRAM bring-up, C runtime setup, memory types, and first page tables.

Article details
Status
Building Publicly
Subcategory
Booting
Last reviewed
26 Aug 2026
20 sections

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

Primary references

  1. System V ABI: ELF object files
  2. GNU GRUB Multiboot2 specification
Diagram