diff --git a/Cargo.lock b/Cargo.lock index ded1094d8..513726330 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,6 +184,7 @@ dependencies = [ "aster-softirq", "aster-systree", "aster-time", + "aster-uart", "aster-util", "aster-virtio", "atomic-integer-wrapper", @@ -338,6 +339,19 @@ dependencies = [ "spin", ] +[[package]] +name = "aster-uart" +version = "0.1.0" +dependencies = [ + "aster-console", + "component", + "fdt", + "inherit-methods-macro", + "log", + "ostd", + "spin", +] + [[package]] name = "aster-util" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0199f977f..3fcc0779d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "kernel/comps/softirq", "kernel/comps/systree", "kernel/comps/time", + "kernel/comps/uart", "kernel/comps/virtio", "kernel/libs/aster-bigtcp", "kernel/libs/aster-rights", @@ -103,6 +104,7 @@ aster-pci = { path = "kernel/comps/pci" } aster-softirq = { path = "kernel/comps/softirq" } aster-systree = { path = "kernel/comps/systree" } aster-time = { path = "kernel/comps/time" } +aster-uart = { path = "kernel/comps/uart" } aster-virtio = { path = "kernel/comps/virtio" } # Crates under kernel/libs diff --git a/Components.toml b/Components.toml index f73ab22ec..5e6227ec6 100644 --- a/Components.toml +++ b/Components.toml @@ -14,6 +14,7 @@ pci = { name = "aster-pci" } softirq = { name = "aster-softirq" } systree = { name = "aster-systree" } time = { name = "aster-time" } +uart = { name = "aster-uart" } virtio = { name = "aster-virtio" } [whitelist] diff --git a/Makefile b/Makefile index 3abb2734a..29a17de90 100644 --- a/Makefile +++ b/Makefile @@ -244,6 +244,7 @@ OSDK_CRATES := \ kernel/comps/softirq \ kernel/comps/systree \ kernel/comps/time \ + kernel/comps/uart \ kernel/comps/virtio \ kernel/libs/aster-bigtcp \ kernel/libs/aster-util \ diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 9544d1d66..e7e33ca9a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -22,6 +22,7 @@ aster-rights-proc.workspace = true aster-softirq.workspace = true aster-systree.workspace = true aster-time.workspace = true +aster-uart.workspace = true aster-util.workspace = true aster-virtio.workspace = true atomic-integer-wrapper.workspace = true diff --git a/kernel/comps/uart/Cargo.toml b/kernel/comps/uart/Cargo.toml new file mode 100644 index 000000000..570c0c7a9 --- /dev/null +++ b/kernel/comps/uart/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "aster-uart" +version = "0.1.0" +edition.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aster-console.workspace = true +component.workspace = true +inherit-methods-macro.workspace = true +log.workspace = true +ostd.workspace = true +spin.workspace = true + +[target.riscv64imac-unknown-none-elf.dependencies] +fdt = { version = "0.1.5", features = ["pretty-printing"] } + +[lints] +workspace = true diff --git a/kernel/comps/uart/src/arch/loongarch/mod.rs b/kernel/comps/uart/src/arch/loongarch/mod.rs new file mode 100644 index 000000000..32c1efff6 --- /dev/null +++ b/kernel/comps/uart/src/arch/loongarch/mod.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd::arch::serial::SERIAL_PORT; + +use crate::{ + CONSOLE_NAME, + alloc::string::ToString, + console::{Uart, UartConsole}, +}; + +pub(super) fn init() { + let Some(uart) = SERIAL_PORT.get() else { + return; + }; + + let uart_console = UartConsole::new(uart); + + aster_console::register_device(CONSOLE_NAME.to_string(), uart_console.clone()); + + // TODO: Set up the IRQ line and handle the received data. + // Suppress the dead code warnings of the related methods. + let _ = || uart_console.trigger_input_callbacks(); + let _ = || uart.flush(); + + log::info!("[UART]: Registered NS16550A as a console"); +} diff --git a/kernel/comps/uart/src/arch/riscv/mod.rs b/kernel/comps/uart/src/arch/riscv/mod.rs new file mode 100644 index 000000000..7d4c634f9 --- /dev/null +++ b/kernel/comps/uart/src/arch/riscv/mod.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd::arch::boot::DEVICE_TREE; + +mod ns16550a; + +pub(super) fn init() { + let device_tree = DEVICE_TREE.get().unwrap(); + + if let Some(ns16550a_node) = device_tree.find_compatible(&["ns16550a"]) { + ns16550a::init(ns16550a_node); + } +} diff --git a/kernel/comps/uart/src/arch/riscv/ns16550a.rs b/kernel/comps/uart/src/arch/riscv/ns16550a.rs new file mode 100644 index 000000000..0ab295184 --- /dev/null +++ b/kernel/comps/uart/src/arch/riscv/ns16550a.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::string::ToString; + +use fdt::node::FdtNode; +use ostd::{ + arch::irq::{IRQ_CHIP, InterruptSourceInFdt, MappedIrqLine}, + console::uart_ns16650a::{Ns16550aAccess, Ns16550aRegister, Ns16550aUart}, + io::IoMem, + irq::IrqLine, + mm::VmIoOnce, + sync::SpinLock, +}; +use spin::Once; + +use crate::{ + CONSOLE_NAME, + console::{Uart, UartConsole}, +}; + +/// Access to serial registers via `IoMem`. +struct SerialAccess { + io_mem: IoMem, +} + +impl Ns16550aAccess for SerialAccess { + fn read(&self, reg: Ns16550aRegister) -> u8 { + self.io_mem.read_once(reg as u16 as usize).unwrap() + } + + fn write(&mut self, reg: Ns16550aRegister, val: u8) { + self.io_mem.write_once(reg as u16 as usize, &val).unwrap(); + } +} + +/// IRQ line for UART serial. +static IRQ_LINE: Once = Once::new(); + +pub(super) fn init(fdt_node: FdtNode) { + let Some(reg) = fdt_node.reg().and_then(|mut regs| regs.next()) else { + log::info!("[UART]: Failed to read 'reg' property from NS16550A node"); + return; + }; + let Some(reg_size) = reg.size else { + log::info!("[UART]: Incomplete 'reg' property found in NS16550A node"); + return; + }; + + let reg_addr = reg.starting_address as usize; + let Ok(io_mem) = IoMem::acquire(reg_addr..reg_addr + reg_size) else { + log::info!("[UART]: I/O memory is not available for NS16550A"); + return; + }; + + let Some(intr_parent) = fdt_node + .property("interrupt-parent") + .and_then(|prop| prop.as_usize()) + else { + log::info!("[UART]: Failed to read 'interrupt-parent' property from NS16550A node"); + return; + }; + let Some(intr) = fdt_node.interrupts().and_then(|mut intrs| intrs.next()) else { + log::info!("[UART]: Failed to read 'interrupts' property from NS16550A node"); + return; + }; + + let Ok(mut irq_line) = IrqLine::alloc().and_then(|irq_line| { + IRQ_CHIP.get().unwrap().map_fdt_pin_to( + InterruptSourceInFdt { + interrupt_parent: intr_parent as u32, + interrupt: intr as u32, + }, + irq_line, + ) + }) else { + log::info!("[UART]: IRQ line is not available for NS16550A"); + return; + }; + + let mut uart = Ns16550aUart::new(SerialAccess { io_mem }); + uart.init(); + + let uart_console = UartConsole::new(SpinLock::new(uart)); + + aster_console::register_device(CONSOLE_NAME.to_string(), uart_console.clone()); + + let cloned_uart_console = uart_console.clone(); + irq_line.on_active(move |_| cloned_uart_console.trigger_input_callbacks()); + IRQ_LINE.call_once(move || irq_line); + uart_console.uart().flush(); + + log::info!("[UART]: Registered NS16550A as a console"); +} diff --git a/kernel/comps/uart/src/arch/x86/mod.rs b/kernel/comps/uart/src/arch/x86/mod.rs new file mode 100644 index 000000000..929397579 --- /dev/null +++ b/kernel/comps/uart/src/arch/x86/mod.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::string::ToString; + +use ostd::{ + arch::{ + irq::{IRQ_CHIP, MappedIrqLine}, + serial::SERIAL_PORT, + }, + irq::IrqLine, +}; +use spin::Once; + +use crate::{ + CONSOLE_NAME, + console::{Uart, UartConsole}, +}; + +/// ISA interrupt number for UART serial. +// FIXME: The interrupt number should be retrieved from the ACPI table instead of being hard-coded. +const ISA_INTR_NUM: u8 = 4; + +/// IRQ line for UART serial. +static IRQ_LINE: Once = Once::new(); + +pub(super) fn init() { + let Some(uart) = SERIAL_PORT.get() else { + return; + }; + + let Ok(mut irq_line) = IrqLine::alloc().and_then(|irq_line| { + IRQ_CHIP + .get() + .unwrap() + .map_isa_pin_to(irq_line, ISA_INTR_NUM) + }) else { + log::info!("[UART]: IRQ line is not available"); + return; + }; + + let uart_console = UartConsole::new(uart); + + aster_console::register_device(CONSOLE_NAME.to_string(), uart_console.clone()); + + irq_line.on_active(move |_| uart_console.trigger_input_callbacks()); + IRQ_LINE.call_once(move || irq_line); + uart.flush(); + + log::info!("[UART]: Registered NS16550A as a console"); +} diff --git a/kernel/comps/uart/src/console.rs b/kernel/comps/uart/src/console.rs new file mode 100644 index 000000000..6ddf7362c --- /dev/null +++ b/kernel/comps/uart/src/console.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{sync::Arc, vec::Vec}; +use core::fmt::Debug; + +use aster_console::{AnyConsoleDevice, ConsoleCallback}; +use inherit_methods_macro::inherit_methods; +use ostd::{ + console::uart_ns16650a::{Ns16550aAccess, Ns16550aUart}, + mm::VmReader, + sync::{LocalIrqDisabled, SpinLock}, +}; + +/// A UART console. +pub(super) struct UartConsole { + uart: U, + callbacks: SpinLock, LocalIrqDisabled>, +} + +impl Debug for UartConsole { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("UartConsole").finish_non_exhaustive() + } +} + +impl UartConsole { + /// Creates a new UART console. + pub(super) fn new(uart: U) -> Arc { + Arc::new(Self { + uart, + callbacks: SpinLock::new(Vec::new()), + }) + } + + /// Returns a reference to the UART instance. + #[cfg_attr(not(target_arch = "riscv64"), expect(dead_code))] + pub(super) fn uart(&self) -> &U { + &self.uart + } + + // Triggers the registered input callbacks. + pub(super) fn trigger_input_callbacks(&self) { + let mut buf = [0; 16]; + + loop { + let num_rcv = self.uart.recv(&mut buf); + if num_rcv == 0 { + break; + } + + let reader = VmReader::from(&buf[..num_rcv]); + for callback in self.callbacks.lock().iter() { + (callback)(reader.clone()); + } + + if num_rcv < buf.len() { + break; + } + } + } +} + +impl AnyConsoleDevice for UartConsole { + fn send(&self, buf: &[u8]) { + self.uart.send(buf); + } + + fn register_callback(&self, callback: &'static ConsoleCallback) { + self.callbacks.lock().push(callback); + } +} + +/// A trait that abstracts UART devices. +pub(super) trait Uart { + /// Sends a sequence of bytes to UART. + fn send(&self, buf: &[u8]); + + /// Receives a sequence of bytes from UART and returns the number of received bytes. + #[must_use] + fn recv(&self, buf: &mut [u8]) -> usize; + + /// Flushes the received buffer. + /// + /// This method should be called after setting up the IRQ handlers to ensure new received data + /// will trigger IRQs. + fn flush(&self); +} + +impl Uart for SpinLock, LocalIrqDisabled> { + fn send(&self, buf: &[u8]) { + let mut uart = self.lock(); + + for byte in buf { + // TODO: This is termios-specific behavior and should be part of the TTY implementation + // instead of the serial console implementation. See the ONLCR flag for more details. + if *byte == b'\n' { + uart.send(b'\r'); + } + uart.send(*byte); + } + } + + fn recv(&self, buf: &mut [u8]) -> usize { + let mut uart = self.lock(); + + for (i, byte) in buf.iter_mut().enumerate() { + let Some(recv_byte) = uart.recv() else { + return i; + }; + *byte = recv_byte; + } + + buf.len() + } + + fn flush(&self) { + let mut uart = self.lock(); + + while uart.recv().is_some() {} + } +} + +#[inherit_methods(from = "(**self)")] +impl Uart for &SpinLock, LocalIrqDisabled> { + fn send(&self, buf: &[u8]); + fn recv(&self, buf: &mut [u8]) -> usize; + fn flush(&self); +} diff --git a/kernel/comps/uart/src/lib.rs b/kernel/comps/uart/src/lib.rs new file mode 100644 index 000000000..18e12d8a6 --- /dev/null +++ b/kernel/comps/uart/src/lib.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Universal asynchronous receiver-transmitter (UART). + +#![no_std] +#![deny(unsafe_code)] + +extern crate alloc; + +use component::{ComponentInitError, init_component}; + +#[cfg_attr(target_arch = "x86_64", path = "arch/x86/mod.rs")] +#[cfg_attr(target_arch = "riscv64", path = "arch/riscv/mod.rs")] +#[cfg_attr(target_arch = "loongarch64", path = "arch/loongarch/mod.rs")] +mod arch; + +mod console; + +pub const CONSOLE_NAME: &str = "Uart-Console"; + +#[init_component] +fn init() -> Result<(), ComponentInitError> { + arch::init(); + Ok(()) +} diff --git a/ostd/src/arch/loongarch/mod.rs b/ostd/src/arch/loongarch/mod.rs index 196f15d77..cf3dae50e 100644 --- a/ostd/src/arch/loongarch/mod.rs +++ b/ostd/src/arch/loongarch/mod.rs @@ -11,7 +11,7 @@ mod io; pub(crate) mod iommu; pub(crate) mod irq; pub(crate) mod mm; -pub(crate) mod serial; +pub mod serial; pub(crate) mod task; mod timer; pub mod trap; diff --git a/ostd/src/arch/x86/mod.rs b/ostd/src/arch/x86/mod.rs index 40e42ca10..351b713f0 100644 --- a/ostd/src/arch/x86/mod.rs +++ b/ostd/src/arch/x86/mod.rs @@ -11,7 +11,7 @@ pub mod irq; pub mod kernel; pub(crate) mod mm; mod power; -pub(crate) mod serial; +pub mod serial; pub(crate) mod task; mod timer; pub mod trap;