Monolithic Drivers

When one thinks about device drivers in an OS, one thinks of a single thing, a single block in a block diagram with these two primary interfaces:

  • The device monolithic driver exposes a single, standard device driver interface. With the Virtual File System (VFS), this provides the application user interface to the driver functionality. And
  • A low-level interface to the hardware that is managed by the device driver.

Upper Half and Lower Half Drivers

NuttX supports many, many different MCU platforms, each with many similar but distinct built-in peripherals.
Certainly we could imagine a realization where each such peripheral is supported by monolithic driver as described in the preceding paragraph.
That would involve a lot code duplication, however.
The MCU peripherals may be unique at a low, register-level interface.
However, the peripherals are really very similar at a higher level of abstraction.

NuttX reduces the duplication, both in the code and in driver development, using the notion of Upper Half and Lower Half drivers.
Such an implementation results in two things; two blocks in the system block diagram: The upper half driver in a group of common, shared drivers, and the MCU-specific lower half driver.

As before, each of these two driver components has two functional interfaces.
For the upper half driver:

  • The upper half device driver exposes a single, standard driver interface. With the Virtual File System (VFS), this, again, provides the application user interface to the driver functionality. And
  • The upper-half side of the lower-half interface to the MCU-specific hardware that is managed by the lower-half device driver.

And for the lower half driver:

  • The lower-half side of the interface to the the upper0half driver, and
  • The low-level interface to the hardware that is managed by the lower half device driver.

One to Many: Encapsulation and Polymorphism

These modular upper- and lower-half drivers have certain properties that you would associate with an object oriented design: Encapsulation, data abstraction, and polymorphism certainly.
Because of this encapsulation, the upper-half driver is complete unaware of any implementation details within the lower-half driver.
Everything needed for the upper- and lower-half drivers to integrate is provided by the defined interface between between those two things.
In fact, a single upper-half driver may service many lower-half driver instances in a one-to-many relationship.

As an example, some MCUs support {{UART}}s, {{USART}}s functioning as {{UART}}s, Low-Power {{UART}}s ({{LPUART}}s), and other Flexible devices that may function as {{UART}}s.
Each of these is managed by a separate lower-half driver that can be found in the appropriate src/ directory under arch/.
In addition a board could have off-chip, external 16550 UART hardware (which has a common lower-half driver).
Yet all of them would be supported by the single, common, serial upper half driver that can be found at drivers/serial/serial.c.
This is only possible due to the object-like properties of the lower-half driver implementations.

  • No labels