From 8d7ff36e5a38dc84ec036be0230d6c2d16703a87 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 30 Aug 2024 15:38:36 +0200 Subject: [PATCH] Implement real-time clock --- Cargo.lock | 3 + core/Cargo.toml | 1 + core/src/mac/mod.rs | 1 + core/src/mac/rtc.rs | 164 ++++++++++++++++++++++++++++++++++++++++++++ core/src/mac/via.rs | 13 +++- 5 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 core/src/mac/rtc.rs diff --git a/Cargo.lock b/Cargo.lock index be0d7f3..22d60cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,7 +215,9 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", "windows-targets 0.52.6", ] @@ -927,6 +929,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec", + "chrono", "crossbeam", "crossbeam-channel", "either", diff --git a/core/Cargo.toml b/core/Cargo.toml index 5d0207a..fa43e95 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] anyhow = { version = "1.0.86", features = ["backtrace"] } arrayvec = { version = "0.7.4", features = ["serde"] } +chrono = "0.4.38" crossbeam = "0.8.4" crossbeam-channel = "0.5.13" either = "1.13.0" diff --git a/core/src/mac/mod.rs b/core/src/mac/mod.rs index 8c34ce1..dc03b8d 100644 --- a/core/src/mac/mod.rs +++ b/core/src/mac/mod.rs @@ -1,6 +1,7 @@ pub mod bus; pub mod iwm; pub mod keyboard; +pub mod rtc; pub mod scc; pub mod via; pub mod video; diff --git a/core/src/mac/rtc.rs b/core/src/mac/rtc.rs new file mode 100644 index 0000000..368cb26 --- /dev/null +++ b/core/src/mac/rtc.rs @@ -0,0 +1,164 @@ +use chrono::{Local, NaiveDate}; +use log::*; + +/// Macintosh Real-Time Clock +pub struct Rtc { + io_enable: bool, + io_clk: bool, + + write_cmd: Option, + byte_in: u8, + byte_in_bit: usize, + data_out: u8, + sending: bool, + + data: RtcData, +} + +#[derive(Default)] +pub struct RtcData { + writeprotect: bool, + seconds: u32, + pram: [u8; 0x14], +} + +impl Default for Rtc { + fn default() -> Self { + // Initialize clock from host system + let seconds = Local::now() + .naive_local() + .signed_duration_since( + NaiveDate::from_ymd_opt(1904, 1, 1) + .unwrap() + .and_hms_opt(0, 0, 0) + .unwrap(), + ) + .num_seconds() as u32; + + Self { + io_enable: false, + io_clk: false, + write_cmd: None, + byte_in: 0, + byte_in_bit: 0, + data_out: 0, + sending: false, + data: RtcData { + writeprotect: true, + seconds, + pram: [0; 0x14], + }, + } + } +} + +impl Rtc { + /// Pokes the RTC that one second has passed + /// In the emulator, one second interrupt is driven by the VIA for ease. + pub fn second(&mut self) { + self.data.seconds = self.data.seconds.wrapping_add(1); + } + + /// Updates RTC I/O lines from the VIA. + pub fn io(&mut self, enable: bool, clk: bool, data: bool) -> bool { + let mut res = true; + + if enable { + // Disabled + self.io_enable = true; + return true; + } + if !enable && self.io_enable { + // Reset + self.io_enable = false; + self.data_out = 0; + self.write_cmd = None; + self.byte_in = 0; + self.byte_in_bit = 0; + self.sending = false; + } + + if !self.sending && clk && !self.io_clk { + // Receiving command + self.byte_in <<= 1; + if data { + self.byte_in |= 1; + } + self.byte_in_bit += 1; + if self.byte_in_bit >= 8 { + if let Some(cmd) = self.write_cmd { + // Second byte of write + self.cmd_write(cmd, self.byte_in); + self.write_cmd = None; + } else if self.byte_in & 0x80 == 0 { + // Write - read another byte + self.write_cmd = Some(self.byte_in); + } else { + self.data_out = self.cmd_read(self.byte_in); + self.sending = true; + } + self.byte_in = 0; + self.byte_in_bit = 0; + } + } else if self.sending && clk && !self.io_clk { + // Sending response + res = self.data_out & 0x80 != 0; + self.data_out = self.data_out.wrapping_shl(1); + } + + self.io_clk = clk; + res + } + + /// Process a command from the CPU that writes to the RTC + fn cmd_write(&mut self, cmd: u8, val: u8) { + let scmd = (cmd >> 2) & 0b11111; + match scmd { + 0x00 | 0x04 => { + self.data.seconds = (self.data.seconds & 0xFFFFFF00) | (val as u32); + } + 0x01 | 0x05 => { + self.data.seconds = (self.data.seconds & 0xFFFF00FF) | ((val as u32) << 8); + } + 0x02 | 0x06 => { + self.data.seconds = (self.data.seconds & 0xFF00FFFF) | ((val as u32) << 16); + } + 0x03 | 0x07 => { + self.data.seconds = (self.data.seconds & 0x00FFFFFF) | ((val as u32) << 24); + } + 0x0C => { + // Test register + } + 0x0D => self.data.writeprotect = val & 0x80 != 0, + 0x08..=0x0B => { + self.data.pram[usize::from(scmd - 0x08)] = val; + } + 0x10..=0x1F => { + self.data.pram[usize::from(scmd - 0x10 + 4)] = val; + } + _ => { + warn!( + "Unknown RTC write command: {:02X} {:08b}, data: {:02X}", + cmd, cmd, val + ); + } + } + } + + /// Process a command from the CPU that reads from the RTC + fn cmd_read(&self, cmd: u8) -> u8 { + let scmd = (cmd >> 2) & 0b11111; + match scmd { + 0x00 | 0x04 => self.data.seconds as u8, + 0x01 | 0x05 => (self.data.seconds >> 8) as u8, + 0x02 | 0x06 => (self.data.seconds >> 16) as u8, + 0x03 | 0x07 => (self.data.seconds >> 24) as u8, + 0x08..=0x0B => self.data.pram[usize::from(scmd - 0x08)], + 0x10..=0x1F => self.data.pram[usize::from(scmd - 0x10 + 4)], + _ => { + warn!("Unknown RTC read command: {:02X} {:08b}", cmd, cmd); + 0 + } + } + } +} diff --git a/core/src/mac/via.rs b/core/src/mac/via.rs index 4c70a6e..ef75ef8 100644 --- a/core/src/mac/via.rs +++ b/core/src/mac/via.rs @@ -1,5 +1,6 @@ use crate::bus::{Address, BusMember}; use crate::mac::keyboard::Keyboard; +use crate::mac::rtc::Rtc; use crate::tickable::{Tickable, Ticks}; use crate::types::{Byte, Field16}; @@ -198,6 +199,7 @@ pub struct Via { t2_enable: bool, pub keyboard: Keyboard, + rtc: Rtc, } impl Via { @@ -224,6 +226,7 @@ impl Via { kbdshift_out_time: 0, keyboard: Keyboard::default(), + rtc: Rtc::default(), } } } @@ -244,9 +247,6 @@ impl BusMember
for Via { self.ifr.set_kbddata(false); self.ifr.set_kbdclock(false); - // TODO remove RTC stub - self.b_in.set_rtcdata(false); - Some((self.b_in.0 & !self.ddrb.0) | (self.b_out.0 & self.ddrb.0)) } @@ -331,6 +331,12 @@ impl BusMember
for Via { self.ifr.set_kbdclock(false); self.b_out.0 = val; + let rtcin = self.rtc.io( + self.b_out.rtcenb(), + self.b_out.rtcclk(), + self.b_out.rtcdata(), + ); + self.b_in.set_rtcdata(rtcin); Some(()) } @@ -388,6 +394,7 @@ impl Tickable for Via { if self.onesec >= ONESEC_TICKS { self.onesec -= ONESEC_TICKS; self.ifr.set_onesec(true); + self.rtc.second(); } // Timer 2