Search code examples
cdriverorganization

Program organization with device drivers in C


Say I have two device drivers and I want them to share the same interface so a caller doesn't know which driver it is talking to exactly. How would I organize this in C? I have thought of a couple of ways:

First: Create a pair of .c/.h files for both drivers with the same interface and create a switch in the caller:

//main.c:

#ifdef USING_DRIVER_1
 #include "driver_1.h"
#else
 #include "driver_2.h"
#endif // USING_DRIVER_1

Second: Use a single header and create a file-long switch in the drivers' source file like so:

//driver_1.c:

#ifdef USING_DRIVER_1
#include "driver.h"

bool func(uint32_t var)
{
    foo(var);
}
#endif // USING_DRIVER_1

//driver_2.c:

#ifndef USING_DRIVER_1
#include "driver.h"

bool func(uint32_t var)
{
    bar(var);
}
#endif // !USING_DRIVER_1

Third: This one is a lot like the second one but instead of using switch statements in files themselves, a specific driver is chosen in the makefile or IDE equivalent:

#makefile:

SRC = main.c
#SRC += driver_1.c
SRC += driver_2.c

I'm sure one of these is superior to others and there are probably some I haven't thought of. How is it done in practice?

EDIT:

Details about my particular system: my target is an ARM microcontroller and my dev. environment is an IDE. Device drivers are for two different revisions and will never be used at the same time so each build should contain only one version. Devices themselves are modems operating via AT commands.


Solution

  • All three variants are actually useful. Which to choose depends on what you actually need:

    1. Selecting the driver from the caller would add both drivers to the code. That only makes sense if you switch drivers at run-time. Then it would be the (only) way to go. Use e.g. function pointers or two identical const structs which provide the interface (function pointer and possibly other data).
    2. A global switch is plain ugly and not possible across functions and declarations. Better would be conditional compilation using #if .. #elif #end. That makes sense if the two drivers have only minor differences, e.g. different SPI interfaces (SPI1 vs. SPI2 ...). Then this is the way to go. With some effort in the build-tool you can even use this for case 1. (one file for two different drivers, but not my recommendation).
    3. If both drivers are substantial different in their implementation, but have the same interface, take the third approach, but use a single header or both drivers (see below).

    Note for all but the first approach, both drivers have to provide an identical interface to the application. The first approach actually allows for differences, but that would actually require the user code treat them different and that's likely not what you want. Using a single header file for both drivers (e.g.: "spi_memory.h" and "spi_flash.c" vs. "spi_eeprom.c") does ensure the application does not see an actual difference - as long as the drivers also behave identically, of course. Minor differences can be caught by variables in the interface (e.g. extern size_t memory_size;) or functions (the better approach).