base/
uart.rs

1pub macro wait_for {
2   ($cond:expr) => {
3      while !$cond {
4         core::hint::spin_loop()
5      }
6   }
7}
8
9bitflags!{
10   /// Line status flags
11   struct LineStatusFlags: u8 {
12      const INPUT_FULL = 1;
13      // 1 to 4 unknown
14      const OUTPUT_EMPTY = 1 << 5;
15      // 6 and 7 unknown
16   }
17}
18
19bitflags!{
20   struct InterruptEnableFlags: u8 {
21      const RECEIVED = 1;
22      const SENT = 1 << 1;
23      const ERRORED = 1 << 2;
24      const STATUS_CHANGE = 1 << 3;
25      // 4 to 7 are unused
26   }
27}
28
29// SERIAL PORT //
30
31/// An x86 IO port-mapped UART implementation.
32#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
33#[derive(Debug)]
34pub struct SerialPort(u16/*our base port*/);
35
36impl SerialPort {
37   /// The base port.
38   ///
39   /// Read and write.
40   fn base_port(&self) -> u16 {
41      self.0
42   }
43
44   /// Interrupt enable port.
45   ///
46   /// Write only.
47   fn interrupt_enable(&self) -> u16 {
48      return self.base_port() + 1;
49   }
50
51   /// FIFO-control port.
52   ///
53   /// Write only.
54   fn fifo_control(&self) -> u16 {
55      return self.base_port() + 2;
56   }
57
58   /// Line control port.
59   ///
60   /// Write only.
61   fn line_control(&self) -> u16 {
62      return self.base_port() + 3;
63   }
64
65   /// Modem control port.
66   ///
67   /// Write only.
68   fn modem_control(&self) -> u16 {
69      return self.base_port() + 4;
70   }
71
72   /// Line status port.
73   ///
74   /// Read only.
75   fn line_status_port(&self) -> u16 {
76      return self.base_port() + 5;
77   }
78
79   fn line_status(&self) -> LineStatusFlags {
80      return unsafe {
81         LineStatusFlags::from_bits_truncate(x86::io::inb(self.line_status_port()))
82      };
83   }
84
85   /// Creates a new serial port interface on the given I/O base port.
86   ///
87   /// This function is unsafe because the caller must ensure that the given base address
88   /// really points to a serial port device and that the caller has the necessary rights
89   /// to perform the I/O operation.
90   pub const unsafe fn new(base: u16) -> Self {
91      return SerialPort(base);
92   }
93
94   /// Initializes the serial port.
95   ///
96   /// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used.
97   pub fn init(&mut self) {
98      unsafe {
99         // Disable interrupts
100         x86::io::outb(self.interrupt_enable(), 0x00);
101
102         // Enable DLAB
103         x86::io::outb(self.line_control(), 0x80);
104
105         // Set maximum speed to 38400 bps by configuring DLL and DLM
106         x86::io::outb(self.base_port(), 0x03);
107         x86::io::outb(self.interrupt_enable(), 0x00);
108
109         // Disable DLAB and set data word length to 8 bits
110         x86::io::outb(self.line_control(), 0x03);
111
112         // Enable FIFO, clear TX/RX queues and
113         // set interrupt watermark at 14 bytes
114         x86::io::outb(self.fifo_control(), 0xc7);
115
116         // Mark data terminal ready, signal request to send
117         // and enable auxiliary output #2 (used as interrupt line for CPU)
118         x86::io::outb(self.modem_control(), 0x0b);
119
120         // Enable interrupts
121         x86::io::outb(self.interrupt_enable(), 0x01);
122      }
123   }
124
125   /// Sends a byte on the serial port.
126   pub fn send(&mut self, data: u8) {
127      unsafe {
128         match data {
129            8 | 0x7F => {
130               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
131               x86::io::outb(self.base_port(), 8);
132               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
133               x86::io::outb(self.base_port(), b' ');
134               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
135               x86::io::outb(self.base_port(), 8);
136            }
137            _ => {
138               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
139               x86::io::outb(self.base_port(), data);
140            }
141         }
142      }
143   }
144
145   /// Sends a raw byte on the serial port, intended for binary data.
146   pub fn send_raw(&mut self, data: u8) {
147      unsafe {
148         wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
149         x86::io::outb(self.base_port(), data);
150      }
151   }
152
153   /// Receives a byte on the serial port.
154   pub fn receive(&mut self) -> u8 {
155      unsafe {
156         wait_for!(self.line_status().contains(LineStatusFlags::INPUT_FULL));
157         x86::io::inb(self.base_port())
158      }
159   }
160}
161
162impl fmt::Write for SerialPort {
163   fn write_str(&mut self, s: &str) -> fmt::Result {
164      for byte in s.bytes() {
165         self.send(byte);
166      }
167      Ok(())
168   }
169}
170
171// MEMORY MAPPED //
172
173#[derive(Debug)]
174pub struct MmioPort {
175   data: AtomicPtr<u8>,
176   interrupt_enable: AtomicPtr<u8>,
177   fifo_control: AtomicPtr<u8>,
178   line_control: AtomicPtr<u8>,
179   modem_control: AtomicPtr<u8>,
180   line_status: AtomicPtr<u8>,
181}
182
183impl MmioPort {
184   /// Creates a new UART interface on the given memory mapped address.
185   ///
186   /// This function is unsafe because the caller must ensure that the given base address
187   /// really points to a serial port device.
188   #[rustversion::attr(since(1.61), const)]
189   pub unsafe fn new(base: usize) -> Self {
190      let base_pointer = base as *mut u8;
191      return MmioPort{
192         data: AtomicPtr::new(base_pointer),
193         interrupt_enable: AtomicPtr::new(base_pointer.add(1)),
194         fifo_control: AtomicPtr::new(base_pointer.add(2)),
195         line_control: AtomicPtr::new(base_pointer.add(3)),
196         modem_control: AtomicPtr::new(base_pointer.add(4)),
197         line_status: AtomicPtr::new(base_pointer.add(5)),
198      };
199   }
200
201   /// Initializes the memory-mapped UART.
202   ///
203   /// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used.
204   pub fn init(&mut self) {
205      let self_interrupt_enable = self.interrupt_enable.load(Ordering::Relaxed);
206      let self_line_control = self.line_control.load(Ordering::Relaxed);
207      let self_data = self.data.load(Ordering::Relaxed);
208      let self_fifo_control = self.fifo_control.load(Ordering::Relaxed);
209      let self_modem_control = self.modem_control.load(Ordering::Relaxed);
210
211      unsafe {
212         // Disable interrupts
213         self_interrupt_enable.write(0x00);
214
215         // Enable DLAB
216         self_line_control.write(0x80);
217
218         // Set maximum speed to 38400 bps by configuring DLL and DLM
219         self_data.write(0x03);
220         self_interrupt_enable.write(0x00);
221
222         // Disable DLAB and set data word length to 8 bits
223         self_line_control.write(0x03);
224
225         // Enable FIFO, clear TX/RX queues and
226         // set interrupt watermark at 14 bytes
227         self_fifo_control.write(0xC7);
228
229         // Mark data terminal ready, signal request to send
230         // and enable auxilliary output #2 (used as interrupt line for CPU)
231         self_modem_control.write(0x0B);
232
233         // Enable interrupts
234         self_interrupt_enable.write(0x01);
235      }
236   }
237
238   fn line_status(&mut self) -> LineStatusFlags {
239      unsafe { LineStatusFlags::from_bits_truncate(*self.line_status.load(Ordering::Relaxed)) }
240   }
241
242   /// Sends a byte on the serial port.
243   pub fn send(&mut self, data: u8) {
244      let self_data = self.data.load(Ordering::Relaxed);
245
246      unsafe {
247         match data {
248            8 | 0x7F => {
249               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
250               self_data.write(8);
251               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
252               self_data.write(b' ');
253               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
254               self_data.write(8)
255            }
256            _ => {
257               wait_for!(self.line_status().contains(LineStatusFlags::OUTPUT_EMPTY));
258               self_data.write(data);
259            }
260         }
261      }
262   }
263
264   /// Receives a byte on the serial port.
265   pub fn receive(&mut self) -> u8 {
266      let self_data = self.data.load(Ordering::Relaxed);
267
268      unsafe {
269         wait_for!(self.line_status().contains(LineStatusFlags::INPUT_FULL));
270         self_data.read()
271      }
272   }
273}
274
275impl fmt::Write for MmioPort {
276   fn write_str(&mut self, s: &str) -> fmt::Result {
277      for byte in s.bytes() {
278         self.send(byte);
279      }
280
281      Ok(())
282   }
283}
284
285// IMPORTS //
286
287use {
288   bitflags::bitflags,
289   core::{
290      fmt,
291      sync::atomic::{
292         AtomicPtr, Ordering,
293      },
294   },
295};