Support UART console

This commit is contained in:
Ruihan Li 2025-12-22 22:00:03 +08:00 committed by Tate, Hongliang Tian
parent 81c2f8d4bd
commit 000ad53c9f
14 changed files with 376 additions and 2 deletions

14
Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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]

View File

@ -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 \

View File

@ -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

View File

@ -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

View File

@ -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");
}

View File

@ -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);
}
}

View File

@ -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<MappedIrqLine> = 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");
}

View File

@ -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<MappedIrqLine> = 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");
}

View File

@ -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<U: Uart> {
uart: U,
callbacks: SpinLock<Vec<&'static ConsoleCallback>, LocalIrqDisabled>,
}
impl<U: Uart> Debug for UartConsole<U> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("UartConsole").finish_non_exhaustive()
}
}
impl<U: Uart> UartConsole<U> {
/// Creates a new UART console.
pub(super) fn new(uart: U) -> Arc<Self> {
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<U: Uart + Send + Sync + 'static> AnyConsoleDevice for UartConsole<U> {
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<A: Ns16550aAccess> Uart for SpinLock<Ns16550aUart<A>, 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<A: Ns16550aAccess> Uart for &SpinLock<Ns16550aUart<A>, LocalIrqDisabled> {
fn send(&self, buf: &[u8]);
fn recv(&self, buf: &mut [u8]) -> usize;
fn flush(&self);
}

View File

@ -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(())
}

View File

@ -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;

View File

@ -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;