use spin::Once;
use crate::{
arch::{
boot::DEVICE_TREE,
io::io_mem::{read_once, write_once},
mm::paddr_to_daddr,
},
console::uart_ns16650a::{Ns16550aAccess, Ns16550aRegister, Ns16550aUart},
sync::{LocalIrqDisabled, SpinLock},
};
pub static SERIAL_PORT: Once<SpinLock<Ns16550aUart<SerialAccess>, LocalIrqDisabled>> = Once::new();
pub struct SerialAccess {
base: *mut u8,
}
unsafe impl Send for SerialAccess {}
unsafe impl Sync for SerialAccess {}
impl SerialAccess {
const unsafe fn new(base: *mut u8) -> Self {
Self { base }
}
}
impl Ns16550aAccess for SerialAccess {
fn read(&self, reg: Ns16550aRegister) -> u8 {
unsafe { read_once(self.base.add(reg as usize)) }
}
fn write(&mut self, reg: Ns16550aRegister, val: u8) {
unsafe { write_once(self.base.add(reg as usize), val) }
}
}
pub(crate) fn init() {
let Some(base_address) = lookup_uart_base_address() else {
return;
};
let access = unsafe { SerialAccess::new(paddr_to_daddr(base_address) as *mut u8) };
let mut serial = Ns16550aUart::new(access);
serial.init();
SERIAL_PORT.call_once(move || SpinLock::new(serial));
}
fn lookup_uart_base_address() -> Option<usize> {
let device_tree = DEVICE_TREE.get().unwrap();
let stdout_path = device_tree
.find_node("/chosen")?
.property("stdout-path")?
.as_str()?;
let stdout = device_tree.find_node(stdout_path)?;
if stdout.compatible()?.all().any(|c| c == "ns16550a") {
Some(stdout.reg()?.next()?.starting_address as usize)
} else {
None
}
}