From: Kit Rhett Aultman Date: Sat, 7 Sep 2024 03:00:31 +0000 (-0400) Subject: Rename console to uart X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=8dc30337b11bc55b6a70111db8f5f44c3322a3d7;p=riscv_baremetal.git Rename console to uart The term "console" felt like a misnomer; the console is really just a way to access the uart. A more complete concept of "console" will include line discipline and similar higher-level concepts. This is all in preparation to switch over to using Minicom as the standard interface. As we head towards running this on hardware, it's going to make more sense to make the serial terminal experience on Minicom a good one. --- diff --git a/src/console.rs b/src/console.rs deleted file mode 100644 index f337df9..0000000 --- a/src/console.rs +++ /dev/null @@ -1,91 +0,0 @@ -use spinning_top::Spinlock; -use uart_16550::MmioSerialPort; - -/* - * The goal here is to produce a console logging singleton - * that can then be accessed through print! and println! - * macros. Singletons are kinda frowned on in Rust, but - * this can still be done. It requires some kind of sync - * mutex (I use spinning_top because the stdlib Mutex is - * not available), hiding initialization state (this uses - * an Option because the static initializaer code needs - * to be some kind of constant expression), and an AP - * that mutates the state but doesn't leak it (here, it's - * output-only macros). - */ - -pub struct Console { - serial_port: MmioSerialPort, -} - -impl Console { - pub unsafe fn new(base: usize) -> Self { - let mut serial_port = MmioSerialPort::new(base); - serial_port.init(); - Console { serial_port } - } - - pub fn write(&mut self, data: u8) { - self.serial_port.send(data); - } -} - -// Write trait is used in the macros below; this basicallt -// is a simple function for writing a string slice out to -// the console. - -impl core::fmt::Write for Console { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - return self.serial_port.write_str(s); - } -} - -static SERIAL_BASE: usize = 0x1000_0000; - -// Spinlock provides us a means to have a global console that is initialized -// at runtime. Note below that locking outside of this module seems to have -// odd behaviors, especially failure to ever acquire the lock. -// The public specifier is here to enable the use of macros. -pub static CONSOLE: Spinlock> = Spinlock::new(None); - -// Since we can't use something like lazy_static!, we're left with having -// to explicitly call the init first. This should be done only once -// at early initialization time -pub fn init_console() { - let mut console = CONSOLE.lock(); - *console = Some(unsafe { Console::new(SERIAL_BASE) }) -} - -// Read a byte from the underlying UART, clearing any asserted interrupt in the process. -// It's not entirely clear why, but locking CONSOLE outside of this module, or at least doing -// so while in interrupt context, seems to never return, but providing a public function -// to do the work gets around this problem. -pub fn console_read() -> u8 { - return CONSOLE - .lock() - .as_mut() - .unwrap_or_else(|| panic!("Failed to acquire console for reading!")) - .serial_port - .receive(); -} - -// These, like a lot of the code in here, came courtesy of Meyer Zinn's -// baremetal Rust tutorials: https://meyerzinn.tech/posts/2023/03/08/p1-printing-and-allocating/ -// I don't know how to use macros that well yet! - -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => ({ - use core::fmt::Write; - $crate::console::CONSOLE.lock().as_mut().map(|writer| { - writer.write_fmt(format_args!($($arg)*)).unwrap() - }); - }); -} - -/// println prints a formatted string to the [CONSOLE] with a trailing newline character. -#[macro_export] -macro_rules! println { - ($fmt:expr) => ($crate::print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => ($crate::print!(concat!($fmt, "\n"), $($arg)*)); -} diff --git a/src/main.rs b/src/main.rs index 3e81694..b7d72ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ #![feature(panic_info_message)] #[macro_use] -mod console; +mod uart; mod heap; mod trap; @@ -90,10 +90,10 @@ unsafe extern "C" fn _start() -> ! { #[no_mangle] // Again, being mindful of the C calling convention extern "C" fn entry() -> ! { // Init serial console - use crate::console; + use crate::uart; use core::arch::asm; - console::init_console(); - println!("Console init complete"); + uart::init_uart(); + println!("UART init complete"); // Init heap use crate::heap; diff --git a/src/trap.rs b/src/trap.rs index 7f3885b..b40787f 100644 --- a/src/trap.rs +++ b/src/trap.rs @@ -1,4 +1,4 @@ -use crate::console::console_read; +use crate::uart::uart_read; use core::arch::asm; use plic::Plic; @@ -90,7 +90,7 @@ extern "C" fn m_trap( let plic = get_plic(); match plic.claim(0) { Some(int) => { - let data = console_read(); + let data = uart_read(); print!("{}", data as char); plic.complete(0, int); } diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 0000000..d0d6cc6 --- /dev/null +++ b/src/uart.rs @@ -0,0 +1,91 @@ +use spinning_top::Spinlock; +use uart_16550::MmioSerialPort; + +/* + * The goal here is to produce a console logging singleton + * that can then be accessed through print! and println! + * macros. Singletons are kinda frowned on in Rust, but + * this can still be done. It requires some kind of sync + * mutex (I use spinning_top because the stdlib Mutex is + * not available), hiding initialization state (this uses + * an Option because the static initializaer code needs + * to be some kind of constant expression), and an AP + * that mutates the state but doesn't leak it (here, it's + * output-only macros). + */ + +pub struct Uart { + serial_port: MmioSerialPort, +} + +impl Uart { + pub unsafe fn new(base: usize) -> Self { + let mut serial_port = MmioSerialPort::new(base); + serial_port.init(); + Uart { serial_port } + } + + pub fn write(&mut self, data: u8) { + self.serial_port.send(data); + } +} + +// Write trait is used in the macros below; this basicallt +// is a simple function for writing a string slice out to +// the console. + +impl core::fmt::Write for Uart { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + return self.serial_port.write_str(s); + } +} + +static SERIAL_BASE: usize = 0x1000_0000; + +// Spinlock provides us a means to have a global console that is initialized +// at runtime. Note below that locking outside of this module seems to have +// odd behaviors, especially failure to ever acquire the lock. +// The public specifier is here to enable the use of macros. +pub static UART: Spinlock> = Spinlock::new(None); + +// Since we can't use something like lazy_static!, we're left with having +// to explicitly call the init first. This should be done only once +// at early initialization time +pub fn init_uart() { + let mut uart = UART.lock(); + *uart = Some(unsafe { Uart::new(SERIAL_BASE) }) +} + +// Read a byte from the underlying UART, clearing any asserted interrupt in the process. +// It's not entirely clear why, but locking CONSOLE outside of this module, or at least doing +// so while in interrupt context, seems to never return, but providing a public function +// to do the work gets around this problem. +pub fn uart_read() -> u8 { + return UART + .lock() + .as_mut() + .unwrap_or_else(|| panic!("Failed to acquire console for reading!")) + .serial_port + .receive(); +} + +// These, like a lot of the code in here, came courtesy of Meyer Zinn's +// baremetal Rust tutorials: https://meyerzinn.tech/posts/2023/03/08/p1-printing-and-allocating/ +// I don't know how to use macros that well yet! + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ({ + use core::fmt::Write; + $crate::uart::UART.lock().as_mut().map(|writer| { + writer.write_fmt(format_args!($($arg)*)).unwrap() + }); + }); +} + +/// println prints a formatted string to the [CONSOLE] with a trailing newline character. +#[macro_export] +macro_rules! println { + ($fmt:expr) => ($crate::print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::print!(concat!($fmt, "\n"), $($arg)*)); +}