#![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::(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})"); } if last_pc == 0x002e8c0e { //log::error!("right after e8c0e // pc = {pc:08x}"); //break 'cpustep; } last_pc = pc; if pc == 0x002eec52 { log::info!("hit eec52!"); } if pc == 0x002e8c0e { log::warn!("hit e8c0e! setting r0 = 0x81"); cpu.reg_set(Mode::User, 0, 0x81); } 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, } 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], }