Skip to content
Merged
268 changes: 149 additions & 119 deletions src/dev/hpc3.rs

Large diffs are not rendered by default.

248 changes: 148 additions & 100 deletions src/dev/ioc.rs

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions src/dev/mc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::thread;
use crate::traits::{BusRead8, BusRead16, BusRead32, BusRead64, BUS_OK, BUS_ERR, BusDevice, Device, Signal, Resettable, Saveable, MachineEvent};
use crate::snapshot::{get_field, u32_slice_to_toml, load_u32_slice, toml_u32, toml_bool, hex_u32};
use crate::dev::eeprom_93c56::Eeprom93c56;
use crate::dev::ioc::{Ioc, IocInterrupt};
use crate::irq::IrqLine;
use std::io::Write as IoWrite;

// The GIO virtual DMA engine — registers, µTLB translation and the transfer
Expand Down Expand Up @@ -65,7 +65,8 @@ struct MemoryControllerState {
cpu_cycle_acc: u64,
rpss_cycle_acc: u64,
cpu: Option<Weak<dyn Device>>,
ioc: Option<Ioc>,
/// The GIO DMA engine's interrupt (the IOC's MC DMA line).
dma_irq: Option<IrqLine>,
}

#[derive(Clone)]
Expand Down Expand Up @@ -134,7 +135,7 @@ impl MemoryController {
cpu_cycle_acc: 0,
rpss_cycle_acc: 0,
cpu: None,
ioc: None,
dma_irq: None,
})),
giodma: Arc::new(GioDma::new()),
phys: Arc::new(OnceLock::new()),
Expand Down Expand Up @@ -243,8 +244,9 @@ impl MemoryController {
self.state.lock().cpu = Some(cpu);
}

pub fn set_ioc(&self, ioc: Ioc) {
self.state.lock().ioc = Some(ioc);
/// Wire the GIO DMA engine's interrupt.
pub fn set_dma_irq(&self, irq: IrqLine) {
self.state.lock().dma_irq = Some(irq);
}

pub fn set_phys(&self, phys: Arc<dyn BusDevice>) {
Expand Down Expand Up @@ -527,7 +529,7 @@ impl MemoryController {

/// The IOC handle, cloned out so the caller does not hold the MC state lock
/// while raising an interrupt.
pub(crate) fn ioc(&self) -> Option<Ioc> { self.state.lock().ioc.clone() }
pub(crate) fn dma_irq(&self) -> Option<IrqLine> { self.state.lock().dma_irq.clone() }

/// Current GIO64_ARB value (bus widths / arbitration per GIO slot).
pub(crate) fn gio64_arb(&self) -> u32 { self.state.lock().regs[(REG_GIO64_ARB / 4) as usize] }
Expand Down Expand Up @@ -877,8 +879,8 @@ impl BusDevice for MemoryController {
dma.run = (dma.run & !0xF) | (val & 0xF);
drop(dma);
if val == 0 {
if let Some(ioc) = &state.ioc {
ioc.set_interrupt(IocInterrupt::McDma, false);
if let Some(irq) = &state.dma_irq {
irq.set(false);
}
}
BUS_OK
Expand Down
5 changes: 2 additions & 3 deletions src/dev/mc_vdma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ use std::io::Write as IoWrite;

use crate::devlog::LogModule;
use crate::traits::BUS_BUSY;
use crate::dev::ioc::IocInterrupt;
use crate::dev::mc::MemoryController;

// ── DMA register offsets (MC base 0x1FA00000) ───────────────────────────────
Expand Down Expand Up @@ -411,8 +410,8 @@ pub struct VdmaResult {

impl MemoryController {
pub(crate) fn signal_dma_interrupt(&self) {
if let Some(ioc) = self.ioc() {
ioc.set_interrupt(IocInterrupt::McDma, true);
if let Some(irq) = self.dma_irq() {
irq.set(true);
}
}

Expand Down
23 changes: 13 additions & 10 deletions src/dev/mgras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@ pub struct Mgras {
/// a full FIFO.
pf_wait: crate::PfLevel,
pf_fifo_full: crate::PfLevel,
ioc: crate::dev::ioc::Ioc,
/// The board's three GIO interrupts, as the slot wires them: FIFO,
/// general, retrace (see [`Line`]).
irqs: [crate::irq::IrqLine; 3],
/// Current level of the general interrupt line.
general: AtomicBool,
/// The FIFO interrupt's inputs, kept apart from `front` for the HQ3
Expand Down Expand Up @@ -456,14 +458,16 @@ impl Mgras {
/// Build the board in place on the heap: its multi-megabyte state is
/// zero-initialised without touching the stack, and only the fields that
/// are not valid zeroed are written.
pub fn new(board: GraphicsBoard, ioc: crate::dev::ioc::Ioc, heartbeat: Arc<AtomicU64>, fasttick: Arc<AtomicU64>) -> Arc<Self> {
/// `irqs` are the FIFO, general and retrace interrupts, as the slot
/// wires them.
pub fn new(board: GraphicsBoard, irqs: [crate::irq::IrqLine; 3], heartbeat: Arc<AtomicU64>, fasttick: Arc<AtomicU64>) -> Arc<Self> {
let mut a: Arc<MaybeUninit<Self>> = Arc::new_zeroed();
let p = Arc::get_mut(&mut a).unwrap().as_mut_ptr();
// SAFETY: `p` points at zeroed, exclusively owned memory. Every field
// that is not valid zeroed is written below, before assume_init.
let m = unsafe {
addr_of_mut!((*p).kind).write(board);
addr_of_mut!((*p).ioc).write(ioc);
addr_of_mut!((*p).irqs).write(irqs);
addr_of_mut!((*p).general).write(AtomicBool::new(false));
addr_of_mut!((*p).fifo_cfg).write(AtomicU32::new(0));
addr_of_mut!((*p).fifo_line).write(SpinLock::new(false));
Expand Down Expand Up @@ -852,15 +856,14 @@ impl Mgras {
*self.mem.lock() = Some(mem);
}

/// Drive one of the board's interrupt lines (graphics slot wiring).
/// Drive one of the board's interrupt lines.
fn set_line(&self, line: Line, active: bool) {
use crate::dev::ioc::IocInterrupt;
let src = match line {
Line::Fifo => IocInterrupt::GioSgFifo,
Line::General => IocInterrupt::GioSgGraphics,
Line::Retrace => IocInterrupt::GioSgRetrace,
let n = match line {
Line::Fifo => 0,
Line::General => 1,
Line::Retrace => 2,
};
self.ioc.set_interrupt(src, active);
self.irqs[n].set(active);
}

/// Wire up the CPU cycle counter for the status bar's MIPS figure.
Expand Down
2 changes: 1 addition & 1 deletion src/dev/mgras/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ pub(super) mod tests {
/// A board of kind `kind` with its engine threads running (no display).
pub fn live_board_of(kind: GraphicsBoard) -> Arc<Mgras> {
let hb = Arc::new(std::sync::atomic::AtomicU64::new(0));
let m = Mgras::new(kind, crate::dev::ioc::Ioc::new(false), hb.clone(), hb);
let m = Mgras::new(kind, std::array::from_fn(|_| crate::irq::IrqLine::unconnected()), hb.clone(), hb);
m.start_engines();
m
}
Expand Down
40 changes: 24 additions & 16 deletions src/dev/ng1/rex3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1354,11 +1354,11 @@ pub struct Rex3 {
fb_dirty: AtomicBool,
pub screen: Arc<Mutex<Rex3Screen>>,
/// Drives the real REX3 `VV_INT_N` pin (vertical retrace / Kaleidoscope).
pub vblank_cb: Mutex<Option<Arc<dyn Fn(bool) + Send + Sync>>>,
pub vblank_cb: Mutex<Option<crate::irq::IrqLine>>,
/// Drives the real REX3 `FIFO_INT_N` pin (GFIFO/BFIFO above/below
/// interrupt — one physical pin for both directions, selected by
/// `CONFIG_GFIFOABOVEINT`; see `update_gfifo_irqs`).
pub fifo_full_cb: Mutex<Option<Arc<dyn Fn(bool) + Send + Sync>>>,
pub fifo_full_cb: Mutex<Option<crate::irq::IrqLine>>,
/// Perfetto edge trackers: guest sees GFXBUSY, CPU stalled on a full
/// GFIFO, GFIFO level as STATUS reports it.
pf_wait: crate::PfLevel,
Expand Down Expand Up @@ -1671,12 +1671,14 @@ impl Rex3 {
pub const DIAG_LOOP_DRAW_BLOCK: u64 = 1 << 24;
pub const DIAG_LOOP_EXECUTE_GO: u64 = 1 << 25;

pub fn set_vblank_callback(&self, cb: Arc<dyn Fn(bool) + Send + Sync>) {
*self.vblank_cb.lock() = Some(cb);
/// Wire `VV_INT_N` (vertical retrace) to `irq`.
pub fn set_vblank_irq(&self, irq: crate::irq::IrqLine) {
*self.vblank_cb.lock() = Some(irq);
}

pub fn set_fifo_full_callback(&self, cb: Arc<dyn Fn(bool) + Send + Sync>) {
*self.fifo_full_cb.lock() = Some(cb);
/// Wire `FIFO_INT_N` to `irq`.
pub fn set_fifo_full_irq(&self, irq: crate::irq::IrqLine) {
*self.fifo_full_cb.lock() = Some(irq);
}

/// Program VC2 with a host-side Newport timing preset (see `[graphics] resolution`).
Expand Down Expand Up @@ -1732,8 +1734,8 @@ impl Rex3 {
self.config.status.fetch_and(!STATUS_GFIFO_INT, Ordering::Relaxed);
}

if let Some(cb) = self.fifo_full_cb.lock().clone() {
cb(crossed);
if let Some(irq) = self.fifo_full_cb.lock().clone() {
irq.set(crossed);
}
}

Expand Down Expand Up @@ -3109,9 +3111,12 @@ impl Rex3 {
}
{
self.diag.fetch_or(Self::DIAG_LOCK_VBLANK_CB, Ordering::Relaxed);
let cb = self.vblank_cb.lock().clone();
let irq = self.vblank_cb.lock().clone();
self.diag.fetch_and(!Self::DIAG_LOCK_VBLANK_CB, Ordering::Relaxed);
if let Some(cb) = cb { cb(true); }
if let Some(irq) = irq {
crate::pf_instant!(crate::perfetto::events::REX3_VBLANK);
irq.set(true);
}
}

// Present: compositor → overlay → status bar → swap
Expand Down Expand Up @@ -3169,9 +3174,12 @@ impl Rex3 {
}
{
self.diag.fetch_or(Self::DIAG_LOCK_VBLANK_CB, Ordering::Relaxed);
let cb = self.vblank_cb.lock().clone();
let irq = self.vblank_cb.lock().clone();
self.diag.fetch_and(!Self::DIAG_LOCK_VBLANK_CB, Ordering::Relaxed);
if let Some(cb) = cb { cb(true); }
if let Some(irq) = irq {
crate::pf_instant!(crate::perfetto::events::REX3_VBLANK);
irq.set(true);
}
}
}

Expand Down Expand Up @@ -4138,13 +4146,13 @@ impl Rex3 {
if reg_offset == REX3_STATUS {
let had_vrint = self.config.status.fetch_and(!STATUS_VRINT, Ordering::Relaxed) & STATUS_VRINT != 0;
if had_vrint {
let cb = self.vblank_cb.lock().clone();
if let Some(cb) = cb { cb(false); }
let irq = self.vblank_cb.lock().clone();
if let Some(irq) = irq { irq.set(false); }
}
let had_gfifo = self.config.status.fetch_and(!STATUS_GFIFO_INT, Ordering::Relaxed) & STATUS_GFIFO_INT != 0;
if had_gfifo {
let cb = self.fifo_full_cb.lock().clone();
if let Some(cb) = cb { cb(false); }
let irq = self.fifo_full_cb.lock().clone();
if let Some(irq) = irq { irq.set(false); }
}
let dcb = self.dcb.lock();
if let Some(until) = dcb.backbusy_until {
Expand Down
30 changes: 19 additions & 11 deletions src/dev/ps2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ use crate::traits::{Device, Resettable, Saveable};
use crate::snapshot::{get_field, toml_u8, toml_bool, hex_u8};
use crate::devlog::LogModule;
use crate::config::KeyboardLayout;

/// Callback trait for PS/2 interrupts
pub trait Ps2Callback: Send + Sync {
/// Set the interrupt line state
fn set_interrupt(&self, active: bool);
}
use crate::irq::IrqLine;

enum Ps2Source {
Keyboard,
Expand Down Expand Up @@ -56,7 +51,8 @@ struct Ps2State {
/// Combined PS/2 Keyboard and Mouse Controller
pub struct Ps2Controller {
state: Mutex<Ps2State>,
callback: Option<Arc<dyn Ps2Callback>>,
/// The controller's interrupt: up while a byte waits to be read.
irq: Option<IrqLine>,
running: AtomicBool,
/// Host keyboard shape (`KeyboardLayout as u8`). Host config, not guest
/// state, so it is not in snapshots.
Expand All @@ -65,7 +61,7 @@ pub struct Ps2Controller {

impl Ps2Controller {
/// Create a new PS/2 Controller
pub fn new(callback: Option<Arc<dyn Ps2Callback>>) -> Self {
pub fn new(irq: Option<IrqLine>) -> Self {
Self {
state: Mutex::new(Ps2State {
rx_queue: VecDeque::new(),
Expand All @@ -83,7 +79,7 @@ impl Ps2Controller {
key_modes: [3; 256],
held_keys: [false; 256],
}),
callback,
irq,
running: AtomicBool::new(false),
layout: AtomicU8::new(KeyboardLayout::Ansi as u8),
}
Expand All @@ -96,11 +92,11 @@ impl Ps2Controller {

/// Update interrupt state based on queue status
fn update_interrupt(&self) {
if let Some(cb) = &self.callback {
if let Some(line) = &self.irq {
let state = self.state.lock();
let len = state.rx_queue.len();
drop(state);
cb.set_interrupt(len > 0);
line.set(len > 0);
}
}

Expand Down Expand Up @@ -1254,6 +1250,18 @@ impl Saveable for Ps2Controller {
mod tests {
use super::*;

/// The interrupt is up exactly while a byte waits to be read.
#[test]
fn the_irq_line_follows_the_receive_queue() {
let (probe, line) = crate::irq::LevelProbe::line();
let ps2 = Ps2Controller::new(Some(line));
ps2.start();
ps2.write_data(0xF4); // enable scanning: answered with an ACK
assert!(probe.level(), "the ACK is waiting");
assert_eq!(ps2.read_data(), 0xFA);
assert!(!probe.level(), "and once read, the line drops");
}

#[test]
fn set3_make_break_suppresses_host_repeat_and_preserves_release() {
let ps2 = Ps2Controller::new(None);
Expand Down
Loading
Loading