diff options
author | gennyble <gen@nyble.dev> | 2025-03-05 05:48:04 -0600 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2025-03-05 05:48:04 -0600 |
commit | 557766c22a17d42ac58bd39cda9889f1f6b7f5bc (patch) | |
tree | 78ba1a6a78f9c5b74f05c0c96cd4fb59dc260665 /src | |
download | nokia3310emu-557766c22a17d42ac58bd39cda9889f1f6b7f5bc.tar.gz nokia3310emu-557766c22a17d42ac58bd39cda9889f1f6b7f5bc.zip |
inititty comitty
Diffstat (limited to 'src')
-rwxr-xr-x | src/currentcontroller.rs | 207 | ||||
-rwxr-xr-x | src/main.rs | 199 | ||||
-rwxr-xr-x | src/mem.rs | 244 |
3 files changed, 650 insertions, 0 deletions
diff --git a/src/currentcontroller.rs b/src/currentcontroller.rs new file mode 100755 index 0000000..7a39770 --- /dev/null +++ b/src/currentcontroller.rs @@ -0,0 +1,207 @@ +use std::time::SystemTime; + +use time::OffsetDateTime; + +use crate::mem::{GensioState, Mem}; + +pub struct CurrentController { + pub reg: Option<u8>, + pub control_reg: u8, + //TODO: make enum + pub charging_mode: u8, + pub tenbit_adc: u16, + pub head_hook_detection: u8, + //TODO: make enum + pub watchdog_setting: u8, + pub rtc_enable: u8, + //TODO: track time in struct + pub alarm_minute: u8, + pub alarm_hour: u8, + pub rtc_calibration: u8, + /// Bit 0 - RTC bat present + /// Bit 3/3 - CContINT 3-7 + pub irq: u8, + /// True(1)/False(0) + pub irq_mask: u8, +} + +impl CurrentController { + pub fn new() -> Self { + Self { + reg: None, + control_reg: 0, + charging_mode: 0, + tenbit_adc: 0, + head_hook_detection: 0, + watchdog_setting: 0, + rtc_enable: 0, + alarm_minute: 0, + alarm_hour: 12, + rtc_calibration: 0, + irq: 0, + irq_mask: 0, + } + } + + pub fn event(&mut self, mem: &mut Mem) { + log::trace!("CCont value={:02X}", mem.inner[Mem::CCONT_WRITE]); + log::trace!( + "CCont write >> 3 value={:02X}", + mem.inner[Mem::CCONT_WRITE] >> 3 + ); + log::trace!( + "CCont read ^ 4 >> 3 value={:02X}", + (mem.inner[Mem::CCONT_WRITE] ^ 0x04) >> 3 + ); + + let and_four = mem.inner[Mem::CCONT_WRITE] & 0x04; + + match self.reg { + None => { + if and_four > 0 { + self.read(mem.inner[Mem::CCONT_WRITE], mem); + } else { + self.reg = Some(mem.inner[Mem::CCONT_WRITE]) + } + } + Some(reg) => { + self.write(reg, mem); + self.reg = None; + } + } + } + + pub fn read(&mut self, addr: u8, mem: &mut Mem) { + log::trace!("ccont::read()"); + let reg = (addr ^ 0x04) >> 3; + + match reg { + 0x02 => { + log::trace!("CCont A/D 0-7 Read"); + let adc = self.tenbit_adc.to_be_bytes(); + mem.inner[Mem::CCONT_READ] = adc[0]; + } + 0x03 => { + log::trace!("CCont Headset/Hooked flag?"); + mem.inner[Mem::CCONT_READ] = self.head_hook_detection; + } + 0x04 => { + log::trace!("CCont A/D 8-9 Read & test bits"); + let adc = self.tenbit_adc.to_be_bytes(); + mem.inner[Mem::CCONT_READ] = ((adc[1] >> 6) & 3) | 0b101100_00; + } + 0x09 => { + log::trace!("CCont RTC Second"); + let time = OffsetDateTime::now_local().unwrap(); + mem.inner[Mem::CCONT_READ] = time.second(); + } + 0x0A => { + log::trace!("CCont RTC Minute"); + let time = OffsetDateTime::now_local().unwrap(); + mem.inner[Mem::CCONT_READ] = time.minute(); + } + 0x0B => { + log::trace!("CCont RTC Hour"); + let time = OffsetDateTime::now_local().unwrap(); + mem.inner[Mem::CCONT_READ] = time.hour(); + } + 0x0C => { + log::trace!("CCont RTC Day"); + let time = OffsetDateTime::now_local().unwrap(); + mem.inner[Mem::CCONT_READ] = time.day(); + } + 0x0D => { + log::trace!("CCont RTC Alarm Minute"); + mem.inner[Mem::CCONT_READ] = self.alarm_minute; + } + 0x0E => { + log::trace!("CCont RTC Alarm Hour"); + mem.inner[Mem::CCONT_READ] = self.alarm_hour; + } + 0x0F => { + log::trace!("CCont RTC Calibration"); + mem.inner[Mem::CCONT_READ] = self.rtc_calibration; + } + 0x10 => { + log::trace!("CCont RTC Interrupt Controller"); + mem.inner[Mem::CCONT_READ] = self.irq; + } + 0x11 => { + log::trace!("CCont RTC IRQ Mask"); + mem.inner[Mem::CCONT_READ] = self.irq_mask; + } + reg => { + log::trace!("CCont !!! {reg:02X} unknown read!"); + panic!() + } + } + + //mem.gensio_status_clear(); + mem.set_gensio_status(GensioState::DataRead); + //mem.set_gensio_status(GensioState::TransactionReady); + } + + pub fn write(&mut self, addr: u8, mem: &mut Mem) { + log::trace!("ccont::write()"); + let reg = addr >> 3; + let val = mem.inner[Mem::CCONT_WRITE]; + + match reg { + 0x00 => { + log::trace!("CCont control register"); + self.control_reg = val; + } + 0x01 => { + log::trace!("CCont PWM (charger)"); + self.charging_mode = val; + } + 0x03 => { + log::trace!("CCont A/D Read 0-7 (why call write?)"); + log::info!("Not writing to AD due to confusion on how that works"); + } + 0x05 => { + log::trace!("CCont Headset/Hooked flag?"); + self.head_hook_detection = val; + } + 0x06 => { + log::trace!("CCont Set Watchdog"); + self.watchdog_setting = val; + } + 0x07 => { + log::trace!("CCont RTC Enable"); + self.rtc_enable = val; + } + 0x08 => { + log::trace!("CCont RTC Second"); + log::info!("Ignoring RTC Second write due to current RTC implementation") + } + 0x0C => { + log::trace!("CCont RTC Day"); + log::info!("Ignoring RTC Day write due to current RTC implementation") + } + 0x0D => { + log::trace!("CCont RTC Alarm Minute"); + self.alarm_minute = val; + } + 0x0E => { + log::trace!("CCont RTC Alarm Hour"); + self.alarm_hour = val; + } + 0x0F => { + log::trace!("CCont RTC Callibration"); + self.rtc_calibration = val; + } + 0x10 => { + log::trace!("CCont RTC Interrupt Controller"); + self.irq = val; + } + 0x11 => { + log::trace!("CCont RTC IRQ Mask"); + self.irq_mask = val; + } + reg => { + log::trace!("CCont !!! {reg:02X} unknown register") + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100755 index 0000000..1279ceb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,199 @@ +#![allow(clippy::new_without_default)] +#![allow(clippy::unusual_byte_groupings)] +use std::{ + collections::HashMap, + sync::mpsc::TryRecvError, + time::{Duration, Instant}, +}; + +use armv4t_emu::{reg, Cpu, Exception, Memory, Mode}; + +use crate::mem::{Ctsi, Event, GenSIO, GensioState, Mem, Uif}; + +mod currentcontroller; +mod mem; + +use currentcontroller::CurrentController; + +fn main() { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Info) + .with_module_level("nokia3310emu::currentcontroller", log::LevelFilter::Trace) + //.with_module_level("pc_all", log::LevelFilter::Trace) + .with_module_level("eec46_jmp", log::LevelFilter::Trace) + .with_module_level("armv4t_emu", log::LevelFilter::Info) + .env() + .with_colors(true) + .init() + .unwrap(); + + // Channel so the memory controller can tell us when memory mapped + // IO is activated + let (mut tx, mut rx) = std::sync::mpsc::sync_channel::<Event>(10); + + // Setup Memory + let mut memory_array = vec![0; 0x800000]; + let file = std::fs::read("My 3310 NR1 v5.79.fls").unwrap(); + //let file = std::fs::read("FuBu3310 v6.39 (PPM B).fls").unwrap(); + + memory_array[0x00200000..0x003FFFFF + 1].copy_from_slice(&file); + + let mut mem = Mem { + inner: &mut memory_array, + tx, + }; + + // Current Controller (CCont) + let mut ccont = CurrentController::new(); + + // Setup CPU + let mut cpu = Cpu::new(); + // We don't have a bootloader. Hope everything is okay and jump straight + // to where the bootloader does + cpu.reg_set(Mode::User, reg::PC, 0x200040); + + let mut hit_eec46 = false; + // Main loop + let mut last_pc = 0x0; + let mut print_all_pc = false; + let mut jump_counter = JumpCounter::default(); + + let known_jumps = vec![ + 0x002ef9e6, // Delay_r0_loop + ]; + + let start = Instant::now(); + let run_duration = Duration::from_millis(5000); + + 'cpustep: while cpu.step(&mut mem) { + // Log jumps to make debugging easier. We can look at these + // addresses in Ghidra + let pc = cpu.reg_get(Mode::User, reg::PC); + if print_all_pc { + log::trace!(target: "pc_all", "[{}] {pc:08x}", pc as isize - last_pc as isize); + } + + if (pc as isize - last_pc as isize).abs() > 4 { + log::trace!("jump detect - {last_pc:08X} => {pc:08X}",); + let count = jump_counter.jump(last_pc, pc); + + if count >= 100 && count % 100 == 0 && !known_jumps.contains(&pc) { + log::trace!(target: "eec46_jmp", "[{count:<6}] {last_pc:08x} => {pc:08x}"); + } + } + + if pc == 0x002eebec && last_pc != 0x002eebec { + log::info!("Entered eebec loop!"); + } + + if pc == 0x002eec44 { + //let r5 = cpu.reg_get(Mode::User, 5); + // We set R5 here because the next two instructions are as follows: + // [MainLoop?] eec46: cmp r5, 0x1 + // eec48: bne [MainLoop?] + // So if we do not set r5 to 0x1, we'll loop back to eec46 forever + log::warn!("hit eec44... setting register 5!"); + cpu.reg_set(Mode::User, 5, 0x1); + } + + if pc == 0x002eec46 && !hit_eec46 { + hit_eec46 = true; + print_all_pc = true; + log::info!("hit eec46!"); + } else if pc != 0x002eec48 && pc != 0x002eec46 && hit_eec46 { + hit_eec46 = false; + print_all_pc = true; + log::warn!("broke from eec46! (pc = {pc:x})"); + } + last_pc = pc; + + if pc == 0x002eec52 { + log::info!("hit eec52!"); + } + + match rx.try_recv() { + Err(TryRecvError::Empty) => (), + Err(TryRecvError::Disconnected) => panic!("event sender disconnected"), + Ok(event) => match event { + Event::GenSIO(GenSIO::StartTransaction) => { + let start_byte = mem.inner[Mem::GENSIO_START_TRANSACTION]; + if start_byte == 0x25 { + mem.gensio_status_clear(); + mem.set_gensio_status(GensioState::DataWrite); + mem.set_gensio_status(GensioState::TransactionReady); + } + } + + Event::GenSIO(GenSIO::CContWrite) => ccont.event(&mut mem), + + Event::GenSIO(GenSIO::LcdCtrl) => { + let dog = mem.inner[Mem::GENSIO_LCD_CTRL]; + log::info!("GENSIO LCD Mystery {dog:02X} -- {dog:08b}"); + //mem.inner[Mem::UIF_CTRL_IO_2] = 0b0010_0000; + } + + Event::Ctsi(Ctsi::AsicWatchdog) => { + let dog = mem.inner[Mem::CTSI_ASIC_WATCHDOG]; + log::info!("CTSI Clock ={dog}"); + } + + Event::Ctsi(Ctsi::WriteClockControl) => { + let clock = mem.inner[Mem::CTSI_CLOCK_CONTROL]; + log::info!("CTSI Clock {clock:08b}"); + } + + Event::Uif(Uif::CtrlIo2) => { + let ctrl = mem.inner[Mem::UIF_CTRL_IO_2]; + log::info!("CTSI Clock {ctrl:08b}"); + } + }, + } + + if start.elapsed() >= run_duration { + log::error!("hit run_duration max runtime. breaking from cpustep loop"); + break 'cpustep; + } + + // Run slow + std::thread::sleep(Duration::from_micros(10)); + } + + println!("cpu halted"); +} + +#[derive(Debug, Default)] +pub struct JumpCounter { + jmps: Vec<Jump>, +} + +impl JumpCounter { + pub fn jump(&mut self, from: u32, to: u32) -> usize { + match self.jmps.iter_mut().find(|j| j.from == from && j.to == to) { + None => { + self.jmps.push(Jump::new(from, to)); + 1 + } + Some(jmp) => { + jmp.count += 1; + jmp.count + } + } + } +} + +#[derive(Debug)] +pub struct Jump { + from: u32, + to: u32, + count: usize, +} + +impl Jump { + pub fn new(from: u32, to: u32) -> Self { + Self { from, to, count: 1 } + } +} + +pub struct Lcd { + pub data: [u8; 44 * 84], +} diff --git a/src/mem.rs b/src/mem.rs new file mode 100755 index 0000000..34d16c6 --- /dev/null +++ b/src/mem.rs @@ -0,0 +1,244 @@ +use std::sync::mpsc::SyncSender; + +use armv4t_emu::Memory; + +pub struct Mem<'m> { + pub inner: &'m mut [u8], + pub tx: SyncSender<Event>, +} + +impl<'m> Memory for Mem<'m> { + fn r8(&mut self, addr: u32) -> u8 { + self.special_addr(addr, false); + + self.inner[addr as usize] + } + + fn r16(&mut self, addr: u32) -> u16 { + self.special_addr(addr, false); + + u16::from_be_bytes([self.inner[addr as usize], self.inner[addr as usize + 1]]) + } + + fn r32(&mut self, addr: u32) -> u32 { + self.special_addr(addr, false); + + u32::from_be_bytes([ + self.inner[addr as usize], + self.inner[addr as usize + 1], + self.inner[addr as usize + 2], + self.inner[addr as usize + 3], + ]) + } + + fn w8(&mut self, addr: u32, val: u8) { + self.special_addr(addr, true); + + self.inner[addr as usize] = val; + } + + fn w16(&mut self, addr: u32, val: u16) { + self.special_addr(addr, true); + + let bytes = val.to_be_bytes(); + self.inner[addr as usize] = bytes[0]; + self.inner[addr as usize + 1] = bytes[1]; + } + + fn w32(&mut self, addr: u32, val: u32) { + self.special_addr(addr, true); + + let bytes = val.to_be_bytes(); + self.inner[addr as usize] = bytes[0]; + self.inner[addr as usize + 1] = bytes[1]; + self.inner[addr as usize + 2] = bytes[2]; + self.inner[addr as usize + 3] = bytes[3]; + } +} + +impl<'m> Mem<'m> { + const IO: u32 = 0x00020000; + const IO_END: u32 = 0x000200FF; + + pub const CTSI_ASIC_WATCHDOG: usize = Self::IO as usize | 0x03; + pub const CTSI_IRQ_LINES: usize = Self::IO as usize | 0x09; + pub const CTSI_CLOCK_CONTROL: usize = Self::IO as usize | 0x0D; + + pub const CCONT_WRITE: usize = Self::IO as usize | 0x2C; + pub const GENSIO_START_TRANSACTION: usize = Self::IO as usize | 0x2D; + pub const UIF_CTRL_IO_2: usize = Self::IO as usize | 0x32; + pub const CCONT_READ: usize = Self::IO as usize | 0x6C; + pub const GENSIO_STATUS: usize = Self::IO as usize | 0x6D; + pub const GENSIO_LCD_CTRL: usize = Self::IO as usize | 0x6E; + + pub fn set_gensio_status(&mut self, state: GensioState) { + let byte = &mut self.inner[Self::GENSIO_STATUS]; + + match state { + GensioState::DataWrite => *byte |= 1, + GensioState::TransactionReady => *byte |= 2, + GensioState::DataRead => *byte |= 5, + } + } + + pub fn gensio_status_clear(&mut self) { + self.inner[Self::GENSIO_STATUS] = 0x00; + } + + fn special_addr(&mut self, addr: u32, write: bool) { + if (Self::IO..=Self::IO_END).contains(&addr) { + self.special_io(addr, write) + } + } + + fn special_io(&mut self, addr: u32, write: bool) { + let io_addr = addr ^ Self::IO; + + let meaning = match io_addr { + // 00-3F + 0x00 => "ASIC Version", + 0x01 => "MCU reset control register", + 0x02 => "DSP reset control register", + + 0x03 => { + self.tx.send(Event::Ctsi(Ctsi::AsicWatchdog)).unwrap(); + + "CTSI ASIC Watchdog Write Register" + } + 0x0A => "CTSI FIQ lines mask", + 0x0B => "CTSI IRQ lines mask ", + 0x0D => { + self.tx.send(Event::Ctsi(Ctsi::WriteClockControl)).unwrap(); + + "CTSI Clock Control Register" + } + 0x20 => "McuGenIO Signal Lines", + 0x28 => "Keyboard ROW signal lines", + 0x2A => "Keyboard COL signal lines", + 0x2C => { + self.tx.send(Event::GenSIO(GenSIO::CContWrite)).unwrap(); + + "CCont write" + } + 0x2D => { + self.tx + .send(Event::GenSIO(GenSIO::StartTransaction)) + .unwrap(); + + "GENSIO start transaction" + } + 0x2E => "GENSIO LCD data write", + 0x32 => { + self.tx.send(Event::Uif(Uif::CtrlIo2)).unwrap(); + + "UIF CTRL IO 2" + } + addr if addr >= 0x36 && addr <= 0x3F => "SIM something [0x36 to 0x3F]", + + // 40-7F + 0x6B => "Keyboard COL interrupt mask", + 0x6C => "CCont read", + 0x6D => { + self.gensio_status(); + "GENSIO status" + } + 0x6E => { + self.tx.send(Event::GenSIO(GenSIO::LcdCtrl)).unwrap(); + + "GENSIO LCD Ctrl" + } + 0x6F => "GENSIO - 3/SELECT1", + 0x70 => "CTRL I/O Direction 0,1", + 0x71 => "CTRL I/O Direction 1,1", + 0x72 => "CTRL I/O Direction 2,1", + 0x73 => "CTRL I/O Direction 3,1", + + //80-BF + 0xA8 => "Keyboard ROW I/O direction", + 0xAD => "GENSIO - 1/SELECT2", + 0xAE => "GENSIO - 2/SELECT2", + 0xAF => "GENSIO - 3/SELECT2", + 0xB0 => "CTRL I/O Direction 0,2", + 0xB1 => "CTRL I/O Direction 1,2", + 0xB2 => "CTRL I/O Direction 2,2", + 0xB3 => "CTRL I/O Direction 3,2", + + //C0-FF + 0xED => "GENSIO - 1/SELECT3", + 0xEE => "GENSIO - 2/SELECT3", + 0xEF => "GENSIO - 3/SELECT3", + 0xF0 => "CTRL I/O Input 0", + 0xF1 => "CTRL I/O Input 1", + 0xF2 => "CTRL I/O Input 2", + 0xF3 => "CTRL I/O Input 3", + + _ => "unknown IO", + }; + + let read_write = if write { "w" } else { "r" }; + + log::info!("IO {read_write} [{io_addr:02X}] - {meaning}"); + } + + fn gensio_status(&mut self) { + let base = (Self::IO | 0x2D) as usize; + let ctrl_io_dir_1 = self.inner[base]; + log::info!("CTRL Start Transaction -- {:08b}", ctrl_io_dir_1); + + log::info!("GENSIO Status -- {:08b}", self.inner[Self::GENSIO_STATUS]); + + self.print_ctrl_io_direction_1(); + self.print_ctrl_io_direction_2(); + } + + fn print_ctrl_io_direction_1(&self) { + let base = (Self::IO | 0x70) as usize; + let ctrl_io_dir_1 = &self.inner[base..base + 4]; + log::info!( + "CTRL I/O Direction (1) -- {:08b} {:08b} {:08b} {:08b}", + ctrl_io_dir_1[0], + ctrl_io_dir_1[1], + ctrl_io_dir_1[2], + ctrl_io_dir_1[3] + ); + } + + fn print_ctrl_io_direction_2(&self) { + let base = (Self::IO | 0xB0) as usize; + let ctrl_io_dir_1 = &self.inner[base..base + 4]; + log::info!( + "CTRL I/O Direction (2) -- {:08b} {:08b} {:08b} {:08b}", + ctrl_io_dir_1[0], + ctrl_io_dir_1[1], + ctrl_io_dir_1[2], + ctrl_io_dir_1[3] + ); + } +} + +pub enum GensioState { + DataWrite, + TransactionReady, + DataRead, +} + +pub enum Event { + GenSIO(GenSIO), + Ctsi(Ctsi), + Uif(Uif), +} + +pub enum GenSIO { + StartTransaction, + CContWrite, + LcdCtrl, +} + +pub enum Ctsi { + AsicWatchdog, + WriteClockControl, +} + +pub enum Uif { + CtrlIo2, +} |