feat(audio): APUBUG-PRODUCER-001 — XAudio register driver client + opt-in callback ticker
Replace the three XAudio kernel-export stubs (Register/Unregister/SubmitFrame) with canary-faithful implementations and add a periodic buffer-complete callback ticker reusing the existing SavedCallbackCtx injection machinery. Canary parity: - xboxkrnl_audio.cc:56-93 — read callback_ptr[0..1], wrap callback_arg in a 4-byte big-endian guest heap buffer (`wrapped_callback_arg`), write `0x4155_xxxx` to *driver_ptr. - audio_system.cc:139-141 — guest callback receives r3 = wrapped pointer, not raw callback_arg. - audio_driver.h:21-24 — frame rate 256 samples / 48 kHz ≈ 5.33 ms. Implementation: - New `crates/xenia-kernel/src/xaudio.rs` — `XAudioClient`, `XAudioState` (8-slot table, pending FIFO, dual-mode ticker), `XAUDIO_INSTR_PERIOD = 48_000` (lockstep) and `XAUDIO_PERIOD = 5.333 ms` (--parallel), same pattern as KRNBUG-D08 v-sync. - `try_inject_audio_callback` in xenia-app mirrors `try_inject_graphics_interrupt`, shares `interrupts.saved` slot for mutex with graphics callbacks. Gating: ticker + injector run only when `--xaudio-tick` / `XENIA_XAUDIO_TICK=1`. Default off because Sylpheed's audio callback enters an infinite `KeWaitForSingleObject` loop on first invocation (canary's host worker thread provides the buffer-completion fence we don't model), which hijacks a guest HW thread and regresses `swaps=2 → 1`. Default-off preserves the lockstep `sylpheed_n*m.json` goldens exactly. Producer hunt outcome (FALSIFIED for parked handles 0x1004/0x100c/0x15e4): at `-n 500M --xaudio-tick` all 3 handles still show `signal_attempts=0 (primary=0, ghost=0)`. Audio callback is not the missing producer. Next candidate per audit-findings.md is Timer DPC delivery (KeSetTimer / KeInsertQueueDpc). Tests: 562 → 576 green (10 in `xaudio.rs`, 4 in `exports.rs`). Lockstep `--stable-digest -n 100M` default-off: instructions=100000002, swaps=2 (matches pre-change baseline byte-for-byte). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,23 @@ pub struct KernelState {
|
||||
/// the callback set by `VdSetGraphicsInterruptCallback` and tracks
|
||||
/// the paused-context snapshot while HW thread 0 is running it.
|
||||
pub interrupts: crate::interrupts::InterruptState,
|
||||
/// XAudio render-driver clients + buffer-complete callback ticker.
|
||||
/// Mirrors canary's [`xenia/apu/audio_system.cc`] worker — registered
|
||||
/// guest callbacks can fire at the audio frame rate so guest threads
|
||||
/// parked on audio-buffer events get woken (APUBUG-PRODUCER-001).
|
||||
/// Shares the [`crate::interrupts::InterruptState::saved`] /
|
||||
/// `injected_ref` slot at injection time; mutual exclusion with
|
||||
/// graphics interrupts is enforced by the injector's
|
||||
/// `is_in_callback()` guard.
|
||||
pub xaudio: crate::xaudio::XAudioState,
|
||||
/// Default false. When true, the round prologue runs the XAudio
|
||||
/// ticker + `try_inject_audio_callback`. Off by default because the
|
||||
/// callback firing shifts the boot trajectory under Sylpheed
|
||||
/// (regresses `swaps=2`→`1` and 12×s `imports`), which would break
|
||||
/// the `sylpheed_n*m.json` lockstep goldens. Flipped on by
|
||||
/// `--xaudio-tick` / `XENIA_XAUDIO_TICK=1` for the diagnostic
|
||||
/// producer-hunt path.
|
||||
pub xaudio_tick_enabled: bool,
|
||||
/// Per-handle refcount. Since `NtDuplicateObject` aliases (returns the
|
||||
/// source handle value as the "new" handle rather than minting a fresh
|
||||
/// id), a single handle commonly has multiple logical references. This
|
||||
@@ -202,6 +219,8 @@ impl KernelState {
|
||||
vfs: None,
|
||||
ui: None,
|
||||
interrupts: crate::interrupts::InterruptState::default(),
|
||||
xaudio: crate::xaudio::XAudioState::default(),
|
||||
xaudio_tick_enabled: false,
|
||||
handle_refcount: HashMap::new(),
|
||||
pending_timer_fires: Vec::new(),
|
||||
audit: HandleAudit::default(),
|
||||
|
||||
Reference in New Issue
Block a user