Adds `walk_guest_back_chain` (PPC EABI back-chain walker) and a
`record_create_with_stack` audit hook gated on `--trace-handles-focus`.
NtCreateEvent / NtCreateSemaphore / NtCreateTimer / XamTaskSchedule now
route through the new helper so focused handles capture up to 6 stack
frames at allocation time. Diagnostic-only, read-only memory access:
unfocused handles pay one HashSet lookup, focused ones pay six
back-chain dereferences. Lockstep determinism preserved.
End-to-end finding: handles 0x1004 (8-instance pool via static ctor at
0x8280F810), 0x100c (singleton built inside main()), 0x15e0 (singleton
in distinct cluster) are silph-framework dispatcher objects whose
producer code is unreached at -n 500M. The producer hunt now has class
ownership; vtable/RTTI readout is the next step.
Tests: 576 → 581 green. `--stable-digest -n 100M` instructions=100000002
unchanged. Master HEAD prior: 9d45efe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
469 lines
17 KiB
Rust
469 lines
17 KiB
Rust
//! HLE kernel export implementations (xam.xex).
|
|
|
|
use crate::objects::KernelObject;
|
|
use crate::state::{GuestMemoryPcr, KernelState, ModuleId};
|
|
use crate::thread::allocate_thread_image;
|
|
use xenia_cpu::PpcContext;
|
|
use xenia_cpu::scheduler::SpawnParams;
|
|
use xenia_memory::{GuestMemory, MemoryAccess};
|
|
|
|
pub fn register_exports(state: &mut KernelState) {
|
|
use ModuleId::Xam;
|
|
|
|
// Net
|
|
state.register_export(Xam, 0x01, "NetDll_WSAStartup", stub_success);
|
|
state.register_export(Xam, 0x02, "NetDll_WSACleanup", stub_success);
|
|
|
|
// Input
|
|
state.register_export(Xam, 0x0190, "XamInputGetCapabilities", xam_input_get_capabilities);
|
|
state.register_export(Xam, 0x0191, "XamInputGetState", xam_input_get_state);
|
|
state.register_export(Xam, 0x0192, "XamInputSetState", xam_input_set_state);
|
|
state.register_export(Xam, 0x0198, "XamInputGetKeystrokeEx", xam_input_get_keystroke);
|
|
|
|
// Inactivity
|
|
state.register_export(Xam, 0x01A0, "XamEnableInactivityProcessing", stub_success);
|
|
state.register_export(Xam, 0x01A1, "XamResetInactivity", stub_success);
|
|
|
|
// Loader
|
|
state.register_export(Xam, 0x01A4, "XamLoaderLaunchTitle", xam_loader_launch_title);
|
|
state.register_export(Xam, 0x01A9, "XamLoaderTerminateTitle", xam_loader_terminate_title);
|
|
|
|
// Task
|
|
state.register_export(Xam, 0x01AF, "XamTaskSchedule", xam_task_schedule);
|
|
state.register_export(Xam, 0x01B1, "XamTaskCloseHandle", stub_success);
|
|
state.register_export(Xam, 0x01B3, "XamTaskShouldExit", stub_return_zero);
|
|
|
|
// Alloc
|
|
state.register_export(Xam, 0x01EA, "XamAlloc", xam_alloc);
|
|
state.register_export(Xam, 0x01EC, "XamFree", stub_success);
|
|
|
|
// Msg
|
|
state.register_export(Xam, 0x01F4, "XMsgInProcessCall", stub_success);
|
|
state.register_export(Xam, 0x01F7, "XMsgStartIORequest", stub_success);
|
|
state.register_export(Xam, 0x01FC, "XMsgStartIORequestEx", stub_success);
|
|
|
|
// User
|
|
state.register_export(Xam, 0x020A, "XamUserGetXUID", xam_user_get_xuid);
|
|
state.register_export(Xam, 0x020E, "XamUserGetName", xam_user_get_name);
|
|
state.register_export(Xam, 0x0210, "XamUserGetSigninState", stub_return_zero);
|
|
state.register_export(Xam, 0x0219, "XamUserReadProfileSettings", xam_user_read_profile_settings);
|
|
state.register_export(Xam, 0x021A, "XamUserWriteProfileSettings", stub_success);
|
|
|
|
// Enum
|
|
state.register_export(Xam, 0x0250, "XamEnumerate", stub_error_no_more_files);
|
|
|
|
// Content
|
|
state.register_export(Xam, 0x0258, "XamContentCreate", stub_success);
|
|
state.register_export(Xam, 0x025A, "XamContentClose", stub_success);
|
|
state.register_export(Xam, 0x025B, "XamContentDelete", stub_success);
|
|
state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", stub_success);
|
|
state.register_export(Xam, 0x025E, "XamContentGetDeviceData", stub_success);
|
|
state.register_export(Xam, 0x025F, "XamContentGetDeviceName", stub_success);
|
|
state.register_export(Xam, 0x0260, "XamContentSetThumbnail", stub_success);
|
|
state.register_export(Xam, 0x0262, "XamContentGetCreator", stub_success);
|
|
state.register_export(Xam, 0x0265, "XamContentGetDeviceState", stub_success);
|
|
|
|
// System
|
|
state.register_export(Xam, 0x0280, "XamGetExecutionId", xam_get_execution_id);
|
|
state.register_export(Xam, 0x0282, "XamGetSystemVersion", xam_get_system_version);
|
|
|
|
// Notify
|
|
state.register_export(Xam, 0x028A, "XamNotifyCreateListener", xam_notify_create_listener);
|
|
state.register_export(Xam, 0x028B, "XNotifyGetNext", xnotify_get_next);
|
|
state.register_export(Xam, 0x028C, "XNotifyPositionUI", stub_success);
|
|
|
|
// Achievements/Stats
|
|
state.register_export(Xam, 0x02EE, "XamUserCreateAchievementEnumerator", stub_success);
|
|
state.register_export(Xam, 0x02F7, "XamUserCreateStatsEnumerator", stub_success);
|
|
|
|
// UI
|
|
state.register_export(Xam, 0x02BC, "XamShowSigninUI", stub_success);
|
|
state.register_export(Xam, 0x02C1, "XamShowKeyboardUI", stub_success);
|
|
state.register_export(Xam, 0x02CB, "XamShowDeviceSelectorUI", stub_success);
|
|
state.register_export(Xam, 0x02D5, "XamShowGamerCardUIForXUID", stub_success);
|
|
state.register_export(Xam, 0x02D9, "XamShowDirtyDiscErrorUI", stub_success);
|
|
state.register_export(Xam, 0x02DC, "XamShowMessageBoxUIEx", stub_success);
|
|
|
|
// Session
|
|
state.register_export(Xam, 0x0316, "XamSessionCreateHandle", xam_session_create_handle);
|
|
state.register_export(Xam, 0x0317, "XamSessionRefObjByHandle", stub_success);
|
|
|
|
// Locale
|
|
state.register_export(Xam, 0x03CB, "XGetAVPack", xget_avpack);
|
|
state.register_export(Xam, 0x03CC, "XGetGameRegion", xget_game_region);
|
|
state.register_export(Xam, 0x03CD, "XGetLanguage", xget_language);
|
|
state.register_export(Xam, 0x03D1, "XGetVideoMode", xget_video_mode);
|
|
}
|
|
|
|
// ===== Generic stubs =====
|
|
|
|
fn stub_success(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
fn stub_return_zero(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
fn stub_error_no_more_files(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
ctx.gpr[3] = 0x12; // ERROR_NO_MORE_FILES
|
|
}
|
|
|
|
// ===== Input =====
|
|
|
|
/// Helper: pack a `GamepadState` into a 12-byte key used to detect input
|
|
/// changes. Cheap to compare across frames.
|
|
fn gamepad_key(state: &xenia_hid::GamepadState) -> u128 {
|
|
let mut bytes = [0u8; 16];
|
|
bytes[0..2].copy_from_slice(&state.buttons.to_be_bytes());
|
|
bytes[2] = state.left_trigger;
|
|
bytes[3] = state.right_trigger;
|
|
bytes[4..6].copy_from_slice(&state.left_stick_x.to_be_bytes());
|
|
bytes[6..8].copy_from_slice(&state.left_stick_y.to_be_bytes());
|
|
bytes[8..10].copy_from_slice(&state.right_stick_x.to_be_bytes());
|
|
bytes[10..12].copy_from_slice(&state.right_stick_y.to_be_bytes());
|
|
u128::from_be_bytes(bytes)
|
|
}
|
|
|
|
fn xam_input_get_capabilities(
|
|
ctx: &mut PpcContext,
|
|
mem: &GuestMemory,
|
|
state: &mut KernelState,
|
|
) {
|
|
// r3 = user_index, r4 = flags, r5 = out X_INPUT_CAPABILITIES*
|
|
let user = ctx.gpr[3] as u32;
|
|
let out_ptr = ctx.gpr[5] as u32;
|
|
let connected = state.ui.as_ref().is_some_and(|ui| ui.is_connected(user));
|
|
if !connected {
|
|
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
|
|
return;
|
|
}
|
|
xenia_hid::write_input_capabilities(mem, out_ptr);
|
|
ctx.gpr[3] = xenia_hid::errors::SUCCESS as u64;
|
|
}
|
|
|
|
fn xam_input_get_state(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
|
// r3 = user_index, r4 = flags, r5 = out X_INPUT_STATE*
|
|
let user = ctx.gpr[3] as u32;
|
|
let out_ptr = ctx.gpr[5] as u32;
|
|
let Some(ui) = state.ui.as_ref() else {
|
|
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
|
|
return;
|
|
};
|
|
if !ui.is_connected(user) {
|
|
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
|
|
return;
|
|
}
|
|
let gamepad = ui.snapshot_gamepad();
|
|
let key = gamepad_key(&gamepad);
|
|
if key != state.last_input_bytes {
|
|
state.input_packet_number = state.input_packet_number.wrapping_add(1);
|
|
state.last_input_bytes = key;
|
|
}
|
|
xenia_hid::write_input_state(mem, out_ptr, state.input_packet_number, &gamepad);
|
|
ctx.gpr[3] = xenia_hid::errors::SUCCESS as u64;
|
|
}
|
|
|
|
fn xam_input_set_state(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut KernelState) {
|
|
// r3 = user_index, r4 = flags, r5 = X_INPUT_VIBRATION*
|
|
// Rumble is out of scope for Phase 1; we accept the call and return
|
|
// success so games don't retry in a tight loop, but we never actually
|
|
// shake anything.
|
|
let user = ctx.gpr[3] as u32;
|
|
let connected = state.ui.as_ref().is_some_and(|ui| ui.is_connected(user));
|
|
if !connected {
|
|
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
|
|
return;
|
|
}
|
|
ctx.gpr[3] = xenia_hid::errors::SUCCESS as u64;
|
|
}
|
|
|
|
fn xam_input_get_keystroke(
|
|
ctx: &mut PpcContext,
|
|
_mem: &GuestMemory,
|
|
_state: &mut KernelState,
|
|
) {
|
|
// No keyboard input in Phase 1 — always "queue empty". Games that only
|
|
// use the gamepad ignore this return code; those that drive text entry
|
|
// through the keystroke queue simply get a permanently empty queue, which
|
|
// manifests as no virtual-keyboard input — acceptable for minimal UI.
|
|
ctx.gpr[3] = xenia_hid::errors::EMPTY as u64;
|
|
}
|
|
|
|
// ===== Loader =====
|
|
|
|
fn xam_loader_launch_title(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
tracing::warn!("XamLoaderLaunchTitle called");
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
fn xam_loader_terminate_title(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
tracing::warn!("XamLoaderTerminateTitle called");
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
// ===== Task =====
|
|
|
|
/// `XamTaskSchedule(callback, message, optional_ptr, handle_ptr_out)` —
|
|
/// spawn a guest thread that runs `callback(message)` asynchronously.
|
|
/// Mirrors xenia-canary's `XamTaskSchedule_entry` (xam_task.cc:43-80):
|
|
/// stack is `max(0x4000, page-aligned default)`, the new thread enters at
|
|
/// `callback` with `message` in r3, and the resulting thread handle is
|
|
/// written to `handle_ptr_out`.
|
|
fn xam_task_schedule(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
|
let callback = ctx.gpr[3] as u32;
|
|
let message_ptr = ctx.gpr[4] as u32;
|
|
let optional_ptr = ctx.gpr[5] as u32;
|
|
let handle_ptr = ctx.gpr[6] as u32;
|
|
|
|
if optional_ptr != 0 {
|
|
let v1 = mem.read_u32(optional_ptr);
|
|
let v2 = mem.read_u32(optional_ptr + 4);
|
|
tracing::info!("XamTaskSchedule: args v1={:#010x} v2={:#010x}", v1, v2);
|
|
}
|
|
|
|
let stack_size = std::cmp::max(0x4000u32, (0x10_0000u32 + 0xFFF) & !0xFFF);
|
|
|
|
let Some(image) = allocate_thread_image(state, mem, stack_size, 0) else {
|
|
tracing::error!("XamTaskSchedule: failed to allocate thread image");
|
|
ctx.gpr[3] = 0xC000_009A; // STATUS_INSUFFICIENT_RESOURCES
|
|
return;
|
|
};
|
|
|
|
use std::sync::atomic::Ordering;
|
|
let tid = state.next_thread_id.fetch_add(1, Ordering::Relaxed);
|
|
let handle = state.alloc_handle_for(KernelObject::Thread {
|
|
id: tid,
|
|
hw_id: None,
|
|
exit_code: None,
|
|
waiters: Vec::new(),
|
|
});
|
|
|
|
let tls_slot_count = state.next_tls_index.load(Ordering::Relaxed);
|
|
let params = SpawnParams {
|
|
entry: callback,
|
|
start_context: message_ptr,
|
|
stack_base: image.stack_base,
|
|
stack_size: image.stack_size,
|
|
pcr_base: image.pcr_base,
|
|
tls_base: image.tls_base,
|
|
thread_handle: handle,
|
|
guest_tid: tid,
|
|
create_suspended: false,
|
|
is_initial: false,
|
|
tls_slot_count,
|
|
affinity_mask: 0,
|
|
priority: 0,
|
|
ideal_processor: None,
|
|
};
|
|
match state.scheduler.spawn(params, &mut GuestMemoryPcr(mem)) {
|
|
Ok(hw_id) => {
|
|
metrics::counter!("scheduler.spawn.ok").increment(1);
|
|
if let Some(KernelObject::Thread { hw_id: slot, .. }) = state.objects.get_mut(&handle) {
|
|
*slot = Some(hw_id);
|
|
}
|
|
if handle_ptr != 0 {
|
|
mem.write_u32(handle_ptr, handle);
|
|
}
|
|
state.audit_create_with_ctx(handle, "Thread", ctx, mem, "XamTaskSchedule");
|
|
tracing::info!(
|
|
"XamTaskSchedule: tid={} handle={:#x} hw={} callback={:#010x} message={:#010x}",
|
|
tid,
|
|
handle,
|
|
hw_id,
|
|
callback,
|
|
message_ptr,
|
|
);
|
|
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
|
}
|
|
Err(_) => {
|
|
metrics::counter!("scheduler.spawn.rejected").increment(1);
|
|
tracing::error!("XamTaskSchedule: no free HW thread slot");
|
|
ctx.gpr[3] = 0xC000_009A;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== Alloc =====
|
|
|
|
fn xam_alloc(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
|
// r3 = flags, r4 = size, r5 = out_ptr_ptr
|
|
let size = ctx.gpr[4] as u32;
|
|
let out_ptr = ctx.gpr[5] as u32;
|
|
|
|
match state.heap_alloc(size, mem) {
|
|
Some(addr) => {
|
|
if out_ptr != 0 {
|
|
mem.write_u32(out_ptr, addr);
|
|
}
|
|
ctx.gpr[3] = 0; // SUCCESS
|
|
}
|
|
None => {
|
|
ctx.gpr[3] = 0x8007_000E; // E_OUTOFMEMORY
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== User =====
|
|
|
|
fn xam_user_get_xuid(ctx: &mut PpcContext, mem: &GuestMemory, _state: &mut KernelState) {
|
|
// r3 = user_index, r4 = xuid_ptr
|
|
let xuid_ptr = ctx.gpr[4] as u32;
|
|
if xuid_ptr != 0 {
|
|
mem.write_u64(xuid_ptr, 0); // No XUID
|
|
}
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
fn xam_user_get_name(ctx: &mut PpcContext, mem: &GuestMemory, _state: &mut KernelState) {
|
|
// r3 = user_index, r4 = buffer, r5 = buffer_size
|
|
let buffer = ctx.gpr[4] as u32;
|
|
if buffer != 0 {
|
|
mem.write_u8(buffer, 0); // Empty string
|
|
}
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
fn xam_user_read_profile_settings(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
// Return error — no profile
|
|
ctx.gpr[3] = 0x0000_048B; // ERROR_NOT_FOUND
|
|
}
|
|
|
|
// ===== System =====
|
|
|
|
fn xam_get_execution_id(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
|
// r3 = execution_id_ptr_ptr — write pointer to execution info
|
|
let ptr_ptr = ctx.gpr[3] as u32;
|
|
if ptr_ptr != 0 {
|
|
// Allocate and fill a fake XEX_EXECUTION_ID structure
|
|
if let Some(exec_id_addr) = state.heap_alloc(0x18, mem) {
|
|
mem.write_u32(exec_id_addr, 0x535107D4); // title_id (Project Sylpheed)
|
|
mem.write_u32(exec_id_addr + 4, 0x2D2E2EEB); // media_id
|
|
mem.write_u16(exec_id_addr + 8, 0); // version
|
|
mem.write_u16(exec_id_addr + 10, 0); // base_version
|
|
mem.write_u16(exec_id_addr + 12, 1); // disc_number
|
|
mem.write_u16(exec_id_addr + 14, 1); // disc_count
|
|
mem.write_u32(ptr_ptr, exec_id_addr);
|
|
}
|
|
}
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
fn xam_get_system_version(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
ctx.gpr[3] = 0x2000_0000; // System version
|
|
}
|
|
|
|
// ===== Notify =====
|
|
|
|
fn xam_notify_create_listener(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut KernelState) {
|
|
let handle = state.alloc_handle();
|
|
ctx.gpr[3] = handle as u64;
|
|
}
|
|
|
|
fn xnotify_get_next(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
// r3 = handle, r4 = id_ptr, r5 = param_ptr
|
|
ctx.gpr[3] = 0; // FALSE (no notifications)
|
|
}
|
|
|
|
// ===== Session =====
|
|
|
|
fn xam_session_create_handle(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
|
// r3 = handle_ptr
|
|
let handle_ptr = ctx.gpr[3] as u32;
|
|
let handle = state.alloc_handle();
|
|
if handle_ptr != 0 {
|
|
mem.write_u32(handle_ptr, handle);
|
|
}
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
// ===== Locale =====
|
|
|
|
fn xget_avpack(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
ctx.gpr[3] = 0x16; // HDMI
|
|
}
|
|
|
|
fn xget_game_region(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
ctx.gpr[3] = 0xFF; // All regions
|
|
}
|
|
|
|
fn xget_language(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
|
ctx.gpr[3] = 1; // English
|
|
}
|
|
|
|
fn xget_video_mode(ctx: &mut PpcContext, mem: &GuestMemory, _state: &mut KernelState) {
|
|
// r3 = video_mode_ptr
|
|
let ptr = ctx.gpr[3] as u32;
|
|
if ptr != 0 {
|
|
mem.write_u32(ptr, 1280); // width
|
|
mem.write_u32(ptr + 4, 720); // height
|
|
mem.write_u32(ptr + 8, 0); // is_interlaced
|
|
mem.write_u32(ptr + 12, 1); // is_widescreen
|
|
mem.write_u32(ptr + 16, 60); // refresh_rate
|
|
}
|
|
ctx.gpr[3] = 0;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use xenia_memory::page_table::MemoryProtect;
|
|
|
|
const SCRATCH_BASE: u32 = 0x4000_0000;
|
|
|
|
fn fresh() -> (PpcContext, GuestMemory, KernelState) {
|
|
let mut mem = GuestMemory::new().expect("memory init");
|
|
mem.alloc(SCRATCH_BASE, 0x1000, MemoryProtect::READ | MemoryProtect::WRITE)
|
|
.expect("scratch page must commit");
|
|
let mut state = KernelState::new();
|
|
state.install_initial_thread(
|
|
PpcContext::default(),
|
|
0x7000_0000,
|
|
0x10_0000,
|
|
SCRATCH_BASE + 0x800,
|
|
SCRATCH_BASE + 0xC00,
|
|
0xF000_0001,
|
|
&mut mem,
|
|
);
|
|
state.scheduler.begin_slot_visit(0);
|
|
(PpcContext::default(), mem, state)
|
|
}
|
|
|
|
#[test]
|
|
fn xam_task_schedule_spawns_real_thread() {
|
|
let (mut ctx, mut mem, mut state) = fresh();
|
|
|
|
let callback_pc: u32 = 0x824a_93c8;
|
|
let message_ptr: u32 = SCRATCH_BASE + 0x100;
|
|
let handle_out: u32 = SCRATCH_BASE + 0x200;
|
|
ctx.gpr[3] = callback_pc as u64;
|
|
ctx.gpr[4] = message_ptr as u64;
|
|
ctx.gpr[5] = 0;
|
|
ctx.gpr[6] = handle_out as u64;
|
|
ctx.lr = 0x824a_9a14;
|
|
|
|
xam_task_schedule(&mut ctx, &mut mem, &mut state);
|
|
|
|
assert_eq!(ctx.gpr[3], 0, "XamTaskSchedule must return STATUS_SUCCESS");
|
|
|
|
let handle = mem.read_u32(handle_out);
|
|
assert!(handle >= 0x1000, "handle must be allocated, got {:#x}", handle);
|
|
|
|
let r = state
|
|
.scheduler
|
|
.find_by_handle(handle)
|
|
.expect("spawned thread must be findable by handle");
|
|
let new_ctx = state.scheduler.ctx_mut_ref(r);
|
|
assert_eq!(new_ctx.pc, callback_pc, "entry PC must be the callback");
|
|
assert_eq!(
|
|
new_ctx.gpr[3] as u32, message_ptr,
|
|
"r3 must hold the message pointer"
|
|
);
|
|
|
|
match state.objects.get(&handle) {
|
|
Some(KernelObject::Thread { hw_id: Some(_), .. }) => {}
|
|
other => panic!("expected Thread object with hw_id set, got {:?}", other),
|
|
}
|
|
}
|
|
}
|