monitor: implement IO for monitor console master
authorKit Rhett Aultman <kit@kitaultman.com>
Thu, 17 Oct 2024 03:10:44 +0000 (23:10 -0400)
committerKit Rhett Aultman <kit@kitaultman.com>
Thu, 17 Oct 2024 03:10:44 +0000 (23:10 -0400)
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.

src/main.rs
src/monitor.rs [new file with mode: 0644]
src/trap.rs
src/uart.rs
src/uart_console.rs [new file with mode: 0644]

index a12d09b60755387a7bedf4712d87cd16ed42cb83..199123a4833b0cb89d60c8dbb600bb75ea380f73 100644 (file)
@@ -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<UartConsole>;
     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::<Monitor>(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 (file)
index 0000000..33f9367
--- /dev/null
@@ -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<UartConsole>,
+}
+
+impl ConsoleListener for Monitor {
+    fn new(uart_console: Weak<UartConsole>) -> Arc<Self> {
+        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("(*)");
+    }
+}
index b40cb034b1117cb675caff386e5976ca45b9844a..1faf738543828d558f954df800b29657a94a82ba 100644 (file)
@@ -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<UartConsole>,
 }
 
 
 impl TrapFrame {
-    pub fn new(u: Uart) -> Self {
+    pub fn new(u: Arc<UartConsole>) -> 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);
                         }
                         _ => {
index 29b50fadb1bcec4b3e05d4789db95cc3952dc243..d4c35b34c9cb103d63447688c9a258fe4fc16c5e 100644 (file)
@@ -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<MmioSerialPort>,
 }
 
 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 (file)
index 0000000..aabaabb
--- /dev/null
@@ -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<UartConsole>) -> Arc<Self>
+    where
+        Self: Sized;
+    fn new_input_line(&self, s: &String);
+    fn init(&self);
+}
+
+pub struct UartConsole {
+    uart: Uart,
+    buf: Spinlock<Vec<u8>>,
+    listener: Arc<dyn ConsoleListener>,
+}
+
+impl UartConsole {
+    pub fn new<T: ConsoleListener>(u: Uart) -> Arc<Self> {
+        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();
+    }
+}