Early Initialization

Document Revision: 26h1.0
Source: zxfoundation/init/main.c


1. Initialization Sequence

zxfoundation_global_initialize performs early initialization in strict order before enabling interrupts or starting APs:

StepActionNotes
1zxfl_lowcore_setup()Install kernel new PSWs in the BSP lowcore
2diag_setup() + printk_initialize()Enable console output
3Validate boot->magic == ZXFL_MAGICPanic if wrong
4Validate boot->binding_tokenRecompute and compare; panic on mismatch
5validate_stack_frame()Verify ZXVL stack canaries
6verify_kernel_checksums()Re-verify SHA-256 segment digests from HHDM
7Print machine/LPAR/CPU infoIf ZXFL_FLAG_SYSINFO / ZXFL_FLAG_SMP set
8percpu_init_bsp()Initialize BSP per-CPU block at prefix+0x200
9arch_cpu_features_init(boot)Detect STFLE facilities, populate feature flags
10rcu_init()Initialize RCU subsystem
11pmm_init(boot)Register usable memory regions; reserve loader/kernel/pool
12mmu_init()Install 8 KB VA-0 lowcore window; scrub identity map; inherit EDAT-1/2 state. Order is mandatory — see §4.
13vmm_init()Set up vmalloc region
14slab_init()Initialize slab caches
15kmalloc_init()Initialize kmalloc size classes
16trap_init()Install program-check new PSW; enable trap handler
17smp_init()Start all APs (SIGP sequence); each AP calls trap_init()
18sched_init()BSP becomes idle (PID 0); spawns kernel_init (PID 1)

2. Security Checks (Steps 3–6)

These checks run before any subsystem is initialized. A failure at any point calls panic(), which loads a disabled-wait PSW.

Binding token (step 4): The kernel recomputes ZXVL_COMPUTE_TOKEN(stfle_fac[0], ipl_schid) and compares it to boot->binding_token. This ties the running kernel to the specific hardware and IPL device — a protocol struct copied from another machine will fail here.

Stack frame (step 5): The loader writes a two-word canary at boot->kernel_stack_top. The kernel verifies frame[0] == ZXVL_FRAME_MAGIC_A and frame[1] == ZXVL_FRAME_MAGIC_B ^ binding_token. A mismatch indicates stack corruption or an unauthorized loader.

Checksum re-verification (step 6): The kernel re-reads the zxvl_checksum_table_t from kernel_phys_start + ZXVL_CKSUM_TABLE_OFFSET (via HHDM) and recomputes SHA-256 for each PT_LOAD segment. This catches any modification to the kernel image between loader verification and kernel execution.


3. PMM Reservation (Step 10)

pmm_init registers all ZXFL_MEM_USABLE regions from the boot protocol memory map, then marks the following ranges as reserved:

RangeReason
[0, 1 MB)Lowcore + loader code
[kernel_phys_start, kernel_phys_end)Kernel image
[pool_base, pgtbl_pool_end)Bootloader page table pool
Each module's [phys_start, phys_start + size)Loaded modules

4. MMU Initialization Ordering Invariant (Step 12)

mmu_init() takes ownership of the bootloader ASCE and replaces the bootloader's 8 GB identity map with a precise 8 KB window at VA 0. This operation has a strict, unbreakable ordering requirement rooted in z/Architecture hardware behavior.

Why VA 0 Must Always Be Mapped

Every interrupt handler entry stub (trap_pgm_entry, trap_ext_entry, etc.) begins with:

lg  %r1, LC_ASYNC_STACK(0)   // effective VA = 0x0350

The zero base register is not an error — it is the only way to load a value before registers have been saved. Because DAT is active when this runs, VA 0x350 must be translated successfully. If the mapping is absent even for one instruction cycle while interrupts are unmasked, a program-check fires, SAVE_FRAME tries to load from VA 0x350 again, and the CPU enters an infinite Region-first-translation exception (0x0039) death loop.

Required Sequence in mmu_init()

 Step 1: mmu_map_page(VA 0x0000 → PA 0x0000)   // build mapping first
 Step 2: mmu_map_page(VA 0x1000 → PA 0x1000)   // both pages of the lowcore
 Step 3: scrub r1[1..2046]                      // revoke identity map
 Step 4: mmu_flush_tlb_local()                  // make scrub visible to CPU

Steps 1–2 must precede steps 3–4. The new 8 KB mapping is committed into the live R1 table before any identity entry is removed, so VA 0x350 is always valid.

Can This Be Avoided by Enabling DAT Earlier?

No. The requirement is not a consequence of when DAT is enabled; it comes from how SAVE_FRAME accesses the lowcore. Even if ZXFL enabled DAT internally and passed the kernel a fully virtual address space, the kernel's entry.S would still execute lg %r1, 0x350(0) and still require VA 0x350 to be mapped. This is standard z/Architecture operating system design — Linux s390x, z/VM, and z/OS all maintain an equivalent lowcore window at virtual address 0 for the same reason. See docs/src/kernel/trap.md for the full architectural rationale.