//! Guest-thread image allocation — shared by the initial thread setup in //! `xenia-app/src/main.rs` and `ExCreateThread`. Stack, PCR, and TLS blocks //! all come from the existing kernel bump allocators so layout is consistent. use xenia_memory::{GuestMemory, MemoryAccess}; use crate::state::KernelState; /// Addresses the caller passes to `Scheduler::spawn` / the initial-thread /// setup. Matches xenia-canary's per-thread allocations: a stack, a PCR, and /// a TLS block. #[derive(Debug, Clone, Copy)] pub struct ThreadImage { pub stack_base: u32, pub stack_size: u32, pub pcr_base: u32, pub tls_base: u32, } /// Allocate stack + PCR + TLS for one guest thread and initialize the PCR /// fields that games read in their thread prolog. /// /// - Stack comes from `KernelState::stack_alloc` (bump allocator at /// 0x7100_0000 upward). The returned base is the *bottom*; callers /// compute SP as `base + size`. /// - PCR and TLS are fixed 4 KiB pages allocated via `heap_alloc` so they /// land in the user heap region together with other kernel metadata. /// - `hw_thread_id` is written at PCR+0x2C so `KeGetCurrentProcessorNumber`- /// style reads from r13 resolve correctly even though we never register /// that export. pub fn allocate_thread_image( kernel: &mut KernelState, mem: &GuestMemory, stack_size: u32, hw_thread_id: u8, ) -> Option { // Round stack size to a page and give games a minimum that matches // xenia-canary's 16 MiB default when callers request 0 (common for // ExCreateThread when the caller lets the kernel pick). let stack_size = if stack_size == 0 { 0x10_0000 } else { (stack_size + 0xFFF) & !0xFFF }; // stack_alloc returns top-of-stack; we need the base. let stack_top = kernel.stack_alloc(stack_size, mem)?; let stack_base = stack_top - stack_size; let pcr_base = kernel.heap_alloc(0x1000, mem)?; let tls_base = kernel.heap_alloc(0x1000, mem)?; // PCR layout (canary xboxkrnl/xboxkrnl_module.cc, simplified): // +0x000 tls_ptr → TLS block base // +0x02C current_processor_id → HW thread id (0..5) // +0x100 current_thread → placeholder non-zero tag // +0x150 dpc_active → 0 (no DPC queued) mem.write_u32(pcr_base, tls_base); mem.write_u32(pcr_base + 0x2C, hw_thread_id as u32); mem.write_u32(pcr_base + 0x100, 0x1000); mem.write_u32(pcr_base + 0x150, 0); Some(ThreadImage { stack_base, stack_size, pcr_base, tls_base, }) }