Support UART console
This commit is contained in:
parent
459acfcbec
commit
c55dd78b69
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
1
Makefile
1
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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue