uart/trap/main: remove global read_uart fn
authorKit Rhett Aultman <kit@kitaultman.com>
Sun, 29 Sep 2024 18:58:30 +0000 (14:58 -0400)
committerKit Rhett Aultman <kit@kitaultman.com>
Sun, 29 Sep 2024 18:58:30 +0000 (14:58 -0400)
The previous implementation of the UART interrupt handler relied on a
public global function, read_uart, in order to get data waiting in the
UART's registers.  This is an artifact of the Uart code which focuses on
having a single global Uart, largely so the println!() macros can work
with it.

It seems a bit awkward going forward having all possible read/write
points of the UART going through global functions; indeed, future code
would like to work simply by writing bytes or strings into interfaces
and not worrying about where it goes, so as a stepping stone on that
way, this commit refactors out the global read_uart( ) function and
replaces it with a scheme where the interrupt handler has its own
interface to the UART.

Having multiple Uart objects is fine for now but really only because we
know we're single-threading.  More ideal would be, in the future, for
each Uart to be a reference to a single Uart struct, since the Uart's
underlying implementation already has some amount of concurrency
control.  At the end of the day, there's one single UART on the system,
and we really want to create a situation where code writes into, say, a
Console interface instead of the Uart, and there's a "console manager"
deciding what's going to the UART and what isn't.

Baby steps, though.

src/main.rs
src/trap.rs
src/uart.rs

index b7d72ac58c08231a953e8d9526c887ac5e24693b..a12d09b60755387a7bedf4712d87cd16ed42cb83 100644 (file)
@@ -14,6 +14,13 @@ use core::arch::asm;
 use core::arch::global_asm;
 use core::panic::PanicInfo;
 
+extern crate alloc;
+use alloc::boxed::Box;
+
+use trap::TrapFrame;
+use uart::Uart;
+use uart::SERIAL_BASE;
+
 global_asm!(include_str!("trap.S"));
 
 // cfg not test is set here because the analyzer and the test system
@@ -100,10 +107,11 @@ extern "C" fn entry() -> ! {
     // Safety: initialize once (and that's done now)
     unsafe { heap::init() };
 
-    // 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
     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)));
         asm!(
         // Turn on floating point support
         "li t0, 1 << 13",
@@ -112,7 +120,7 @@ extern "C" fn entry() -> ! {
         "csrw mscratch, {trap_frame}",
         "lla t2, m_trap_vector",
         "csrw mtvec, t2",
-        trap_frame = in(reg)(&raw mut trap::TRAP_FRAME as usize)
+        trap_frame = in(reg)(Box::into_raw(trap_frame) as usize)
           );
 
         // We're now good to take interrupts
index b40787f7e689a7666cf2c6ebf11b5bcf189ab508..b40cb034b1117cb675caff386e5976ca45b9844a 100644 (file)
@@ -1,9 +1,10 @@
 use crate::uart::uart_read;
+use crate::uart::Uart;
 use core::arch::asm;
 use plic::Plic;
 
 #[repr(C)]
-#[derive(Clone, Copy)]
+//#[derive(Clone, Copy)]
 pub struct TrapFrame {
     pub regs: [usize; 32], // 8 bytes per register; 32 gp regs (0 - 255)
     pub fregs: [usize; 32], // 8 bytes per register; 32 fp regs (256 - 511)
@@ -17,19 +18,20 @@ 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,
 }
 
+
 impl TrapFrame {
-    pub const fn new() -> Self {
+    pub fn new(u: Uart) -> Self {
         TrapFrame {
             regs: [0; 32],
             fregs: [0; 32],
+            uart: u
         }
     }
 }
 
-pub static mut TRAP_FRAME: TrapFrame = TrapFrame::new();
-
 // unsafe: call only once!
 pub unsafe fn init_interrupts() {
     asm!("csrsi mstatus, 0b1000", "li t2, 0xaaa", "csrw mie, t2");
@@ -52,7 +54,7 @@ extern "C" fn m_trap(
     cause: usize,
     hart: usize,
     _status: usize,
-    _frame: &mut TrapFrame,
+    frame: &mut TrapFrame,
 ) -> usize {
     // We're going to handle all traps in machine mode. RISC-V lets
     // us delegate to supervisor mode, but switching out SATP (virtual memory)
@@ -90,8 +92,8 @@ extern "C" fn m_trap(
                     let plic = get_plic();
                     match plic.claim(0) {
                         Some(int) => {
-                            let data = uart_read();
-                            print!("{}", data as char);
+                            let data = frame.uart.read();
+                            frame.uart.write(data);
                             plic.complete(0, int);
                         }
                         _ => {
index 73bbc4168dfb90fd09edfb11ab581ec5f8f8e22e..29b50fadb1bcec4b3e05d4789db95cc3952dc243 100644 (file)
@@ -28,6 +28,10 @@ impl Uart {
     pub fn write(&mut self, data: u8) {
         self.serial_port.send(data);
     }
+
+    pub fn read(&mut self) -> u8 {
+        return self.serial_port.receive();
+    }
 }
 
 // Write trait is used in the macros below; this basicallt
@@ -40,7 +44,7 @@ impl core::fmt::Write for Uart {
     }
 }
 
-static SERIAL_BASE: usize = 0x1000_0000;
+pub 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
@@ -56,19 +60,6 @@ pub fn init_uart() {
     *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!