Skip to main content

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
34impl Ns16550aRegister {
35    /// A variant with the largest value.
36    pub const MAX: Self = Self::ModemStat;
37}
38
39/// A trait that provides methods to access NS16550A registers.
40pub trait Ns16550aAccess {
41    /// Reads from an NS16550A register.
42    fn read(&self, reg: Ns16550aRegister) -> u8;
43
44    /// Writes to an NS16550A register.
45    fn write(&mut self, reg: Ns16550aRegister, val: u8);
46}
47
48/// An NS16550A UART.
49#[derive(Debug)]
50pub struct Ns16550aUart<A: Ns16550aAccess> {
51    access: A,
52}
53
54bitflags! {
55    struct LineStat: u8 {
56        /// Data ready (DR).
57        const DR    = 1 << 0;
58        /// Transmitter holding register empty (THRE).
59        const THRE  = 1 << 5;
60    }
61}
62
63impl<A: Ns16550aAccess> Ns16550aUart<A> {
64    /// Creates a new instance.
65    pub const fn new(access: A) -> Self {
66        Self { access }
67    }
68
69    /// Initializes the device.
70    ///
71    /// This will set the baud rate to 115200 bps and configure IRQs to trigger when new data is
72    /// received.
73    pub fn init(&mut self) {
74        // Divisor Latch Access Bit.
75        const DLAB: u8 = 0x80;
76
77        // Baud Rate: 115200 bps / divisor
78        self.access.write(Ns16550aRegister::LineCtrl, DLAB);
79        self.access.write(Ns16550aRegister::DataOrDivisorLo, 0x01);
80        self.access.write(Ns16550aRegister::IntEnOrDivisorHi, 0x00);
81
82        // Line Control: 8-bit, no parity, one stop bit.
83        self.access.write(Ns16550aRegister::LineCtrl, 0x03);
84        // FIFO Control: Disabled.
85        self.access.write(Ns16550aRegister::FifoCtrl, 0x00);
86        // Modem Control: IRQs enabled, RTS/DSR set.
87        self.access.write(Ns16550aRegister::ModemCtrl, 0x0B);
88        // Interrupt Enable: IRQs on received data.
89        self.access.write(Ns16550aRegister::IntEnOrDivisorHi, 0x01);
90    }
91
92    /// Sends a byte.
93    ///
94    /// If no room is available, it will spin until there is room.
95    pub fn send(&mut self, data: u8) {
96        while !self.line_stat().contains(LineStat::THRE) {
97            core::hint::spin_loop();
98        }
99
100        self.access.write(Ns16550aRegister::DataOrDivisorLo, data);
101    }
102
103    /// Receives a byte.
104    ///
105    /// If no byte is available, it will return `None`.
106    pub fn recv(&mut self) -> Option<u8> {
107        if !self.line_stat().contains(LineStat::DR) {
108            return None;
109        }
110
111        Some(self.access.read(Ns16550aRegister::DataOrDivisorLo))
112    }
113
114    fn line_stat(&self) -> LineStat {
115        LineStat::from_bits_truncate(self.access.read(Ns16550aRegister::LineStat))
116    }
117}
118
119impl<A: Ns16550aAccess> fmt::Write for Ns16550aUart<A> {
120    fn write_str(&mut self, s: &str) -> fmt::Result {
121        for c in s.as_bytes() {
122            if *c == b'\n' {
123                self.send(b'\r');
124            }
125            self.send(*c);
126        }
127        Ok(())
128    }
129}