diff --git a/src/basic_uart.rs b/src/basic_uart.rs deleted file mode 100644 index 0e209ba..0000000 --- a/src/basic_uart.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2025 taitep -// SPDX-License-Identifier: BSD-2-Clause -// -// This file is part of TRVE (https://gitea.taitep.se/taitep/trve) -// See LICENSE file in the project root for full license text. - -use std::collections::VecDeque; -use std::io; -use std::os::fd::AsFd; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::Duration; - -use nix::fcntl::fcntl; -use nix::fcntl::{FcntlArg, OFlag}; -use trve::exceptions::MemoryExceptionType; -use trve::mem::MemDeviceInterface; - -/// byte 0: rx/tx -/// byte 1: status (------rt, r=rxready, t=txready)/none -pub struct BasicUart { - rx_buf: Mutex>, -} - -impl BasicUart { - pub fn new() -> Self { - // Make sure stdin is nonblocking - let stdin = io::stdin(); - let fd = stdin.as_fd(); - let flags = fcntl(fd, FcntlArg::F_GETFL).unwrap(); - fcntl( - fd, - FcntlArg::F_SETFL(OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK), - ) - .unwrap(); - - BasicUart { - rx_buf: Mutex::new(VecDeque::new()), - } - } - - pub fn spawn_poller(self, poll_interval: Duration) -> Arc { - let shared = Arc::new(self); - - let uart_clone = shared.clone(); - - thread::spawn(move || { - loop { - uart_clone.poll(); - thread::sleep(poll_interval); - } - }); - - shared - } - - fn write(&self, byte: u8) { - print!("{}", byte as char); - } - - fn read(&self) -> u8 { - self.rx_buf.lock().unwrap().pop_front().unwrap_or(0) - } - - fn can_read(&self) -> bool { - !self.rx_buf.lock().unwrap().is_empty() - } - - pub fn poll(&self) { - let mut rx_buf = self.rx_buf.lock().unwrap(); - - let mut buffer = [0u8; 1]; - while let Ok(1) = nix::unistd::read(io::stdin().as_fd(), &mut buffer) { - rx_buf.push_back(buffer[0]); - } - } -} - -impl MemDeviceInterface for BasicUart { - fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> { - match addr { - 0 => { - self.write(value); - Ok(()) - } - _ => Err(MemoryExceptionType::AccessFault), - } - } - fn read_byte(&self, addr: u64) -> Result { - match addr { - 0 => Ok(self.read()), - 1 => Ok(1 | (self.can_read() as u8) << 1), - _ => Err(MemoryExceptionType::AccessFault), - } - } -} diff --git a/src/devices.rs b/src/devices.rs new file mode 100644 index 0000000..b1fc0cf --- /dev/null +++ b/src/devices.rs @@ -0,0 +1 @@ +pub mod serial; diff --git a/src/devices/serial.rs b/src/devices/serial.rs new file mode 100644 index 0000000..7e90748 --- /dev/null +++ b/src/devices/serial.rs @@ -0,0 +1,140 @@ +// Copyright (c) 2026 taitep +// SPDX-License-Identifier: BSD-2-Clause +// +// This file is part of TRVE (https://gitea.taitep.se/taitep/trve) +// See LICENSE file in the project root for full license text. + +use std::{ + io::{Read, Write}, + sync::{Arc, Mutex}, + time::Duration, +}; + +mod fifo; +use fifo::UartFifo; + +use crate::{exceptions::MemoryExceptionType, mem::MemDeviceInterface}; + +pub struct SifiveUart { + rx: Mutex<(UartFifo<2048>, bool)>, + tx: Mutex<(UartFifo<2048>, bool)>, +} + +impl SifiveUart { + pub fn new_arc() -> Arc { + Arc::new(Self { + rx: Mutex::new((UartFifo::default(), false)), + tx: Mutex::new((UartFifo::default(), false)), + }) + } + + pub fn spawn_io_thread( + self: Arc, + mut rx_backend: R, + mut tx_backend: T, + interval: Duration, + ) { + std::thread::spawn(move || { + loop { + { + // Read data + let mut rx_guard = self.rx.lock().expect("could not lock uart RX half"); + let (rx_buf, rx_en) = &mut *rx_guard; + + if *rx_en { + let _ = rx_buf.read_from(&mut rx_backend); + } + } + { + // Write data + let mut tx_guard = self.tx.lock().expect("could not lock uart RX half"); + let (tx_buf, tx_en) = &mut *tx_guard; + + if *tx_en { + let _ = tx_buf.write_to(&mut tx_backend); + let _ = tx_backend.flush(); + } + } + + std::thread::sleep(interval); + } + }); + } +} + +impl MemDeviceInterface for SifiveUart { + fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> { + // dbg!(addr, value); + match addr { + 0x00 => { + // TXDATA + let (ref mut tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half"); + tx_buf.push_single_byte(value as u8); + Ok(()) + } + 0x08 => { + // TXCTRL + let (_, ref mut tx_en) = *self.tx.lock().expect("could not lock uart TX half"); + *tx_en = value & 1 != 0; + Ok(()) + } + 0x04 => Ok(()), // RXDATA + 0x0c => { + // RXCTRL + let (_, ref mut rx_en) = *self.rx.lock().expect("could not lock uart RX half"); + *rx_en = value & 1 != 0; + Ok(()) + } + 0x10 => Ok(()), // IE + 0x14 => Ok(()), // IP + 0x18 => Ok(()), // DIV + _ => { + if addr < 0x1c { + Err(MemoryExceptionType::AddressMisaligned) + } else { + Err(MemoryExceptionType::AccessFault) + } + } + } + } + + fn read_word(&self, addr: u64) -> Result { + // dbg!(addr); + match addr { + 0x00 => { + // TXDATA + let (ref tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half"); + Ok(if tx_buf.is_full() { 0x80000000 } else { 0 }) + } + 0x08 => { + // TXCTRL + let (_, tx_en) = *self.tx.lock().expect("could not lock uart TX half"); + Ok(if tx_en { 1 } else { 0 }) + } + 0x04 => { + // RXDATA + let (ref mut rx_buf, _) = *self.rx.lock().expect("could not lock uart RX half"); + Ok(match rx_buf.pop_single_byte() { + None => 0x80000000, + Some(b) => b as u32, + }) + } + 0x0c => { + // RXCTRL + let (_, rx_en) = *self.rx.lock().expect("could not lock uart RX half"); + Ok(if rx_en { 1 } else { 0 }) + } + 0x10 => Ok(0), // IE + 0x14 => Ok(0), // IP + 0x18 => Ok(1), // DIV + + _ => { + if addr < 0x1c { + Err(MemoryExceptionType::AddressMisaligned) + } else { + Err(MemoryExceptionType::AccessFault) + } + } + } + } +} diff --git a/src/devices/serial/fifo.rs b/src/devices/serial/fifo.rs new file mode 100644 index 0000000..dbf4659 --- /dev/null +++ b/src/devices/serial/fifo.rs @@ -0,0 +1,113 @@ +// Copyright (c) 2026 taitep +// SPDX-License-Identifier: BSD-2-Clause +// +// This file is part of TRVE (https://gitea.taitep.se/taitep/trve) +// See LICENSE file in the project root for full license text. + +use std::io::{self, Read, Write}; + +pub struct UartFifo { + buf: [u8; CAP], + head: usize, + tail: usize, + len: usize, +} + +impl UartFifo { + pub fn pop_single_byte(&mut self) -> Option { + if self.is_empty() { + return None; + } + + let value = self.buf[self.tail]; + self.advance_read(1); + Some(value) + } + + pub fn push_single_byte(&mut self, value: u8) -> bool { + if self.is_full() { + return false; + } + + self.buf[self.head] = value; + self.advance_write(1); + true + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn is_full(&self) -> bool { + self.len == CAP + } + + fn write_slice(&mut self) -> &mut [u8] { + if self.is_full() { + return &mut []; + } + + if self.head >= self.tail { + &mut self.buf[self.head..] + } else { + &mut self.buf[self.head..self.tail] + } + } + + fn advance_write(&mut self, n: usize) { + debug_assert!(n <= CAP - self.len); + self.head = (self.head + n) % CAP; + self.len += n; + } + + fn read_slice(&self) -> &[u8] { + if self.is_empty() { + return &[]; + } + + if self.tail < self.head { + &self.buf[self.tail..self.head] + } else { + &self.buf[self.tail..] + } + } + + fn advance_read(&mut self, n: usize) { + debug_assert!(n <= self.len); + self.tail = (self.tail + n) % CAP; + self.len -= n; + } + + pub fn read_from(&mut self, reader: &mut R) -> io::Result { + let slice = self.write_slice(); + if slice.is_empty() { + return Ok(0); + } + + let n = reader.read(slice)?; + self.advance_write(n); + Ok(n) + } + + pub fn write_to(&mut self, writer: &mut W) -> io::Result { + let slice = self.read_slice(); + if slice.is_empty() { + return Ok(0); + } + + let n = writer.write(slice)?; + self.advance_read(n); + Ok(n) + } +} + +impl Default for UartFifo { + fn default() -> Self { + UartFifo { + buf: [0; SIZE], + head: 0, + tail: 0, + len: 0, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a74dffb..9d4865c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod core; mod decode; +pub mod devices; pub mod exceptions; pub mod gdb; mod instructions; diff --git a/src/main.rs b/src/main.rs index 7be2d20..50d5de0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,12 +4,14 @@ // This file is part of TRVE (https://gitea.taitep.se/taitep/trve) // See LICENSE file in the project root for full license text. -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{io, os::fd::AsFd, path::PathBuf, sync::Arc, time::Duration}; use clap::Parser; +use nix::fcntl::{FcntlArg, OFlag, fcntl}; use trve::{ core::Core, + devices, exceptions::MemoryExceptionType, gdb, mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram}, @@ -17,8 +19,6 @@ use trve::{ use anyhow::Result; -use crate::basic_uart::BasicUart; - mod execload; /// Taitep's RISC-V Emulator @@ -42,8 +42,16 @@ fn main() -> Result<()> { let mut mmio_root = MmioRoot::default(); mmio_root.insert(0, Arc::new(DbgOut)); - let uart = BasicUart::new(); - let uart = uart.spawn_poller(Duration::from_millis(10)); + if let Err(e) = fcntl(io::stdin().as_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) { + eprintln!("Could not make stdout nonblocking, skipping. Error: {e}"); + } + + let uart = devices::serial::SifiveUart::new_arc(); + uart.clone().spawn_io_thread( + std::io::stdin(), + std::io::stdout(), + Duration::from_millis(10), + ); mmio_root.insert(0x10000, uart); let mem_cfg = MemConfig { @@ -67,8 +75,6 @@ fn main() -> Result<()> { Ok(()) } -mod basic_uart; - struct DbgOut; impl MemDeviceInterface for DbgOut {