Expand description
A simple driver for 16550-like UARTs.
This driver is primarily tested against the QEMU emulated 16550, which itself is intended to emulate a National Semiconductor PC16550D. From QEMU’s HardwareManuals wiki page, the emulated device is described by this datasheet: https://wiki.qemu.org/images/1/18/PC16550D.pdf.
The COM1-4 defined here are, again, primarily in service of QEMU’s PC layout, but should be relatively general.
§Nomenclature
Several names around the 16550 vary from document to document, but here we generally use names
as written in the National Semiconductor PC16550D datasheet above. Table II on page 14 is a
useful reference for both bits in the 16550’s registers and names of the registers
themselves. Further, the PC16550D datasheet refers to individual bits in a register as
<reg><N>, so “FCR0” refers to FIFO Control Register bit 0, for “FIFO Enable.”
§Implementation
Ports as implemented in this driver are split into a “read” part and a “write” part, which provide access to not-quite-disjoint sets of registers for a port. Ports can be written to by any task ay any time, requiring the write lock to do so, but reading a port happens in an interrupt context which can take the read lock at any time.
The behavior implemented by the read half of a port must be able to tolerate execution
concurrent with any behavior implemented on the write half of a port, and vice versa with the
write half of a port tolerating any behavior implemented on the read half of a port. As a
trivial example, reading and writing a port both operate on the data register, but one is
performed with inb while the other is performed with outb; no interleaving of inb/outb
on the data register will cause misbehavior, so reads and writes can be split in this way.
When a 16550 raises an interrupt, the pending interrupts cannot change as a result of a write,
so the read lock may read IIR. Conceivably, modifying modem_ctrl may change the port’s
pending interrupts, so the write lock must not modify this register while interrupts are
unmasked.
If we must operate with exclusive access to all port registers, we have to:
- acquire the port write lock
- disable interrupts for the port
- acquire the port read lock
- perform the operation
- release the read lock
- enable interrupts for the port
- release the write lock
This ordering is critical and depends on current serial implementation! The write lock excludes other processors without blocking interrupts from completing, disabling interrupts prevents new interrupts from firing, and acquiring the read lock only completes when any potentially in-flight interrupt is done with the port state. If two processors race to perform management operations, the write lock prevents one processor from enabling interrupts between the other disabling interrupts and acquiring the read lock.
If serial interrupts must ever take a write lock, or interrupts intermittently drop the read lock, we’ll have to add additional synchronization to guarantee no combination of read/write operations can deadlock.
Structs§
- Blocking
- Nonblocking
- Port
- Read
Lock - A lock for the read parts of a serial port. The read and write parts of this port state are described in the documentation for this module.
- Write
Lock - A lock for the write parts of a serial port. The read and write parts of this port state are described in the documentation for this module.