Compare commits
3 Commits
iterate-2T
...
iterate-2W
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a91f4c550b | ||
|
|
66bd805726 | ||
|
|
ad9c8e4cb8 |
@@ -1540,8 +1540,19 @@ fn cmd_exec_inner(
|
|||||||
mem.write_u32(addr, block);
|
mem.write_u32(addr, block);
|
||||||
}
|
}
|
||||||
("xboxkrnl.exe", 0x01BE) => {
|
("xboxkrnl.exe", 0x01BE) => {
|
||||||
// VdGlobalDevice — passed through to Vd* shims. Write 0.
|
// VdGlobalDevice — a *pointer to* a global D3D-device cell.
|
||||||
mem.write_u32(addr, 0);
|
// Mirror xenia-canary RegisterVideoExports (xboxkrnl_video.cc:
|
||||||
|
// 557-564): allocate a 4-byte cell, point the import slot at
|
||||||
|
// it, and zero the cell. The guest's graphics init then stores
|
||||||
|
// its device object INTO the cell (e.g. sub_824C6DC0 @
|
||||||
|
// 0x824C6F18 `stw r31, 0([0x82000750])`), and the swap-complete
|
||||||
|
// callback sub_824CE2B8 reads it back via the two-level
|
||||||
|
// `[[VdGlobalDevice]+0]+15160` to bump the swap counter (clock
|
||||||
|
// B). Writing 0 directly here (the old behaviour) made that
|
||||||
|
// store land at address 0 and the swap counter never advance —
|
||||||
|
// freezing the title-loop's per-frame manager update.
|
||||||
|
let cell = alloc_zero(0x4, &mut mem, &mut kernel);
|
||||||
|
mem.write_u32(addr, cell);
|
||||||
}
|
}
|
||||||
("xboxkrnl.exe", 0x01C0) => {
|
("xboxkrnl.exe", 0x01C0) => {
|
||||||
// VdGpuClockInMHz
|
// VdGpuClockInMHz
|
||||||
@@ -2327,10 +2338,22 @@ fn coord_post_round(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if kernel.gpu.has_pending_interrupts() {
|
if kernel.gpu.has_pending_interrupts() {
|
||||||
for _pi in kernel.gpu.take_pending_interrupts() {
|
for pi in kernel.gpu.take_pending_interrupts() {
|
||||||
|
// Canary `ExecutePacketType3_INTERRUPT` dispatches the callback
|
||||||
|
// once per set bit of `cpu_mask` with that bit's index as the
|
||||||
|
// target CPU (`DispatchInterruptCallback(1, n)`). The guest's
|
||||||
|
// swap-acknowledge fence stores `cpu_mask`, and the ISR clears
|
||||||
|
// `1 << current_cpu` from it — so the ISR must run impersonating
|
||||||
|
// the masked CPU or the fence never reaches 0. Sylpheed uses a
|
||||||
|
// single-bit mask (`0x4` → CPU 2); take the lowest set bit.
|
||||||
|
let cpu = if pi.cpu_mask == 0 {
|
||||||
|
xenia_kernel::interrupts::VSYNC_TARGET_CPU
|
||||||
|
} else {
|
||||||
|
pi.cpu_mask.trailing_zeros().min(5) as u8
|
||||||
|
};
|
||||||
kernel
|
kernel
|
||||||
.interrupts
|
.interrupts
|
||||||
.queue_interrupt(xenia_kernel::INTERRUPT_SOURCE_CP);
|
.queue_interrupt(xenia_kernel::INTERRUPT_SOURCE_CP, cpu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3534,7 +3557,17 @@ fn dispatch_graphics_interrupts(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// X_KPCR offset of `prcb_data.current_cpu` (canary `xthread.cc`
|
||||||
|
/// `SetActiveCpu` → `pcr.prcb_data.current_cpu`). The guest graphics
|
||||||
|
/// ISR reads it via `lbz r10, 268(r13)` to decide which per-CPU bit of
|
||||||
|
/// the swap-acknowledge fence to clear.
|
||||||
|
const PCR_CURRENT_CPU_OFF: u32 = 268;
|
||||||
|
|
||||||
while let Some(source) = kernel.interrupts.peek_next() {
|
while let Some(source) = kernel.interrupts.peek_next() {
|
||||||
|
let target_cpu = kernel
|
||||||
|
.interrupts
|
||||||
|
.peek_next_cpu()
|
||||||
|
.unwrap_or(xenia_kernel::interrupts::VSYNC_TARGET_CPU);
|
||||||
// Victim selection: Ready first, then Blocked (canary's
|
// Victim selection: Ready first, then Blocked (canary's
|
||||||
// `XThread::GetCurrentThread()` analog — any live thread will
|
// `XThread::GetCurrentThread()` analog — any live thread will
|
||||||
// do for borrowing context). Skip Idle/Exited/ServicingIrq.
|
// do for borrowing context). Skip Idle/Exited/ServicingIrq.
|
||||||
@@ -3604,6 +3637,19 @@ fn dispatch_graphics_interrupts(
|
|||||||
saved
|
saved
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Impersonate the interrupt's target CPU on the borrowed thread's
|
||||||
|
// PCR, mirroring canary `EmulateCPInterruptDPC` →
|
||||||
|
// `XThread::SetActiveCpu(cpu)`. The guest swap-complete ISR clears
|
||||||
|
// `1 << [pcr.current_cpu]` from the per-present swap-acknowledge
|
||||||
|
// fence; if it runs on the wrong CPU it clears the wrong bit and
|
||||||
|
// the GPU's trailing `WAIT_REG_MEM` on that fence never releases —
|
||||||
|
// stranding the present/title loop. Save/restore so borrowing a
|
||||||
|
// thread doesn't permanently rewrite its processor number.
|
||||||
|
let pcr_addr = (kernel.scheduler.ctx_mut_ref(target_ref).gpr[13] as u32)
|
||||||
|
.wrapping_add(PCR_CURRENT_CPU_OFF);
|
||||||
|
let saved_cpu = mem.read_u8(pcr_addr);
|
||||||
|
mem.write_u8(pcr_addr, target_cpu);
|
||||||
|
|
||||||
// Stash the previous `scheduler.current` (call_export reaches
|
// Stash the previous `scheduler.current` (call_export reaches
|
||||||
// it; imports the ISR calls must dispatch on the borrowed
|
// it; imports the ISR calls must dispatch on the borrowed
|
||||||
// thread). Restore on the way out.
|
// thread). Restore on the way out.
|
||||||
@@ -3696,6 +3742,7 @@ fn dispatch_graphics_interrupts(
|
|||||||
|
|
||||||
// Restore the borrowed context.
|
// Restore the borrowed context.
|
||||||
saved.restore(kernel.scheduler.ctx_mut_ref(target_ref));
|
saved.restore(kernel.scheduler.ctx_mut_ref(target_ref));
|
||||||
|
mem.write_u8(pcr_addr, saved_cpu);
|
||||||
kernel.scheduler.current = prev_current;
|
kernel.scheduler.current = prev_current;
|
||||||
kernel.interrupts.delivered += 1;
|
kernel.interrupts.delivered += 1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"instructions": 50000001,
|
"instructions": 50000014,
|
||||||
"imports": 451500,
|
"imports": 352251,
|
||||||
"unimpl": 0,
|
"unimpl": 0,
|
||||||
"draws": 78,
|
"draws": 718,
|
||||||
"swaps": 4,
|
"swaps": 147,
|
||||||
"unique_render_targets": 2,
|
"unique_render_targets": 2,
|
||||||
"shader_blobs_live": 3,
|
"shader_blobs_live": 6,
|
||||||
"texture_cache_entries": 0
|
"texture_cache_entries": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1544,6 +1544,15 @@ pub mod reg {
|
|||||||
/// `XE_GPU_REG_D1MODE_VBLANK_VLINE_STATUS` (Canary register_table.inc:1126).
|
/// `XE_GPU_REG_D1MODE_VBLANK_VLINE_STATUS` (Canary register_table.inc:1126).
|
||||||
/// Bit 0 = VBLANK_INT_OCCURRED.
|
/// Bit 0 = VBLANK_INT_OCCURRED.
|
||||||
pub const D1MODE_VBLANK_VLINE_STATUS: u32 = 0x1951;
|
pub const D1MODE_VBLANK_VLINE_STATUS: u32 = 0x1951;
|
||||||
|
/// `XE_GPU_REG_D1MODE_VIEWPORT_SIZE` / `AVIVO_D1MODE_VIEWPORT_SIZE`
|
||||||
|
/// (Canary `register_table.inc:1134`). Packs the active display resolution
|
||||||
|
/// as `(width << 16) | height` with 12-bit fields. The guest's
|
||||||
|
/// swap-complete interrupt callback (`sub_824CE2B8`) divides by the low
|
||||||
|
/// 12 bits (`height`) as a refresh-pacing term, so a 0 read makes its
|
||||||
|
/// `twi` divide-by-zero guard trap and abort the ISR before it clears the
|
||||||
|
/// swap-acknowledge fence. Canary returns the constant below from
|
||||||
|
/// `GraphicsSystem::ReadRegister` (graphics_system.cc:311).
|
||||||
|
pub const D1MODE_VIEWPORT_SIZE: u32 = 0x1961;
|
||||||
/// `XE_GPU_REG_VGT_EVENT_INITIATOR` — set by EVENT_WRITE.
|
/// `XE_GPU_REG_VGT_EVENT_INITIATOR` — set by EVENT_WRITE.
|
||||||
pub const VGT_EVENT_INITIATOR: u32 = 0x21F9;
|
pub const VGT_EVENT_INITIATOR: u32 = 0x21F9;
|
||||||
/// `XE_GPU_REG_COHER_STATUS_HOST` — coherency bits
|
/// `XE_GPU_REG_COHER_STATUS_HOST` — coherency bits
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ pub fn build_region(mmio: &GpuMmio) -> MmioRegion {
|
|||||||
reg::D1MODE_VBLANK_VLINE_STATUS => {
|
reg::D1MODE_VBLANK_VLINE_STATUS => {
|
||||||
read_vblank_status.load(Ordering::Relaxed)
|
read_vblank_status.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
// AVIVO_D1MODE_VIEWPORT_SIZE: the active display resolution
|
||||||
|
// (1280x720) packed as `(width << 16) | height`. Canary
|
||||||
|
// serves this constant from `GraphicsSystem::ReadRegister`
|
||||||
|
// (graphics_system.cc:311). The guest swap-complete interrupt
|
||||||
|
// callback divides by the low 12 bits (`height = 0x2D0`); a 0
|
||||||
|
// read trips its `twi` divide-guard and aborts the ISR before
|
||||||
|
// it acknowledges the per-present swap fence — which strands
|
||||||
|
// the present/title loop. Mirror canary exactly.
|
||||||
|
reg::D1MODE_VIEWPORT_SIZE => 0x0500_02D0,
|
||||||
_ => {
|
_ => {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
reg = format_args!("{reg_index:#x}"),
|
reg = format_args!("{reg_index:#x}"),
|
||||||
|
|||||||
@@ -2999,24 +2999,25 @@ fn vd_swap(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
|||||||
// xboxkrnl_video.cc:479. Currently skipped (see below).
|
// xboxkrnl_video.cc:479. Currently skipped (see below).
|
||||||
let _ = fetch_dwords; // silence unused — will be live again under the deferred path
|
let _ = fetch_dwords; // silence unused — will be live again under the deferred path
|
||||||
|
|
||||||
// iterate-2T: mirror xenia-canary `VdSwap_entry` (xboxkrnl_video.cc:518-548)
|
// iterate-2V: mirror xenia-canary `VdSwap_entry` (xboxkrnl_video.cc:518-548)
|
||||||
// FAITHFULLY. The game reserves 64 dwords (256 bytes) in the primary ring
|
// FAITHFULLY. The game reserves 64 dwords (256 bytes) in the primary ring
|
||||||
// at `buffer_ptr`; canary writes a `PM4_TYPE0(SHADER_CONSTANT_FETCH_00_0)`
|
// at `buffer_ptr`; canary writes a `PM4_TYPE0(SHADER_CONSTANT_FETCH_00_0)`
|
||||||
// fetch-constant patch followed by `PM4_TYPE3(PM4_XE_SWAP)`, then pads with
|
// fetch-constant patch followed by `PM4_TYPE3(PM4_XE_SWAP)`, then pads with
|
||||||
// NOPs. We do the same, then bump WPTR by 64 so the drain consumes the
|
// NOPs — and **NEVER touches `CP_RB_WPTR`**. The game advances the primary
|
||||||
// PM4_XE_SWAP **in command-stream order** — i.e. AFTER any in-stream
|
// ring write-pointer itself via its own doorbell once it has finished
|
||||||
// callback-arming Type-0 writes the game already queued.
|
// populating the reserved slot, so VdSwap only fills the bytes.
|
||||||
//
|
//
|
||||||
// Why this matters (the iterate-2T root): the previous M2b short-circuit
|
// iterate-2V FIX (the bug this removes): a prior revision bumped the
|
||||||
// called `notify_xe_swap` directly from the HLE, which synthesized a CP
|
// primary ring `CP_RB_WPTR` out-of-band here (`extend_write_ptr_by(64)`).
|
||||||
// swap-complete interrupt OUT OF BAND. When that interrupt reached the
|
// But `buffer_ptr` (~0x4add6efc) is NOT inside the primary ring (base
|
||||||
// graphics ISR (`sub_824BE9A0`) before D3D had armed its swap-callback
|
// ~0x4adcd000, 8192 dwords) — it lives ~10k dwords past it, in the
|
||||||
// slot (`[gfx+10772]+16` still the `0xBADF00D` placeholder), the ISR hit
|
// renderer indirect-buffer region. The bogus WPTR bump pushed the GPU
|
||||||
// its "ERR[D3D]: Unanticipated CPU_INTERRUPT. Sign of a corrupt command
|
// read-pointer PAST the guest's real write-pointer, the drain treated the
|
||||||
// buffer?" assert (`twi` at 0x824BE9DC). Routing the swap through the ring
|
// overshoot as a circular wrap, and **re-executed the splash's draw
|
||||||
// packet keeps the interrupt naturally ordered after arming, matching
|
// indirect-buffers ~2×** — inflating draws to 78 (real splash ≈ 28; 12
|
||||||
// canary (whose VdSwap raises NO interrupt itself; swap-complete CP
|
// INDIRECT_BUFFERs vs the real 6). Canary's `VdSwap_entry` writes the
|
||||||
// interrupts come only from in-stream `PM4_INTERRUPT` packets).
|
// block and returns; the swap-complete CP interrupt comes only from the
|
||||||
|
// game's own in-stream `PM4_INTERRUPT` packets, never from VdSwap.
|
||||||
if buffer_ptr != 0 {
|
if buffer_ptr != 0 {
|
||||||
let mut off = 0u32;
|
let mut off = 0u32;
|
||||||
let mut put = |i: &mut u32, v: u32| {
|
let mut put = |i: &mut u32, v: u32| {
|
||||||
@@ -3052,12 +3053,15 @@ fn vd_swap(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
|||||||
put(&mut off, xenia_gpu::pm4::make_packet_type2());
|
put(&mut off, xenia_gpu::pm4::make_packet_type2());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.gpu.extend_write_ptr_by(64);
|
// NOTE: We deliberately do NOT bump `CP_RB_WPTR` here (see the iterate-2V
|
||||||
|
// comment above). The drain below consumes only the packets the game has
|
||||||
|
// legitimately advanced the write-pointer over.
|
||||||
|
|
||||||
// Drain the ring; the PM4_XE_SWAP we just queued (and any in-stream
|
// Drain the ring up to whatever the game has actually submitted; any
|
||||||
// PM4_INTERRUPT) executes in order. The PM4_XE_SWAP handler calls
|
// in-stream `PM4_INTERRUPT` / draw packets execute in order. The
|
||||||
// `notify_xe_swap` for host swap bookkeeping; no synthetic interrupt is
|
// reserved-slot PM4_XE_SWAP is consumed by the GPU only once the game
|
||||||
// raised (see `notify_xe_swap`).
|
// advances its own doorbell over it. The swap-counter safety net below
|
||||||
|
// keeps host swap bookkeeping live in the meantime.
|
||||||
let drained = state.gpu.drain_to_current_wptr(mem);
|
let drained = state.gpu.drain_to_current_wptr(mem);
|
||||||
tracing::debug!(drained, "VdSwap: drained PM4 packets");
|
tracing::debug!(drained, "VdSwap: drained PM4 packets");
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ use xenia_cpu::ThreadRef;
|
|||||||
pub const INTERRUPT_SOURCE_VSYNC: u32 = 0;
|
pub const INTERRUPT_SOURCE_VSYNC: u32 = 0;
|
||||||
pub const INTERRUPT_SOURCE_CP: u32 = 1;
|
pub const INTERRUPT_SOURCE_CP: u32 = 1;
|
||||||
|
|
||||||
|
/// The processor the graphics ISR impersonates for a v-sync interrupt.
|
||||||
|
/// Canary hard-codes this: `MarkVblank` → `DispatchInterruptCallback(0, 2)`
|
||||||
|
/// (graphics_system.cc:478). CP interrupts instead use the bit index of the
|
||||||
|
/// `PM4_INTERRUPT` `cpu_mask`.
|
||||||
|
pub const VSYNC_TARGET_CPU: u8 = 2;
|
||||||
|
|
||||||
/// Guest-registered V-sync / graphics-interrupt callback (from
|
/// Guest-registered V-sync / graphics-interrupt callback (from
|
||||||
/// `VdSetGraphicsInterruptCallback`).
|
/// `VdSetGraphicsInterruptCallback`).
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -145,9 +151,16 @@ pub type PendingLocalIrq = [std::sync::atomic::AtomicU8;
|
|||||||
pub struct InterruptState {
|
pub struct InterruptState {
|
||||||
/// Registered callback (set by `VdSetGraphicsInterruptCallback`).
|
/// Registered callback (set by `VdSetGraphicsInterruptCallback`).
|
||||||
pub callback: Option<GraphicsInterruptCallback>,
|
pub callback: Option<GraphicsInterruptCallback>,
|
||||||
/// Bounded FIFO of pending interrupt sources awaiting injection.
|
/// Bounded FIFO of pending interrupts awaiting injection, as
|
||||||
/// Push-back on queue, pop-front on inject. Over-cap pushes drop.
|
/// `(source, target_cpu)`. Push-back on queue, pop-front on inject.
|
||||||
pub pending: VecDeque<u32>,
|
/// Over-cap pushes drop. `target_cpu` is the processor the graphics
|
||||||
|
/// ISR must impersonate (canary `XThread::SetActiveCpu` / the
|
||||||
|
/// `DispatchInterruptCallback(source, cpu)` argument): the bit index
|
||||||
|
/// of the CP `PM4_INTERRUPT` `cpu_mask` for source=1, and a fixed `2`
|
||||||
|
/// for vsync (canary `DispatchInterruptCallback(0, 2)`). The ISR reads
|
||||||
|
/// it from the PCR (`[r13+268]`) to clear the matching per-CPU bit of
|
||||||
|
/// the swap-acknowledge fence.
|
||||||
|
pub pending: VecDeque<(u32, u8)>,
|
||||||
/// When `Some`, some HW thread is currently running a callback; on
|
/// When `Some`, some HW thread is currently running a callback; on
|
||||||
/// return-to-sentinel we restore this and clear the flag.
|
/// return-to-sentinel we restore this and clear the flag.
|
||||||
pub saved: Option<SavedCallbackCtx>,
|
pub saved: Option<SavedCallbackCtx>,
|
||||||
@@ -211,8 +224,9 @@ impl InterruptState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue an interrupt for the next safe injection point.
|
/// Queue an interrupt for the next safe injection point. `cpu` is the
|
||||||
pub fn queue_interrupt(&mut self, source: u32) {
|
/// processor the ISR must impersonate (see `pending`).
|
||||||
|
pub fn queue_interrupt(&mut self, source: u32, cpu: u8) {
|
||||||
if self.callback.is_none() {
|
if self.callback.is_none() {
|
||||||
self.dropped += 1;
|
self.dropped += 1;
|
||||||
return;
|
return;
|
||||||
@@ -221,18 +235,23 @@ impl InterruptState {
|
|||||||
self.dropped += 1;
|
self.dropped += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.pending.push_back(source);
|
self.pending.push_back((source, cpu));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peek at the next pending source without removing it.
|
/// Peek at the next pending source without removing it.
|
||||||
pub fn peek_next(&self) -> Option<u32> {
|
pub fn peek_next(&self) -> Option<u32> {
|
||||||
self.pending.front().copied()
|
self.pending.front().map(|&(source, _)| source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peek at the target CPU of the next pending interrupt.
|
||||||
|
pub fn peek_next_cpu(&self) -> Option<u8> {
|
||||||
|
self.pending.front().map(|&(_, cpu)| cpu)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop the next pending source (called by the injector after it has
|
/// Pop the next pending source (called by the injector after it has
|
||||||
/// committed to dispatching it).
|
/// committed to dispatching it).
|
||||||
pub fn take_next(&mut self) -> Option<u32> {
|
pub fn take_next(&mut self) -> Option<u32> {
|
||||||
self.pending.pop_front()
|
self.pending.pop_front().map(|(source, _)| source)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// **Legacy** — instruction-count v-sync ticker. Kept for unit tests
|
/// **Legacy** — instruction-count v-sync ticker. Kept for unit tests
|
||||||
@@ -249,7 +268,7 @@ impl InterruptState {
|
|||||||
let periods = self.vsync_accumulator / VSYNC_INSTR_PERIOD;
|
let periods = self.vsync_accumulator / VSYNC_INSTR_PERIOD;
|
||||||
self.vsync_accumulator %= VSYNC_INSTR_PERIOD;
|
self.vsync_accumulator %= VSYNC_INSTR_PERIOD;
|
||||||
for _ in 0..periods {
|
for _ in 0..periods {
|
||||||
self.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
self.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -288,7 +307,7 @@ impl InterruptState {
|
|||||||
self.last_vsync_instant = Some(anchor + advance);
|
self.last_vsync_instant = Some(anchor + advance);
|
||||||
let to_queue = (periods as usize).min(INTERRUPT_QUEUE_CAP);
|
let to_queue = (periods as usize).min(INTERRUPT_QUEUE_CAP);
|
||||||
for _ in 0..to_queue {
|
for _ in 0..to_queue {
|
||||||
self.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
self.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -306,7 +325,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn queue_interrupt_drops_without_callback() {
|
fn queue_interrupt_drops_without_callback() {
|
||||||
let mut s = InterruptState::default();
|
let mut s = InterruptState::default();
|
||||||
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
assert_eq!(s.dropped, 1);
|
assert_eq!(s.dropped, 1);
|
||||||
assert!(s.pending.is_empty());
|
assert!(s.pending.is_empty());
|
||||||
}
|
}
|
||||||
@@ -315,9 +334,9 @@ mod tests {
|
|||||||
fn queue_interrupt_fifo_preserves_order() {
|
fn queue_interrupt_fifo_preserves_order() {
|
||||||
let mut s = InterruptState::default();
|
let mut s = InterruptState::default();
|
||||||
s.set_callback(0x1000, 0xAB);
|
s.set_callback(0x1000, 0xAB);
|
||||||
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
s.queue_interrupt(INTERRUPT_SOURCE_CP);
|
s.queue_interrupt(INTERRUPT_SOURCE_CP, 2);
|
||||||
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
assert_eq!(s.dropped, 0);
|
assert_eq!(s.dropped, 0);
|
||||||
// FIFO: take_next hands them out in push order.
|
// FIFO: take_next hands them out in push order.
|
||||||
assert_eq!(s.take_next(), Some(INTERRUPT_SOURCE_VSYNC));
|
assert_eq!(s.take_next(), Some(INTERRUPT_SOURCE_VSYNC));
|
||||||
@@ -331,11 +350,11 @@ mod tests {
|
|||||||
let mut s = InterruptState::default();
|
let mut s = InterruptState::default();
|
||||||
s.set_callback(0x1000, 0xAB);
|
s.set_callback(0x1000, 0xAB);
|
||||||
for _ in 0..INTERRUPT_QUEUE_CAP {
|
for _ in 0..INTERRUPT_QUEUE_CAP {
|
||||||
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
}
|
}
|
||||||
// Over-cap: drops rather than evicting the oldest.
|
// Over-cap: drops rather than evicting the oldest.
|
||||||
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC);
|
s.queue_interrupt(INTERRUPT_SOURCE_VSYNC, VSYNC_TARGET_CPU);
|
||||||
assert_eq!(s.dropped, 2);
|
assert_eq!(s.dropped, 2);
|
||||||
assert_eq!(s.pending.len(), INTERRUPT_QUEUE_CAP);
|
assert_eq!(s.pending.len(), INTERRUPT_QUEUE_CAP);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user