Refactor&improve the scheduler to handle missed events
Solves #168 Former-commit-id: 09104a6ebefdaf2e33ae22eb08860d9711ecb66b Former-commit-id: e07219e40fa187f516dd266ab753a67f3c7e796b
This commit is contained in:
parent
7af9bc5760
commit
91453e8777
|
@ -254,7 +254,7 @@ impl EepromChip {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate) fn is_transmitting(&self) -> bool {
|
pub(crate) fn is_transmitting(&self) -> bool {
|
||||||
use SpiState::*;
|
use SpiState::*;
|
||||||
match self.state {
|
match self.state {
|
||||||
TxData | TxDummy => true,
|
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.state = SpiState::RxInstruction;
|
||||||
self.reset_rx_buffer();
|
self.reset_rx_buffer();
|
||||||
self.reset_tx_buffer();
|
self.reset_tx_buffer();
|
||||||
|
@ -280,7 +280,7 @@ impl EepromChip {
|
||||||
/// Eeprom controller can programmed with DMA accesses in 16bit mode
|
/// Eeprom controller can programmed with DMA accesses in 16bit mode
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct EepromController {
|
pub struct EepromController {
|
||||||
pub(in crate) chip: RefCell<EepromChip>,
|
pub(crate) chip: RefCell<EepromChip>,
|
||||||
detect: bool,
|
detect: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub trait GpioDevice: Sized {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Gpio {
|
pub struct Gpio {
|
||||||
pub(in crate) rtc: Option<Rtc>,
|
pub(crate) rtc: Option<Rtc>,
|
||||||
direction: GpioState,
|
direction: GpioState,
|
||||||
control: GpioPortControl,
|
control: GpioPortControl,
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub struct Cartridge {
|
||||||
size: usize,
|
size: usize,
|
||||||
gpio: Option<Gpio>,
|
gpio: Option<Gpio>,
|
||||||
symbols: Option<SymbolTable>, // TODO move it somewhere else
|
symbols: Option<SymbolTable>, // TODO move it somewhere else
|
||||||
pub(in crate) backup: BackupMedia,
|
pub(crate) backup: BackupMedia,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::arm7tdmi::memory::{MemoryAccess, MemoryInterface};
|
||||||
use super::cartridge::BackupMedia;
|
use super::cartridge::BackupMedia;
|
||||||
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
||||||
use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
|
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 super::sysbus::SysBus;
|
||||||
|
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
|
@ -118,7 +118,7 @@ impl DmaChannel {
|
||||||
return start_immediately;
|
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 word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
|
||||||
let count = match self.internal.count {
|
let count = match self.internal.count {
|
||||||
0 => match self.id {
|
0 => match self.id {
|
||||||
|
@ -197,9 +197,6 @@ impl DmaChannel {
|
||||||
pub struct DmaController {
|
pub struct DmaController {
|
||||||
pub channels: [DmaChannel; 4],
|
pub channels: [DmaChannel; 4],
|
||||||
pending_set: u8,
|
pending_set: u8,
|
||||||
#[serde(skip)]
|
|
||||||
#[serde(default = "Scheduler::new_shared")]
|
|
||||||
scheduler: SharedScheduler,
|
|
||||||
#[cfg(feature = "debugger")]
|
#[cfg(feature = "debugger")]
|
||||||
pub trace: bool,
|
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 {
|
impl DmaController {
|
||||||
pub fn new(interrupt_flags: SharedInterruptFlags, scheduler: SharedScheduler) -> DmaController {
|
pub fn new(interrupt_flags: SharedInterruptFlags) -> DmaController {
|
||||||
DmaController {
|
DmaController {
|
||||||
channels: [
|
channels: [
|
||||||
DmaChannel::new(0, interrupt_flags.clone()),
|
DmaChannel::new(0, interrupt_flags.clone()),
|
||||||
|
@ -228,8 +219,6 @@ impl DmaController {
|
||||||
DmaChannel::new(3, interrupt_flags.clone()),
|
DmaChannel::new(3, interrupt_flags.clone()),
|
||||||
],
|
],
|
||||||
pending_set: 0,
|
pending_set: 0,
|
||||||
scheduler: scheduler,
|
|
||||||
|
|
||||||
#[cfg(feature = "debugger")]
|
#[cfg(feature = "debugger")]
|
||||||
trace: false,
|
trace: false,
|
||||||
}
|
}
|
||||||
|
@ -242,13 +231,13 @@ impl DmaController {
|
||||||
pub fn perform_work(&mut self, sb: &mut SysBus) {
|
pub fn perform_work(&mut self, sb: &mut SysBus) {
|
||||||
for id in 0..4 {
|
for id in 0..4 {
|
||||||
if self.pending_set & (1 << id) != 0 {
|
if self.pending_set & (1 << id) != 0 {
|
||||||
self.channels[id].xfer(sb);
|
self.channels[id].transfer(sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.pending_set = 0;
|
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 {
|
match ofs {
|
||||||
0 => self.channels[channel_id].write_src_low(value),
|
0 => self.channels[channel_id].write_src_low(value),
|
||||||
2 => self.channels[channel_id].write_src_high(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);
|
let start_immediately = self.channels[channel_id].write_dma_ctrl(value);
|
||||||
if start_immediately {
|
if start_immediately {
|
||||||
// DMA actually starts after 3 cycles
|
// DMA actually starts after 3 cycles
|
||||||
self.scheduler
|
sched.schedule((EventType::DmaActivateChannel(channel_id), 3));
|
||||||
.push(EventType::DmaActivateChannel(channel_id), 3);
|
|
||||||
} else {
|
} else {
|
||||||
self.deactivate_channel(channel_id);
|
self.deactivate_channel(channel_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,17 +78,24 @@ impl GameBoyAdvance {
|
||||||
};
|
};
|
||||||
|
|
||||||
let interrupt_flags = Rc::new(Cell::new(IrqBitmask(0)));
|
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 intc = InterruptController::new(interrupt_flags.clone());
|
||||||
let gpu = Box::new(Gpu::new(scheduler.clone(), interrupt_flags.clone()));
|
let gpu = Box::new(Gpu::new(&mut scheduler, interrupt_flags.clone()));
|
||||||
let dmac = DmaController::new(interrupt_flags.clone(), scheduler.clone());
|
let dmac = DmaController::new(interrupt_flags.clone());
|
||||||
let timers = Timers::new(interrupt_flags.clone(), scheduler.clone());
|
let timers = Timers::new(interrupt_flags.clone());
|
||||||
let sound_controller = Box::new(SoundController::new(
|
let sound_controller = Box::new(SoundController::new(
|
||||||
scheduler.clone(),
|
&mut scheduler,
|
||||||
audio_device.borrow().get_sample_rate() as f32,
|
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(
|
let sysbus = Shared::new(SysBus::new(
|
||||||
scheduler.clone(),
|
scheduler.clone(),
|
||||||
io_devs.clone(),
|
io_devs.clone(),
|
||||||
|
@ -193,7 +200,6 @@ impl GameBoyAdvance {
|
||||||
self.sysbus.set_ewram(decoded.ewram);
|
self.sysbus.set_ewram(decoded.ewram);
|
||||||
// Redistribute shared pointers
|
// Redistribute shared pointers
|
||||||
self.io_devs.connect_irq(self.interrupt_flags.clone());
|
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.connect_scheduler(self.scheduler.clone());
|
||||||
self.sysbus.set_io_devices(self.io_devs.clone());
|
self.sysbus.set_io_devices(self.io_devs.clone());
|
||||||
self.sysbus.cartridge.update_from(decoded.cartridge);
|
self.sysbus.cartridge.update_from(decoded.cartridge);
|
||||||
|
@ -254,7 +260,7 @@ impl GameBoyAdvance {
|
||||||
|
|
||||||
// Register an event to mark the end of this run
|
// Register an event to mark the end of this run
|
||||||
self.scheduler
|
self.scheduler
|
||||||
.push(EventType::RunLimitReached, cycles_to_run);
|
.schedule_at(EventType::RunLimitReached, run_start_time + cycles_to_run);
|
||||||
|
|
||||||
let mut running = true;
|
let mut running = true;
|
||||||
while running {
|
while running {
|
||||||
|
@ -274,47 +280,52 @@ impl GameBoyAdvance {
|
||||||
self.io_devs.haltcnt = HaltState::Running;
|
self.io_devs.haltcnt = HaltState::Running;
|
||||||
} else {
|
} else {
|
||||||
self.scheduler.fast_forward_to_next();
|
self.scheduler.fast_forward_to_next();
|
||||||
let (event, cycles_late) = self
|
self.handle_events(&mut running)
|
||||||
.scheduler
|
|
||||||
.pop_pending_event()
|
|
||||||
.unwrap_or_else(|| unreachable!());
|
|
||||||
self.handle_event(event, cycles_late, &mut running);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some((event, cycles_late)) = self.scheduler.pop_pending_event() {
|
self.handle_events(&mut running);
|
||||||
self.handle_event(event, cycles_late, &mut running);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let total_cycles_ran = self.scheduler.timestamp() - run_start_time;
|
let total_cycles_ran = self.scheduler.timestamp() - run_start_time;
|
||||||
total_cycles_ran - cycles_to_run
|
total_cycles_ran - cycles_to_run
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
fn handle_events(&mut self, run_limit_flag: &mut bool) {
|
||||||
fn handle_event(&mut self, event: EventType, cycles_late: usize, running: &mut bool) {
|
|
||||||
let io = &mut (*self.io_devs);
|
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 => {
|
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) => {
|
EventType::TimerOverflow(channel_id) => {
|
||||||
let timers = &mut io.timers;
|
let timers = &mut io.timers;
|
||||||
let dmac = &mut io.dmac;
|
let dmac = &mut io.dmac;
|
||||||
let apu = &mut io.sound;
|
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(
|
EventType::Gpu(gpu_event) => Some(io.gpu.on_event(
|
||||||
event,
|
gpu_event,
|
||||||
cycles_late,
|
|
||||||
&mut *self.sysbus,
|
&mut *self.sysbus,
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
#[cfg(not(feature = "no_video_interface"))]
|
||||||
&self.video_device,
|
&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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use super::bus::*;
|
use super::bus::*;
|
||||||
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
|
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
|
||||||
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
||||||
use super::sched::*;
|
use super::sched::{EventType, FutureEvent, GpuEvent, Scheduler};
|
||||||
pub use super::sysbus::consts::*;
|
pub use super::sysbus::consts::*;
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
#[cfg(not(feature = "no_video_interface"))]
|
||||||
use super::VideoInterface;
|
use super::VideoInterface;
|
||||||
|
@ -113,11 +113,6 @@ type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
|
||||||
pub struct Gpu {
|
pub struct Gpu {
|
||||||
interrupt_flags: SharedInterruptFlags,
|
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 ?
|
/// how many cycles left until next gpu state ?
|
||||||
cycles_left_for_current_state: usize,
|
cycles_left_for_current_state: usize,
|
||||||
|
|
||||||
|
@ -153,15 +148,11 @@ impl InterruptConnect for Gpu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SchedulerConnect for Gpu {
|
type FutureGpuEvent = (GpuEvent, usize);
|
||||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
|
||||||
self.scheduler = scheduler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gpu {
|
impl Gpu {
|
||||||
pub fn new(mut scheduler: SharedScheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
|
pub fn new(sched: &mut Scheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
|
||||||
scheduler.push_gpu_event(GpuEvent::HDraw, CYCLES_HDRAW);
|
sched.schedule((EventType::Gpu(GpuEvent::HDraw), CYCLES_HDRAW));
|
||||||
|
|
||||||
fn alloc_scanline_buffer() -> Box<[Rgb15]> {
|
fn alloc_scanline_buffer() -> Box<[Rgb15]> {
|
||||||
vec![Rgb15::TRANSPARENT; DISPLAY_WIDTH].into_boxed_slice()
|
vec![Rgb15::TRANSPARENT; DISPLAY_WIDTH].into_boxed_slice()
|
||||||
|
@ -169,7 +160,6 @@ impl Gpu {
|
||||||
|
|
||||||
Gpu {
|
Gpu {
|
||||||
interrupt_flags,
|
interrupt_flags,
|
||||||
scheduler,
|
|
||||||
dispcnt: DisplayControl::from(0x80),
|
dispcnt: DisplayControl::from(0x80),
|
||||||
dispstat: Default::default(),
|
dispstat: Default::default(),
|
||||||
bgcnt: Default::default(),
|
bgcnt: Default::default(),
|
||||||
|
@ -367,7 +357,7 @@ impl Gpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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;
|
self.dispstat.hblank_flag = true;
|
||||||
if self.dispstat.hblank_irq_enable {
|
if self.dispstat.hblank_irq_enable {
|
||||||
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
||||||
|
@ -382,7 +372,7 @@ impl Gpu {
|
||||||
&mut self,
|
&mut self,
|
||||||
dma_notifier: &mut D,
|
dma_notifier: &mut D,
|
||||||
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
||||||
) -> (GpuEvent, usize) {
|
) -> FutureGpuEvent {
|
||||||
self.update_vcount(self.vcount + 1);
|
self.update_vcount(self.vcount + 1);
|
||||||
|
|
||||||
if self.vcount < DISPLAY_HEIGHT {
|
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;
|
self.dispstat.hblank_flag = true;
|
||||||
if self.dispstat.hblank_irq_enable {
|
if self.dispstat.hblank_irq_enable {
|
||||||
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
||||||
|
@ -427,7 +417,7 @@ impl Gpu {
|
||||||
(GpuEvent::VBlankHBlank, CYCLES_HBLANK)
|
(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 {
|
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
|
||||||
self.update_vcount(self.vcount + 1);
|
self.update_vcount(self.vcount + 1);
|
||||||
self.dispstat.hblank_flag = false;
|
self.dispstat.hblank_flag = false;
|
||||||
|
@ -444,13 +434,13 @@ impl Gpu {
|
||||||
pub fn on_event<D>(
|
pub fn on_event<D>(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: GpuEvent,
|
event: GpuEvent,
|
||||||
extra_cycles: usize,
|
|
||||||
dma_notifier: &mut D,
|
dma_notifier: &mut D,
|
||||||
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
||||||
) where
|
) -> FutureEvent
|
||||||
|
where
|
||||||
D: DmaNotifer,
|
D: DmaNotifer,
|
||||||
{
|
{
|
||||||
let (next_event, cycles) = match event {
|
let (event, when) = match event {
|
||||||
GpuEvent::HDraw => self.handle_hdraw_end(dma_notifier),
|
GpuEvent::HDraw => self.handle_hdraw_end(dma_notifier),
|
||||||
GpuEvent::HBlank => self.handle_hblank_end(
|
GpuEvent::HBlank => self.handle_hblank_end(
|
||||||
dma_notifier,
|
dma_notifier,
|
||||||
|
@ -460,8 +450,7 @@ impl Gpu {
|
||||||
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw_end(),
|
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw_end(),
|
||||||
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
|
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
|
||||||
};
|
};
|
||||||
self.scheduler
|
(EventType::Gpu(event), when)
|
||||||
.push(EventType::Gpu(next_event), cycles - extra_cycles);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,8 +562,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gpu_state_machine() {
|
fn test_gpu_state_machine() {
|
||||||
let mut sched = Scheduler::new_shared();
|
let mut sched = Scheduler::new();
|
||||||
let mut gpu = Gpu::new(sched.clone(), Rc::new(Cell::new(Default::default())));
|
let mut gpu = Gpu::new(&mut sched, Rc::new(Cell::new(Default::default())));
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
#[cfg(not(feature = "no_video_interface"))]
|
||||||
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
|
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
#[cfg(not(feature = "no_video_interface"))]
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::gpu::*;
|
||||||
use super::interrupt::{InterruptConnect, InterruptController, SharedInterruptFlags};
|
use super::interrupt::{InterruptConnect, InterruptController, SharedInterruptFlags};
|
||||||
use super::keypad;
|
use super::keypad;
|
||||||
use super::mgba_debug::DebugPort;
|
use super::mgba_debug::DebugPort;
|
||||||
use super::sched::{SchedulerConnect, SharedScheduler};
|
use super::sched::{Scheduler, SchedulerConnect, SharedScheduler};
|
||||||
use super::sound::SoundController;
|
use super::sound::SoundController;
|
||||||
use super::sysbus::SysBusPtr;
|
use super::sysbus::SysBusPtr;
|
||||||
use super::timer::Timers;
|
use super::timer::Timers;
|
||||||
|
@ -40,6 +40,9 @@ pub struct IoDevices {
|
||||||
// HACK
|
// HACK
|
||||||
// my ownership design sucks
|
// my ownership design sucks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
#[serde(default = "Scheduler::new_shared")]
|
||||||
|
scheduler: SharedScheduler,
|
||||||
|
#[serde(skip)]
|
||||||
#[serde(default = "SysBusPtr::default")]
|
#[serde(default = "SysBusPtr::default")]
|
||||||
sysbus_ptr: SysBusPtr,
|
sysbus_ptr: SysBusPtr,
|
||||||
}
|
}
|
||||||
|
@ -51,6 +54,7 @@ impl IoDevices {
|
||||||
dmac: DmaController,
|
dmac: DmaController,
|
||||||
timers: Timers,
|
timers: Timers,
|
||||||
sound_controller: Box<SoundController>,
|
sound_controller: Box<SoundController>,
|
||||||
|
scheduler: SharedScheduler,
|
||||||
) -> IoDevices {
|
) -> IoDevices {
|
||||||
IoDevices {
|
IoDevices {
|
||||||
intc,
|
intc,
|
||||||
|
@ -63,7 +67,7 @@ impl IoDevices {
|
||||||
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
||||||
waitcnt: WaitControl(0),
|
waitcnt: WaitControl(0),
|
||||||
debug: DebugPort::new(),
|
debug: DebugPort::new(),
|
||||||
|
scheduler: scheduler,
|
||||||
sysbus_ptr: Default::default(),
|
sysbus_ptr: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,10 +88,7 @@ impl InterruptConnect for IoDevices {
|
||||||
|
|
||||||
impl SchedulerConnect for IoDevices {
|
impl SchedulerConnect for IoDevices {
|
||||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||||
self.gpu.connect_scheduler(scheduler.clone());
|
self.scheduler = scheduler.clone();
|
||||||
self.sound.connect_scheduler(scheduler.clone());
|
|
||||||
self.dmac.connect_scheduler(scheduler.clone());
|
|
||||||
self.timers.connect_scheduler(scheduler.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ impl Bus for IoDevices {
|
||||||
REG_IE => io.intc.interrupt_enable.0 as u16,
|
REG_IE => io.intc.interrupt_enable.0 as u16,
|
||||||
REG_IF => io.intc.interrupt_flags.get().value() 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),
|
SOUND_BASE..=SOUND_END => io.sound.handle_read(io_addr),
|
||||||
REG_DMA0CNT_H => io.dmac.channels[0].ctrl.0,
|
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_IE => io.intc.interrupt_enable.0 = value,
|
||||||
REG_IF => io.intc.clear(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 => {
|
SOUND_BASE..=SOUND_END => {
|
||||||
io.sound.handle_write(io_addr, value);
|
io.sound.handle_write(io_addr, value);
|
||||||
|
@ -272,7 +275,8 @@ impl Bus for IoDevices {
|
||||||
DMA_BASE..=REG_DMA3CNT_H => {
|
DMA_BASE..=REG_DMA3CNT_H => {
|
||||||
let ofs = io_addr - DMA_BASE;
|
let ofs = io_addr - DMA_BASE;
|
||||||
let channel_id = (ofs / 12) as usize;
|
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 => {
|
REG_WAITCNT => {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::cell::Cell;
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::BinaryHeap;
|
use std::collections::BinaryHeap;
|
||||||
|
|
||||||
|
@ -42,27 +41,21 @@ pub struct Event {
|
||||||
typ: EventType,
|
typ: EventType,
|
||||||
/// Timestamp in cycles
|
/// Timestamp in cycles
|
||||||
time: usize,
|
time: usize,
|
||||||
cancel: Cell<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
fn new(typ: EventType, time: usize) -> Event {
|
pub fn new(typ: EventType, time: usize) -> Event {
|
||||||
Event {
|
Event { typ, time }
|
||||||
typ,
|
|
||||||
time,
|
|
||||||
cancel: Cell::new(false),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_type(&self) -> EventType {
|
fn get_type(&self) -> EventType {
|
||||||
self.typ
|
self.typ
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_canceled(&self) -> bool {
|
/// Future event is an event to be scheduled in x cycles from now
|
||||||
self.cancel.get()
|
pub type FutureEvent = (EventType, usize);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Event {
|
impl Ord for Event {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
@ -140,27 +133,27 @@ impl Scheduler {
|
||||||
self.events.peek().map(|e| e.typ)
|
self.events.peek().map(|e| e.typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule an event to be executed in `cycles` cycles from now
|
/// Schedule an event to be executed in `when` cycles from now
|
||||||
pub fn push(&mut self, typ: EventType, cycles: usize) {
|
pub fn schedule(&mut self, event: FutureEvent) {
|
||||||
let event = Event::new(typ, self.timestamp + cycles);
|
let (typ, when) = event;
|
||||||
|
let event = Event::new(typ, self.timestamp + when);
|
||||||
self.events.push(event);
|
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`
|
/// Cancel all events with type `typ`
|
||||||
/// This method is rather expansive to call
|
/// This method is rather expansive to call since we are reallocating the entire event tree
|
||||||
pub fn cancel(&mut self, typ: EventType) {
|
pub fn cancel_pending(&mut self, typ: EventType) {
|
||||||
|
let mut new_events = BinaryHeap::with_capacity(NUM_EVENTS);
|
||||||
self.events
|
self.events
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|e| e.typ == typ)
|
.filter(|e| e.typ != typ)
|
||||||
.for_each(|e| e.cancel.set(true));
|
.for_each(|e| new_events.push(e.clone()));
|
||||||
}
|
self.events = new_events;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the scheduler timestamp
|
/// Updates the scheduler timestamp
|
||||||
|
@ -174,11 +167,7 @@ impl Scheduler {
|
||||||
if self.timestamp >= event.time {
|
if self.timestamp >= event.time {
|
||||||
// remove the event
|
// remove the event
|
||||||
let event = self.events.pop().unwrap_or_else(|| unreachable!());
|
let event = self.events.pop().unwrap_or_else(|| unreachable!());
|
||||||
if !event.is_canceled() {
|
Some((event.get_type(), event.time))
|
||||||
Some((event.get_type(), self.timestamp - event.time))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,10 +63,6 @@ type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct SoundController {
|
pub struct SoundController {
|
||||||
#[serde(skip)]
|
|
||||||
#[serde(default = "Scheduler::new_shared")]
|
|
||||||
scheduler: SharedScheduler,
|
|
||||||
|
|
||||||
cycles: usize, // cycles count when we last provided a new sample.
|
cycles: usize, // cycles count when we last provided a new sample.
|
||||||
|
|
||||||
mse: bool,
|
mse: bool,
|
||||||
|
@ -105,19 +101,12 @@ pub struct SoundController {
|
||||||
output_buffer: Vec<StereoSample<f32>>,
|
output_buffer: Vec<StereoSample<f32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SchedulerConnect for SoundController {
|
|
||||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
|
||||||
self.scheduler = scheduler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SoundController {
|
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 resampler = CosineResampler::new(32768_f32, audio_device_sample_rate);
|
||||||
let cycles_per_sample = 512;
|
let cycles_per_sample = 512;
|
||||||
scheduler.push(EventType::Apu(ApuEvent::Sample), cycles_per_sample);
|
sched.schedule((EventType::Apu(ApuEvent::Sample), cycles_per_sample));
|
||||||
SoundController {
|
SoundController {
|
||||||
scheduler,
|
|
||||||
cycles_per_sample,
|
cycles_per_sample,
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
mse: false,
|
mse: false,
|
||||||
|
@ -334,7 +323,7 @@ impl SoundController {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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];
|
let mut sample = [0f32; 2];
|
||||||
|
|
||||||
for channel in 0..=1 {
|
for channel in 0..=1 {
|
||||||
|
@ -360,20 +349,17 @@ impl SoundController {
|
||||||
(right.round() as i16) * (std::i16::MAX / 512),
|
(right.round() as i16) * (std::i16::MAX / 512),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
(EventType::Apu(ApuEvent::Sample), self.cycles_per_sample)
|
||||||
self.scheduler
|
|
||||||
.push_apu_event(ApuEvent::Sample, self.cycles_per_sample - extra_cycles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_event(
|
pub fn on_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: ApuEvent,
|
event: ApuEvent,
|
||||||
extra_cycles: usize,
|
|
||||||
audio_device: &AudioDeviceRcRefCell,
|
audio_device: &AudioDeviceRcRefCell,
|
||||||
) {
|
) -> FutureEvent {
|
||||||
match event {
|
match event {
|
||||||
ApuEvent::Sample => self.on_sample(extra_cycles, audio_device),
|
ApuEvent::Sample => self.on_sample(audio_device),
|
||||||
_ => debug!("got {:?} event", event),
|
_ => unimplemented!("got {:?} event", event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,8 @@ pub type SysBusPtr = WeakPointer<SysBus>;
|
||||||
|
|
||||||
impl SchedulerConnect for SysBus {
|
impl SchedulerConnect for SysBus {
|
||||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||||
self.scheduler = scheduler;
|
self.scheduler = scheduler.clone();
|
||||||
|
self.io.connect_scheduler(scheduler.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::dma::DmaController;
|
use super::dma::DmaController;
|
||||||
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
||||||
use super::iodev::consts::*;
|
use super::iodev::consts::*;
|
||||||
use super::sched::{EventType, Scheduler, SchedulerConnect, SharedScheduler};
|
use super::sched::{EventType, FutureEvent, Scheduler};
|
||||||
use super::sound::SoundController;
|
use super::sound::SoundController;
|
||||||
|
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
|
@ -93,9 +93,6 @@ impl Timer {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Timers {
|
pub struct Timers {
|
||||||
#[serde(skip)]
|
|
||||||
#[serde(default = "Scheduler::new_shared")]
|
|
||||||
scheduler: SharedScheduler,
|
|
||||||
timers: [Timer; 4],
|
timers: [Timer; 4],
|
||||||
running_timers: u8,
|
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 {
|
impl std::ops::Index<usize> for Timers {
|
||||||
type Output = Timer;
|
type Output = Timer;
|
||||||
fn index(&self, index: usize) -> &Self::Output {
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
@ -131,9 +122,8 @@ impl std::ops::IndexMut<usize> for Timers {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timers {
|
impl Timers {
|
||||||
pub fn new(interrupt_flags: SharedInterruptFlags, scheduler: SharedScheduler) -> Timers {
|
pub fn new(interrupt_flags: SharedInterruptFlags) -> Timers {
|
||||||
Timers {
|
Timers {
|
||||||
scheduler,
|
|
||||||
timers: [
|
timers: [
|
||||||
Timer::new(0, interrupt_flags.clone()),
|
Timer::new(0, interrupt_flags.clone()),
|
||||||
Timer::new(1, 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];
|
let timer = &mut self.timers[id];
|
||||||
timer.is_scheduled = true;
|
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;
|
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) {
|
fn cancel_timer_event(&mut self, id: usize, sched: &mut Scheduler) {
|
||||||
self.scheduler.cancel(EventType::TimerOverflow(id));
|
sched.cancel_pending(EventType::TimerOverflow(id));
|
||||||
self[id].is_scheduled = false;
|
self[id].is_scheduled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,22 +175,15 @@ impl Timers {
|
||||||
pub fn handle_overflow_event(
|
pub fn handle_overflow_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: usize,
|
id: usize,
|
||||||
extra_cycles: usize,
|
overflow_time: usize,
|
||||||
apu: &mut SoundController,
|
apu: &mut SoundController,
|
||||||
dmac: &mut DmaController,
|
dmac: &mut DmaController,
|
||||||
) {
|
) -> FutureEvent {
|
||||||
self.handle_timer_overflow(id, apu, dmac);
|
self.handle_timer_overflow(id, apu, dmac);
|
||||||
|
self.prepare_next_overflow_event(id, overflow_time)
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 timer = &mut self.timers[id];
|
||||||
let new_ctl = TimerCtl(value);
|
let new_ctl = TimerCtl(value);
|
||||||
#[cfg(feature = "debugger")]
|
#[cfg(feature = "debugger")]
|
||||||
|
@ -211,11 +194,11 @@ impl Timers {
|
||||||
timer.ctl = new_ctl;
|
timer.ctl = new_ctl;
|
||||||
if new_enabled && !cascade {
|
if new_enabled && !cascade {
|
||||||
self.running_timers |= 1 << id;
|
self.running_timers |= 1 << id;
|
||||||
self.cancel_timer_event(id);
|
self.cancel_timer_event(id, sched);
|
||||||
self.add_timer_event(id);
|
sched.schedule(self.prepare_next_overflow_event(id, sched.timestamp()));
|
||||||
} else {
|
} else {
|
||||||
self.running_timers &= !(1 << id);
|
self.running_timers &= !(1 << id);
|
||||||
self.cancel_timer_event(id);
|
self.cancel_timer_event(id, sched);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debugger")]
|
#[cfg(feature = "debugger")]
|
||||||
|
@ -231,56 +214,56 @@ impl Timers {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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];
|
let timer = &mut self.timers[id];
|
||||||
if timer.is_scheduled {
|
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
|
// the current value of the counter
|
||||||
timer.sync_timer_data(self.scheduler.timestamp());
|
timer.sync_timer_data(sched.timestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.data
|
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 {
|
match io_addr {
|
||||||
REG_TM0CNT_H => self.timers[0].ctl.0,
|
REG_TM0CNT_H => self.timers[0].ctl.0,
|
||||||
REG_TM1CNT_H => self.timers[1].ctl.0,
|
REG_TM1CNT_H => self.timers[1].ctl.0,
|
||||||
REG_TM2CNT_H => self.timers[2].ctl.0,
|
REG_TM2CNT_H => self.timers[2].ctl.0,
|
||||||
REG_TM3CNT_H => self.timers[3].ctl.0,
|
REG_TM3CNT_H => self.timers[3].ctl.0,
|
||||||
REG_TM0CNT_L => self.read_timer_data(0),
|
REG_TM0CNT_L => self.read_timer_data(0, sched),
|
||||||
REG_TM1CNT_L => self.read_timer_data(1),
|
REG_TM1CNT_L => self.read_timer_data(1, sched),
|
||||||
REG_TM2CNT_L => self.read_timer_data(2),
|
REG_TM2CNT_L => self.read_timer_data(2, sched),
|
||||||
REG_TM3CNT_L => self.read_timer_data(3),
|
REG_TM3CNT_L => self.read_timer_data(3, sched),
|
||||||
_ => unreachable!(),
|
_ => 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 {
|
match io_addr {
|
||||||
REG_TM0CNT_L => {
|
REG_TM0CNT_L => {
|
||||||
self.timers[0].data = value;
|
self.timers[0].data = value;
|
||||||
self.timers[0].initial_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 => {
|
REG_TM1CNT_L => {
|
||||||
self.timers[1].data = value;
|
self.timers[1].data = value;
|
||||||
self.timers[1].initial_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 => {
|
REG_TM2CNT_L => {
|
||||||
self.timers[2].data = value;
|
self.timers[2].data = value;
|
||||||
self.timers[2].initial_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 => {
|
REG_TM3CNT_L => {
|
||||||
self.timers[3].data = value;
|
self.timers[3].data = value;
|
||||||
self.timers[3].initial_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!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue