From: Kit Rhett Aultman Date: Thu, 17 Oct 2024 03:10:44 +0000 (-0400) Subject: monitor: implement IO for monitor console X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=9202bbffd1e9cfb57e315c299df643694e0ca2b5;p=riscv_baremetal.git monitor: implement IO for monitor console This commit introduces the basic infrastructure for the monitor console, specifically providing some basic line-oriented IO so that the monitor (to be styled after Wozmon) can be written easily. This refactors out some of the aspects of the UART code into a UartConsole that can pe paired with a line-oriented "application". This also changes the creation of the TrapFrame into a heap allocation through Box. This is necessary because the UartConsole is owned by the TrapFrame. That sounds awkward, but the interrupt handler is currently the only code path from the "outside world" to reach the UartConsole. It's not possible to "plug in" the monitor if the TrapFrame is statically initialized, so it instead comes from the heap. Because both UartConsole and Monitor need to refer to one another in an abstracted way, one can't really own the other. The solution here is to use Arc::new_cyclic so that reference counting can help the compiler prove that memory won't leak. The UART handling code is now a little awkward-looking; there's still a global static Uart for the println!() macros but then an instantiated UartConsole, too. Longer-term, the goal will be to get rid of the global Uart object and make the println!() macro part of a more comprehensive console management system. --- diff --git a/src/main.rs b/src/main.rs index a12d09b..199123a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ mod uart; mod heap; mod trap; +mod uart_console; +mod monitor; use core::arch::asm; use core::arch::global_asm; @@ -17,6 +19,7 @@ use core::panic::PanicInfo; extern crate alloc; use alloc::boxed::Box; +use alloc::sync::Arc; use trap::TrapFrame; use uart::Uart; use uart::SERIAL_BASE; @@ -98,6 +101,8 @@ unsafe extern "C" fn _start() -> ! { extern "C" fn entry() -> ! { // Init serial console use crate::uart; + use crate::uart_console::UartConsole; + use crate::monitor::Monitor; use core::arch::asm; uart::init_uart(); println!("UART init complete"); @@ -107,11 +112,17 @@ extern "C" fn entry() -> ! { // Safety: initialize once (and that's done now) unsafe { heap::init() }; + + // Declared outside the unsafe block for scoping reasons + // we'll start the console ouside the unsafe block + let console_main_handle: Arc; unsafe { // Load the trap frame for interrupt handling // This must be done here rather than in the _start function because the _start function is naked and // we can't use keywords other than const and sym - let trap_frame = Box::new(TrapFrame::new(Uart::new(SERIAL_BASE))); + let console = UartConsole::new::(Uart::new(SERIAL_BASE)); + console_main_handle = console.clone(); + let trap_frame = Box::new(TrapFrame::new(console)); asm!( // Turn on floating point support "li t0, 1 << 13", @@ -127,9 +138,10 @@ extern "C" fn entry() -> ! { trap::init_interrupts(); } + println!("Going idle!"); println!("Kernel init complete"); + console_main_handle.start(); - println!("Going idle!"); unsafe { asm!("1:", "wfi", "j 1b") } // Hopefully wfi will make this less busy (1b is // 'backwards to 1:' loop {} // Definitely not gonna happen diff --git a/src/monitor.rs b/src/monitor.rs new file mode 100644 index 0000000..33f9367 --- /dev/null +++ b/src/monitor.rs @@ -0,0 +1,33 @@ +use crate::uart_console::UartConsole; +use crate::uart_console::ConsoleListener; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::sync::Weak; + +pub struct Monitor { + console: Weak, +} + +impl ConsoleListener for Monitor { + fn new(uart_console: Weak) -> Arc { + Arc::new_cyclic(|_self_ref| Self { + console: uart_console, + }) + } + + fn new_input_line(&self, s: &String) { + let _ = self.console.upgrade().unwrap().write_str("You said: "); + let _ = self.console.upgrade().unwrap().writeln(s); + self.print_prompt(); + } + + fn init(&self) { self.print_prompt(); } +} + +impl Monitor { + + + pub fn print_prompt(&self) { + let _ = self.console.upgrade().unwrap().write_str("(*)"); + } +} diff --git a/src/trap.rs b/src/trap.rs index b40cb03..1faf738 100644 --- a/src/trap.rs +++ b/src/trap.rs @@ -1,7 +1,7 @@ -use crate::uart::uart_read; -use crate::uart::Uart; +use crate::uart_console::UartConsole; use core::arch::asm; use plic::Plic; +use alloc::sync::Arc; #[repr(C)] //#[derive(Clone, Copy)] @@ -18,16 +18,16 @@ pub struct TrapFrame { // We're also skipping this; it's not needed when we've got only one hart and besides // that we're in machine mode. //pub hartid: usize, // 528 - pub uart: Uart, + pub uart_handler: Arc, } impl TrapFrame { - pub fn new(u: Uart) -> Self { + pub fn new(u: Arc) -> Self { TrapFrame { regs: [0; 32], fregs: [0; 32], - uart: u + uart_handler: u } } } @@ -92,8 +92,7 @@ extern "C" fn m_trap( let plic = get_plic(); match plic.claim(0) { Some(int) => { - let data = frame.uart.read(); - frame.uart.write(data); + frame.uart_handler.data_pending(); plic.complete(0, int); } _ => { diff --git a/src/uart.rs b/src/uart.rs index 29b50fa..d4c35b3 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -1,6 +1,6 @@ use spinning_top::Spinlock; use uart_16550::MmioSerialPort; - +use core::fmt::Write; /* * The goal here is to produce a console logging singleton * that can then be accessed through print! and println! @@ -15,22 +15,25 @@ use uart_16550::MmioSerialPort; */ pub struct Uart { - serial_port: MmioSerialPort, + serial_port: Spinlock, } impl Uart { pub unsafe fn new(base: usize) -> Self { - let mut serial_port = MmioSerialPort::new(base); - serial_port.init(); - Uart { serial_port } + let mut serial = MmioSerialPort::new(base); + serial.init(); + return Uart { serial_port: Spinlock::new(MmioSerialPort::new(base)) } } - pub fn write(&mut self, data: u8) { - self.serial_port.send(data); + pub fn write(&self, data: u8) { + self.serial_port.lock().send(data); } - pub fn read(&mut self) -> u8 { - return self.serial_port.receive(); + pub fn read(&self) -> u8 { + return self.serial_port.lock().receive(); + } + pub fn write_str_immutable(&self, s: &str) -> core::fmt::Result { + return self.serial_port.lock().write_str(s); } } @@ -40,7 +43,7 @@ impl Uart { impl core::fmt::Write for Uart { fn write_str(&mut self, s: &str) -> core::fmt::Result { - return self.serial_port.write_str(s); + return self.write_str_immutable(s); } } diff --git a/src/uart_console.rs b/src/uart_console.rs new file mode 100644 index 0000000..aabaabb --- /dev/null +++ b/src/uart_console.rs @@ -0,0 +1,72 @@ +use crate::uart::Uart; +use alloc::string::String; +use alloc::sync::{Arc, Weak}; +use alloc::vec::Vec; +use core::fmt::Result; +use spinning_top::Spinlock; + +pub trait ConsoleListener: 'static { + fn new(console: Weak) -> Arc + where + Self: Sized; + fn new_input_line(&self, s: &String); + fn init(&self); +} + +pub struct UartConsole { + uart: Uart, + buf: Spinlock>, + listener: Arc, +} + +impl UartConsole { + pub fn new(u: Uart) -> Arc { + let retval = Arc::new_cyclic(|self_ref| { + let monitor = T::new(self_ref.clone()); + let retval = Self { + uart: u, + buf: Spinlock::new(Vec::new()), + listener: monitor, + }; + return retval; + }); + return retval; + } + + pub fn data_pending(&self) { + let data = self.uart.read(); + let mut buf = self.buf.lock(); + match data { + // Backspace; drop last character + b'\x08' => { + buf.pop(); + } + b'\r' => { + buf.push(data); + buf.push(b'\n'); + self.uart.write(data); + self.uart.write(b'\n'); + let line = String::from_utf8_lossy(buf.as_slice()).into_owned(); + self.listener.new_input_line(&line); + buf.clear(); + } + _ => { + buf.push(data); + self.uart.write(data); + } + } + } + + pub fn write_str(&self, s: &str) -> Result { + return self.uart.write_str_immutable(s); + } + + pub fn writeln(&self, s: &str) -> Result { + self.uart.write_str_immutable(s)?; + return self.uart.write_str_immutable("\r\n"); + } + + pub fn start(&self) { + self.listener.init(); + } +}