uart_16550/
port.rs

1use core::fmt;
2
3use crate::{LineStsFlags, WouldBlockError};
4
5/// A x86 I/O port-mapped UART.
6#[cfg_attr(docsrs, doc(cfg(any(target_arch = "x86", target_arch = "x86_64"))))]
7#[derive(Debug)]
8pub struct SerialPort(u16 /* base port */);
9
10impl SerialPort {
11    /// Base port.
12    fn port_base(&self) -> u16 {
13        self.0
14    }
15
16    /// Data port.
17    ///
18    /// Read and write.
19    fn port_data(&self) -> u16 {
20        self.port_base()
21    }
22
23    /// Interrupt enable port.
24    ///
25    /// Write only.
26    fn port_int_en(&self) -> u16 {
27        self.port_base() + 1
28    }
29
30    /// Fifo control port.
31    ///
32    /// Write only.
33    fn port_fifo_ctrl(&self) -> u16 {
34        self.port_base() + 2
35    }
36
37    /// Line control port.
38    ///
39    /// Write only.
40    fn port_line_ctrl(&self) -> u16 {
41        self.port_base() + 3
42    }
43
44    /// Modem control port.
45    ///
46    /// Write only.
47    fn port_modem_ctrl(&self) -> u16 {
48        self.port_base() + 4
49    }
50
51    /// Line status port.
52    ///
53    /// Read only.
54    fn port_line_sts(&self) -> u16 {
55        self.port_base() + 5
56    }
57
58    /// Creates a new serial port interface on the given I/O base port.
59    ///
60    /// This function is unsafe because the caller must ensure that the given base address
61    /// really points to a serial port device and that the caller has the necessary rights
62    /// to perform the I/O operation.
63    pub const unsafe fn new(base: u16) -> Self {
64        Self(base)
65    }
66
67    /// Initializes the serial port.
68    ///
69    /// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used.
70    pub fn init(&mut self) {
71        unsafe {
72            // Disable interrupts
73            x86::io::outb(self.port_int_en(), 0x00);
74
75            // Enable DLAB
76            x86::io::outb(self.port_line_ctrl(), 0x80);
77
78            // Set maximum speed to 38400 bps by configuring DLL and DLM
79            x86::io::outb(self.port_data(), 0x03);
80            x86::io::outb(self.port_int_en(), 0x00);
81
82            // Disable DLAB and set data word length to 8 bits
83            x86::io::outb(self.port_line_ctrl(), 0x03);
84
85            // Enable FIFO, clear TX/RX queues and
86            // set interrupt watermark at 14 bytes
87            x86::io::outb(self.port_fifo_ctrl(), 0xc7);
88
89            // Mark data terminal ready, signal request to send
90            // and enable auxilliary output #2 (used as interrupt line for CPU)
91            x86::io::outb(self.port_modem_ctrl(), 0x0b);
92
93            // Enable interrupts
94            x86::io::outb(self.port_int_en(), 0x01);
95        }
96    }
97
98    fn line_sts(&mut self) -> LineStsFlags {
99        unsafe { LineStsFlags::from_bits_truncate(x86::io::inb(self.port_line_sts())) }
100    }
101
102    /// Sends a byte on the serial port.
103    pub fn send(&mut self, data: u8) {
104        match data {
105            8 | 0x7F => {
106                self.send_raw(8);
107                self.send_raw(b' ');
108                self.send_raw(8);
109            }
110            data => {
111                self.send_raw(data);
112            }
113        }
114    }
115
116    /// Sends a raw byte on the serial port, intended for binary data.
117    pub fn send_raw(&mut self, data: u8) {
118        retry_until_ok!(self.try_send_raw(data))
119    }
120
121    /// Tries to send a raw byte on the serial port, intended for binary data.
122    pub fn try_send_raw(&mut self, data: u8) -> Result<(), WouldBlockError> {
123        if self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {
124            unsafe {
125                x86::io::outb(self.port_data(), data);
126            }
127            Ok(())
128        } else {
129            Err(WouldBlockError)
130        }
131    }
132
133    /// Receives a byte on the serial port.
134    pub fn receive(&mut self) -> u8 {
135        retry_until_ok!(self.try_receive())
136    }
137
138    /// Tries to receive a byte on the serial port.
139    pub fn try_receive(&mut self) -> Result<u8, WouldBlockError> {
140        if self.line_sts().contains(LineStsFlags::INPUT_FULL) {
141            let data = unsafe { x86::io::inb(self.port_data()) };
142            Ok(data)
143        } else {
144            Err(WouldBlockError)
145        }
146    }
147}
148
149impl fmt::Write for SerialPort {
150    fn write_str(&mut self, s: &str) -> fmt::Result {
151        for byte in s.bytes() {
152            self.send(byte);
153        }
154        Ok(())
155    }
156}