use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use core::fmt;
use crate::core::device::{Device, DeviceClass, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::Props;
use crate::core::sched::{AccessKind, LazyHandle};
use crate::core::space::{
AccessConstraints, MemAttrs, MemOps, MemResult, Region as MmioRegion, RegionRef,
};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicU64, LockRank, Mutex, Ordering};
use crate::core::value::Width;
use crate::core::wire::{Level, WireSource};
pub const REGISTER_BASE: u64 = 0xff04;
pub const REGISTER_LEN: u64 = 4;
pub const REGISTER_REGION: &str = "regs";
pub const IRQ_PIN: &str = "irq";
pub const DIV_APU_PIN: &str = "div-apu";
const DIV_APU_BIT: u16 = 1 << 12;
const RELOAD_DELAY: u8 = 4;
pub const POST_BOOT_COUNTER: u16 = 0xab00;
const fn selected_bit(tac: u8) -> u16 {
match tac & 3 {
0 => 1 << 9,
1 => 1 << 3,
2 => 1 << 5,
_ => 1 << 7,
}
}
#[derive(Debug, Clone, Copy, Default)]
struct Regs {
counter: u16,
tima: u8,
tma: u8,
tac: u8,
edge: bool,
apu_edge: bool,
reload: u8,
}
impl Regs {
fn edge_input(&self) -> bool {
self.tac & 0x04 != 0 && self.counter & selected_bit(self.tac) != 0
}
fn settle_edge(&mut self) -> bool {
let now = self.edge_input();
let falling = self.edge && !now;
self.edge = now;
if !falling {
return false;
}
self.tima = self.tima.wrapping_add(1);
if self.tima == 0 {
self.reload = RELOAD_DELAY;
return true;
}
false
}
}
struct Shared {
regs: Mutex<Regs>,
links: Mutex<Links>,
lazy: Mutex<Option<LazyHandle>>,
tick: AtomicU64,
next_event: AtomicU64,
}
#[derive(Debug, Default)]
struct Links {
irq: Option<WireSource>,
div_apu: Option<WireSource>,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Shared")
.field("regs", &self.regs)
.field("tick", &self.tick.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
impl Shared {
fn compute_next_event(&self, regs: &Regs, now: u64) -> u64 {
let to_div = 256 - u64::from(regs.counter & 0xff);
let mut delta = to_div;
if regs.reload > 0 {
delta = delta.min(u64::from(regs.reload));
}
if regs.tac & 0x04 != 0 {
let period = u64::from(selected_bit(regs.tac)) * 2;
let phase = u64::from(regs.counter) % period;
delta = delta.min(period - phase);
}
let apu_period = u64::from(DIV_APU_BIT) * 2;
let apu_phase = u64::from(regs.counter) % apu_period;
delta = delta.min(apu_period - apu_phase);
now + delta.max(1)
}
fn publish(&self, regs: &Regs, now: u64) {
self.tick.store(now, Ordering::Relaxed);
self.next_event
.store(self.compute_next_event(regs, now), Ordering::Relaxed);
}
fn advance_locked(&self, regs: &mut Regs, target: u64) -> (bool, u32) {
let mut now = self.tick.load(Ordering::Relaxed);
let mut irq = false;
let mut apu_edges = 0u32;
while now < target {
now += 1;
if regs.reload > 0 {
regs.reload -= 1;
if regs.reload == 0 {
regs.tima = regs.tma;
irq = true;
}
}
regs.counter = regs.counter.wrapping_add(1);
regs.settle_edge();
let apu_now = regs.counter & DIV_APU_BIT != 0;
if regs.apu_edge && !apu_now {
apu_edges += 1;
}
regs.apu_edge = apu_now;
}
self.tick.store(now, Ordering::Relaxed);
(irq, apu_edges)
}
fn drive(&self, irq: bool, apu_edges: u32) {
let (irq_src, apu_src) = {
let links = self.links.lock();
(links.irq.clone(), links.div_apu.clone())
};
if irq && let Some(src) = irq_src {
src.set(Level::High);
src.set(Level::Low);
}
if let Some(src) = apu_src {
for _ in 0..apu_edges {
src.set(Level::High);
src.set(Level::Low);
}
}
}
fn sync(&self, attrs: MemAttrs) {
let handle = self.lazy.lock().clone();
let Some(handle) = handle else {
return;
};
let kind = if attrs.debug {
AccessKind::Debug
} else {
AccessKind::Guest
};
let _ = handle.sync(kind);
}
}
pub struct GbTimer {
shared: Arc<Shared>,
regs_region: RegionRef,
}
impl fmt::Debug for GbTimer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GbTimer")
.field("regs", &self.shared.regs)
.finish_non_exhaustive()
}
}
impl Default for GbTimer {
fn default() -> Self {
GbTimer::new()
}
}
impl GbTimer {
#[must_use]
pub fn new() -> GbTimer {
let shared = Arc::new(Shared {
regs: Mutex::with_rank(LockRank::DEVICE, Regs::default()),
links: Mutex::with_rank(LockRank::WIRE, Links::default()),
lazy: Mutex::new(None),
tick: AtomicU64::new(0),
next_event: AtomicU64::new(256),
});
let regs_region = Arc::new(MmioRegion::io(
"gb.timer.regs",
REGISTER_LEN,
Arc::new(TimerPort {
shared: Arc::clone(&shared),
}) as Arc<dyn MemOps>,
));
GbTimer {
shared,
regs_region,
}
}
pub fn from_props(props: &Props) -> Result<GbTimer> {
props.reader().finish()?;
Ok(GbTimer::new())
}
#[must_use]
pub fn div(&self) -> u8 {
(self.shared.regs.lock().counter >> 8) as u8
}
#[must_use]
pub fn counter(&self) -> u16 {
self.shared.regs.lock().counter
}
#[must_use]
pub fn tima(&self) -> u8 {
self.shared.regs.lock().tima
}
#[must_use]
pub fn tma(&self) -> u8 {
self.shared.regs.lock().tma
}
#[must_use]
pub fn tac(&self) -> u8 {
self.shared.regs.lock().tac
}
pub fn attach_irq(&self, source: WireSource) {
self.shared.links.lock().irq = Some(source);
}
pub fn attach_div_apu(&self, source: WireSource) {
self.shared.links.lock().div_apu = Some(source);
}
pub fn attach_lazy(&self, handle: LazyHandle) {
*self.shared.lazy.lock() = Some(handle);
}
pub fn advance_to(&self, target: u64) {
let (irq, apu_edges) = {
let mut regs = self.shared.regs.lock();
let out = self.shared.advance_locked(&mut regs, target);
self.shared
.publish(®s, self.shared.tick.load(Ordering::Relaxed));
out
};
self.shared.drive(irq, apu_edges);
}
pub fn advance_by(&self, clocks: u64) {
self.advance_to(self.shared.tick.load(Ordering::Relaxed) + clocks);
}
#[must_use]
pub fn next_event(&self) -> u64 {
self.shared.next_event.load(Ordering::Relaxed)
}
#[must_use]
pub fn read_register(&self, index: u8) -> u8 {
let regs = self.shared.regs.lock();
read_reg(®s, index)
}
pub fn write_register(&self, index: u8, value: u8) {
let irq = {
let mut regs = self.shared.regs.lock();
let out = write_reg(&mut regs, index, value);
self.shared
.publish(®s, self.shared.tick.load(Ordering::Relaxed));
out
};
self.shared.drive(irq, 0);
}
}
fn read_reg(regs: &Regs, index: u8) -> u8 {
match index & 3 {
0 => (regs.counter >> 8) as u8,
1 => regs.tima,
2 => regs.tma,
_ => regs.tac | 0xf8,
}
}
fn write_reg(regs: &mut Regs, index: u8, value: u8) -> bool {
match index & 3 {
0 => {
regs.counter = 0;
regs.settle_edge()
}
1 => {
regs.tima = value;
regs.reload = 0;
false
}
2 => {
regs.tma = value;
false
}
_ => {
regs.tac = value & 0x07;
regs.settle_edge()
}
}
}
struct TimerPort {
shared: Arc<Shared>,
}
impl fmt::Debug for TimerPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TimerPort").finish_non_exhaustive()
}
}
impl MemOps for TimerPort {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
self.shared.sync(attrs);
*byte = read_reg(&self.shared.regs.lock(), offset as u8);
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
self.shared.sync(attrs);
let irq = {
let mut regs = self.shared.regs.lock();
let out = write_reg(&mut regs, offset as u8, *value);
self.shared
.publish(®s, self.shared.tick.load(Ordering::Relaxed));
out
};
let _ = irq;
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO.with_widths(Width::U8, Width::U8)
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: "gb.timer",
version: 1,
summary: "Game Boy divider and timer ($FF04-$FF07), including the 512 Hz APU clock",
properties: &[],
construct: |props| Ok(Box::new(GbTimer::from_props(props)?) as Box<dyn Device>),
};
pub fn register(reg: &mut crate::core::Registry) -> Result<()> {
reg.add(&CLASS)
}
impl Device for GbTimer {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
(name.is_empty() || name == REGISTER_REGION).then(|| Arc::clone(&self.regs_region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
match port {
IRQ_PIN => self.attach_irq(source),
DIV_APU_PIN => self.attach_div_apu(source),
_ => {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!(
"the timer drives `{IRQ_PIN}` and `{DIV_APU_PIN}`, nothing else"
),
});
}
}
Ok(())
}
fn reset(&self, _kind: ResetKind) {
let mut regs = self.shared.regs.lock();
*regs = Regs::default();
regs.counter = POST_BOOT_COUNTER;
regs.edge = regs.edge_input();
regs.apu_edge = regs.counter & DIV_APU_BIT != 0;
self.shared.tick.store(0, Ordering::Relaxed);
self.shared.publish(®s, 0);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let regs = *self.shared.regs.lock();
w.write_u16(regs.counter)?;
w.write_u8(regs.tima)?;
w.write_u8(regs.tma)?;
w.write_u8(regs.tac)?;
w.write_bool(regs.edge)?;
w.write_bool(regs.apu_edge)?;
w.write_u8(regs.reload)?;
w.write_u64(self.shared.tick.load(Ordering::Relaxed))?;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let regs = Regs {
counter: r.read_u16()?,
tima: r.read_u8()?,
tma: r.read_u8()?,
tac: r.read_u8()?,
edge: r.read_bool()?,
apu_edge: r.read_bool()?,
reload: r.read_u8()?,
};
let tick = r.read_u64()?;
let mut slot = self.shared.regs.lock();
*slot = regs;
self.shared.tick.store(tick, Ordering::Relaxed);
self.shared.publish(®s, tick);
Ok(())
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.shared.tick.load(Ordering::Relaxed)
}
fn advance_to(&self, tick: u64) {
GbTimer::advance_to(self, tick);
}
fn next_event_tick(&self) -> Option<u64> {
Some(self.shared.next_event.load(Ordering::Relaxed))
}
fn attach_lazy(&self, handle: LazyHandle) {
GbTimer::attach_lazy(self, handle);
}
}
impl crate::machine::Instance for GbTimer {}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS.name, |props| {
Ok(Arc::new(GbTimer::from_props(props)?))
})
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir};
ClassSchema::new(CLASS.name)
.port(IRQ_PIN, PortDir::Out)
.port(DIV_APU_PIN, PortDir::Out)
.region(REGISTER_REGION)
}