From adbd2a47fd8feddf3effc740ee59549131126619 Mon Sep 17 00:00:00 2001 From: Teufelchen1 Date: Tue, 25 Nov 2025 14:08:56 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F:=20Abstract=20implementat?= =?UTF-8?q?ion=20of=20address=20bus=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/headless.rs | 78 ++++---- src/app/tui.rs | 40 ++-- src/app/ui.rs | 13 +- src/cli.rs | 59 +++--- src/cpu/executer.rs | 18 +- src/cpu/memory.rs | 136 +++----------- src/cpu/mod.rs | 49 ++--- src/hifive1b/mod.rs | 222 +++++++++++++++++++++++ src/{periph => hifive1b}/uart.rs | 27 ++- src/main.rs | 2 + src/periph/backend.rs | 173 ------------------ src/periph/mod.rs | 38 +--- src/utils/map_to_unixsocket.rs | 60 ++++++ src/utils/mod.rs | 11 ++ src/utils/peekable_channel.rs | 43 +++++ src/{periph => utils}/peekable_reader.rs | 4 +- 16 files changed, 506 insertions(+), 467 deletions(-) create mode 100644 src/hifive1b/mod.rs rename src/{periph => hifive1b}/uart.rs (91%) delete mode 100644 src/periph/backend.rs create mode 100644 src/utils/map_to_unixsocket.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/peekable_channel.rs rename src/{periph => utils}/peekable_reader.rs (95%) diff --git a/src/app/headless.rs b/src/app/headless.rs index 9287502..72e125e 100644 --- a/src/app/headless.rs +++ b/src/app/headless.rs @@ -1,70 +1,69 @@ +use crate::hifive1b::Hifive1b; +use crate::utils::map_to_unixsocket; use std::io::{self, Read}; -use std::path::PathBuf; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread::spawn; use std::thread::JoinHandle; use crate::cli; -use crate::cpu::{create_cpu_thread, CPU}; +use crate::cpu::{create_cpu_thread, AddrBus, CPU}; use crate::events::{CpuJob, Event}; -use crate::periph; -use crate::periph::MmapPeripheral; -fn input_thread(sender: &Sender) { +fn input_thread(sender: &Sender, output: Option<&Sender>) { println!("Use ^D to terminate."); let mut buffer = [0; 1]; while let Ok(size) = io::stdin().read(&mut buffer) { if size == 0 { break; } + if let Some(output) = output { + output.send(buffer[0]).unwrap(); + } } sender.send(Event::ExitApp).unwrap(); } -fn create_input_thread(sender: Sender) -> JoinHandle<()> { - spawn(move || input_thread(&sender)) +fn create_input_thread(sender: Sender, output: Option>) -> JoinHandle<()> { + spawn(move || input_thread(&sender, output.as_ref())) } -fn headless_unix_socket(config: &cli::Config, socket_path: &PathBuf) { +pub fn headless(config: &cli::Config) { let (event_sender, event_receiver): (Sender, Receiver) = channel(); let (cpu_sender, cpu_reader): (Sender, Receiver) = channel(); - let tty = periph::new_unix_socket_uart(event_sender.clone(), socket_path); - let cpu_val = if config.bin { - let entry = config.entryaddress; - let baseaddress = config.baseaddress; - CPU::from_bin(&config.file, tty, entry, baseaddress) - } else { - CPU::from_elf(&config.file, tty) - }; - let cpu = Arc::new(Mutex::new(cpu_val)); + let mut hifive1b = Hifive1b::new(event_sender.clone()); - create_input_thread(event_sender.clone()); + let uart0 = hifive1b.uart0channel.take().unwrap(); + if let Some(path) = &config.uart0 { + map_to_unixsocket(uart0, path.clone()); + create_input_thread(event_sender.clone(), None); + } else { + let (uart_tx, uart_rx) = uart0; + create_input_thread(event_sender.clone(), Some(uart_tx)); + spawn(move || loop { + while let Ok(data) = uart_rx.recv() { + print!("{:}", data as char); + } + }); + } - cpu_job_loop( - config, - &cpu, - &event_receiver, - event_sender, - cpu_reader, - &cpu_sender, - ); -} + let uart1 = hifive1b.uart1channel.take().unwrap(); + if let Some(path) = &config.uart1 { + map_to_unixsocket(uart1, path.clone()); + } -fn headless_stdio(config: &cli::Config) { - let (event_sender, event_receiver): (Sender, Receiver) = channel(); - let (cpu_sender, cpu_reader): (Sender, Receiver) = channel(); + let memory_map = hifive1b.memory.take().unwrap(); - let tty = periph::new_stdio_uart(event_sender.clone()); let cpu_val = if config.bin { let entry = config.entryaddress; let baseaddress = config.baseaddress; - CPU::from_bin(&config.file, tty, entry, baseaddress) + CPU::from_bin(&config.file, memory_map, entry, baseaddress) } else { - CPU::from_elf(&config.file, tty) + CPU::from_elf(&config.file, memory_map) }; let cpu = Arc::new(Mutex::new(cpu_val)); + cpu_job_loop( config, &cpu, @@ -75,20 +74,9 @@ fn headless_stdio(config: &cli::Config) { ); } -pub fn headless(config: &cli::Config) { - match config.uart_socket { - Some(ref path) => { - headless_unix_socket(config, path); - } - None => { - headless_stdio(config); - } - } -} - fn cpu_job_loop( config: &cli::Config, - cpu: &Arc>>, + cpu: &Arc>>, event_receiver: &Receiver, event_sender: Sender, cpu_reader: Receiver, diff --git a/src/app/tui.rs b/src/app/tui.rs index 2de3656..25a9010 100644 --- a/src/app/tui.rs +++ b/src/app/tui.rs @@ -1,5 +1,8 @@ +use crate::hifive1b::Hifive1b; +use crate::utils::map_to_unixsocket; use std::io; use std::sync::mpsc; +use std::sync::mpsc::channel; use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread::{spawn, JoinHandle}; @@ -14,9 +17,8 @@ use ratatui::{prelude::CrosstermBackend, Terminal}; use super::ui::ViewState; use crate::cli; -use crate::cpu::{create_cpu_thread, CPU}; +use crate::cpu::{create_cpu_thread, AddrBus, CPU}; use crate::events::{CpuJob, Event}; -use crate::periph; pub enum Job { Step(usize), @@ -53,7 +55,7 @@ fn create_input_thread(sender: Sender) -> JoinHandle<()> { spawn(move || input_thread(&sender)) } -fn event_loop_tui( +fn event_loop_tui( input: &Receiver, cpu: &Arc>>, cpu_sender: &Sender, @@ -135,21 +137,37 @@ fn event_loop_tui( } pub fn tui(config: &cli::Config) { - let (tx, tui_reader): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); - let (tui_writer, rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); - let (event_sender, event_reader): (mpsc::Sender, mpsc::Receiver) = - mpsc::channel(); - let (cpu_sender, cpu_reader): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + let (_tx, mut tui_reader): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + let (mut tui_writer, _rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); - let buffered = periph::new_buffered_uart(rx, tx, event_sender.clone()); + let (event_sender, event_reader): (Sender, Receiver) = channel(); + let (cpu_sender, cpu_reader): (Sender, Receiver) = channel(); + + let mut hifive1b = Hifive1b::new(event_sender.clone()); + + let uart0 = hifive1b.uart0channel.take().unwrap(); + if let Some(path) = &config.uart0 { + map_to_unixsocket(uart0, path.clone()); + } else { + let (uart_tx, uart_rx) = uart0; + tui_reader = uart_rx; + tui_writer = uart_tx; + } + + let uart1 = hifive1b.uart1channel.take().unwrap(); + if let Some(path) = &config.uart1 { + map_to_unixsocket(uart1, path.clone()); + } + + let memory_map = hifive1b.memory.take().unwrap(); let cpu_val = { if config.bin { let entry = config.entryaddress; let baseaddress = config.baseaddress; - CPU::from_bin(&config.file, buffered, entry, baseaddress) + CPU::from_bin(&config.file, memory_map, entry, baseaddress) } else { - CPU::from_elf(&config.file, buffered) + CPU::from_elf(&config.file, memory_map) } }; diff --git a/src/app/ui.rs b/src/app/ui.rs index a31f785..5cb1a38 100644 --- a/src/app/ui.rs +++ b/src/app/ui.rs @@ -1,8 +1,8 @@ //! The terminal user interface is the scope of this file. +use crate::cpu::AddrBus; use crate::cpu::Register; use crate::cpu::CPU; use crate::instructions::Instruction; -use crate::periph::MmapPeripheral; use anyhow::Error; use crossterm::event::KeyEvent; use crossterm::event::MouseEvent; @@ -94,7 +94,7 @@ impl ViewState { self.auto_step } - fn instruction_log_block(log: Rect, cpu: &CPU) -> Paragraph<'_> { + fn instruction_log_block(log: Rect, cpu: &CPU) -> Paragraph<'_> { let log_height = log.height as usize; let last_inst = cpu.last_n_instructions(log_height - 2); let mut last_instruction_list: String = String::new(); @@ -114,7 +114,7 @@ impl ViewState { Paragraph::new(text).block(Block::bordered().title(vec![Span::from("Last Instructions")])) } - fn next_instruction_block(next: Rect, cpu: &CPU) -> Paragraph<'_> { + fn next_instruction_block(next: Rect, cpu: &CPU) -> Paragraph<'_> { let next_height = next.height as usize; let mut next_inst = cpu.next_n_instructions(next_height - 1); let _ = next_inst.remove(0); @@ -253,12 +253,7 @@ impl ViewState { frame.render_widget(paragraph, current_block); } - pub fn ui( - &mut self, - f: &mut Frame, - cpu: &CPU, - uart_rx: &mpsc::Receiver, - ) { + pub fn ui(&mut self, f: &mut Frame, cpu: &CPU, uart_rx: &mpsc::Receiver) { let size = f.size(); let chunks = Layout::default() diff --git a/src/cli.rs b/src/cli.rs index ea5b631..5fbc0f2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -13,10 +13,15 @@ struct Args { #[arg(long, default_value_t = false, verbatim_doc_comment)] headless: bool, - /// If set, connects UART TX/RX to the specified unix socket. - /// If not set, the UART will be mapped to stdio. - #[arg(long, verbatim_doc_comment, requires("headless"))] - uart_socket: Option, + /// If set, connects UART0 TX/RX to the specified unix socket. + /// If not set, the UART0 will be mapped to stdio. + #[arg(long, verbatim_doc_comment)] + uart0: Option, + + /// If set, connects UART1 TX/RX to the specified unix socket. + /// If not set, the UART1 will not be accessible. + #[arg(long, verbatim_doc_comment)] + uart1: Option, /// If set, the emulation result will be checked. /// @@ -52,7 +57,8 @@ struct Args { /// Longterm goal is having a `Config` struct that can be used to save & replay the emulator pub struct Config { pub headless: bool, - pub uart_socket: Option, + pub uart0: Option, + pub uart1: Option, pub testing: bool, pub bin: bool, pub entryaddress: usize, @@ -66,27 +72,16 @@ impl Config { let path = args.file; let file = std::fs::read(&path).context(format!("Could not read file {}", path.display()))?; - if let Some(ref socket_path) = args.uart_socket { - if socket_path.exists() { - let attr = std::fs::metadata(socket_path).context(format!( - "Unable to create unixsocket for the UART backend: {}", - socket_path.display() - ))?; - if attr.file_type().is_socket() { - let _ = std::fs::remove_file(socket_path); - } else { - return Err(anyhow!(std::io::ErrorKind::AlreadyExists)).context(format!( - "Unable to create unixsocket for the UART backend: {}", - socket_path.display() - )); - } - } - } + + clear_socket(args.uart0.as_ref())?; + clear_socket(args.uart1.as_ref())?; + let entryaddress = usize_from_str(&args.entryaddress); let baseaddress = usize_from_str(&args.baseaddress); Ok(Self { headless: args.headless, - uart_socket: args.uart_socket, + uart0: args.uart0, + uart1: args.uart1, testing: args.testing, bin: args.bin, entryaddress, @@ -96,6 +91,26 @@ impl Config { } } +fn clear_socket(uart_path: Option<&std::path::PathBuf>) -> anyhow::Result<()> { + if let Some(ref socket_path) = uart_path { + if socket_path.exists() { + let attr = std::fs::metadata(socket_path).context(format!( + "Unable to create unixsocket for the UART backend: {}", + socket_path.display() + ))?; + if attr.file_type().is_socket() { + let _ = std::fs::remove_file(socket_path); + } else { + return Err(anyhow!(std::io::ErrorKind::AlreadyExists)).context(format!( + "Unable to create unixsocket for the UART backend: {}", + socket_path.display() + )); + } + } + } + Ok(()) +} + fn usize_from_str(text: &str) -> usize { if text.starts_with("0x") { usize::from_str_radix(text.trim_start_matches("0x"), 16).unwrap() diff --git a/src/cpu/executer.rs b/src/cpu/executer.rs index eb19383..4ec0c90 100644 --- a/src/cpu/executer.rs +++ b/src/cpu/executer.rs @@ -1,11 +1,8 @@ //! This file is scoped to a single function: `exec()`. use std::cmp::{max, min}; -use super::CPU; -use crate::{ - instructions::{sign_extend, Instruction, RS1value, RS2value}, - periph::MmapPeripheral, -}; +use super::{AddrBus, CPU}; +use crate::instructions::{sign_extend, Instruction, RS1value, RS2value}; macro_rules! add_signed { ($unsigned:expr, $signed:expr) => {{ @@ -17,7 +14,7 @@ macro_rules! add_signed { }}; } -impl CPU { +impl CPU { /// Executes one instruction. #[allow(clippy::too_many_lines)] pub fn exec( @@ -439,7 +436,8 @@ impl CPU { let addr = rs1 as usize; let value = self.memory.read_word(addr)?; self.register.write(rdindex, value); - self.memory.reservation = Some((addr, value)); + //self.memory.reservation = Some((addr, value)); + self.memory.set_reservation(addr, value); } Instruction::SCW(rdindex, rs1index, rs2index) => { let rs1: RS1value = self.register.read(rs1index); @@ -449,13 +447,15 @@ impl CPU { let value = self.memory.read_word(addr)?; self.register.write(rdindex, 1); - if let Some(reservation) = self.memory.reservation { + //if let Some(reservation) = self.memory.reservation { + if let Some(reservation) = self.memory.get_reservation() { if reservation.0 == addr && reservation.1 == value { self.memory.write_word(addr, rs2)?; self.register.write(rdindex, 0); } } - self.memory.reservation = None; + //self.memory.reservation = None; + self.memory.del_reservation(); } Instruction::AMOSWAPW(rdindex, rs1index, rs2index) => { let addr_rs1: RS1value = self.register.read(rs1index); diff --git a/src/cpu/memory.rs b/src/cpu/memory.rs index 092ae8f..bc9ac56 100644 --- a/src/cpu/memory.rs +++ b/src/cpu/memory.rs @@ -1,138 +1,46 @@ //! This file is scoped around the `Memory` struct. //! If something can not be `impl Memory` it is considered out of scope. -use crate::periph::MmapPeripheral; -pub struct Memory { - pub uart_base: usize, - pub uart: T, - pub uart_limit: usize, - pub ram_base: usize, - pub ram_limit: usize, - pub ram: Vec, - pub rom_base: usize, - pub rom_limit: usize, - pub rom: Vec, - pub reservation: Option<(usize, u32)>, -} +pub trait AddrBus { + fn set_reservation(&mut self, addr: usize, value: u32); -impl Memory { - pub fn default_hifive(uart: T) -> Self { - Self { - uart_base: 0x1001_3000, - uart, - uart_limit: 0x1001_301C, - rom_base: 0x2000_0000, - rom_limit: 0x4000_0000, - rom: vec![0; 0x2000_0000], - ram_base: 0x8000_0000, - ram_limit: 0x8000_8000, - ram: vec![0; 0x8000], - reservation: None, - } - } + fn get_reservation(&mut self) -> Option<(usize, u32)>; - pub fn pending_interrupt(&self) -> Option { - self.uart.pending_interrupt() - } + fn del_reservation(&mut self); - pub fn is_uart(&self, addr: usize) -> bool { - self.uart_base <= addr && addr < self.uart_limit - } + fn pending_interrupt(&self) -> Option; - pub fn is_ram(&self, addr: usize) -> bool { - self.ram_base <= addr && addr < self.ram_limit - } + fn is_ram(&self, addr: usize) -> bool; - pub fn is_rom(&self, addr: usize) -> bool { - self.rom_base <= addr && addr < self.rom_limit - } + fn load_ram_at(&mut self, offset: usize, data: &[u8]); - pub fn read_byte(&self, addr: usize) -> anyhow::Result { - if self.is_ram(addr) { - let index = addr - self.ram_base; - return Ok(u32::from(self.ram[index])); - } - if self.is_rom(addr) { - let index = addr - self.rom_base; - return Ok(u32::from(self.rom[index])); - } - if self.is_uart(addr) { - return Ok(u32::from(self.uart.read(addr - self.uart_base))); - } + fn is_rom(&self, addr: usize) -> bool; - // FIXME: Temporal hack to get RIOT happy in-time for the 1.0 release - #[allow(clippy::match_same_arms)] - match addr { - // PLIC - 0x0C20_0004 => { - // Always ack UART0 interrupt for now - Ok(0x03) - } - 0x0C00_0000..=0x0FFF_FFFF => Ok(0x00), - // RTT - 0x1000_0040..=0x1000_0080 => Ok(0x00), - // PRCI - 0x1000_8000..=0x1000_800F => { - // RIOT uses hfrosccfg, hfxosccfg, pllcfg, plloutdiv, procmoncfg - Ok(0xFF) - } - // GPIO - 0x1001_2000..=0x1001_2FFF => Ok(0xFF), - // timer? - 0x0200_BFF8..=0x0200_BFFF => Ok(0), - 0x0200_4000..=0x0200_4003 => Ok(0), - _ => Err(anyhow::anyhow!( - "Memory: attempted read outside memory map at address: 0x{addr:08X}" - )), - } - } - pub fn read_halfword(&self, index: usize) -> anyhow::Result { + fn load_rom_at(&mut self, offset: usize, data: &[u8]); + + fn load_at(&mut self, offset: usize, data: &[u8]); + + fn read_byte(&self, addr: usize) -> anyhow::Result; + + fn read_halfword(&self, index: usize) -> anyhow::Result { let halfword = (self.read_byte(index + 1)? << 8) + self.read_byte(index)?; Ok(halfword) } - pub fn read_word(&self, index: usize) -> anyhow::Result { + + fn read_word(&self, index: usize) -> anyhow::Result { let word = (self.read_halfword(index + 2)? << 16) + self.read_halfword(index)?; Ok(word) } - pub fn write_byte(&mut self, addr: usize, value: u32) -> anyhow::Result<()> { - if self.is_ram(addr) { - let index = addr - self.ram_base; - self.ram[index] = (value & 0xFF) as u8; - return Ok(()); - } - if self.is_uart(addr) { - self.uart.write(addr - self.uart_base, (value & 0xFF) as u8); - return Ok(()); - } - // FIXME: Temporal hack to get RIOT happy in-time for the 1.0 release - #[allow(clippy::match_same_arms)] - match addr { - // PLIC - 0x0C00_0000..=0x0FFF_FFFF => Ok(()), - // RTT - 0x1000_0040..=0x1000_0080 => Ok(()), - // PRCI - 0x1000_8000..=0x1000_800F => { - // RIOT uses hfrosccfg, hfxosccfg, pllcfg, plloutdiv, procmoncfg - Ok(()) - } - // GPIO - 0x1001_2000..=0x1001_2FFF => Ok(()), - // timer? - 0x0200_BFF8..=0x0200_BFFF => Ok(()), - 0x0200_4000..=0x0200_4007 => Ok(()), - _ => Err(anyhow::anyhow!( - "Memory: attempted write outside writable memory map at address: 0x{addr:08X}" - )), - } - } - pub fn write_halfword(&mut self, index: usize, value: u32) -> anyhow::Result<()> { + fn write_byte(&mut self, addr: usize, value: u32) -> anyhow::Result<()>; + + fn write_halfword(&mut self, index: usize, value: u32) -> anyhow::Result<()> { self.write_byte(index, value)?; self.write_byte(index + 1, value >> 8)?; Ok(()) } - pub fn write_word(&mut self, index: usize, value: u32) -> anyhow::Result<()> { + + fn write_word(&mut self, index: usize, value: u32) -> anyhow::Result<()> { self.write_halfword(index, value)?; self.write_halfword(index + 2, value >> 16)?; Ok(()) diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 5e1d321..4e72feb 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -11,10 +11,8 @@ use elf::ElfBytes; use crate::events::{CpuJob, Event}; use crate::instructions::{decode, Instruction}; -use crate::periph::MmapPeripheral; - -use memory::Memory; +pub use memory::AddrBus; pub use register::{index_to_name, Register}; mod executer; @@ -23,18 +21,18 @@ mod register; const LOG_LENGTH: usize = 80; -pub struct CPU { +pub struct CPU { pub register: Register, - pub memory: Memory, + pub memory: T, pub waits_for_interrupt: bool, instruction_log: [Option<(usize, Instruction)>; LOG_LENGTH], } -impl CPU { - pub fn from_elf(file: &[u8], uart: T) -> Self { +impl CPU { + pub fn from_elf(file: &[u8], memory: T) -> Self { let mut cpu = Self { register: Register::default(), - memory: Memory::default_hifive(uart), + memory, waits_for_interrupt: false, instruction_log: array::from_fn(|_| None), }; @@ -46,18 +44,9 @@ impl CPU { if let Some(segments) = elffile.segments() { for phdr in segments { if phdr.p_type == abi::PT_LOAD { - if let Ok(mut addr) = usize::try_from(phdr.p_paddr) { - if cpu.memory.is_rom(addr) { - for i in elffile.segment_data(&phdr).unwrap() { - cpu.memory.rom[addr - cpu.memory.rom_base] = *i; - addr += 1; - } - } else if cpu.memory.is_ram(addr) { - for i in elffile.segment_data(&phdr).unwrap() { - cpu.memory.ram[addr - cpu.memory.ram_base] = *i; - addr += 1; - } - } + if let Ok(addr) = usize::try_from(phdr.p_paddr) { + let data = elffile.segment_data(&phdr).unwrap(); + cpu.memory.load_at(addr, data); } else { panic!("Could not get PT_LOAD address in your ELF file."); } @@ -73,25 +62,15 @@ impl CPU { cpu } - pub fn from_bin(file: &[u8], uart: T, entry_address: usize, base_address: usize) -> Self { + pub fn from_bin(file: &[u8], memory: T, entry_address: usize, base_address: usize) -> Self { let mut cpu = Self { register: Register::default(), - memory: Memory::default_hifive(uart), + memory, waits_for_interrupt: false, instruction_log: array::from_fn(|_| None), }; - if cpu.memory.is_rom(base_address) { - for (addr, i) in file.iter().enumerate() { - cpu.memory.rom[base_address - cpu.memory.rom_base + addr] = *i; - } - } else if cpu.memory.is_ram(base_address) { - for (addr, i) in file.iter().enumerate() { - cpu.memory.ram[base_address - cpu.memory.ram_base + addr] = *i; - } - } else { - panic!("The provided baseaddress was neither in ROM nor RAM."); - } + cpu.memory.load_at(base_address, file); cpu.register.pc = entry_address as u32; @@ -203,7 +182,7 @@ impl CPU { } } -pub fn create_cpu_thread( +pub fn create_cpu_thread( cpu: &Arc>>, sender: Sender, receiver: Receiver, @@ -212,7 +191,7 @@ pub fn create_cpu_thread( spawn(move || cpu_executor(&cpu2, &sender, &receiver)) } -fn cpu_executor( +fn cpu_executor( cpu: &Arc>>, sender: &Sender, receiver: &Receiver, diff --git a/src/hifive1b/mod.rs b/src/hifive1b/mod.rs new file mode 100644 index 0000000..d68dbf1 --- /dev/null +++ b/src/hifive1b/mod.rs @@ -0,0 +1,222 @@ +use std::sync::mpsc; + +use crate::cpu::AddrBus; +use crate::events; +use crate::periph::MmapPeripheral; +use crate::utils::IOChannel; + +use uart::Uart; + +mod uart; + +pub struct Hifive1b { + pub uart0channel: Option, + pub uart1channel: Option, + pub memory: Option, +} + +impl Hifive1b { + pub fn new(interrupts: mpsc::Sender) -> Self { + let (uart0channel, uart0) = Uart::default(interrupts.clone()); + let (uart1channel, uart1) = Uart::default(interrupts); + let memory = Memory::new(uart0, uart1); + Self { + uart0channel: Some(uart0channel), + uart1channel: Some(uart1channel), + memory: Some(memory), + } + } +} + +pub struct Memory { + pub uart0_base: usize, + pub uart0: Uart, + pub uart0_limit: usize, + pub uart1_base: usize, + pub uart1: Uart, + pub uart1_limit: usize, + pub ram_base: usize, + pub ram_limit: usize, + pub ram: Vec, + pub rom_base: usize, + pub rom_limit: usize, + pub rom: Vec, + pub reservation: Option<(usize, u32)>, +} + +impl Memory { + pub fn new(uart0: Uart, uart1: Uart) -> Self { + Self { + uart0_base: 0x1001_3000, + uart0, + uart0_limit: 0x1001_301C, + uart1_base: 0x1002_3000, + uart1, + uart1_limit: 0x1002_301C, + rom_base: 0x2000_0000, + rom_limit: 0x4000_0000, + rom: vec![0; 0x2000_0000], + ram_base: 0x8000_0000, + ram_limit: 0x8000_8000, + ram: vec![0; 0x8000], + reservation: None, + } + } + + fn is_uart0(&self, addr: usize) -> bool { + self.uart0_base <= addr && addr < self.uart0_limit + } + + fn is_uart1(&self, addr: usize) -> bool { + self.uart1_base <= addr && addr < self.uart1_limit + } +} + +impl AddrBus for Memory { + fn set_reservation(&mut self, addr: usize, value: u32) { + self.reservation = Some((addr, value)); + } + + fn get_reservation(&mut self) -> Option<(usize, u32)> { + self.reservation + } + + fn del_reservation(&mut self) { + self.reservation = None; + } + + fn pending_interrupt(&self) -> Option { + let i0 = self.uart0.pending_interrupt(); + if i0.is_some() { + return i0; + } + let i1 = self.uart1.pending_interrupt(); + if i1.is_some() { + return i1; + } + None + } + + fn is_ram(&self, addr: usize) -> bool { + self.ram_base <= addr && addr < self.ram_limit + } + + fn load_ram_at(&mut self, offset: usize, data: &[u8]) { + let ram = &mut self.ram[offset..]; + for (x, i) in data.iter().enumerate() { + ram[x] = *i; + } + } + + fn is_rom(&self, addr: usize) -> bool { + self.rom_base <= addr && addr < self.rom_limit + } + + fn load_rom_at(&mut self, offset: usize, data: &[u8]) { + let rom = &mut self.rom[offset..]; + for (x, i) in data.iter().enumerate() { + rom[x] = *i; + } + } + + fn load_at(&mut self, addr: usize, data: &[u8]) { + if self.is_ram(addr) { + let offset = addr - self.ram_base; + self.load_ram_at(offset, data); + return; + } else if self.is_rom(addr) { + let offset = addr - self.rom_base; + self.load_rom_at(offset, data); + return; + } + panic!("Can't load at this address"); + } + + fn read_byte(&self, addr: usize) -> anyhow::Result { + if self.is_ram(addr) { + let index = addr - self.ram_base; + return Ok(u32::from(self.ram[index])); + } + if self.is_rom(addr) { + let index = addr - self.rom_base; + return Ok(u32::from(self.rom[index])); + } + if self.is_uart0(addr) { + return Ok(u32::from(self.uart0.read(addr - self.uart0_base))); + } + if self.is_uart1(addr) { + return Ok(u32::from(self.uart1.read(addr - self.uart1_base))); + } + + // FIXME: Temporal hack to get RIOT happy in-time for the 1.0 release + #[allow(clippy::match_same_arms)] + match addr { + // PLIC + 0x0C20_0004 => { + if self.uart0.pending_interrupt().is_some() { + Ok(0x03) + } else if self.uart1.pending_interrupt().is_some() { + Ok(0x04) + } else { + Ok(0x00) + } + } + 0x0C00_0000..=0x0FFF_FFFF => Ok(0x00), + // RTT + 0x1000_0040..=0x1000_0080 => Ok(0x00), + // PRCI + 0x1000_8000..=0x1000_800F => { + // RIOT uses hfrosccfg, hfxosccfg, pllcfg, plloutdiv, procmoncfg + Ok(0xFF) + } + // GPIO + 0x1001_2000..=0x1001_2FFF => Ok(0xFF), + // timer? + 0x0200_BFF8..=0x0200_BFFF => Ok(0), + 0x0200_4000..=0x0200_4003 => Ok(0), + _ => Err(anyhow::anyhow!( + "Memory: attempted read outside memory map at address: 0x{addr:08X}" + )), + } + } + + fn write_byte(&mut self, addr: usize, value: u32) -> anyhow::Result<()> { + if self.is_ram(addr) { + let index = addr - self.ram_base; + self.ram[index] = (value & 0xFF) as u8; + return Ok(()); + } + if self.is_uart0(addr) { + self.uart0 + .write(addr - self.uart0_base, (value & 0xFF) as u8); + return Ok(()); + } + if self.is_uart1(addr) { + self.uart1 + .write(addr - self.uart1_base, (value & 0xFF) as u8); + return Ok(()); + } + + // FIXME: Temporal hack to get RIOT happy in-time for the 1.0 release + #[allow(clippy::match_same_arms)] + match addr { + // PLIC + 0x0C00_0000..=0x0FFF_FFFF => Ok(()), + // RTT + 0x1000_0040..=0x1000_0080 => Ok(()), + // PRCI + 0x1000_8000..=0x1000_800F => { + // RIOT uses hfrosccfg, hfxosccfg, pllcfg, plloutdiv, procmoncfg + Ok(()) + } + // GPIO + 0x1001_2000..=0x1001_2FFF => Ok(()), + // timer? + 0x0200_BFF8..=0x0200_BFFF => Ok(()), + 0x0200_4000..=0x0200_4007 => Ok(()), + _ => Err(anyhow::anyhow!( + "Memory: attempted write outside writable memory map at address: 0x{addr:08X}" + )), + } + } +} diff --git a/src/periph/uart.rs b/src/hifive1b/uart.rs similarity index 91% rename from src/periph/uart.rs rename to src/hifive1b/uart.rs index b23787a..b9229fc 100644 --- a/src/periph/uart.rs +++ b/src/hifive1b/uart.rs @@ -1,7 +1,12 @@ -use super::{InterruptReason, MmapPeripheral, PeripheralBackend}; +use crate::events; +use crate::periph::InterruptReason; +use crate::periph::MmapPeripheral; +use crate::utils::IOChannel; +use crate::utils::PeekableChannel; +use std::sync::mpsc; #[allow(clippy::struct_excessive_bools)] -pub struct Uart { +pub struct Uart { tx_fifo_full: bool, tx_enable: bool, rx_enable: bool, @@ -11,11 +16,12 @@ pub struct Uart { rxwm_ie: bool, txwm_ip: bool, // watermark interrupt pending rxwm_ip: bool, - backend: B, + backend: PeekableChannel, } -impl Uart { - pub fn default(backend: B) -> Self { - Uart { +impl Uart { + pub fn default(interrupts: mpsc::Sender) -> (IOChannel, Self) { + let (iochannel, channel) = PeekableChannel::channel(interrupts); + let new_uart = Uart { tx_fifo_full: false, tx_enable: false, rx_enable: false, @@ -25,12 +31,13 @@ impl Uart { rxwm_ie: false, txwm_ip: false, // watermark interrupt pending rxwm_ip: false, - backend, - } + backend: channel, + }; + (iochannel, new_uart) } } -impl Uart { +impl Uart { #[allow(clippy::match_same_arms)] fn read_uart(&self, address_offset: usize) -> u8 { match address_offset { @@ -168,7 +175,7 @@ impl Uart { } } -impl MmapPeripheral for Uart { +impl MmapPeripheral for Uart { fn read(&self, offset: usize) -> u8 { self.read_uart(offset) } diff --git a/src/main.rs b/src/main.rs index fd2b891..4ab6cb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,10 @@ mod app; mod cli; mod cpu; mod events; +mod hifive1b; mod instructions; mod periph; +mod utils; fn main() -> Result<(), Box> { let config = cli::Config::parse()?; diff --git a/src/periph/backend.rs b/src/periph/backend.rs deleted file mode 100644 index 8a2090d..0000000 --- a/src/periph/backend.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::io; -use std::io::{Read, Write}; -use std::os::unix::net::UnixListener; -use std::os::unix::net::UnixStream; -use std::path::PathBuf; -use std::sync::mpsc; -use std::thread; - -use crate::events::Event; -use crate::periph::peekable_reader::PeekableReader; -use crate::periph::PeripheralBackend; - -/// A backend that reads from stdin and writes to stdout -pub struct BackendTty { - peek_reader: PeekableReader, -} - -impl BackendTty { - pub fn new(interrupts: mpsc::Sender) -> Self { - // setup a PeekableReader that fetches data from stdin - let reader = PeekableReader::new(move || { - let mut buffer: [u8; 1] = [0]; - io::stdin().read_exact(&mut buffer).unwrap(); - let _ = interrupts.send(Event::InterruptUart); - buffer[0] - }); - BackendTty { - peek_reader: reader, - } - } -} - -impl PeripheralBackend for BackendTty { - fn has_data(&self) -> bool { - self.peek_reader.has_data() - } - fn read_cb(&self) -> Option { - if let Some(val) = self.peek_reader.try_recv() { - return Some(val); - } - None - } - - // We can directly print in order to write to stdout - fn write_cb(&self, value: u8) { - print!("{:}", value as char); - } -} - -/// A backend that reads from a given `mpsc::Receiver` and writes to a given `mpsc::Sender` -pub struct BackendBuffered { - writer: mpsc::Sender, - peek_reader: PeekableReader, -} - -impl BackendBuffered { - pub fn new( - input: mpsc::Receiver, - output: mpsc::Sender, - interrupts: mpsc::Sender, - ) -> Self { - let reader = PeekableReader::new(move || { - let data = input.recv().unwrap_or_else(|_| T::default()); - let _ = interrupts.send(Event::InterruptUart); - data - }); - BackendBuffered { - writer: output, - peek_reader: reader, - } - } -} - -impl> PeripheralBackend for BackendBuffered -where - u8: From, -{ - fn has_data(&self) -> bool { - self.peek_reader.has_data() - } - fn read_cb(&self) -> Option { - if let Some(val) = self.peek_reader.try_recv() { - return Some(u8::from(val)); - } - None - } - fn write_cb(&self, value: u8) { - self.writer.send(value.into()).unwrap(); - } -} - -/// A backend that read/writes to a unix socket -pub struct BackendSocket { - buffered_backend: BackendBuffered, -} - -fn unixsocket_writer(input: &mpsc::Receiver, socket_receiver: &mpsc::Receiver) { - let mut maybe_socket: Option = None; - while let Ok(data) = input.recv() { - loop { - if maybe_socket.is_none() { - maybe_socket = Some(socket_receiver.recv().unwrap()); - } - if let Some(ref mut socket) = maybe_socket { - if let Err(_e) = socket.write(&[data]) { - maybe_socket = None; - } else { - break; - } - } - } - } -} - -fn unixsocket_server(input: mpsc::Receiver, output: &mpsc::Sender, socket_path: &PathBuf) { - let (socket_sender, socket_receiver) = mpsc::channel(); - let _handle = thread::Builder::new() - .name("Unixsocket Write".to_owned()) - .spawn(move || unixsocket_writer(&input, &socket_receiver)) - .unwrap(); - - let listener = UnixListener::bind(socket_path).unwrap(); - loop { - if let Ok((mut socket, _addr)) = listener.accept() { - println!("Accepted unixsocket listener"); - let socket2 = socket.try_clone().unwrap(); - socket_sender.send(socket2).unwrap(); - - let mut buf: [u8; 1] = [0; 1]; - while let Ok(_num) = socket.read_exact(&mut buf) { - if output.send(buf[0]).is_err() { - println!("Aborting Unixsocket reader because internal channel got closed"); - return; - } - } - let _ = socket.shutdown(std::net::Shutdown::Both); - } - println!("Restarting unixsocket_server"); - } -} - -impl BackendSocket { - pub fn new(interrupts: mpsc::Sender, socket_path: &PathBuf) -> Self { - let (from_unix_to_triops, triops_receive_from_unix) = mpsc::channel(); - let (from_triops_to_unix, unix_receive_from_triops) = mpsc::channel(); - let path = socket_path.to_owned(); - thread::Builder::new() - .name("Unixsocket Server".to_owned()) - .spawn(move || unixsocket_server(unix_receive_from_triops, &from_unix_to_triops, &path)) - .unwrap(); - - Self { - buffered_backend: BackendBuffered::new( - triops_receive_from_unix, - from_triops_to_unix, - interrupts, - ), - } - } -} - -impl PeripheralBackend for BackendSocket { - fn has_data(&self) -> bool { - self.buffered_backend.has_data() - } - fn read_cb(&self) -> Option { - self.buffered_backend.read_cb() - } - - fn write_cb(&self, value: u8) { - self.buffered_backend.write_cb(value); - } -} diff --git a/src/periph/mod.rs b/src/periph/mod.rs index 054dba7..fbc7822 100644 --- a/src/periph/mod.rs +++ b/src/periph/mod.rs @@ -1,46 +1,10 @@ //! Emulation of hardware peripherals is scoped for this file. //! Currently, only memory mapped peripherals are available via `trait MmapPeripheral`. -use std::path::PathBuf; -use std::sync::mpsc; -mod backend; -mod peekable_reader; -mod uart; - -use backend::{BackendBuffered, BackendSocket, BackendTty}; -use uart::Uart; - -use crate::events::Event; - -type InterruptReason = u32; +pub type InterruptReason = u32; pub trait MmapPeripheral: Send { fn read(&self, offset: usize) -> u8; fn write(&mut self, offset: usize, value: u8); fn pending_interrupt(&self) -> Option; } - -trait PeripheralBackend { - fn has_data(&self) -> bool; - fn read_cb(&self) -> Option; - fn write_cb(&self, value: u8); -} - -pub fn new_buffered_uart( - input: mpsc::Receiver, - output: mpsc::Sender, - interrupt_queue: mpsc::Sender, -) -> impl MmapPeripheral { - Uart::default(BackendBuffered::new(input, output, interrupt_queue)) -} - -pub fn new_stdio_uart(interrupt_queue: mpsc::Sender) -> impl MmapPeripheral { - Uart::default(BackendTty::new(interrupt_queue)) -} - -pub fn new_unix_socket_uart( - interrupt_queue: mpsc::Sender, - socket_path: &PathBuf, -) -> impl MmapPeripheral { - Uart::default(BackendSocket::new(interrupt_queue, socket_path)) -} diff --git a/src/utils/map_to_unixsocket.rs b/src/utils/map_to_unixsocket.rs new file mode 100644 index 0000000..908e211 --- /dev/null +++ b/src/utils/map_to_unixsocket.rs @@ -0,0 +1,60 @@ +use std::io::Read; +use std::io::Write; +use std::os::unix::net::UnixListener; +use std::os::unix::net::UnixStream; +use std::path::PathBuf; +use std::sync::mpsc; +use std::thread; + +use super::IOChannel; + +fn unixsocket_writer(input: &mpsc::Receiver, socket_receiver: &mpsc::Receiver) { + let mut maybe_socket: Option = None; + while let Ok(data) = input.recv() { + loop { + if maybe_socket.is_none() { + maybe_socket = Some(socket_receiver.recv().unwrap()); + } + if let Some(ref mut socket) = maybe_socket { + if let Err(_e) = socket.write(&[data]) { + maybe_socket = None; + } else { + break; + } + } + } + } +} + +fn unixsocket_server(input: mpsc::Receiver, output: &mpsc::Sender, socket_path: PathBuf) { + let (socket_sender, socket_receiver) = mpsc::channel(); + let _handle = thread::Builder::new() + .name("Unixsocket Writer".to_owned()) + .spawn(move || unixsocket_writer(&input, &socket_receiver)) + .unwrap(); + + let listener = UnixListener::bind(socket_path).unwrap(); + loop { + if let Ok((mut socket, _addr)) = listener.accept() { + let socket2 = socket.try_clone().unwrap(); + socket_sender.send(socket2).unwrap(); + + let mut buf: [u8; 1] = [0; 1]; + while let Ok(_num) = socket.read_exact(&mut buf) { + if output.send(buf[0]).is_err() { + println!("Aborting Unixsocket reader because internal channel got closed"); + return; + } + } + let _ = socket.shutdown(std::net::Shutdown::Both); + } + } +} + +pub fn map_to_unixsocket(channel: IOChannel, socket_path: PathBuf) { + let (output, input) = channel; + let _handle = thread::Builder::new() + .name("Unixsocket Reader".to_owned()) + .spawn(move || unixsocket_server(input, &output, socket_path)) + .unwrap(); +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..5fec2ec --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,11 @@ +use std::sync::mpsc; + +mod map_to_unixsocket; +mod peekable_channel; +mod peekable_reader; + +pub use map_to_unixsocket::map_to_unixsocket; +pub use peekable_channel::PeekableChannel; +use peekable_reader::PeekableReader; + +pub type IOChannel = (mpsc::Sender, mpsc::Receiver); diff --git a/src/utils/peekable_channel.rs b/src/utils/peekable_channel.rs new file mode 100644 index 0000000..f2eac4a --- /dev/null +++ b/src/utils/peekable_channel.rs @@ -0,0 +1,43 @@ +use std::sync::mpsc; + +use crate::events::Event; + +use super::PeekableReader; + +/// A backend that reads from a given `mpsc::Receiver` and writes to a given `mpsc::Sender` +/// Emits an event/interrupt whenever data arrives +pub struct PeekableChannel { + writer: mpsc::Sender, + peek_reader: PeekableReader, +} + +impl PeekableChannel { + pub fn channel( + interrupts: mpsc::Sender, + ) -> ((mpsc::Sender, mpsc::Receiver), Self) { + let (tx1, input): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + let (output, rx2): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + let reader = PeekableReader::new(move || { + let data = input.recv().unwrap_or_else(|_| T::default()); + let _ = interrupts.send(Event::InterruptUart); + data + }); + let new_self = Self { + writer: output, + peek_reader: reader, + }; + ((tx1, rx2), new_self) + } + + pub fn has_data(&self) -> bool { + self.peek_reader.has_data() + } + + pub fn read_cb(&self) -> Option { + self.peek_reader.try_recv() + } + + pub fn write_cb(&self, value: T) { + self.writer.send(value).unwrap(); + } +} diff --git a/src/periph/peekable_reader.rs b/src/utils/peekable_reader.rs similarity index 95% rename from src/periph/peekable_reader.rs rename to src/utils/peekable_reader.rs index f553f6e..0d968e8 100644 --- a/src/periph/peekable_reader.rs +++ b/src/utils/peekable_reader.rs @@ -10,9 +10,9 @@ pub struct PeekableReader { impl PeekableReader { /// Creates a new `PeekableReader` - /// Argument `f` function / closure is called repeatedly + /// Argument `read_data` function / closure is called repeatedly /// Should yield a new value everytime that is send to the "receiver" - /// `f` is allowed to block infinitly + /// `read_data` is allowed to block infinitly pub fn new T + Send + 'static>(read_data: F) -> Self { let (tx, rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); let data_mux = Arc::new(Mutex::new(None));