Move NS16650A UART driver outside `arch`
This commit is contained in:
parent
dee39e21d1
commit
81c2f8d4bd
|
|
@ -26,6 +26,7 @@ rto = "rto"
|
||||||
typ = "typ"
|
typ = "typ"
|
||||||
sigfault = "sigfault"
|
sigfault = "sigfault"
|
||||||
sems = "sems"
|
sems = "sems"
|
||||||
|
THRE = "THRE"
|
||||||
|
|
||||||
# Files with svg suffix are ignored to check.
|
# Files with svg suffix are ignored to check.
|
||||||
[type.svg]
|
[type.svg]
|
||||||
|
|
|
||||||
|
|
@ -4,104 +4,62 @@
|
||||||
|
|
||||||
use spin::Once;
|
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! {
|
/// The primary serial port, which serves as an early console.
|
||||||
struct LineStatusRegisterFlags: u8 {
|
pub static SERIAL_PORT: Once<SpinLock<Ns16550aUart<SerialAccess>, LocalIrqDisabled>> = Once::new();
|
||||||
const DR = 1 << 0;
|
|
||||||
const OE = 1 << 1;
|
/// Access to serial registers via I/O memory in LoongArch.
|
||||||
const PE = 1 << 2;
|
pub struct SerialAccess {
|
||||||
const FE = 1 << 3;
|
base: *mut u8,
|
||||||
const BI = 1 << 4;
|
|
||||||
const TFE = 1 << 5;
|
|
||||||
const TE = 1 << 6;
|
|
||||||
const ERROR = 1 << 7;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A memory-mapped UART driver for LoongArch.
|
unsafe impl Send for SerialAccess {}
|
||||||
///
|
unsafe impl Sync for SerialAccess {}
|
||||||
/// Reference: <https://loongson.github.io/LoongArch-Documentation/Loongson-3A5000-usermanual-EN.html#uart-controller>
|
|
||||||
struct Serial {
|
|
||||||
base_address: *mut u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serial {
|
impl SerialAccess {
|
||||||
const DATA_TRANSPORT_REGISTER_OFFSET: usize = 0;
|
|
||||||
const LINE_STATUS_REGISTER_OFFSET: usize = 5;
|
|
||||||
|
|
||||||
/// Creates a serial driver.
|
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// The base address must be a valid UART base address.
|
/// The caller must ensure that the base address is a valid serial base address and that it has
|
||||||
const unsafe fn new(base_address: *mut u8) -> Self {
|
/// exclusive ownership of the serial registers.
|
||||||
Self { base_address }
|
const unsafe fn new(base: *mut u8) -> Self {
|
||||||
}
|
Self { base }
|
||||||
|
|
||||||
/// 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<u8> {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: For correctness purposes, the UART registers should not be accessed concurrently.
|
impl Ns16550aAccess for SerialAccess {
|
||||||
// However, doing so will not cause memory safety violations.
|
fn read(&self, reg: Ns16550aRegister) -> u8 {
|
||||||
unsafe impl Send for Serial {}
|
// SAFETY: `self.base + reg` is a valid register of the serial port.
|
||||||
unsafe impl Sync for Serial {}
|
unsafe { core::ptr::read_volatile(self.base.add(reg as u8 as usize)) }
|
||||||
|
}
|
||||||
|
|
||||||
/// The console UART.
|
fn write(&mut self, reg: Ns16550aRegister, val: u8) {
|
||||||
static CONSOLE_COM1: Once<Serial> = Once::new();
|
// 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.
|
/// Initializes the serial port.
|
||||||
pub(crate) fn init() {
|
pub(crate) fn init() {
|
||||||
let Some(base_address) = lookup_uart_base_address() else {
|
let Some(base_address) = lookup_uart_base_address() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// SAFETY: It is safe because the UART address is acquired from the device tree,
|
|
||||||
// and be mapped in DMW2.
|
// SAFETY:
|
||||||
CONSOLE_COM1.call_once(|| unsafe { Serial::new(paddr_to_daddr(base_address) as *mut u8) });
|
// 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<usize> {
|
fn lookup_uart_base_address() -> Option<usize> {
|
||||||
let device_tree = DEVICE_TREE.get().unwrap();
|
let device_tree = DEVICE_TREE.get().unwrap();
|
||||||
let stdout_path = device_tree
|
let stdout_path = device_tree
|
||||||
|
|
@ -115,19 +73,3 @@ fn lookup_uart_base_address() -> Option<usize> {
|
||||||
None
|
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,35 @@
|
||||||
|
|
||||||
//! The console I/O.
|
//! 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<SpinLock<SbiSerial, LocalIrqDisabled>> =
|
||||||
|
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.
|
/// Initializes the serial port.
|
||||||
pub(crate) fn init() {}
|
pub(crate) fn init() {}
|
||||||
|
|
||||||
/// Sends a byte on the serial port.
|
|
||||||
pub(crate) fn send(data: u8) {
|
|
||||||
sbi_rt::console_write_byte(data);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,3 @@
|
||||||
//! Device-related APIs.
|
//! Device-related APIs.
|
||||||
|
|
||||||
pub mod io_port;
|
pub mod io_port;
|
||||||
pub mod serial;
|
|
||||||
|
|
|
||||||
|
|
@ -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: <https://wiki.osdev.org/Serial_Ports>
|
|
||||||
pub struct SerialPort {
|
|
||||||
/// Data Register
|
|
||||||
data: IoPort<u8, ReadWriteAccess>,
|
|
||||||
/// Interrupt Enable Register
|
|
||||||
int_en: IoPort<u8, WriteOnlyAccess>,
|
|
||||||
/// First In First Out Control Register
|
|
||||||
fifo_ctrl: IoPort<u8, WriteOnlyAccess>,
|
|
||||||
/// Line control Register
|
|
||||||
line_ctrl: IoPort<u8, WriteOnlyAccess>,
|
|
||||||
/// Modem Control Register
|
|
||||||
modem_ctrl: IoPort<u8, WriteOnlyAccess>,
|
|
||||||
/// Line status Register
|
|
||||||
line_status: IoPort<u8, ReadWriteAccess>,
|
|
||||||
/// Modem Status Register
|
|
||||||
modem_status: IoPort<u8, ReadWriteAccess>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,42 +2,87 @@
|
||||||
|
|
||||||
//! The console I/O.
|
//! The console I/O.
|
||||||
|
|
||||||
use super::device::serial::SerialPort;
|
use spin::Once;
|
||||||
use crate::io::reserve_io_port_range;
|
use x86_64::instructions::port::ReadWriteAccess;
|
||||||
|
|
||||||
bitflags::bitflags! {
|
use crate::{
|
||||||
struct LineSts: u8 {
|
console::uart_ns16650a::{Ns16550aAccess, Ns16550aRegister, Ns16550aUart},
|
||||||
const INPUT_FULL = 1;
|
io::{IoPort, reserve_io_port_range},
|
||||||
const OUTPUT_EMPTY = 1 << 5;
|
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<SpinLock<Ns16550aUart<SerialAccess>, 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);
|
reserve_io_port_range!(0x3F8..0x400);
|
||||||
|
|
||||||
/// Initializes the serial port.
|
/// Access to serial registers via I/O ports in x86.
|
||||||
pub(crate) fn init() {
|
#[derive(Debug)]
|
||||||
CONSOLE_COM1_PORT.init();
|
pub struct SerialAccess {
|
||||||
|
data: IoPort<u8, ReadWriteAccess>,
|
||||||
|
int_en: IoPort<u8, ReadWriteAccess>,
|
||||||
|
fifo_ctrl: IoPort<u8, ReadWriteAccess>,
|
||||||
|
line_ctrl: IoPort<u8, ReadWriteAccess>,
|
||||||
|
modem_ctrl: IoPort<u8, ReadWriteAccess>,
|
||||||
|
line_stat: IoPort<u8, ReadWriteAccess>,
|
||||||
|
modem_stat: IoPort<u8, ReadWriteAccess>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_sts() -> LineSts {
|
impl SerialAccess {
|
||||||
LineSts::from_bits_truncate(CONSOLE_COM1_PORT.line_status())
|
/// # Safety
|
||||||
}
|
///
|
||||||
|
/// The caller must ensure that the base port is a valid serial base port and that it has
|
||||||
/// Sends a byte on the serial port.
|
/// exclusive ownership of the serial registers.
|
||||||
pub(crate) fn send(data: u8) {
|
const unsafe fn new(port: u16) -> Self {
|
||||||
match data {
|
// SAFETY: The safety is upheld by the caller.
|
||||||
8 | 0x7F => {
|
unsafe {
|
||||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
Self {
|
||||||
CONSOLE_COM1_PORT.send(8);
|
data: IoPort::new(port),
|
||||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
int_en: IoPort::new(port + 1),
|
||||||
CONSOLE_COM1_PORT.send(b' ');
|
fifo_ctrl: IoPort::new(port + 2),
|
||||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
line_ctrl: IoPort::new(port + 3),
|
||||||
CONSOLE_COM1_PORT.send(8);
|
modem_ctrl: IoPort::new(port + 4),
|
||||||
}
|
line_stat: IoPort::new(port + 5),
|
||||||
_ => {
|
modem_stat: IoPort::new(port + 6),
|
||||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
}
|
||||||
CONSOLE_COM1_PORT.send(data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,28 @@
|
||||||
|
|
||||||
//! Console output.
|
//! 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;
|
pub mod uart_ns16650a;
|
||||||
|
|
||||||
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<Stdout, LocalIrqDisabled> = SpinLock::new(Stdout);
|
|
||||||
|
|
||||||
/// Prints formatted arguments to the console.
|
/// Prints formatted arguments to the console.
|
||||||
pub fn early_print(args: Arguments) {
|
pub fn early_print(args: Arguments) {
|
||||||
|
let Some(serial) = SERIAL_PORT.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
crate::arch::if_tdx_enabled!({
|
crate::arch::if_tdx_enabled!({
|
||||||
// Hold the lock to prevent the logs from interleaving.
|
// Hold the lock to prevent the logs from interleaving.
|
||||||
let _guard = STDOUT.lock();
|
let _guard = serial.lock();
|
||||||
tdx_guest::print(args);
|
tdx_guest::print(args);
|
||||||
} else {
|
} else {
|
||||||
STDOUT.lock().write_fmt(args).unwrap();
|
serial.lock().write_fmt(args).unwrap();
|
||||||
});
|
});
|
||||||
#[cfg(not(target_arch = "x86_64"))]
|
#[cfg(not(target_arch = "x86_64"))]
|
||||||
STDOUT.lock().write_fmt(args).unwrap();
|
serial.lock().write_fmt(args).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints to the console.
|
/// Prints to the console.
|
||||||
|
|
@ -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: <https://bitsavers.trailing-edge.com/components/national/_appNotes/AN-0491.pdf>
|
||||||
|
|
||||||
|
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<A: Ns16550aAccess> {
|
||||||
|
access: A,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct LineStat: u8 {
|
||||||
|
/// Data ready (DR).
|
||||||
|
const DR = 1 << 0;
|
||||||
|
/// Transmitter holding register empty (THRE).
|
||||||
|
const THRE = 1 << 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Ns16550aAccess> Ns16550aUart<A> {
|
||||||
|
/// 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<u8> {
|
||||||
|
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<A: Ns16550aAccess> fmt::Write for Ns16550aUart<A> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ use crate::{Error, prelude::*};
|
||||||
/// PORT.write(PORT.read() + 1)
|
/// PORT.write(PORT.read() + 1)
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
#[derive(Debug)]
|
||||||
pub struct IoPort<T, A> {
|
pub struct IoPort<T, A> {
|
||||||
port: u16,
|
port: u16,
|
||||||
is_overlapping: bool,
|
is_overlapping: bool,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue