mod console; mod ioctl; mod lcd; mod led; mod pins; use std::ffi::c_int; use std::time::{Duration, Instant}; use std::{alloc, mem, thread}; use std::{ffi::CString, path::Path, ptr::NonNull}; pub use self::ioctl::IoCtl; pub use self::pins::{DigitalPin, Pins}; const MCU: &str = "atmega328p"; const FREQUENCY: u32 = 16_000_000; const TIME_STEP: Duration = Duration::from_millis(100); pub struct Simulator { avr: NonNull, } type Cycle = u64; type CyclesTaken = u64; impl Simulator { pub fn atmega328p(path: impl AsRef) -> Self { let c_mcu = CString::new(MCU).unwrap(); let avr = unsafe { simavr_ffi::avr_make_mcu_by_name(c_mcu.as_ptr()) }; let mut avr = NonNull::new(avr).expect("avr_make_mcu_by_name() failed"); let status = unsafe { simavr_ffi::avr_init(avr.as_ptr()) }; assert!(status == 0, "avr_init() failed (status = {})", status); unsafe { avr.as_mut() }.frequency = FREQUENCY; // SAFETY: We know that `elf_firmware_t`'s layout has a non-zero size. let ptr = unsafe { // We use `alloc_zeroed`, because that's what simavr's docs suggest to do. alloc::alloc_zeroed(alloc::Layout::new::()) as *mut simavr_ffi::elf_firmware_t }; // This can fail only if the underlying allocator failed to find enough memory to // allocate the chunk. In that case, panicking is the best we can afford anyway. let ptr = NonNull::new(ptr).unwrap(); let path = path.as_ref().display().to_string().into_bytes(); // SAFETY: Paths cannot contain null-terminators, so a string we've got from // `.display().to_string()` cannot either. let c_path = unsafe { CString::from_vec_unchecked(path) }; let status = unsafe { simavr_ffi::elf_read_firmware(c_path.as_ptr(), ptr.as_ptr()) }; if status != 0 { panic!( "Couldn't load firmware from `{}` (status = {})", c_path.into_string().unwrap(), status ); } unsafe { simavr_ffi::avr_load_firmware(avr.as_ptr(), ptr.as_ptr()); simavr_ffi::avr_reset(avr.as_mut()); } Self { avr } } pub fn start(mut self) { thread::Builder::new() .name("simulator".into()) .spawn(move || loop { let time_taken = Instant::now(); self.run_for(TIME_STEP); let time_taken = time_taken.elapsed(); if time_taken < TIME_STEP { thread::sleep(TIME_STEP - time_taken); } }) .unwrap(); } /// Shorthand for: [`simavr_ffi::avr_ioctl()`]. /// /// # Safety /// /// Callers must ensure that given `ioctl` and `T` match (that is: different ioctls /// require parameters of different kinds, from u32 to structs). pub unsafe fn ioctl(&mut self, ioctl: IoCtl, param: &mut T) -> c_int { simavr_ffi::avr_ioctl( self.avr.as_ptr(), ioctl.into_ffi(), param as *mut _ as *mut _, ) } /// Shorthand for: [`simavr_ffi::avr_io_getirq()`]. pub(crate) fn io_getirq(&self, ioctl: IoCtl, irq: u32) -> NonNull { let ptr = unsafe { simavr_ffi::avr_io_getirq(self.avr.as_ptr(), ioctl.into_ffi(), irq as _) }; NonNull::new(ptr).unwrap() } /// Shorthand for: [`simavr_ffi::avr_alloc_irq()`]. #[allow(dead_code)] pub(crate) fn alloc_irq(&mut self, name: &'static str) -> NonNull { let pool = &mut unsafe { self.avr.as_mut() }.irq_pool as *mut simavr_ffi::avr_irq_pool_t; let name = CString::new(name).unwrap(); let mut name = name.as_ptr(); let names = &mut name as *mut *const i8; let irq = unsafe { simavr_ffi::avr_alloc_irq(pool, 0, 1, names) }; NonNull::new(irq).unwrap() } /// Shorthand for: [`simavr_ffi::avr_irq_register_notify()`]. /// /// # Safety /// /// Callers must ensure that given callback is meant for `irq`. pub(crate) unsafe fn irq_register_notify( irq: NonNull, notify: Option, u32, *mut T)>, param: *mut T, ) { // SAFETY: We're transmuting two parameters: // - `NonNull` -> `*mut ffi::avr_irq_t`, // - `*mut T` -> `*mut c_void` // ...where both conversions are legal. let notify = mem::transmute(notify); // SAFETY: We're transmuting `*mut T` -> `*mut c_void`, which is legal. let param = mem::transmute(param); simavr_ffi::avr_irq_register_notify(irq.as_ptr(), notify, param); } fn cycle(&self) -> Cycle { unsafe { self.avr.as_ref() }.cycle } pub fn step(&mut self) -> CyclesTaken { let cycle = self.cycle(); let state = unsafe { simavr_ffi::avr_run(self.avr.as_ptr()) }; let cycles_taken = self.cycle() - cycle; let state = CpuState::from_ffi(state); match state { CpuState::Running | CpuState::Sleeping => {} CpuState::Crashed => { panic!( "AVR crashed (e.g. the program stepped on an invalid \ instruction)" ); } state => { panic!("Unexpected CPU state: {:?}", state); } } cycles_taken } pub fn run_for(&mut self, duration: Duration) { let mut cycles = (duration.as_secs_f64() * FREQUENCY as f64) as u64; while cycles > 0 { cycles = cycles.saturating_sub(self.step()); } } } // SAFETY: `Simualator` cannot be cloned. unsafe impl Send for Simulator {} #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum CpuState { Limbo, Stopped, Running, Sleeping, Step, StepDone, Done, Crashed, } impl CpuState { pub fn from_ffi(val: i32) -> Self { match val as u32 { simavr_ffi::cpu_Limbo => Self::Limbo, simavr_ffi::cpu_Stopped => Self::Stopped, simavr_ffi::cpu_Running => Self::Running, simavr_ffi::cpu_Sleeping => Self::Sleeping, simavr_ffi::cpu_Step => Self::Step, simavr_ffi::cpu_StepDone => Self::StepDone, simavr_ffi::cpu_Done => Self::Done, simavr_ffi::cpu_Crashed => Self::Crashed, val => { panic!("Unknown CpuState: {}", val); } } } }