diff --git a/.typos.toml b/.typos.toml index e498394cb..fb2f3d5ab 100644 --- a/.typos.toml +++ b/.typos.toml @@ -26,6 +26,7 @@ rto = "rto" typ = "typ" sigfault = "sigfault" sems = "sems" +THRE = "THRE" # Files with svg suffix are ignored to check. [type.svg] diff --git a/ostd/src/arch/loongarch/serial.rs b/ostd/src/arch/loongarch/serial.rs index 94c07e0aa..59a2ce291 100644 --- a/ostd/src/arch/loongarch/serial.rs +++ b/ostd/src/arch/loongarch/serial.rs @@ -4,104 +4,62 @@ use spin::Once; -use crate::arch::{boot::DEVICE_TREE, mm::paddr_to_daddr}; +use crate::{ + arch::{boot::DEVICE_TREE, mm::paddr_to_daddr}, + console::uart_ns16650a::{Ns16550aAccess, Ns16550aRegister, Ns16550aUart}, + sync::{LocalIrqDisabled, SpinLock}, +}; -bitflags::bitflags! { - struct LineStatusRegisterFlags: u8 { - const DR = 1 << 0; - const OE = 1 << 1; - const PE = 1 << 2; - const FE = 1 << 3; - const BI = 1 << 4; - const TFE = 1 << 5; - const TE = 1 << 6; - const ERROR = 1 << 7; - } +/// The primary serial port, which serves as an early console. +pub static SERIAL_PORT: Once, LocalIrqDisabled>> = Once::new(); + +/// Access to serial registers via I/O memory in LoongArch. +pub struct SerialAccess { + base: *mut u8, } -/// A memory-mapped UART driver for LoongArch. -/// -/// Reference: -struct Serial { - base_address: *mut u8, -} +unsafe impl Send for SerialAccess {} +unsafe impl Sync for SerialAccess {} -impl Serial { - const DATA_TRANSPORT_REGISTER_OFFSET: usize = 0; - const LINE_STATUS_REGISTER_OFFSET: usize = 5; - - /// Creates a serial driver. - /// +impl SerialAccess { /// # Safety /// - /// The base address must be a valid UART base address. - const unsafe fn new(base_address: *mut u8) -> Self { - Self { base_address } - } - - /// Sends data to the UART. - fn send(&self, c: u8) { - while !self - .line_status_register_flags() - .contains(LineStatusRegisterFlags::TFE) - { - core::hint::spin_loop(); - } - // SAFETY: The safety requirements are upheld by the constructor. - unsafe { - self.base_address - .add(Self::DATA_TRANSPORT_REGISTER_OFFSET) - .write_volatile(c); - } - } - - /// Receives data from the UART. - fn recv(&self) -> Option { - if self - .line_status_register_flags() - .contains(LineStatusRegisterFlags::DR) - { - // SAFETY: The safety requirements are upheld by the constructor. - Some(unsafe { - self.base_address - .add(Self::DATA_TRANSPORT_REGISTER_OFFSET) - .read_volatile() - }) - } else { - None - } - } - - fn line_status_register_flags(&self) -> LineStatusRegisterFlags { - // SAFETY: The safety requirements are upheld by the constructor. - let c = unsafe { - self.base_address - .add(Self::LINE_STATUS_REGISTER_OFFSET) - .read_volatile() - }; - LineStatusRegisterFlags::from_bits_truncate(c) + /// The caller must ensure that the base address is a valid serial base address and that it has + /// exclusive ownership of the serial registers. + const unsafe fn new(base: *mut u8) -> Self { + Self { base } } } -// SAFETY: For correctness purposes, the UART registers should not be accessed concurrently. -// However, doing so will not cause memory safety violations. -unsafe impl Send for Serial {} -unsafe impl Sync for Serial {} +impl Ns16550aAccess for SerialAccess { + fn read(&self, reg: Ns16550aRegister) -> u8 { + // SAFETY: `self.base + reg` is a valid register of the serial port. + unsafe { core::ptr::read_volatile(self.base.add(reg as u8 as usize)) } + } -/// The console UART. -static CONSOLE_COM1: Once = Once::new(); + fn write(&mut self, reg: Ns16550aRegister, val: u8) { + // SAFETY: `self.base + reg` is a valid register of the serial port. + unsafe { core::ptr::write_volatile(self.base.add(reg as u8 as usize), val) }; + } +} /// Initializes the serial port. pub(crate) fn init() { let Some(base_address) = lookup_uart_base_address() else { return; }; - // SAFETY: It is safe because the UART address is acquired from the device tree, - // and be mapped in DMW2. - CONSOLE_COM1.call_once(|| unsafe { Serial::new(paddr_to_daddr(base_address) as *mut u8) }); + + // SAFETY: + // 1. The base address is valid and correct because it is acquired from the device tree and + // mapped in DMW2. + // 2. FIXME: We should reserve the address region in `io_mem_allocator` to ensure the + // exclusive ownership. + 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)); } -// FIXME: We should reserve the address region in `io_mem_allocator`. fn lookup_uart_base_address() -> Option { let device_tree = DEVICE_TREE.get().unwrap(); let stdout_path = device_tree @@ -115,19 +73,3 @@ fn lookup_uart_base_address() -> Option { None } } - -/// Sends a byte on the serial port. -pub(crate) fn send(data: u8) { - // Note: It is the caller's responsibility to acquire the correct lock and ensure sequential - // access to the UART registers. - let Some(uart) = CONSOLE_COM1.get() else { - return; - }; - match data { - b'\n' => { - uart.send(b'\r'); - uart.send(b'\n'); - } - c => uart.send(c), - } -} diff --git a/ostd/src/arch/riscv/serial.rs b/ostd/src/arch/riscv/serial.rs index 215e735f8..a673a6f6e 100644 --- a/ostd/src/arch/riscv/serial.rs +++ b/ostd/src/arch/riscv/serial.rs @@ -2,10 +2,35 @@ //! The console I/O. +use core::fmt; + +use spin::Once; + +use crate::sync::{LocalIrqDisabled, SpinLock}; + +/// The primary serial port, which serves as an early console. +pub(crate) static SERIAL_PORT: Once> = + Once::initialized(SpinLock::new(SbiSerial::new())); + +/// A serial port that is implemented via RISC-V SBI. +pub(crate) struct SbiSerial { + _private: (), +} + +impl SbiSerial { + const fn new() -> Self { + Self { _private: () } + } +} + +impl fmt::Write for SbiSerial { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.as_bytes() { + sbi_rt::console_write_byte(*c); + } + Ok(()) + } +} + /// Initializes the serial port. pub(crate) fn init() {} - -/// Sends a byte on the serial port. -pub(crate) fn send(data: u8) { - sbi_rt::console_write_byte(data); -} diff --git a/ostd/src/arch/x86/device/mod.rs b/ostd/src/arch/x86/device/mod.rs index cdefb2d30..44ffc29d4 100644 --- a/ostd/src/arch/x86/device/mod.rs +++ b/ostd/src/arch/x86/device/mod.rs @@ -3,4 +3,3 @@ //! Device-related APIs. pub mod io_port; -pub mod serial; diff --git a/ostd/src/arch/x86/device/serial.rs b/ostd/src/arch/x86/device/serial.rs deleted file mode 100644 index 7a7d0f588..000000000 --- a/ostd/src/arch/x86/device/serial.rs +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! A port-mapped UART. Copied from uart_16550. - -#![expect(dead_code)] - -use crate::{ - arch::device::io_port::{ReadWriteAccess, WriteOnlyAccess}, - io::IoPort, -}; -/// A serial port. -/// -/// Serial ports are a legacy communications port common on IBM-PC compatible computers. -/// Ref: -pub struct SerialPort { - /// Data Register - data: IoPort, - /// Interrupt Enable Register - int_en: IoPort, - /// First In First Out Control Register - fifo_ctrl: IoPort, - /// Line control Register - line_ctrl: IoPort, - /// Modem Control Register - modem_ctrl: IoPort, - /// Line status Register - line_status: IoPort, - /// Modem Status Register - modem_status: IoPort, -} - -impl SerialPort { - /// Creates a serial port. - /// - /// # Safety - /// - /// The caller must ensure that the base port is a valid serial base port and that it has - /// exclusive ownership of the serial ports. - pub const unsafe fn new(port: u16) -> Self { - // SAFETY: The safety is upheld by the caller. - unsafe { - Self { - data: IoPort::new(port), - int_en: IoPort::new(port + 1), - fifo_ctrl: IoPort::new(port + 2), - line_ctrl: IoPort::new(port + 3), - modem_ctrl: IoPort::new(port + 4), - line_status: IoPort::new(port + 5), - modem_status: IoPort::new(port + 6), - } - } - } - - /// Initializes the serial port. - pub fn init(&self) { - // Disable interrupts - self.int_en.write(0x00); - // Enable DLAB - self.line_ctrl.write(0x80); - // Set maximum speed to 38400 bps by configuring DLL and DLM - self.data.write(0x03); - self.int_en.write(0x00); - // Disable DLAB and set data word length to 8 bits - self.line_ctrl.write(0x03); - // Enable FIFO, clear TX/RX queues and - // set interrupt watermark at 14 bytes - self.fifo_ctrl.write(0xC7); - // Mark data terminal ready, signal request to send - // and enable auxiliary output #2 (used as interrupt line for CPU) - self.modem_ctrl.write(0x0B); - // Enable interrupts - self.int_en.write(0x01); - } - - /// Sends data to the data port - #[inline] - pub fn send(&self, data: u8) { - self.data.write(data); - } - - /// Receives data from the data port - #[inline] - pub fn recv(&self) -> u8 { - self.data.read() - } - - /// Gets line status - #[inline] - pub fn line_status(&self) -> u8 { - self.line_status.read() - } -} diff --git a/ostd/src/arch/x86/serial.rs b/ostd/src/arch/x86/serial.rs index 04d1cf239..9f4118099 100644 --- a/ostd/src/arch/x86/serial.rs +++ b/ostd/src/arch/x86/serial.rs @@ -2,42 +2,87 @@ //! The console I/O. -use super::device::serial::SerialPort; -use crate::io::reserve_io_port_range; +use spin::Once; +use x86_64::instructions::port::ReadWriteAccess; -bitflags::bitflags! { - struct LineSts: u8 { - const INPUT_FULL = 1; - const OUTPUT_EMPTY = 1 << 5; - } -} +use crate::{ + console::uart_ns16650a::{Ns16550aAccess, Ns16550aRegister, Ns16550aUart}, + io::{IoPort, reserve_io_port_range}, + sync::{LocalIrqDisabled, SpinLock}, +}; -static CONSOLE_COM1_PORT: SerialPort = unsafe { SerialPort::new(0x3F8) }; +/// The primary serial port, which serves as an early console. +pub static SERIAL_PORT: Once, LocalIrqDisabled>> = + Once::initialized(SpinLock::new(Ns16550aUart::new( + // SAFETY: + // 1. It is assumed that the serial port exists and can be accessed via the I/O registers. + // (FIXME: This needs to be confirmed by checking the ACPI table or using kernel + // parameters to obtain early information for building the early console.) + // 2. `reserve_io_port_range` guarantees exclusive ownership of the I/O registers. + unsafe { SerialAccess::new(0x3F8) }, + ))); reserve_io_port_range!(0x3F8..0x400); -/// Initializes the serial port. -pub(crate) fn init() { - CONSOLE_COM1_PORT.init(); +/// Access to serial registers via I/O ports in x86. +#[derive(Debug)] +pub struct SerialAccess { + data: IoPort, + int_en: IoPort, + fifo_ctrl: IoPort, + line_ctrl: IoPort, + modem_ctrl: IoPort, + line_stat: IoPort, + modem_stat: IoPort, } -fn line_sts() -> LineSts { - LineSts::from_bits_truncate(CONSOLE_COM1_PORT.line_status()) -} - -/// Sends a byte on the serial port. -pub(crate) fn send(data: u8) { - match data { - 8 | 0x7F => { - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - CONSOLE_COM1_PORT.send(8); - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - CONSOLE_COM1_PORT.send(b' '); - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - CONSOLE_COM1_PORT.send(8); - } - _ => { - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - CONSOLE_COM1_PORT.send(data); +impl SerialAccess { + /// # Safety + /// + /// The caller must ensure that the base port is a valid serial base port and that it has + /// exclusive ownership of the serial registers. + const unsafe fn new(port: u16) -> Self { + // SAFETY: The safety is upheld by the caller. + unsafe { + Self { + data: IoPort::new(port), + int_en: IoPort::new(port + 1), + fifo_ctrl: IoPort::new(port + 2), + line_ctrl: IoPort::new(port + 3), + modem_ctrl: IoPort::new(port + 4), + line_stat: IoPort::new(port + 5), + modem_stat: IoPort::new(port + 6), + } } } } + +impl Ns16550aAccess for SerialAccess { + fn read(&self, reg: Ns16550aRegister) -> u8 { + match reg { + Ns16550aRegister::DataOrDivisorLo => self.data.read(), + Ns16550aRegister::IntEnOrDivisorHi => self.int_en.read(), + Ns16550aRegister::FifoCtrl => self.fifo_ctrl.read(), + Ns16550aRegister::LineCtrl => self.line_ctrl.read(), + Ns16550aRegister::ModemCtrl => self.modem_ctrl.read(), + Ns16550aRegister::LineStat => self.line_stat.read(), + Ns16550aRegister::ModemStat => self.modem_stat.read(), + } + } + + fn write(&mut self, reg: Ns16550aRegister, val: u8) { + match reg { + Ns16550aRegister::DataOrDivisorLo => self.data.write(val), + Ns16550aRegister::IntEnOrDivisorHi => self.int_en.write(val), + Ns16550aRegister::FifoCtrl => self.fifo_ctrl.write(val), + Ns16550aRegister::LineCtrl => self.line_ctrl.write(val), + Ns16550aRegister::ModemCtrl => self.modem_ctrl.write(val), + Ns16550aRegister::LineStat => self.line_stat.write(val), + Ns16550aRegister::ModemStat => self.modem_stat.write(val), + } + } +} + +/// Initializes the serial port. +pub(crate) fn init() { + SERIAL_PORT.get().unwrap().lock().init(); +} diff --git a/ostd/src/console.rs b/ostd/src/console/mod.rs similarity index 63% rename from ostd/src/console.rs rename to ostd/src/console/mod.rs index a5db63bb0..5773cd0b1 100644 --- a/ostd/src/console.rs +++ b/ostd/src/console/mod.rs @@ -2,35 +2,28 @@ //! Console output. -use core::fmt::{self, Arguments, Write}; +use core::fmt::{Arguments, Write}; -use crate::sync::{LocalIrqDisabled, SpinLock}; +use crate::arch::serial::SERIAL_PORT; -struct Stdout; - -impl Write for Stdout { - fn write_str(&mut self, s: &str) -> fmt::Result { - for &c in s.as_bytes() { - crate::arch::serial::send(c); - } - Ok(()) - } -} - -static STDOUT: SpinLock = SpinLock::new(Stdout); +pub mod uart_ns16650a; /// Prints formatted arguments to the console. pub fn early_print(args: Arguments) { + let Some(serial) = SERIAL_PORT.get() else { + return; + }; + #[cfg(target_arch = "x86_64")] crate::arch::if_tdx_enabled!({ // Hold the lock to prevent the logs from interleaving. - let _guard = STDOUT.lock(); + let _guard = serial.lock(); tdx_guest::print(args); } else { - STDOUT.lock().write_fmt(args).unwrap(); + serial.lock().write_fmt(args).unwrap(); }); #[cfg(not(target_arch = "x86_64"))] - STDOUT.lock().write_fmt(args).unwrap(); + serial.lock().write_fmt(args).unwrap(); } /// Prints to the console. diff --git a/ostd/src/console/uart_ns16650a.rs b/ostd/src/console/uart_ns16650a.rs new file mode 100644 index 000000000..474fc1add --- /dev/null +++ b/ostd/src/console/uart_ns16650a.rs @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! NS16550A UART. +//! +//! This is used as an early console in x86 and LoongArch. It also exists on some (but not all) +//! RISC-V and ARM platforms. +//! +//! Reference: + +use core::fmt; + +use bitflags::bitflags; + +/// Registers of a NS16550A UART. +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum Ns16550aRegister { + /// Receive/Transmit Data Register or Divisor Latch Low. + DataOrDivisorLo, + /// Interrupt Enable Register or Divisor Latch High. + IntEnOrDivisorHi, + /// FIFO Control Register. + FifoCtrl, + /// Line Control Register. + LineCtrl, + /// Modem Control Register. + ModemCtrl, + /// Line Status Register. + LineStat, + /// Modem Status Register. + ModemStat, +} + +/// A trait that provides methods to access NS16550A registers. +pub trait Ns16550aAccess { + /// Reads from an NS16550A register. + fn read(&self, reg: Ns16550aRegister) -> u8; + + /// Writes to an NS16550A register. + fn write(&mut self, reg: Ns16550aRegister, val: u8); +} + +/// An NS16550A UART. +#[derive(Debug)] +pub struct Ns16550aUart { + access: A, +} + +bitflags! { + struct LineStat: u8 { + /// Data ready (DR). + const DR = 1 << 0; + /// Transmitter holding register empty (THRE). + const THRE = 1 << 5; + } +} + +impl Ns16550aUart { + /// Creates a new instance. + pub const fn new(access: A) -> Self { + Self { access } + } + + /// Initializes the device. + /// + /// This will set the baud rate to 115200 bps and configure IRQs to trigger when new data is + /// received. + pub fn init(&mut self) { + // Divisor Latch Access Bit. + const DLAB: u8 = 0x80; + + // Baud Rate: 115200 bps / divisor + self.access.write(Ns16550aRegister::LineCtrl, DLAB); + self.access.write(Ns16550aRegister::DataOrDivisorLo, 0x01); + self.access.write(Ns16550aRegister::IntEnOrDivisorHi, 0x00); + + // Line Control: 8-bit, no parity, one stop bit. + self.access.write(Ns16550aRegister::LineCtrl, 0x03); + // FIFO Control: Disabled. + self.access.write(Ns16550aRegister::FifoCtrl, 0x00); + // Modem Control: IRQs enabled, RTS/DSR set. + self.access.write(Ns16550aRegister::ModemCtrl, 0x0B); + // Interrupt Enable: IRQs on received data. + self.access.write(Ns16550aRegister::IntEnOrDivisorHi, 0x01); + } + + /// Sends a byte. + /// + /// If no room is available, it will spin until there is room. + pub fn send(&mut self, data: u8) { + while !self.line_stat().contains(LineStat::THRE) { + core::hint::spin_loop(); + } + + self.access.write(Ns16550aRegister::DataOrDivisorLo, data); + } + + /// Receives a byte. + /// + /// If no byte is available, it will return `None`. + pub fn recv(&mut self) -> Option { + if !self.line_stat().contains(LineStat::DR) { + return None; + } + + Some(self.access.read(Ns16550aRegister::DataOrDivisorLo)) + } + + fn line_stat(&self) -> LineStat { + LineStat::from_bits_truncate(self.access.read(Ns16550aRegister::LineStat)) + } +} + +impl fmt::Write for Ns16550aUart { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.as_bytes() { + if *c == b'\n' { + self.send(b'\r'); + } + self.send(*c); + } + Ok(()) + } +} diff --git a/ostd/src/io/io_port/mod.rs b/ostd/src/io/io_port/mod.rs index 1b63208a6..a91eb4c57 100644 --- a/ostd/src/io/io_port/mod.rs +++ b/ostd/src/io/io_port/mod.rs @@ -21,7 +21,7 @@ use crate::{Error, prelude::*}; /// PORT.write(PORT.read() + 1) /// } /// ``` -/// +#[derive(Debug)] pub struct IoPort { port: u16, is_overlapping: bool,