ostd/console/
uart_ns16650a.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! NS16550A UART.
4//!
5//! This is used as an early console in x86 and LoongArch. It also exists on some (but not all)
6//! RISC-V and ARM platforms.
7//!
8//! Reference: <https://bitsavers.trailing-edge.com/components/national/_appNotes/AN-0491.pdf>
9
10use core::fmt;
11
12use bitflags::bitflags;
13
14/// Registers of a NS16550A UART.
15#[repr(u8)]
16#[derive(Clone, Copy, Debug)]
17pub enum Ns16550aRegister {
18    /// Receive/Transmit Data Register or Divisor Latch Low.
19    DataOrDivisorLo,
20    /// Interrupt Enable Register or Divisor Latch High.
21    IntEnOrDivisorHi,
22    /// FIFO Control Register.
23    FifoCtrl,
24    /// Line Control Register.
25    LineCtrl,
26    /// Modem Control Register.
27    ModemCtrl,
28    /// Line Status Register.
29    LineStat,
30    /// Modem Status Register.
31    ModemStat,
32}
33
34/// A trait that provides methods to access NS16550A registers.
35pub trait Ns16550aAccess {
36    /// Reads from an NS16550A register.
37    fn read(&self, reg: Ns16550aRegister) -> u8;
38
39    /// Writes to an NS16550A register.
40    fn write(&mut self, reg: Ns16550aRegister, val: u8);
41}
42
43/// An NS16550A UART.
44#[derive(Debug)]
45pub struct Ns16550aUart<A: Ns16550aAccess> {
46    access: A,
47}
48
49bitflags! {
50    struct LineStat: u8 {
51        /// Data ready (DR).
52        const DR    = 1 << 0;
53        /// Transmitter holding register empty (THRE).
54        const THRE  = 1 << 5;
55    }
56}
57
58impl<A: Ns16550aAccess> Ns16550aUart<A> {
59    /// Creates a new instance.
60    pub const fn new(access: A) -> Self {
61        Self { access }
62    }
63
64    /// Initializes the device.
65    ///
66    /// This will set the baud rate to 115200 bps and configure IRQs to trigger when new data is
67    /// received.
68    pub fn init(&mut self) {
69        // Divisor Latch Access Bit.
70        const DLAB: u8 = 0x80;
71
72        // Baud Rate: 115200 bps / divisor
73        self.access.write(Ns16550aRegister::LineCtrl, DLAB);
74        self.access.write(Ns16550aRegister::DataOrDivisorLo, 0x01);
75        self.access.write(Ns16550aRegister::IntEnOrDivisorHi, 0x00);
76
77        // Line Control: 8-bit, no parity, one stop bit.
78        self.access.write(Ns16550aRegister::LineCtrl, 0x03);
79        // FIFO Control: Disabled.
80        self.access.write(Ns16550aRegister::FifoCtrl, 0x00);
81        // Modem Control: IRQs enabled, RTS/DSR set.
82        self.access.write(Ns16550aRegister::ModemCtrl, 0x0B);
83        // Interrupt Enable: IRQs on received data.
84        self.access.write(Ns16550aRegister::IntEnOrDivisorHi, 0x01);
85    }
86
87    /// Sends a byte.
88    ///
89    /// If no room is available, it will spin until there is room.
90    pub fn send(&mut self, data: u8) {
91        while !self.line_stat().contains(LineStat::THRE) {
92            core::hint::spin_loop();
93        }
94
95        self.access.write(Ns16550aRegister::DataOrDivisorLo, data);
96    }
97
98    /// Receives a byte.
99    ///
100    /// If no byte is available, it will return `None`.
101    pub fn recv(&mut self) -> Option<u8> {
102        if !self.line_stat().contains(LineStat::DR) {
103            return None;
104        }
105
106        Some(self.access.read(Ns16550aRegister::DataOrDivisorLo))
107    }
108
109    fn line_stat(&self) -> LineStat {
110        LineStat::from_bits_truncate(self.access.read(Ns16550aRegister::LineStat))
111    }
112}
113
114impl<A: Ns16550aAccess> fmt::Write for Ns16550aUart<A> {
115    fn write_str(&mut self, s: &str) -> fmt::Result {
116        for c in s.as_bytes() {
117            if *c == b'\n' {
118                self.send(b'\r');
119            }
120            self.send(*c);
121        }
122        Ok(())
123    }
124}