kernel/io/
uart.rs

1use core::hint;
2
3use crate::{cmdline, cpu};
4
5#[repr(u32)]
6#[derive(Clone, Copy, Debug)]
7pub enum UartPort {
8    COM1 = 0x3F8,
9}
10
11#[repr(u8)]
12enum UartReg {
13    Data = 0,
14    InterruptEnable = 1,
15    InterruptAndFifoControl = 2,
16    LineControl = 3,
17    ModemControl = 4,
18    LineStatus = 5,
19    #[allow(dead_code)]
20    ModemStatus = 6,
21    #[allow(dead_code)]
22    Scratch = 7,
23}
24
25/// Enables the divisor latch access bit
26const LINE_BAUD_LATCH: u8 = 1 << 7;
27
28/// Controls the Data Terminal Ready Pin
29const MODEM_CTL_DTR: u8 = 1 << 0;
30/// Controls the Request To Send Pin
31const MODEM_CTL_RTS: u8 = 1 << 1;
32/// Controls the Out1 pin
33const MODEM_CTL_OUT1: u8 = 1 << 2;
34/// Controls the Out2 pin (used for interrupts)
35const MODEM_CTL_OUT2: u8 = 1 << 3;
36/// Controls the loopback mode
37const MODEM_CTL_LOOPBACK: u8 = 1 << 4;
38
39const IE_RX_READY: u8 = 1 << 0;
40const IE_TX_READY: u8 = 1 << 1;
41
42/// Got data
43const LINE_RX_READY: u8 = 1 << 0;
44/// Transmitter is empty
45const LINE_TX_EMPTY: u8 = 1 << 5;
46
47fn write_reg(port_addr: UartPort, reg: UartReg, val: u8) {
48    unsafe { cpu::io_out(port_addr as u16 + reg as u16, val) }
49}
50
51fn read_reg(port_addr: UartPort, reg: UartReg) -> u8 {
52    unsafe { cpu::io_in(port_addr as u16 + reg as u16) }
53}
54
55/// Will return `true` if the test pass, otherwise, the serial port is disabled
56fn init_port(port_addr: UartPort) -> bool {
57    // disable interrupts
58    write_reg(port_addr, UartReg::InterruptEnable, 0);
59    // disable FIFO
60    write_reg(port_addr, UartReg::InterruptAndFifoControl, 0);
61
62    // compute divisor
63    let rate = if cmdline::cmdline().uart_baud == 0 {
64        cmdline::cmdline().uart_baud
65    } else {
66        115200
67    };
68    let mut divisor = 115200 / rate;
69    if divisor == 0 {
70        divisor = 1;
71    }
72    if divisor >= 0x10000 {
73        divisor = 0xFFFF;
74    }
75
76    // set baud rate
77    // enable DLAB (change how Data and InterruptEnable)
78    write_reg(port_addr, UartReg::LineControl, LINE_BAUD_LATCH);
79    // set divisor
80    // low byte
81    write_reg(port_addr, UartReg::Data, divisor as u8);
82    // high byte
83    write_reg(port_addr, UartReg::InterruptEnable, (divisor >> 8) as u8);
84    // disable DLAB
85    // set 8 bits, no parity, one stop bit (8N1)
86    write_reg(port_addr, UartReg::LineControl, 0x03);
87
88    // enable FIFO, clear them, with 14-byte threshold
89    // write_reg(port_addr, UartReg::InterruptAndFifoControl, 0x07);
90
91    // enable receive and send interrupts
92    write_reg(
93        port_addr,
94        UartReg::InterruptEnable,
95        IE_RX_READY | IE_TX_READY,
96    );
97
98    // test the chip
99    // set loopback mode
100    write_reg(port_addr, UartReg::ModemControl, MODEM_CTL_LOOPBACK);
101    write_reg(port_addr, UartReg::Data, 0xAA);
102    // wait until we can read
103    while read_reg(port_addr, UartReg::LineStatus) & LINE_RX_READY == 0 {
104        hint::spin_loop();
105    }
106    // check if we got the same value (used later in the return)
107    let val = read_reg(port_addr, UartReg::Data);
108
109    // disable loopback mode go back to normal
110    write_reg(
111        port_addr,
112        UartReg::ModemControl,
113        MODEM_CTL_DTR | MODEM_CTL_RTS | MODEM_CTL_OUT1 | MODEM_CTL_OUT2,
114    );
115
116    // return true if the test pass, otherwise, we don't have serial port enabled
117    val == 0xAA
118}
119
120#[derive(Clone, Debug)]
121pub struct Uart {
122    port_addr: UartPort,
123    is_enabled: bool,
124}
125
126impl Uart {
127    pub const fn new(port_addr: UartPort) -> Self {
128        Self {
129            port_addr,
130            is_enabled: false,
131        }
132    }
133
134    pub fn init(&mut self) {
135        self.is_enabled = cmdline::cmdline().uart && init_port(self.port_addr);
136    }
137
138    /// SAFETY: `init` must be called before calling this function
139    pub unsafe fn write_byte(&self, byte: u8) {
140        if !self.is_enabled {
141            return;
142        }
143
144        // wait until we can send
145        while (read_reg(self.port_addr, UartReg::LineStatus) & LINE_TX_EMPTY) == 0 {
146            hint::spin_loop();
147        }
148        // write the byte
149        write_reg(self.port_addr, UartReg::Data, byte);
150    }
151
152    /// SAFETY: `init` must be called before calling this function
153    pub unsafe fn try_read_byte(&self) -> Option<u8> {
154        if !self.is_enabled {
155            return None;
156        }
157
158        // wait until we can read
159        if (read_reg(self.port_addr, UartReg::LineStatus) & LINE_RX_READY) == 0 {
160            return None;
161        }
162        // read the byte
163        Some(read_reg(self.port_addr, UartReg::Data))
164    }
165
166    #[allow(dead_code)]
167    pub fn interrupt_num(&self) -> u8 {
168        match self.port_addr {
169            UartPort::COM1 => 4,
170        }
171    }
172}