Drivers

The primary function of the microcontroller in HotRIO products is to facilitate interaction with and configuration of various connected devices, such as the ECP5 FPGA, external memory, and ADCs. To streamline access to these devices, a standardized framework has been developed, enabling consistent, efficient, and scalable communication across different hardware components.

Drivers are modular code segments that encapsulate the unique behavior and interaction mechanisms required for each specific device. This includes protocols, state machines, and device-specific characteristics. A driver, essentially implemented as a class, abstracts the interactions with a device and presents them as standardized operations, either through file-based interfaces or as structured class methods.

There are two primary types of drivers in this system:

  • Hardware Drivers encapsulate the functionality required to communicate through one of the microcontroller’s peripheral interfaces. These drivers are static—they are instantiated and configured at compile time, ensuring they align with the hardware configuration of a specific board. Hardware drivers depend on the availability of hardware features, such as SPI pins connected to a specific bus. Once compiled, these drivers are readily available to the HotRIO firmware based on the board’s configuration.

  • Device drivers are designed to manage and interact with a specific device, like an ADC or external memory. Unlike hardware drivers, device drivers can be instantiated dynamically during runtime, allowing flexible configuration as needed. Device drivers may bind to a hardware driver or even to another device driver, forming chains of drivers that facilitate complex interactions.

Driver Aggregation Model

The driver architecture relies on a parent-child relationship, where each driver has a “parent” driver that enables the communication chain. When an operation is performed on a child driver, it may involve calling methods from the parent driver to access the communication interface or trigger specific hardware actions. Each driver thus represents a step in the overall communication process.

For example, consider a scenario where a HotRIO board’s microcontroller uses SPI to connect to an ADC IC. The driver structure for communicating with this ADC IC would include:

  • SPIDriver: A hardware driver instantiated at compile time to manage the microcontroller’s SPI peripheral.

  • ADC IC Driver: A device driver instantiated at runtime, specifically designed to communicate with the ADC IC through the SPI peripheral.

  • The ADC IC Driver is linked as a child of the SPIDriver, so any ADC-related operation flows through the SPIDriver for SPI access.

This model can be expanded as needed, with device drivers linking to other device drivers to form communication chains, enabling intricate multi-device interactions.

Driver Implementation Rules

To ensure reliable operation, all drivers adhere to the following principles:

  • Hardware Driver Uniqueness: Each hardware driver must be instantiated only once, at compile time, and linked to its designated microcontroller peripheral.

  • Single Parent Rule: Each driver must have a single parent driver that provides the necessary communication capabilities. The only exceptions are hardware drivers, which act as the root in the driver chain and thus have no parent driver.

  • Multiple Child Support: Any driver can have multiple child drivers, each representing a device connected to the one the driver represents. This allows a scalable and modular structure where devices can be linked hierarchically.

By following these guidelines, developers can effectively use and expand the driver framework, ensuring a consistent and reliable means of device interaction in the HotRIO ecosystem.

Thread safety

To ensure thread-safe operation, each hardware driver in the HotRIO system employs a recursive mutex. This mutex must be acquired by any device driver directly or indirectly connected to the hardware driver before accessing the associated peripheral (Remember that due to the Single Parent Rule, each device driver at any chain level is only connected through its parents to a single hardware driver). The use of recursive mutexes ensures that:

  • Thread-Safe Access: Only one thread can access the hardware driver at any given time, preventing race conditions and ensuring that multiple threads do not interfere with each other when accessing shared hardware resources.

  • Consistency and Atomicity: By locking the recursive mutex, device drivers guarantee that each communication operation is both consistent and atomic, preventing partial or interrupted transactions that could lead to inconsistent device states.

  • Recursive Locking Support: A recursive mutex allows the same thread to lock the mutex multiple times without causing a deadlock, which is crucial for scenarios where a driver may invoke multiple layers of calls within its own context. For example, if a device driver calls multiple methods on a hardware driver in sequence, the recursive mutex allows the driver to maintain exclusive access without prematurely releasing control.

This mutex model is vital for applications where multiple devices may access a shared communication line (such as SPI) through a single hardware driver. By enforcing these mutex locks, the driver structure ensures that the integrity of each communication sequence is maintained, even in a multithreaded environment.

Example: Thread-Safe ADC Read Operation with an SPI Switch Driver

In this setup, we have three drivers in a chain that ensures thread-safe access to the ADC device connected via an SPI switch:

  • SPIDriver (Hardware Driver): Directly manages the SPI peripheral of the microcontroller, including a recursive mutex for thread safety.

  • SPISwitchDriver (Intermediate Device Driver): Controls an SPI switch, allowing dynamic routing to multiple SPI devices (e.g., an ADC or another SPI device). The SPISwitchDriver is linked as a child of SPIDriver.

  • ADCDriver (Device Driver): Manages communication with the ADC, requiring the SPI switch to be set to the correct channel for access. ADCDriver is linked as a child of SPISwitchDriver.

Step-by-Step Process of the ADC Read Operation

  1. A read request is made to the ADCDriver to acquire data from the ADC connected via the SPI switch.

  2. Locking Mutexes in Sequence:

    • The ADCDriver starts by locking the SPISwitchDriver’s recursive mutex to gain exclusive access to the switch control functions.

    • SPISwitchDriver then locks the SPIDriver’s recursive mutex, ensuring exclusive access to the SPI peripheral. This prevents any other driver from using the SPI interface until the read operation is complete.

    • Finally, SPIDriver locks its own mutex as an additional measure of thread safety, completing the locking chain from ADCDriver down to SPIDriver.

  3. Setting Up the SPI Switch and Accessing the SPI Peripheral:

    • The ADCDriver requests that SPISwitchDriver route the SPI signal to the correct channel for the ADC. SPISwitchDriver sets the SPI switch to route signals to the ADC’s channel.

    • With the switch set, SPISwitchDriver calls SPIDriver to perform the actual SPI read operation. SPIDriver sends the read command over the SPI bus, retrieves the ADC data, and returns it to SPISwitchDriver.

  4. Returning Data Up the Chain:

    • After receiving the ADC data, SPIDriver returns the data to SPISwitchDriver.

    • SPISwitchDriver passes the data back to ADCDriver, which processes the information as needed.

  5. Releasing Mutexes in Reverse Order:

    • SPIDriver first releases its recursive mutex, making the SPI peripheral available for other potential users.

    • SPISwitchDriver then releases its mutex, unlocking control of the SPI switch for other devices.

    • Finally, ADCDriver releases the mutex it holds on SPISwitchDriver, completing the operation.

By sequentially locking and unlocking mutexes, this process ensures that only one thread has access to the entire communication chain, from ADCDriver to SPIDriver. This structure prevents concurrent access to the SPI switch and the SPI peripheral, maintaining atomicity and consistency throughout the operation.

Driver Table

The Driver Table is a global object within the HotRIO firmware that stores references to all instantiated hardware drivers. This centralized structure allows any device driver in the system to locate and interact with other drivers by navigating through the hierarchical relationships starting from a hardware driver.

Purpose and Structure

The Driver Table serves as a registry for all hardware drivers, providing a single point of access to these critical resources. Since each device driver ultimately depends on a hardware driver to communicate with its respective peripheral, this table enables efficient and organized access to every device driver in the system.

Driver Tree and Linked List Structure

The relationships between drivers are organized as a hierarchical driver tree, where:

  • Hardware drivers (roots of the tree) represent the fundamental communication interfaces like SPI, I2C, or UART.

  • Device drivers are linked as child nodes to these hardware drivers or to other device drivers, forming branches.

The tree’s structure is implemented using a linked list. Each driver knows its parent, allowing upward navigation, and each parent driver maintains references to its children, enabling downward navigation. This structure allows for flexible and dynamic relationships between drivers:

  • Parent-Child Links: Each driver has a reference to a single parent driver (except for hardware drivers, which are root nodes with no parent). This ensures that every communication operation can be relayed upward through the appropriate communication interface.

  • Child References: Hardware drivers and device drivers can have one or more child drivers, represented as linked list nodes. These children reference the connected devices or subsystems they control.

This linked list-based hierarchy allows developers to traverse the driver tree efficiently, beginning with a hardware driver in the Driver Table and progressing through each level of device drivers to locate the desired device.

Accessing Drivers via the Driver Table

To access any device in the system, a developer can use the Driver Table to retrieve the relevant hardware driver and then traverse its linked list of child drivers to reach the target device driver. This approach ensures that every device driver can be easily located and accessed as needed, facilitating modular design and robust driver management.

The Driver Table, therefore, serves as the backbone of driver organization in the HotRIO firmware, providing structured access and efficient management of all communication drivers, ensuring consistent and predictable access to peripherals throughout the system.

Dynamic Driver Configuration

The HotRIO system supports dynamic driver configuration, enabling the instantiation and linking of device drivers at runtime via a command-line interface (CLI). This flexibility allows users to configure the system based on the current application requirements without the need for recompilation. The dynamic configuration process involves specifying key parameters such as the parent driver, driver type, and driver name, which facilitates the creation and linking of new drivers in the system.

Driver Instantiation Process

To instantiate a new driver dynamically, the following steps are executed:

  1. Command-Line Input: A command-line sequence is issued to instantiate a new driver. The command must specify the following parameters:

    • Parent Driver Name: The unique identifier for the parent driver to which the new driver will be linked. The parent driver must be pre-existing in the system and provide the necessary communication interface for the new device driver.

    • Driver Type: The type of driver to be instantiated, which could either be a hardware driver (e.g., SPI, I2C) or a device driver (e.g., ADC, sensor). The driver type must be valid and correspond to a predefined driver class.

    • Driver Name: A unique name for the new driver instance. This name is used to reference the driver throughout the system and must not conflict with any existing driver names to ensure correct identification.

  2. Driver Instantiation:

    Once the command has been issued with the necessary parameters, the following steps occur:

    The system identifies the parent driver based on the provided unique name and ensures it is correctly instantiated and available for use. Finding the parent driver is done via the driver table, which is methodically traversed to find the driver with the same name and use it as a parent.

    The system validates the driver type and ensures that a corresponding driver class or module is available to be instantiated. If the driver type is valid, the system allocates memory for the new driver object.

    The system calls the driver builder function associated with the specified driver type. The builder function is responsible for initializing the driver object, setting up the necessary parameters, and preparing the driver for operation.

  3. Linking the Driver:

    After the driver object has been instantiated, it is linked to its parent driver to establish the hierarchical relationship between the two. The parent driver’s communication capabilities are made available to the new driver, allowing the new driver to interact with the peripheral or device in a thread-safe manner.

    Prior to the actual linking of the two drivers, the child driver is given the parent driver reference to check for type compatibility. Not all device driver types are compatible with all other driver types, meaning that a certain device driver type expects its parent to be of a certain type of device. The linking and whole operation can only continue if the child accepts its parent driver type.

    The driver linkage ensures that the new driver operates in the context of the parent driver, inheriting its communication interface and resources.

  4. Virtual Filesystem Interface (Optional):

    Depending on the driver type and its functionality, one or more virtual files may be created in the system’s virtual filesystem. These files serve as an interface to interact with the newly instantiated device or peripheral.

    For example, an ADC driver might expose a file in the virtual filesystem to allow users to read data from the ADC via standard file operations (e.g., read(), write(), ioctl()).

    The creation and management of these files are handled by the driver builder function, which is responsible for creating the appropriate files and defining the interactions with the underlying hardware.

Driver types

Following, a reference to all the different driver types currently supported by the HotRIO firmware is given: