Sharing Hardware Registers: When to replace multiple writers with a shared resource driver.
Posted by Jeremy Cooke
When designing a complex system it is occasionally necessary to coordinate interaction with some sort of shared resource. Traditionally this is done with synchronization objects provided by the operating system such as critical sections or mutexes. This technique is fine for a simple resource and when relatively few writers need access to it, but for complicated resources accessed by multiple treads, processes, drivers, and/or applications, a new paradigm may be more appropriate. A good example of a suggested shared resource driver is a GPIO driver. Each driver may share a GPIO register and perform read-modify-writes, which poses a problem if each driver is not synchronized.
Rather than accessing the resource directly, it should be abstracted away into its own driver. All parties interested in accessing the resource can do so by merely opening its driver and utilizing its exposed interface. Internally, the driver will synchronize access via standard operating system constructs and deal with any error conditions that may arise. Callers have much less to worry about since all necessary synchronization and bookkeeping has been offloaded to the driver itself.
By using this technique the programmer is often able to eliminate critical section constructs which would otherwise be littered throughout the code base. Every access of the shared resource is now done through a user designed API which both abstracts access and increases program readability across the board.
The shared resource driver should also preprocess and error check input data as well as gracefully handle errors with respect to the shared resource it is guarding. Since error code is now centralized, all developers can be assured that accesses to the resource will be protected. This is a major plus in joint development scenarios!
The embedded system has a multi-channel analog to digital converter with devices attached to each channel. Multiple, disparate drivers are all interested in accessing their respective devices. The hardware allows software initiated ‘all channel’ conversions with the result vector provided in a common memory array. If multiple threads request a conversion there is a potential that one may corrupt another if they are not properly synchronized.
In this particular system there are analog quantities of interest to the battery driver, the USB driver, and the system’s backlight driver. The battery driver is concerned with the current voltage and temperature of the battery as well as charging voltage and current. The USB driver monitors the voltage of the USB VBUS line and the backlight driver monitors ambient light with a photodiode. Since these functions are handled in their own drivers all access to the shared resource must be controlled in a more advanced manner. Figure 1 below depicts each driver that wants access to the hardware ADC.
The analog to digital converter will be abstracted through the use of a dedicated driver. The driver allows multiple writers to initiate ‘all channel’ conversions and results are returned in a user specified memory array (see Figure 2 below).
The driver exposes an interface that includes a function to both perform the A/D conversion and return the results. Functionality is prototyped as follows:
AD_CHANNEL_BAT_VOLTAGE = 0,
BOOL PerformADConversion(USHORT *pOutBuffer, DWORD OutSize);
Here pOutBuffer points to a user supplied array in which results are returned and who’s size must equal TOTAL_AD_CHANNELS. Care must be taken when copying results to any user data area by using functions such as CeSafeCopyMemory which can handle cases when memory is invalid.
The PerformADConversion function can insantiate one critical section for synchronization, as seen in Figure 3:
In this design all hardware access is protected in a centralized area of code by one critical section. Performance is increased through the use of a light weight critical section instead of a slower synchronization object (such as a mutex). The developer can now further fine tune for performance with all gains delivered to each calling driver simultaneously.
While there are many positive aspects of using a dedicated driver in this way, it is also true that this approach may be slightly slower. There is some overhead when calling into a driver through standard Windows CE methods. Consequently it is best to structure code in a way that the number of driver accesses is minimized. In any case, the gains achieved through judicious use of this methodology far outweigh the cost.