The most common way is to have the drivers implement the same interface, but use different function names to implement them, and then use function pointers to select which one is being used.
For example, let's say you have a number of different temperature sensor modules, each with their own driver. They all implement the same interface:
trigger() - to trigger a new measurement
ready() - to return whether a new measurement is ready
read() - to return the measurement
Driver
foo defines
foo_trigger(),
foo_ready(), and
foo_read().
Declare a common function pointer structure visible to all drivers implementing a similar module. Here,
struct temperature_sensor {
int (*trigger)(void);
int (*ready)(void);
int (*read)(void);
};
Have the probe function for each driver return the structure, if it detects it is installed, and NULL otherwise:
const struct temperature_sensor foo_sensor = {
.trigger = foo_trigger,
.ready = foo_ready,
.read = foo_read,
};
const struct temperature_sensor *foo_probe(void)
{
if (this module is detected) {
return foo_sensor;
} else {
return NULL;
}
}
At probe time, the common code calls each probe function for each module interface. In this case,
const struct temperature_sensor *temperature_sensor;
void temperature_sensor_init(void) {
#ifdef FOO_INCLUDED
if ((temperature_sensor = foo_probe()))
return;
#endif
#ifdef BAR_INCLUDED
if ((temperature_sensor = bar_probe()))
return;
#endif
#ifdef BAZ_INCLUDED
if ((temperature_sensor = baz_probe()))
return;
#edif
/* No temperature sensor module found. */
temperature_sensor = NULL;
}
and at run time, the function pointers in the structure are used. You can even create wrapper functions to hide the complexity:
static inline int temperature_sensor_trigger(void) {
if (temperature_sensor)
return temperature_sensor->trigger();
else
return NOT_AVAILABLE;
}
static inline int temperature_sensor_ready(void) {
if (temperature_sensor)
return temperature_sensor->ready();
else
return NEVER_READY;
}
static inline int temperature_sensor_read(void) {
if (temperature_sensor)
return temperature_sensor->read();
else
return NOT_AVAILABLE;
}
Because I personally use GNU toolchain (avr-gcc or arm-gcc), I can use macros to declare members into an array (of known modules) in different files, so that
temperature_sensor_init() becomes a loop over an array, and it does not need to know anything about the actual drivers; the driver implementations "slot in" automagically. This is based on the
section attribute supported by GCC, and the linker providing symbols the code can use to treat the section as an array. (Essentially, with GCC toolchain, you can create arrays whose elements are declared in different files, in different compilation units.)
Obviously, the exact definition of the structure containing the function pointers is CRITICAL. Personally, I like to include a string (a pointer to a string in ROM/Flash) declaring the name of the module.
If the structure containing the function pointers needs runtime data, say a variable identifying the bus the module is attached to, then the structure must be in RAM instead. It is not a big issue, since it's just a few bytes. Then, the _init function does not return a pointer, but populates the structure it is given a pointer to.