Refactor&improve the scheduler to handle missed events

Solves #168


Former-commit-id: 09104a6ebefdaf2e33ae22eb08860d9711ecb66b
Former-commit-id: e07219e40fa187f516dd266ab753a67f3c7e796b
This commit is contained in:
Michel Heily 2022-09-04 02:49:04 +03:00
parent 7af9bc5760
commit 91453e8777
11 changed files with 142 additions and 191 deletions

View file

@ -254,7 +254,7 @@ impl EepromChip {
result
}
pub(in crate) fn is_transmitting(&self) -> bool {
pub(crate) fn is_transmitting(&self) -> bool {
use SpiState::*;
match self.state {
TxData | TxDummy => true,
@ -262,7 +262,7 @@ impl EepromChip {
}
}
pub(in crate) fn reset(&mut self) {
pub(crate) fn reset(&mut self) {
self.state = SpiState::RxInstruction;
self.reset_rx_buffer();
self.reset_tx_buffer();
@ -280,7 +280,7 @@ impl EepromChip {
/// Eeprom controller can programmed with DMA accesses in 16bit mode
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct EepromController {
pub(in crate) chip: RefCell<EepromChip>,
pub(crate) chip: RefCell<EepromChip>,
detect: bool,
}

View file

@ -27,7 +27,7 @@ pub trait GpioDevice: Sized {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Gpio {
pub(in crate) rtc: Option<Rtc>,
pub(crate) rtc: Option<Rtc>,
direction: GpioState,
control: GpioPortControl,
}

View file

@ -44,7 +44,7 @@ pub struct Cartridge {
size: usize,
gpio: Option<Gpio>,
symbols: Option<SymbolTable>, // TODO move it somewhere else
pub(in crate) backup: BackupMedia,
pub(crate) backup: BackupMedia,
}
impl Cartridge {

View file

@ -2,7 +2,7 @@ use super::arm7tdmi::memory::{MemoryAccess, MemoryInterface};
use super::cartridge::BackupMedia;
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
use super::sched::{EventType, Scheduler, SchedulerConnect, SharedScheduler};
use super::sched::{EventType, Scheduler};
use super::sysbus::SysBus;
use num::FromPrimitive;
@ -118,7 +118,7 @@ impl DmaChannel {
return start_immediately;
}
fn xfer(&mut self, sb: &mut SysBus) {
fn transfer(&mut self, sb: &mut SysBus) {
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
let count = match self.internal.count {
0 => match self.id {
@ -197,9 +197,6 @@ impl DmaChannel {
pub struct DmaController {
pub channels: [DmaChannel; 4],
pending_set: u8,
#[serde(skip)]
#[serde(default = "Scheduler::new_shared")]
scheduler: SharedScheduler,
#[cfg(feature = "debugger")]
pub trace: bool,
}
@ -212,14 +209,8 @@ impl InterruptConnect for DmaController {
}
}
impl SchedulerConnect for DmaController {
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
self.scheduler = scheduler;
}
}
impl DmaController {
pub fn new(interrupt_flags: SharedInterruptFlags, scheduler: SharedScheduler) -> DmaController {
pub fn new(interrupt_flags: SharedInterruptFlags) -> DmaController {
DmaController {
channels: [
DmaChannel::new(0, interrupt_flags.clone()),
@ -228,8 +219,6 @@ impl DmaController {
DmaChannel::new(3, interrupt_flags.clone()),
],
pending_set: 0,
scheduler: scheduler,
#[cfg(feature = "debugger")]
trace: false,
}
@ -242,13 +231,13 @@ impl DmaController {
pub fn perform_work(&mut self, sb: &mut SysBus) {
for id in 0..4 {
if self.pending_set & (1 << id) != 0 {
self.channels[id].xfer(sb);
self.channels[id].transfer(sb);
}
}
self.pending_set = 0;
}
pub fn write_16(&mut self, channel_id: usize, ofs: u32, value: u16) {
pub fn write_16(&mut self, channel_id: usize, ofs: u32, value: u16, sched: &mut Scheduler) {
match ofs {
0 => self.channels[channel_id].write_src_low(value),
2 => self.channels[channel_id].write_src_high(value),
@ -262,8 +251,7 @@ impl DmaController {
let start_immediately = self.channels[channel_id].write_dma_ctrl(value);
if start_immediately {
// DMA actually starts after 3 cycles
self.scheduler
.push(EventType::DmaActivateChannel(channel_id), 3);
sched.schedule((EventType::DmaActivateChannel(channel_id), 3));
} else {
self.deactivate_channel(channel_id);
}

View file

@ -78,17 +78,24 @@ impl GameBoyAdvance {
};
let interrupt_flags = Rc::new(Cell::new(IrqBitmask(0)));
let scheduler = Scheduler::new_shared();
let mut scheduler = Scheduler::new_shared();
let intc = InterruptController::new(interrupt_flags.clone());
let gpu = Box::new(Gpu::new(scheduler.clone(), interrupt_flags.clone()));
let dmac = DmaController::new(interrupt_flags.clone(), scheduler.clone());
let timers = Timers::new(interrupt_flags.clone(), scheduler.clone());
let gpu = Box::new(Gpu::new(&mut scheduler, interrupt_flags.clone()));
let dmac = DmaController::new(interrupt_flags.clone());
let timers = Timers::new(interrupt_flags.clone());
let sound_controller = Box::new(SoundController::new(
scheduler.clone(),
&mut scheduler,
audio_device.borrow().get_sample_rate() as f32,
));
let io_devs = Shared::new(IoDevices::new(intc, gpu, dmac, timers, sound_controller));
let io_devs = Shared::new(IoDevices::new(
intc,
gpu,
dmac,
timers,
sound_controller,
scheduler.clone(),
));
let sysbus = Shared::new(SysBus::new(
scheduler.clone(),
io_devs.clone(),
@ -193,7 +200,6 @@ impl GameBoyAdvance {
self.sysbus.set_ewram(decoded.ewram);
// Redistribute shared pointers
self.io_devs.connect_irq(self.interrupt_flags.clone());
self.io_devs.connect_scheduler(self.scheduler.clone());
self.sysbus.connect_scheduler(self.scheduler.clone());
self.sysbus.set_io_devices(self.io_devs.clone());
self.sysbus.cartridge.update_from(decoded.cartridge);
@ -254,7 +260,7 @@ impl GameBoyAdvance {
// Register an event to mark the end of this run
self.scheduler
.push(EventType::RunLimitReached, cycles_to_run);
.schedule_at(EventType::RunLimitReached, run_start_time + cycles_to_run);
let mut running = true;
while running {
@ -274,47 +280,52 @@ impl GameBoyAdvance {
self.io_devs.haltcnt = HaltState::Running;
} else {
self.scheduler.fast_forward_to_next();
let (event, cycles_late) = self
.scheduler
.pop_pending_event()
.unwrap_or_else(|| unreachable!());
self.handle_event(event, cycles_late, &mut running);
self.handle_events(&mut running)
}
}
}
}
while let Some((event, cycles_late)) = self.scheduler.pop_pending_event() {
self.handle_event(event, cycles_late, &mut running);
}
self.handle_events(&mut running);
}
let total_cycles_ran = self.scheduler.timestamp() - run_start_time;
total_cycles_ran - cycles_to_run
}
#[inline]
fn handle_event(&mut self, event: EventType, cycles_late: usize, running: &mut bool) {
fn handle_events(&mut self, run_limit_flag: &mut bool) {
let io = &mut (*self.io_devs);
match event {
while let Some((event, event_time)) = self.scheduler.pop_pending_event() {
// Since we only examine the scheduler queue every so often, most events will be handled late by a few cycles.
// We sacrifice accuricy in favor of performance, otherwise we would have to check the event queue
// every cpu cycle, where in 99% of cases it will always be empty.
let new_event = match event {
EventType::RunLimitReached => {
*running = false;
*run_limit_flag = false;
None
}
EventType::DmaActivateChannel(channel_id) => {
io.dmac.activate_channel(channel_id);
None
}
EventType::DmaActivateChannel(channel_id) => io.dmac.activate_channel(channel_id),
EventType::TimerOverflow(channel_id) => {
let timers = &mut io.timers;
let dmac = &mut io.dmac;
let apu = &mut io.sound;
timers.handle_overflow_event(channel_id, cycles_late, apu, dmac);
Some(timers.handle_overflow_event(channel_id, event_time, apu, dmac))
}
EventType::Gpu(event) => io.gpu.on_event(
event,
cycles_late,
EventType::Gpu(gpu_event) => Some(io.gpu.on_event(
gpu_event,
&mut *self.sysbus,
#[cfg(not(feature = "no_video_interface"))]
&self.video_device,
),
EventType::Apu(event) => io.sound.on_event(event, cycles_late, &self.audio_device),
)),
EventType::Apu(event) => Some(io.sound.on_event(event, &self.audio_device)),
};
if let Some((new_event, when)) = new_event {
// We schedule events added by event handlers relative to the handled event time
self.scheduler.schedule_at(new_event, event_time + when)
}
}
}

View file

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use super::bus::*;
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
use super::sched::*;
use super::sched::{EventType, FutureEvent, GpuEvent, Scheduler};
pub use super::sysbus::consts::*;
#[cfg(not(feature = "no_video_interface"))]
use super::VideoInterface;
@ -113,11 +113,6 @@ type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
pub struct Gpu {
interrupt_flags: SharedInterruptFlags,
/// When deserializing this struct using serde, make sure to call connect_scheduler
#[serde(skip)]
#[serde(default = "Scheduler::new_shared")]
scheduler: SharedScheduler,
/// how many cycles left until next gpu state ?
cycles_left_for_current_state: usize,
@ -153,15 +148,11 @@ impl InterruptConnect for Gpu {
}
}
impl SchedulerConnect for Gpu {
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
self.scheduler = scheduler;
}
}
type FutureGpuEvent = (GpuEvent, usize);
impl Gpu {
pub fn new(mut scheduler: SharedScheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
scheduler.push_gpu_event(GpuEvent::HDraw, CYCLES_HDRAW);
pub fn new(sched: &mut Scheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
sched.schedule((EventType::Gpu(GpuEvent::HDraw), CYCLES_HDRAW));
fn alloc_scanline_buffer() -> Box<[Rgb15]> {
vec![Rgb15::TRANSPARENT; DISPLAY_WIDTH].into_boxed_slice()
@ -169,7 +160,6 @@ impl Gpu {
Gpu {
interrupt_flags,
scheduler,
dispcnt: DisplayControl::from(0x80),
dispstat: Default::default(),
bgcnt: Default::default(),
@ -367,7 +357,7 @@ impl Gpu {
}
#[inline]
fn handle_hdraw_end<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> (GpuEvent, usize) {
fn handle_hdraw_end<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> FutureGpuEvent {
self.dispstat.hblank_flag = true;
if self.dispstat.hblank_irq_enable {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
@ -382,7 +372,7 @@ impl Gpu {
&mut self,
dma_notifier: &mut D,
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
) -> (GpuEvent, usize) {
) -> FutureGpuEvent {
self.update_vcount(self.vcount + 1);
if self.vcount < DISPLAY_HEIGHT {
@ -419,7 +409,7 @@ impl Gpu {
}
}
fn handle_vblank_hdraw_end(&mut self) -> (GpuEvent, usize) {
fn handle_vblank_hdraw_end(&mut self) -> FutureGpuEvent {
self.dispstat.hblank_flag = true;
if self.dispstat.hblank_irq_enable {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
@ -427,7 +417,7 @@ impl Gpu {
(GpuEvent::VBlankHBlank, CYCLES_HBLANK)
}
fn handle_vblank_hblank_end(&mut self) -> (GpuEvent, usize) {
fn handle_vblank_hblank_end(&mut self) -> FutureGpuEvent {
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
self.update_vcount(self.vcount + 1);
self.dispstat.hblank_flag = false;
@ -444,13 +434,13 @@ impl Gpu {
pub fn on_event<D>(
&mut self,
event: GpuEvent,
extra_cycles: usize,
dma_notifier: &mut D,
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
) where
) -> FutureEvent
where
D: DmaNotifer,
{
let (next_event, cycles) = match event {
let (event, when) = match event {
GpuEvent::HDraw => self.handle_hdraw_end(dma_notifier),
GpuEvent::HBlank => self.handle_hblank_end(
dma_notifier,
@ -460,8 +450,7 @@ impl Gpu {
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw_end(),
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
};
self.scheduler
.push(EventType::Gpu(next_event), cycles - extra_cycles);
(EventType::Gpu(event), when)
}
}
@ -573,8 +562,8 @@ mod tests {
#[test]
fn test_gpu_state_machine() {
let mut sched = Scheduler::new_shared();
let mut gpu = Gpu::new(sched.clone(), Rc::new(Cell::new(Default::default())));
let mut sched = Scheduler::new();
let mut gpu = Gpu::new(&mut sched, Rc::new(Cell::new(Default::default())));
#[cfg(not(feature = "no_video_interface"))]
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
#[cfg(not(feature = "no_video_interface"))]

View file

@ -8,7 +8,7 @@ use super::gpu::*;
use super::interrupt::{InterruptConnect, InterruptController, SharedInterruptFlags};
use super::keypad;
use super::mgba_debug::DebugPort;
use super::sched::{SchedulerConnect, SharedScheduler};
use super::sched::{Scheduler, SchedulerConnect, SharedScheduler};
use super::sound::SoundController;
use super::sysbus::SysBusPtr;
use super::timer::Timers;
@ -40,6 +40,9 @@ pub struct IoDevices {
// HACK
// my ownership design sucks
#[serde(skip)]
#[serde(default = "Scheduler::new_shared")]
scheduler: SharedScheduler,
#[serde(skip)]
#[serde(default = "SysBusPtr::default")]
sysbus_ptr: SysBusPtr,
}
@ -51,6 +54,7 @@ impl IoDevices {
dmac: DmaController,
timers: Timers,
sound_controller: Box<SoundController>,
scheduler: SharedScheduler,
) -> IoDevices {
IoDevices {
intc,
@ -63,7 +67,7 @@ impl IoDevices {
keyinput: keypad::KEYINPUT_ALL_RELEASED,
waitcnt: WaitControl(0),
debug: DebugPort::new(),
scheduler: scheduler,
sysbus_ptr: Default::default(),
}
}
@ -84,10 +88,7 @@ impl InterruptConnect for IoDevices {
impl SchedulerConnect for IoDevices {
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
self.gpu.connect_scheduler(scheduler.clone());
self.sound.connect_scheduler(scheduler.clone());
self.dmac.connect_scheduler(scheduler.clone());
self.timers.connect_scheduler(scheduler.clone());
self.scheduler = scheduler.clone();
}
}
@ -124,7 +125,7 @@ impl Bus for IoDevices {
REG_IE => io.intc.interrupt_enable.0 as u16,
REG_IF => io.intc.interrupt_flags.get().value() as u16,
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_read(io_addr),
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_read(io_addr, &io.scheduler),
SOUND_BASE..=SOUND_END => io.sound.handle_read(io_addr),
REG_DMA0CNT_H => io.dmac.channels[0].ctrl.0,
@ -263,7 +264,9 @@ impl Bus for IoDevices {
REG_IE => io.intc.interrupt_enable.0 = value,
REG_IF => io.intc.clear(value),
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_write(io_addr, value),
REG_TM0CNT_L..=REG_TM3CNT_H => {
io.timers.handle_write(io_addr, value, &mut io.scheduler)
}
SOUND_BASE..=SOUND_END => {
io.sound.handle_write(io_addr, value);
@ -272,7 +275,8 @@ impl Bus for IoDevices {
DMA_BASE..=REG_DMA3CNT_H => {
let ofs = io_addr - DMA_BASE;
let channel_id = (ofs / 12) as usize;
io.dmac.write_16(channel_id, ofs % 12, value)
io.dmac
.write_16(channel_id, ofs % 12, value, &mut io.scheduler)
}
REG_WAITCNT => {

View file

@ -1,4 +1,3 @@
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
@ -42,27 +41,21 @@ pub struct Event {
typ: EventType,
/// Timestamp in cycles
time: usize,
cancel: Cell<bool>,
}
impl Event {
fn new(typ: EventType, time: usize) -> Event {
Event {
typ,
time,
cancel: Cell::new(false),
}
pub fn new(typ: EventType, time: usize) -> Event {
Event { typ, time }
}
#[inline]
fn get_type(&self) -> EventType {
self.typ
}
}
fn is_canceled(&self) -> bool {
self.cancel.get()
}
}
/// Future event is an event to be scheduled in x cycles from now
pub type FutureEvent = (EventType, usize);
impl Ord for Event {
fn cmp(&self, other: &Self) -> Ordering {
@ -140,27 +133,27 @@ impl Scheduler {
self.events.peek().map(|e| e.typ)
}
/// Schedule an event to be executed in `cycles` cycles from now
pub fn push(&mut self, typ: EventType, cycles: usize) {
let event = Event::new(typ, self.timestamp + cycles);
/// Schedule an event to be executed in `when` cycles from now
pub fn schedule(&mut self, event: FutureEvent) {
let (typ, when) = event;
let event = Event::new(typ, self.timestamp + when);
self.events.push(event);
}
/// Schedule an event to be executed at an exact timestamp, can be used to schedule "past" events.
pub fn schedule_at(&mut self, event_typ: EventType, timestamp: usize) {
self.events.push(Event::new(event_typ, timestamp));
}
/// Cancel all events with type `typ`
/// This method is rather expansive to call
pub fn cancel(&mut self, typ: EventType) {
/// This method is rather expansive to call since we are reallocating the entire event tree
pub fn cancel_pending(&mut self, typ: EventType) {
let mut new_events = BinaryHeap::with_capacity(NUM_EVENTS);
self.events
.iter()
.filter(|e| e.typ == typ)
.for_each(|e| e.cancel.set(true));
}
pub fn push_gpu_event(&mut self, e: GpuEvent, cycles: usize) {
self.push(EventType::Gpu(e), cycles);
}
pub fn push_apu_event(&mut self, e: ApuEvent, cycles: usize) {
self.push(EventType::Apu(e), cycles);
.filter(|e| e.typ != typ)
.for_each(|e| new_events.push(e.clone()));
self.events = new_events;
}
/// Updates the scheduler timestamp
@ -174,11 +167,7 @@ impl Scheduler {
if self.timestamp >= event.time {
// remove the event
let event = self.events.pop().unwrap_or_else(|| unreachable!());
if !event.is_canceled() {
Some((event.get_type(), self.timestamp - event.time))
} else {
None
}
Some((event.get_type(), event.time))
} else {
None
}

View file

@ -63,10 +63,6 @@ type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SoundController {
#[serde(skip)]
#[serde(default = "Scheduler::new_shared")]
scheduler: SharedScheduler,
cycles: usize, // cycles count when we last provided a new sample.
mse: bool,
@ -105,19 +101,12 @@ pub struct SoundController {
output_buffer: Vec<StereoSample<f32>>,
}
impl SchedulerConnect for SoundController {
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
self.scheduler = scheduler;
}
}
impl SoundController {
pub fn new(mut scheduler: SharedScheduler, audio_device_sample_rate: f32) -> SoundController {
pub fn new(sched: &mut Scheduler, audio_device_sample_rate: f32) -> SoundController {
let resampler = CosineResampler::new(32768_f32, audio_device_sample_rate);
let cycles_per_sample = 512;
scheduler.push(EventType::Apu(ApuEvent::Sample), cycles_per_sample);
sched.schedule((EventType::Apu(ApuEvent::Sample), cycles_per_sample));
SoundController {
scheduler,
cycles_per_sample,
cycles: 0,
mse: false,
@ -334,7 +323,7 @@ impl SoundController {
}
#[inline]
fn on_sample(&mut self, extra_cycles: usize, audio_device: &AudioDeviceRcRefCell) {
fn on_sample(&mut self, audio_device: &AudioDeviceRcRefCell) -> FutureEvent {
let mut sample = [0f32; 2];
for channel in 0..=1 {
@ -360,20 +349,17 @@ impl SoundController {
(right.round() as i16) * (std::i16::MAX / 512),
]);
});
self.scheduler
.push_apu_event(ApuEvent::Sample, self.cycles_per_sample - extra_cycles);
(EventType::Apu(ApuEvent::Sample), self.cycles_per_sample)
}
pub fn on_event(
&mut self,
event: ApuEvent,
extra_cycles: usize,
audio_device: &AudioDeviceRcRefCell,
) {
) -> FutureEvent {
match event {
ApuEvent::Sample => self.on_sample(extra_cycles, audio_device),
_ => debug!("got {:?} event", event),
ApuEvent::Sample => self.on_sample(audio_device),
_ => unimplemented!("got {:?} event", event),
}
}
}

View file

@ -161,7 +161,8 @@ pub type SysBusPtr = WeakPointer<SysBus>;
impl SchedulerConnect for SysBus {
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
self.scheduler = scheduler;
self.scheduler = scheduler.clone();
self.io.connect_scheduler(scheduler.clone());
}
}

View file

@ -1,7 +1,7 @@
use super::dma::DmaController;
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
use super::iodev::consts::*;
use super::sched::{EventType, Scheduler, SchedulerConnect, SharedScheduler};
use super::sched::{EventType, FutureEvent, Scheduler};
use super::sound::SoundController;
use num::FromPrimitive;
@ -93,9 +93,6 @@ impl Timer {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Timers {
#[serde(skip)]
#[serde(default = "Scheduler::new_shared")]
scheduler: SharedScheduler,
timers: [Timer; 4],
running_timers: u8,
@ -111,12 +108,6 @@ impl InterruptConnect for Timers {
}
}
impl SchedulerConnect for Timers {
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
self.scheduler = scheduler;
}
}
impl std::ops::Index<usize> for Timers {
type Output = Timer;
fn index(&self, index: usize) -> &Self::Output {
@ -131,9 +122,8 @@ impl std::ops::IndexMut<usize> for Timers {
}
impl Timers {
pub fn new(interrupt_flags: SharedInterruptFlags, scheduler: SharedScheduler) -> Timers {
pub fn new(interrupt_flags: SharedInterruptFlags) -> Timers {
Timers {
scheduler,
timers: [
Timer::new(0, interrupt_flags.clone()),
Timer::new(1, interrupt_flags.clone()),
@ -147,16 +137,16 @@ impl Timers {
}
}
fn add_timer_event(&mut self, id: usize) {
fn prepare_next_overflow_event(&mut self, id: usize, start_time: usize) -> FutureEvent {
let timer = &mut self.timers[id];
timer.is_scheduled = true;
timer.start_time = self.scheduler.timestamp();
timer.start_time = start_time;
let cycles = (timer.ticks_to_overflow() as usize) << timer.prescalar_shift;
self.scheduler.push(EventType::TimerOverflow(id), cycles);
(EventType::TimerOverflow(id), cycles)
}
fn cancel_timer_event(&mut self, id: usize) {
self.scheduler.cancel(EventType::TimerOverflow(id));
fn cancel_timer_event(&mut self, id: usize, sched: &mut Scheduler) {
sched.cancel_pending(EventType::TimerOverflow(id));
self[id].is_scheduled = false;
}
@ -185,22 +175,15 @@ impl Timers {
pub fn handle_overflow_event(
&mut self,
id: usize,
extra_cycles: usize,
overflow_time: usize,
apu: &mut SoundController,
dmac: &mut DmaController,
) {
) -> FutureEvent {
self.handle_timer_overflow(id, apu, dmac);
// TODO: re-use add_timer_event function
let timer = &mut self.timers[id];
timer.is_scheduled = true;
timer.start_time = self.scheduler.timestamp() - extra_cycles;
let cycles = (timer.ticks_to_overflow() as usize) << timer.prescalar_shift;
self.scheduler
.push(EventType::TimerOverflow(id), cycles - extra_cycles);
self.prepare_next_overflow_event(id, overflow_time)
}
pub fn write_timer_ctl(&mut self, id: usize, value: u16) {
pub fn write_timer_ctl(&mut self, id: usize, value: u16, sched: &mut Scheduler) {
let timer = &mut self.timers[id];
let new_ctl = TimerCtl(value);
#[cfg(feature = "debugger")]
@ -211,11 +194,11 @@ impl Timers {
timer.ctl = new_ctl;
if new_enabled && !cascade {
self.running_timers |= 1 << id;
self.cancel_timer_event(id);
self.add_timer_event(id);
self.cancel_timer_event(id, sched);
sched.schedule(self.prepare_next_overflow_event(id, sched.timestamp()));
} else {
self.running_timers &= !(1 << id);
self.cancel_timer_event(id);
self.cancel_timer_event(id, sched);
}
#[cfg(feature = "debugger")]
@ -231,56 +214,56 @@ impl Timers {
}
#[inline]
fn read_timer_data(&mut self, id: usize) -> u16 {
fn read_timer_data(&mut self, id: usize, sched: &Scheduler) -> u16 {
let timer = &mut self.timers[id];
if timer.is_scheduled {
// this timer is controlled by the scheduler so we need to manually calculate
// this timer is controlled by the sched so we need to manually calculate
// the current value of the counter
timer.sync_timer_data(self.scheduler.timestamp());
timer.sync_timer_data(sched.timestamp());
}
timer.data
}
pub fn handle_read(&mut self, io_addr: u32) -> u16 {
pub fn handle_read(&mut self, io_addr: u32, sched: &Scheduler) -> u16 {
match io_addr {
REG_TM0CNT_H => self.timers[0].ctl.0,
REG_TM1CNT_H => self.timers[1].ctl.0,
REG_TM2CNT_H => self.timers[2].ctl.0,
REG_TM3CNT_H => self.timers[3].ctl.0,
REG_TM0CNT_L => self.read_timer_data(0),
REG_TM1CNT_L => self.read_timer_data(1),
REG_TM2CNT_L => self.read_timer_data(2),
REG_TM3CNT_L => self.read_timer_data(3),
REG_TM0CNT_L => self.read_timer_data(0, sched),
REG_TM1CNT_L => self.read_timer_data(1, sched),
REG_TM2CNT_L => self.read_timer_data(2, sched),
REG_TM3CNT_L => self.read_timer_data(3, sched),
_ => unreachable!(),
}
}
pub fn handle_write(&mut self, io_addr: u32, value: u16) {
pub fn handle_write(&mut self, io_addr: u32, value: u16, sched: &mut Scheduler) {
match io_addr {
REG_TM0CNT_L => {
self.timers[0].data = value;
self.timers[0].initial_data = value;
}
REG_TM0CNT_H => self.write_timer_ctl(0, value),
REG_TM0CNT_H => self.write_timer_ctl(0, value, sched),
REG_TM1CNT_L => {
self.timers[1].data = value;
self.timers[1].initial_data = value;
}
REG_TM1CNT_H => self.write_timer_ctl(1, value),
REG_TM1CNT_H => self.write_timer_ctl(1, value, sched),
REG_TM2CNT_L => {
self.timers[2].data = value;
self.timers[2].initial_data = value;
}
REG_TM2CNT_H => self.write_timer_ctl(2, value),
REG_TM2CNT_H => self.write_timer_ctl(2, value, sched),
REG_TM3CNT_L => {
self.timers[3].data = value;
self.timers[3].initial_data = value;
}
REG_TM3CNT_H => self.write_timer_ctl(3, value),
REG_TM3CNT_H => self.write_timer_ctl(3, value, sched),
_ => unreachable!(),
}
}