static SERIAL_BASE: usize = 0x1000_0000;
-// I'm not actually sure Spinlock is necessary here, because I saw that the
-// uart_16550 code already uses atomic_ptr, which sure sounds like it has
-// synchronization features.
-
+// 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<Option<Console>> = Spinlock::new(None);
// Since we can't use something like lazy_static!, we're left with having
*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!
+use crate::console::console_read;
use core::arch::asm;
use plic::Plic;
let plic = get_plic();
match plic.claim(0) {
Some(int) => {
- let uart = 0x1000_0000 as *mut u8;
- let data = uart.read_volatile();
+ let data = console_read();
print!("{}", data as char);
plic.complete(0, int);
}