about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2025-03-05 05:48:04 -0600
committergennyble <gen@nyble.dev>2025-03-05 05:48:04 -0600
commit557766c22a17d42ac58bd39cda9889f1f6b7f5bc (patch)
tree78ba1a6a78f9c5b74f05c0c96cd4fb59dc260665 /src
downloadnokia3310emu-557766c22a17d42ac58bd39cda9889f1f6b7f5bc.tar.gz
nokia3310emu-557766c22a17d42ac58bd39cda9889f1f6b7f5bc.zip
inititty comitty
Diffstat (limited to 'src')
-rwxr-xr-xsrc/currentcontroller.rs207
-rwxr-xr-xsrc/main.rs199
-rwxr-xr-xsrc/mem.rs244
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,
+}