hal_x86_64/
serial.rs

1//! A simple driver for 16550-like UARTs.
2//!
3//! This driver is primarily tested against the QEMU emulated 16550, which itself is intended to
4//! emulate a National Semiconductor PC16550D. From QEMU's
5//! [HardwareManuals](https://wiki.qemu.org/Documentation/HardwareManuals) wiki page, the emulated
6//! device is described by this datasheet: <https://wiki.qemu.org/images/1/18/PC16550D.pdf>.
7//!
8//! The COM1-4 defined here are, again, primarily in service of QEMU's PC layout, but should be
9//! relatively general.
10//!
11//! ## Nomenclature
12//!
13//! Several names around the 16550 vary from document to document, but here we generally use names
14//! as written in the National Semiconductor PC16550D datasheet above. Table II on page 14 is a
15//! useful reference for both bits in the 16550's registers and names of the registers
16//! themselves. Further, the PC16550D datasheet refers to individual bits in a register as
17//! `<reg><N>`, so "FCR0" refers to FIFO Control Register bit 0, for "FIFO Enable."
18//!
19//! ## Implementation
20//!
21//! Ports as implemented in this driver are split into a "read" part and a "write" part, which
22//! provide access to not-quite-disjoint sets of registers for a port. Ports can be written to by
23//! any task ay any time, requiring the write lock to do so, but reading a port happens in an
24//! interrupt context which can take the read lock at any time.
25//!
26//! The behavior implemented by the read half of a port must be able to tolerate execution
27//! concurrent with any behavior implemented on the write half of a port, and vice versa with the
28//! write half of a port tolerating any behavior implemented on the read half of a port. As a
29//! trivial example, reading and writing a port both operate on the data register, but one is
30//! performed with `inb` while the other is performed with `outb`; no interleaving of `inb`/`outb`
31//! on the data register will cause misbehavior, so reads and writes can be split in this way.
32//!
33//! When a 16550 raises an interrupt, the pending interrupts cannot change as a result of a write,
34//! so the read lock may read IIR. Conceivably, modifying `modem_ctrl` may change the port's
35//! pending interrupts, so the write lock must not modify this register while interrupts are
36//! unmasked.
37//!
38//! If we must operate with exclusive access to all port registers, we have to:
39//!
40//! * acquire the port write lock
41//! * disable interrupts for the port
42//! * acquire the port read lock
43//! * perform the operation
44//! * release the read lock
45//! * enable interrupts for the port
46//! * release the write lock
47//!
48//! This ordering is critical and depends on current serial implementation! The write lock excludes
49//! other processors without blocking interrupts from completing, disabling interrupts prevents new
50//! interrupts from firing, and acquiring the read lock only completes when any potentially
51//! in-flight interrupt is done with the port state. If two processors race to perform management
52//! operations, the write lock prevents one processor from enabling interrupts between the other
53//! disabling interrupts and acquiring the read lock.
54//!
55//! If serial interrupts must ever take a write lock, or interrupts intermittently drop the read
56//! lock, we'll have to add additional synchronization to guarantee no combination of read/write
57//! operations can deadlock.
58
59use crate::cpu;
60use core::{fmt, marker::PhantomData};
61use mycelium_util::{
62    io,
63    sync::{
64        blocking::{Mutex, MutexGuard},
65        spin::Spinlock,
66        Lazy,
67    },
68};
69
70static COM1: Lazy<Option<Port>> = Lazy::new(|| Port::new(0x3F8).ok());
71static COM2: Lazy<Option<Port>> = Lazy::new(|| Port::new(0x2F8).ok());
72static COM3: Lazy<Option<Port>> = Lazy::new(|| Port::new(0x3E8).ok());
73static COM4: Lazy<Option<Port>> = Lazy::new(|| Port::new(0x2E8).ok());
74
75pub fn com1() -> Option<&'static Port> {
76    COM1.as_ref()
77}
78
79pub fn com2() -> Option<&'static Port> {
80    COM2.as_ref()
81}
82
83pub fn com3() -> Option<&'static Port> {
84    COM3.as_ref()
85}
86
87pub fn com4() -> Option<&'static Port> {
88    COM4.as_ref()
89}
90
91// For representation simplicity, the variants here have values corresponding to the IIR bits that
92// produce these variants.
93#[derive(Debug)]
94pub enum Pc16550dInterrupt {
95    ModemStatus = 0b0000,
96    TransmitterHoldingRegEmpty = 0b0010,
97    ReceivedDataAvailable = 0b0100,
98    ReceiverLineStatus = 0b110,
99    CharacterTimeout = 0b1100,
100}
101
102pub struct Port {
103    read_inner: Mutex<ReadRegisters, Spinlock>,
104    write_inner: Mutex<WriteRegisters, Spinlock>,
105}
106
107/// A lock for the read parts of a serial port. The read and write parts of this port state are
108/// described in the [documentation for this module](crate::serial#implementation).
109pub struct ReadLock<'a, B = Blocking> {
110    // This is the non-moveable part.
111    inner: MutexGuard<'a, ReadRegisters, Spinlock>,
112    _is_blocking: PhantomData<B>,
113}
114
115/// A lock for the write parts of a serial port. The read and write parts of this port state are
116/// described in the [documentation for this module](crate::serial#implementation).
117pub struct WriteLock<'a, B = Blocking> {
118    // This is the non-moveable part.
119    inner: WriteLockInner<'a>,
120    _is_blocking: PhantomData<B>,
121}
122
123struct WriteLockInner<'a> {
124    inner: MutexGuard<'a, WriteRegisters, Spinlock>,
125    prev_divisor: Option<u16>,
126}
127
128/// The registers involved in handling a read of the UART.
129///
130/// This is closely related to the registers and implementation around `WriteRegisters`; we will
131/// concurrently access the port for reading and writing. While `data` is used in both reading and
132/// writing, it is only read while reading, and only written while writing, which means readers and
133/// writers do not interfere with one another.
134// #[derive(Debug)]
135struct ReadRegisters {
136    data: cpu::Port,
137    // This register is called the Interrupt Identification Register ("IIR") in the PC16550D
138    // datasheet from which QEMU's implementation is derived, but other datasheets for similar
139    // parts also call this the "Interrupt Status Register" or "ISR".
140    iir: cpu::Port,
141    status: cpu::Port,
142}
143
144// #[derive(Debug)]
145struct WriteRegisters {
146    data: cpu::Port,
147    irq_enable: cpu::Port,
148    line_ctrl: cpu::Port,
149    modem_ctrl: cpu::Port,
150    status: cpu::Port,
151    baud_rate_divisor: u16,
152}
153
154#[derive(Debug, Copy, Clone, Eq, PartialEq)]
155pub struct Blocking {
156    _p: (),
157}
158
159#[derive(Debug, Copy, Clone, Eq, PartialEq)]
160pub struct Nonblocking {
161    _p: (),
162}
163
164impl Port {
165    pub const MAX_BAUD_RATE: usize = 115_200;
166
167    pub fn new(port: u16) -> io::Result<Self> {
168        let scratch_test = unsafe {
169            const TEST_BYTE: u8 = 69;
170            let scratch_port = cpu::Port::at(port + 7);
171            scratch_port.writeb(TEST_BYTE);
172            scratch_port.readb() == TEST_BYTE
173        };
174
175        if !scratch_test {
176            return Err(io::Error::new(
177                io::ErrorKind::InvalidInput,
178                "scrach port was not writeable, is there a serial port at this address?",
179            ));
180        }
181
182        let read_registers = ReadRegisters {
183            data: cpu::Port::at(port),
184            iir: cpu::Port::at(port + 2),
185            status: cpu::Port::at(port + 5),
186        };
187        let mut write_registers = WriteRegisters {
188            data: cpu::Port::at(port),
189            irq_enable: cpu::Port::at(port + 1),
190            line_ctrl: cpu::Port::at(port + 3),
191            modem_ctrl: cpu::Port::at(port + 4),
192            status: cpu::Port::at(port + 5),
193            baud_rate_divisor: 3,
194        };
195        let fifo_ctrl = cpu::Port::at(port + 2);
196
197        // Disable all interrupts
198        write_registers.without_irqs(|registers| unsafe {
199            // Set divisor to 38400 baud
200            registers.set_baud_rate_divisor(3)?;
201
202            // 8 bits, no parity, one stop bit
203            registers.line_ctrl.writeb(0x03);
204
205            // Enable FIFO with 14-byte threshold
206            fifo_ctrl.writeb(0xC7);
207
208            // RTS/DSR set
209            registers.modem_ctrl.writeb(0x0B);
210
211            Ok::<(), io::Error>(())
212        })?;
213
214        Ok(Self {
215            read_inner: Mutex::new_with_raw_mutex(read_registers, Spinlock::new()),
216            write_inner: Mutex::new_with_raw_mutex(write_registers, Spinlock::new()),
217        })
218    }
219
220    pub fn read_lock(&self) -> ReadLock<'_> {
221        ReadLock {
222            inner: self.read_inner.lock(),
223            _is_blocking: PhantomData,
224        }
225    }
226
227    pub fn write_lock(&self) -> WriteLock<'_> {
228        WriteLock {
229            inner: WriteLockInner {
230                inner: self.write_inner.lock(),
231                prev_divisor: None,
232            },
233            _is_blocking: PhantomData,
234        }
235    }
236
237    /// Forcibly unlock the serial port, releasing any locks held by other cores
238    /// or in other functions.
239    ///
240    /// # Safety
241    ///
242    ///  /!\ only call this when oopsing!!! /!\
243    pub unsafe fn force_unlock(&self) {
244        self.read_inner.force_unlock();
245        self.write_inner.force_unlock();
246    }
247}
248
249impl ReadRegisters {
250    #[inline]
251    fn iir(&self) -> u8 {
252        unsafe { self.iir.readb() }
253    }
254
255    #[inline]
256    fn line_status(&self) -> u8 {
257        unsafe { self.status.readb() }
258    }
259
260    #[inline]
261    fn is_read_ready(&self) -> bool {
262        self.line_status() & 1 != 0
263    }
264
265    #[inline]
266    fn read_blocking(&mut self) -> u8 {
267        while !self.is_read_ready() {}
268        unsafe { self.data.readb() }
269    }
270
271    #[inline]
272    fn read_nonblocking(&mut self) -> io::Result<u8> {
273        if self.is_read_ready() {
274            Ok(unsafe { self.data.readb() })
275        } else {
276            Err(io::Error::from(io::ErrorKind::WouldBlock))
277        }
278    }
279}
280
281impl WriteRegisters {
282    const DLAB_BIT: u8 = 0b1000_0000;
283
284    fn without_irqs<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
285        unsafe {
286            self.irq_enable.writeb(0x00);
287        }
288        let res = f(self);
289        unsafe {
290            self.irq_enable.writeb(0x01);
291        }
292        res
293    }
294
295    fn set_baud_rate_divisor(&mut self, divisor: u16) -> io::Result<u16> {
296        let prev = self.baud_rate_divisor;
297        if divisor == 0 {
298            return Err(io::Error::new(
299                io::ErrorKind::InvalidInput,
300                "baud rate divisor must be greater than 0",
301            ));
302        }
303
304        let lcr_state = unsafe { self.line_ctrl.readb() };
305        if lcr_state & Self::DLAB_BIT != 0 {
306            return Err(io::Error::new(
307                io::ErrorKind::Other,
308                "DLAB bit already set, what the heck!",
309            ));
310        }
311
312        unsafe {
313            // set the Divisor Latch Access Bit. now, the data port and irq enable
314            // port can be used to set the least and most significant bytes of the
315            // divisor, respectively.
316            self.line_ctrl.writeb(lcr_state | Self::DLAB_BIT);
317
318            // least significant byte
319            self.data.writeb((divisor & 0x00FF) as u8);
320            // most significant byte
321            self.irq_enable.writeb((divisor >> 8) as u8);
322
323            self.line_ctrl.writeb(lcr_state);
324        }
325
326        self.baud_rate_divisor = divisor;
327
328        Ok(prev)
329    }
330
331    #[inline]
332    fn line_status(&self) -> u8 {
333        unsafe { self.status.readb() }
334    }
335
336    #[inline]
337    fn is_write_ready(&self) -> bool {
338        self.line_status() & 0x20 != 0
339    }
340
341    #[inline]
342    fn write_blocking(&mut self, byte: u8) {
343        while !self.is_write_ready() {}
344        unsafe { self.data.writeb(byte) }
345    }
346
347    #[inline]
348    fn write_nonblocking(&mut self, byte: u8) -> io::Result<()> {
349        if self.is_write_ready() {
350            unsafe {
351                self.data.writeb(byte);
352            }
353            Ok(())
354        } else {
355            Err(io::Error::from(io::ErrorKind::WouldBlock))
356        }
357    }
358}
359
360impl<'a> ReadLock<'a> {
361    pub fn set_non_blocking(self) -> ReadLock<'a, Nonblocking> {
362        ReadLock {
363            inner: self.inner,
364            _is_blocking: PhantomData,
365        }
366    }
367}
368
369impl<'a> WriteLock<'a> {
370    pub fn set_non_blocking(self) -> WriteLock<'a, Nonblocking> {
371        WriteLock {
372            inner: self.inner,
373            _is_blocking: PhantomData,
374        }
375    }
376}
377
378impl<B> ReadLock<'_, B> {
379    pub fn check_interrupt_type(&mut self) -> io::Result<Option<Pc16550dInterrupt>> {
380        // IIR bits 0 through 3 describe what happened to produce an interrupt, with bits 4 and 5
381        // always 0, and bits 6 and 7 set to 1 if FIFOs are enabled (e.g. `FCR` bit 0 is set).
382        let iir = self.inner.iir();
383
384        if iir & 0b0011_0000 != 0b0000_0000 {
385            return Err(io::Error::new(
386                io::ErrorKind::Other,
387                "IIR indicates bogus interrupt bits",
388            ));
389        }
390
391        // The mapping here is described in "Table IV. Interrupt Control Functions" on page 17.
392        // TODO(ixi): probably should check IIR bits 6 and 7? punting on everything related to FIFO
393        // though.
394        let interrupt = match iir & 0b1111 {
395            0b0000 => Pc16550dInterrupt::ModemStatus,
396            0b0001 => {
397                return Ok(None);
398            }
399            0b0010 => Pc16550dInterrupt::TransmitterHoldingRegEmpty,
400            0b0100 => Pc16550dInterrupt::ReceivedDataAvailable,
401            0b0110 => Pc16550dInterrupt::ReceiverLineStatus,
402            0b1100 => Pc16550dInterrupt::CharacterTimeout,
403            _ => {
404                return Err(io::Error::new(
405                    io::ErrorKind::Other,
406                    "IIR indicates unrecognized status",
407                ));
408            }
409        };
410
411        Ok(Some(interrupt))
412    }
413}
414
415impl<B> WriteLock<'_, B> {
416    /// Set the serial port's baud rate for this `Lock`.
417    ///
418    /// When the `Lock` is dropped, the baud rate will be set to the previous value.
419    ///
420    /// # Errors
421    ///
422    /// This returns an `InvalidInput` error if the target baud rate exceeds the
423    /// maximum (115200), if the maximum baud rate is not divisible by the
424    /// target, or if the target is so low that the resulting baud rate divisor
425    /// is greater than `u16::MAX` (pretty unlikely!).
426    pub fn set_baud_rate(&mut self, baud: usize) -> io::Result<()> {
427        if baud > Port::MAX_BAUD_RATE {
428            return Err(io::Error::new(
429                io::ErrorKind::InvalidInput,
430                "cannot exceed max baud rate (115200)",
431            ));
432        }
433
434        if Port::MAX_BAUD_RATE % baud != 0 {
435            return Err(io::Error::new(
436                io::ErrorKind::InvalidInput,
437                "max baud rate not divisible by target",
438            ));
439        }
440
441        let divisor = Port::MAX_BAUD_RATE / baud;
442        if divisor > (u16::MAX as usize) {
443            return Err(io::Error::new(
444                io::ErrorKind::InvalidInput,
445                "divisor for target baud rate is too high!",
446            ));
447        }
448
449        self.inner.set_baud_rate_divisor(divisor as u16)
450    }
451}
452
453impl io::Read for ReadLock<'_, Blocking> {
454    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
455        for byte in buf.iter_mut() {
456            *byte = self.inner.read_blocking();
457        }
458        Ok(buf.len())
459    }
460}
461
462impl io::Read for ReadLock<'_, Nonblocking> {
463    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
464        for byte in buf.iter_mut() {
465            // not ideal that this loses how many bytes were read if any read would block
466            *byte = self.inner.read_nonblocking()?;
467        }
468        Ok(buf.len())
469    }
470}
471
472impl io::Write for WriteLock<'_, Blocking> {
473    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
474        for &byte in buf.iter() {
475            self.inner.write_blocking(byte)
476        }
477        Ok(buf.len())
478    }
479
480    fn flush(&mut self) -> io::Result<()> {
481        while !self.inner.is_write_ready() {}
482        Ok(())
483    }
484}
485
486impl fmt::Write for WriteLock<'_, Blocking> {
487    fn write_str(&mut self, s: &str) -> fmt::Result {
488        for byte in s.bytes() {
489            self.inner.write_blocking(byte)
490        }
491        Ok(())
492    }
493}
494
495impl io::Write for WriteLock<'_, Nonblocking> {
496    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
497        for &byte in buf.iter() {
498            self.inner.write_nonblocking(byte)?;
499        }
500        Ok(buf.len())
501    }
502
503    fn flush(&mut self) -> io::Result<()> {
504        while !self.inner.is_write_ready() {}
505        Ok(())
506    }
507}
508
509impl WriteLockInner<'_> {
510    #[inline(always)]
511    fn is_write_ready(&self) -> bool {
512        self.inner.is_write_ready()
513    }
514
515    #[inline(always)]
516    fn write_nonblocking(&mut self, byte: u8) -> io::Result<()> {
517        self.inner.write_nonblocking(byte)
518    }
519
520    #[inline(always)]
521    fn write_blocking(&mut self, byte: u8) {
522        self.inner.write_blocking(byte)
523    }
524
525    #[inline(always)]
526    fn set_baud_rate_divisor(&mut self, divisor: u16) -> io::Result<()> {
527        let prev: u16 = self
528            .inner
529            .without_irqs(|inner| inner.set_baud_rate_divisor(divisor))?;
530        self.prev_divisor = Some(prev);
531
532        Ok(())
533    }
534}
535
536impl Drop for WriteLockInner<'_> {
537    fn drop(&mut self) {
538        if let Some(divisor) = self.prev_divisor {
539            // Disable IRQs.
540            self.inner.without_irqs(|inner| {
541                // Reset the previous baud rate divisor.
542                let _ = inner.set_baud_rate_divisor(divisor);
543            });
544        }
545    }
546}
547
548impl<'a> mycelium_trace::writer::MakeWriter<'a> for &Port {
549    type Writer = WriteLock<'a, Blocking>;
550    fn make_writer(&'a self) -> Self::Writer {
551        self.write_lock()
552    }
553
554    fn line_len(&self) -> usize {
555        120
556    }
557}