Kernel entry and CPU bring-up
The kernel's arm64 entry point. _start / start_first_cpu establish bootstrap page tables and stacks, common_start enables the MMU and installs the runtime vectors, and start_cpu brings up secondary cores.
LowResetVectorBase source global
Checks the reset handler for global and CPU-specific reset-assist functions,
then jumps to the reset handler with boot args and cpu data. This is copied
to the first physical page during CPU bootstrap (see cpu.c).
Variables:
x19 - Reset handler data pointer
x20 - Boot args pointer
x21 - CPU data pointer
LEXT(LowResetVectorBase) /* * On reset, both RVBAR_EL1 and VBAR_EL1 point here. SPSel.SP is 1, * so on reset the CPU will jump to offset 0x0 and on exceptions * the CPU will jump to offset 0x200, 0x280, 0x300, or 0x380. * In order for both the reset vector and exception vectors to * coexist in the same space, the reset code is moved to the end * of the exception vector area. */ b EXT(reset_vector) /* EL1 SP1: These vectors trap errors during early startup on non-boot CPUs. */ .align 9 b . .align 7 b . .align 7 b . .align 7 b . .align 7 .globl EXT(reset_vector)
reset_vector source global
LEXT(reset_vector) // Preserve x0 for start_first_cpu, if called // Unlock the core for debugging msr OSLAR_EL1, xzr msr DAIFSet, #(DAIFSC_ALL) // Disable all interrupts #if !(defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR)) // Set low reset vector before attempting any loads adrp x0, EXT(LowExceptionVectorBase)@page add x0, x0, EXT(LowExceptionVectorBase)@pageoff msr VBAR_EL1, x0 #endif // Process reset handlers adrp x19, EXT(ResetHandlerData)@page // Get address of the reset handler data add x19, x19, EXT(ResetHandlerData)@pageoff mrs x15, MPIDR_EL1 // Load MPIDR to get CPU number #if HAS_CLUSTER and x0, x15, #0xFFFF // CPU number in Affinity0, cluster ID in Affinity1 #else and x0, x15, #0xFF // CPU number is in MPIDR Affinity Level 0 #endif ldr x1, [x19, CPU_DATA_ENTRIES] // Load start of data entries add x3, x1, MAX_CPUS * 16 // end addr of data entries = start + (16 * MAX_CPUS)
Lcheck_cpu_data_entry source
end addr of data entries = start + (16 * MAX_CPUS)
Lcheck_cpu_data_entry: ldr x21, [x1, CPU_DATA_PADDR] // Load physical CPU data address cbz x21, Lnext_cpu_data_entry ldr w2, [x21, CPU_PHYS_ID] // Load ccc cpu phys id cmp x0, x2 // Compare cpu data phys cpu and MPIDR_EL1 phys cpu b.eq Lfound_cpu_data_entry // Branch if match
Lnext_cpu_data_entry source
Branch if match
Lnext_cpu_data_entry: add x1, x1, #16 // Increment to the next cpu data entry cmp x1, x3 b.eq Lskip_cpu_reset_handler // Not found b Lcheck_cpu_data_entry // loop
Lfound_cpu_data_entry source
loop
Lfound_cpu_data_entry: #ifdef APPLEEVEREST /* * On H15, we need to configure PIO-only tunables and to apply * PIO lockdown as early as possible. */ SET_PIO_ONLY_REGISTERS x21, x2, x3, x4, x5, x6 #endif /* APPLEEVEREST */ adrp x20, EXT(const_boot_args)@page add x20, x20, EXT(const_boot_args)@pageoff ldr x0, [x21, CPU_RESET_HANDLER] // Call CPU reset handler cbz x0, Lskip_cpu_reset_handler // Validate that our handler is one of the two expected handlers adrp x2, EXT(resume_idle_cpu)@page add x2, x2, EXT(resume_idle_cpu)@pageoff cmp x0, x2 beq 1f adrp x2, EXT(start_cpu)@page add x2, x2, EXT(start_cpu)@pageoff cmp x0, x2 bne Lskip_cpu_reset_handler 1: #if HAS_BP_RET bl EXT(set_bp_ret) #endif #if __ARM_KERNEL_PROTECT__ && defined(KERNEL_INTEGRITY_KTRR) /* * Populate TPIDR_EL1 (in case the CPU takes an exception while * turning on the MMU). */ … more in source
Lskip_cpu_reset_handler source
__ARM_KERNEL_PROTECT__
Lskip_cpu_reset_handler: b . // Hang if the handler is NULL or returns .align 3 .global EXT(LowResetVectorEnd)
LowResetVectorEnd source global
Hang if the handler is NULL or returns
LEXT(LowResetVectorEnd) .global EXT(SleepToken) #if WITH_CLASSIC_S2R
SleepToken source global
LEXT(SleepToken) .space (stSize_NUM),0 #endif .section __DATA_CONST,__const .align 3 .globl EXT(ResetHandlerData)
ResetHandlerData source global
LEXT(ResetHandlerData) .space (rhdSize_NUM),0 // (filled with 0s) .text /* * __start trampoline is located at a position relative to LowResetVectorBase * so that iBoot can compute the reset vector position to set IORVBAR using * only the kernel entry point. Reset vector = (__start & ~0xfff) */ .align 3 .globl EXT(_start)
_start source global
__start trampoline is located at a position relative to LowResetVectorBase
so that iBoot can compute the reset vector position to set IORVBAR using
only the kernel entry point. Reset vector = (__start & ~0xfff)
LEXT(_start) ARM64_PROLOG b EXT(start_first_cpu) /* * Provides an early-boot exception vector so that the processor will spin * and preserve exception information (e.g., ELR_EL1) when early CPU bootstrap * code triggers an exception. This is copied to the second physical page * during CPU bootstrap (see cpu.c). */ .align 12, 0 .global EXT(LowExceptionVectorBase)
LowExceptionVectorBase source global
Provides an early-boot exception vector so that the processor will spin
and preserve exception information (e.g., ELR_EL1) when early CPU bootstrap
code triggers an exception. This is copied to the second physical page
during CPU bootstrap (see cpu.c).
LEXT(LowExceptionVectorBase) /* EL1 SP 0 */ b . .align 7 b . .align 7 b . .align 7 b . /* EL1 SP1 */ .align 7 b . .align 7 b . .align 7 b . .align 7 b . /* EL0 64 */ .align 7 b . .align 7 b . .align 7 b . .align 7 b . /* EL0 32 */ .align 7 b . .align 7 b . .align 7 b . .align 7 … more in source
bootstrap_instructions source global
Provide a global symbol so that we can narrow the V=P mapping to cover
this page during arm_vm_init.
LEXT(bootstrap_instructions) #endif /* defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR) */ .align 2 .globl EXT(resume_idle_cpu)
resume_idle_cpu source global
defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR)
LEXT(resume_idle_cpu) adrp lr, EXT(arm_init_idle_cpu)@page add lr, lr, EXT(arm_init_idle_cpu)@pageoff b start_cpu .align 2 .globl EXT(start_cpu)
start_cpu source global
LEXT(start_cpu) adrp lr, EXT(arm_init_cpu)@page add lr, lr, EXT(arm_init_cpu)@pageoff b start_cpu .align 2 start_cpu: #if defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR) // This is done right away in reset vector for pre-KTRR devices // Set low reset vector now that we are in the KTRR-free zone adrp x0, EXT(LowExceptionVectorBase)@page add x0, x0, EXT(LowExceptionVectorBase)@pageoff MSR_VBAR_EL1_X0 #endif /* defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR) */ // x20 set to BootArgs phys address // x21 set to cpu data phys address // Get the kernel memory parameters from the boot args ldr x22, [x20, BA_VIRT_BASE] // Get the kernel virt base ldr x23, [x20, BA_PHYS_BASE] // Get the kernel phys base ldr x24, [x20, BA_MEM_SIZE] // Get the physical memory size adrp x25, EXT(bootstrap_pagetables)@page // Get the start of the page tables ldr x26, [x20, BA_BOOT_FLAGS] // Get the kernel boot flags // Set TPIDR_EL0 with cached CPU info ldr x0, [x21, CPU_TPIDR_EL0] msr TPIDR_EL0, x0 // Set TPIDRRO_EL0 to 0 msr TPIDRRO_EL0, xzr // Set the exception stack pointer … more in source
start_cpu source
start_cpu: #if defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR) // This is done right away in reset vector for pre-KTRR devices // Set low reset vector now that we are in the KTRR-free zone adrp x0, EXT(LowExceptionVectorBase)@page add x0, x0, EXT(LowExceptionVectorBase)@pageoff MSR_VBAR_EL1_X0 #endif /* defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR) */ // x20 set to BootArgs phys address // x21 set to cpu data phys address // Get the kernel memory parameters from the boot args ldr x22, [x20, BA_VIRT_BASE] // Get the kernel virt base ldr x23, [x20, BA_PHYS_BASE] // Get the kernel phys base ldr x24, [x20, BA_MEM_SIZE] // Get the physical memory size adrp x25, EXT(bootstrap_pagetables)@page // Get the start of the page tables ldr x26, [x20, BA_BOOT_FLAGS] // Get the kernel boot flags // Set TPIDR_EL0 with cached CPU info ldr x0, [x21, CPU_TPIDR_EL0] msr TPIDR_EL0, x0 // Set TPIDRRO_EL0 to 0 msr TPIDRRO_EL0, xzr // Set the exception stack pointer ldr x0, [x21, CPU_EXCEPSTACK_TOP] // Set SP_EL1 to exception stack #if defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR) mov x1, lr … more in source
start_first_cpu source global
_start_first_cpu
Cold boot init routine. Called from __start
x0 - Boot args
LEXT(start_first_cpu) // Unlock the core for debugging msr OSLAR_EL1, xzr msr DAIFSet, #(DAIFSC_ALL) // Disable all interrupts mov x20, x0 mov x21, #0 // Set low reset vector before attempting any loads adrp x0, EXT(LowExceptionVectorBase)@page add x0, x0, EXT(LowExceptionVectorBase)@pageoff MSR_VBAR_EL1_X0 // Get the kernel memory parameters from the boot args ldr x22, [x20, BA_VIRT_BASE] // Get the kernel virt base ldr x23, [x20, BA_PHYS_BASE] // Get the kernel phys base ldr x24, [x20, BA_MEM_SIZE] // Get the physical memory size adrp x25, EXT(bootstrap_pagetables)@page // Get the start of the page tables ldr x26, [x20, BA_BOOT_FLAGS] // Get the kernel boot flags // Clear the registers that will be used to store the userspace thread pointer and CPU number. // We may not actually be booting from ordinal CPU 0, so this register will be updated // in ml_parse_cpu_topology(), which happens later in bootstrap. msr TPIDRRO_EL0, xzr msr TPIDR_EL0, xzr // Set up exception stack pointer adrp x0, EXT(excepstack_top)@page // Load top of exception stack add x0, x0, EXT(excepstack_top)@pageoff add x0, x0, x22 // Convert to KVA sub x0, x0, x23 // Set SP_EL1 to exception stack … more in source
Linvalidate_bootstrap source
Shift by 2 for num entries on 4 pages
Linvalidate_bootstrap: // do { str x0, [x1], #(1 << TTE_SHIFT) // Invalidate and advance subs x2, x2, #1 // entries-- b.ne Linvalidate_bootstrap // } while (entries != 0) /* * In order to reclaim memory on targets where TZ0 (or some other entity) * must be located at the base of memory, iBoot may set the virtual and * physical base addresses to immediately follow whatever lies at the * base of physical memory. * * If the base address belongs to TZ0, it may be dangerous for xnu to map * it (as it may be prefetched, despite being technically inaccessible). * In order to avoid this issue while keeping the mapping code simple, we * may continue to use block mappings, but we will only map the kernelcache * mach header to the end of memory. * * Given that iBoot guarantees that the unslid kernelcache base address * will begin on an L2 boundary, this should prevent us from accidentally * mapping TZ0. */ adrp x0, EXT(_mh_execute_header)@page // address of kernel mach header add x0, x0, EXT(_mh_execute_header)@pageoff ldr w1, [x0, #0x18] // load mach_header->flags tbz w1, #0x1f, Lkernelcache_base_found // if MH_DYLIB_IN_CACHE unset, base is kernel mach header ldr w1, [x0, #0x20] // load first segment cmd (offset sizeof(kernel_mach_header_t)) cmp w1, #0x19 // must be LC_SEGMENT_64 bne . ldr x1, [x0, #0x38] // load first segment vmaddr sub x1, x0, x1 // compute slide MOV64 x0, VM_KERNEL_LINK_ADDRESS add x0, x0, x1 // base is kernel link address + slide
Lkernelcache_base_found source
base is kernel link address + slide
Lkernelcache_base_found: /* * Adjust physical and virtual base addresses to account for physical * memory preceeding xnu Mach-O header * x22 - Kernel virtual base * x23 - Kernel physical base * x24 - Physical memory size */ sub x18, x0, x23 sub x24, x24, x18 add x22, x22, x18 add x23, x23, x18 /* * x0 - V=P virtual cursor * x4 - V=P physical cursor * x14 - KVA virtual cursor * x15 - KVA physical cursor */ mov x4, x0 mov x14, x22 mov x15, x23 /* * Allocate L1 tables * x1 - V=P L1 page * x3 - KVA L1 page * x2 - free mem pointer from which we allocate a variable number of L2 * pages. The maximum number of bootstrap page table pages is limited to * BOOTSTRAP_TABLE_SIZE. For a 2G 4k page device, assuming the worst-case * slide, we need 1xL1 and up to 3xL2 pages (1GB mapped per L1 entry), so * 8 total pages for V=P and KVA. */ mov x1, x25 add x3, x1, PGBYTES … more in source
common_start source
Begin common CPU initialization
Regster state:
x20 - PA of boot args
x21 - zero on cold boot, PA of cpu data on warm reset
x22 - Kernel virtual base
x23 - Kernel physical base
x25 - PA of the V=P pagetable root
lr - KVA of C init routine
sp - SP_EL0 selected
SP_EL0 - KVA of CPU's interrupt stack
SP_EL1 - KVA of CPU's exception stack
TPIDRRO_EL0 - CPU number
common_start: #if HAS_NEX_PG mov x19, lr bl EXT(set_nex_pg) mov lr, x19 #endif // Set the translation control register. MOV64 x1, TCR_EL1_BOOT MSR_TCR_EL1_X1 /* Set up translation table base registers. * TTBR0 - V=P table @ top of kernel * TTBR1 - KVA table @ top of kernel + 1 page */ #if defined(KERNEL_INTEGRITY_KTRR) || defined(KERNEL_INTEGRITY_CTRR) || defined(KERNEL_INTEGRITY_PV_CTRR) /* Note that for KTRR configurations, the V=P map will be modified by * arm_vm_init.c. */ #endif and x0, x25, #(TTBR_BADDR_MASK) mov x19, lr bl EXT(set_mmu_ttb) mov lr, x19 add x0, x25, PGBYTES and x0, x0, #(TTBR_BADDR_MASK) MSR_TTBR1_EL1_X0 // Set up MAIR attr0 for normal memory, attr1 for device memory mov x0, xzr mov x1, #(MAIR_WRITEBACK << MAIR_ATTR_SHIFT(CACHE_ATTRINDX_WRITEBACK)) orr x0, x0, x1 mov x1, #(MAIR_WRITETHRU << MAIR_ATTR_SHIFT(CACHE_ATTRINDX_WRITETHRU)) … more in source
Ltrampoline source
Return to arm_init()
Ltrampoline: // Load VA of the trampoline adrp x0, arm_init_tramp@page add x0, x0, arm_init_tramp@pageoff add x0, x0, x22 sub x0, x0, x23 // Branch to the trampoline br x0 /* * V=P to KVA trampoline. * x0 - KVA of cpu data pointer */ .text .align 2
arm_init_tramp source
V=P to KVA trampoline.
x0 - KVA of cpu data pointer
arm_init_tramp: ARM64_JUMP_TARGET /* On a warm boot, the full kernel translation table is initialized in * addition to the bootstrap tables. The layout is as follows: * * +--Top of Memory--+ * ... * | | * | Primary Kernel | * | Trans. Table | * | | * +--Top + 5 pages--+ * | | * | Invalid Table | * | | * +--Top + 4 pages--+ * | | * | KVA Table | * | | * +--Top + 2 pages--+ * | | * | V=P Table | * | | * +--Top of Kernel--+ * | | * | Kernel Mach-O | * | | * ... * +---Kernel Base---+ */ mov x19, lr // Convert CPU data PA to VA and set as first argument mov x0, x21 … more in source
MSR_VBAR_EL1_X0 source macro
.macro MSR_VBAR_EL1_X0 #if defined(KERNEL_INTEGRITY_KTRR) mov x1, lr bl EXT(pinst_set_vbar) mov lr, x1 #else msr VBAR_EL1, x0 #endif .endmacro
MSR_TCR_EL1_X1 source macro
.macro MSR_TCR_EL1_X1 #if defined(KERNEL_INTEGRITY_KTRR) mov x0, x1 mov x1, lr bl EXT(pinst_set_tcr) mov lr, x1 #else msr TCR_EL1, x1 #endif .endmacro
MSR_TTBR1_EL1_X0 source macro
.macro MSR_TTBR1_EL1_X0 #if defined(KERNEL_INTEGRITY_KTRR) mov x1, lr bl EXT(pinst_set_ttbr1) mov lr, x1 #else msr TTBR1_EL1, x0 #endif .endmacro
MSR_SCTLR_EL1_X0 source macro
.macro MSR_SCTLR_EL1_X0 #if defined(KERNEL_INTEGRITY_KTRR) mov x1, lr // This may abort, do so on SP1 bl EXT(pinst_spsel_1) bl EXT(pinst_set_sctlr) msr SPSel, #0 // Back to SP0 mov lr, x1 #else msr SCTLR_EL1, x0 #endif /* defined(KERNEL_INTEGRITY_KTRR) */ .endmacro
create_l1_table_entry source macro
.macro create_l1_table_entry and $3, $0, #(ARM_PTE_T1_REGION_MASK(TCR_EL1_BOOT)) lsr $3, $3, #(ARM_TT_L1_SHIFT) // Get index in L1 table for L2 table lsl $3, $3, #(TTE_SHIFT) // Convert index into pointer offset add $3, $1, $3 // Get L1 entry pointer mov $4, #(ARM_TTE_BOOT_TABLE) // Get L1 table entry template and $5, $2, #(ARM_TTE_TABLE_MASK) // Get address bits of L2 table orr $5, $4, $5 // Create table entry for L2 table str $5, [$3] // Write entry to L1 table .endmacro
create_l2_block_entries source macro
.macro create_l2_block_entries and $4, $0, #(ARM_TT_L2_INDEX_MASK) lsr $4, $4, #(ARM_TTE_BLOCK_L2_SHIFT) // Get index in L2 table for block entry lsl $4, $4, #(TTE_SHIFT) // Convert index into pointer offset add $4, $2, $4 // Get L2 entry pointer mov $5, #(ARM_TTE_BOOT_BLOCK_LOWER) // Get L2 block entry template orr $5, $5, #(ARM_TTE_BOOT_BLOCK_UPPER) and $6, $1, #(ARM_TTE_BLOCK_L2_MASK) // Get address bits of block mapping orr $6, $5, $6 mov $5, $3 mov $7, #(ARM_TT_L2_SIZE) 1: str $6, [$4], #(1 << TTE_SHIFT) // Write entry to L2 table and advance add $6, $6, $7 // Increment the output address subs $5, $5, #1 // Decrement the number of entries b.ne 1b .endmacro
create_bootstrap_mapping source macro
.macro create_bootstrap_mapping /* calculate entries left in this page */ and $5, $0, #(ARM_TT_L2_INDEX_MASK) lsr $5, $5, #(ARM_TT_L2_SHIFT) mov $6, #(TTE_PGENTRIES) sub $5, $6, $5 /* allocate an L2 table */ 3: add $4, $4, PGBYTES /* create_l1_table_entry(virt_base, L1 table, L2 table, scratch1, scratch2, scratch3) */ create_l1_table_entry $0, $3, $4, $6, $7, $8 /* determine how many entries to map this loop - the smaller of entries * remaining in page and total entries left */ cmp $2, $5 csel $5, $2, $5, lt /* create_l2_block_entries(virt_base, phys_base, L2 table, num_ents, scratch1, scratch2, scratch3) */ create_l2_block_entries $0, $1, $4, $5, $6, $7, $8, $9 /* subtract entries just mapped and bail out if we're done */ subs $2, $2, $5 beq 2f /* entries left to map - advance base pointers */ add $0, $0, $5, lsl #(ARM_TT_L2_SHIFT) add $1, $1, $5, lsl #(ARM_TT_L2_SHIFT) mov $5, #(TTE_PGENTRIES) /* subsequent loops map (up to) a whole L2 page */ b 3b 2: .endmacro