Module serial

Source
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
ReadLock
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.
WriteLock
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.

Enums§

Pc16550dInterrupt

Functions§

com1
com2
com3
com4