Generic driver interface, extensions?
Posted: Fri Apr 03, 2020 4:26 pm
Imagine a driver interface for keyboard (pseudocode):
Wonderful.
We can easily define something similar for ps/2, bluetooth, etc.
Now, imagine I get a fancy 'gaming' keyboard with LEDs whose colours can be changed. The driver for that keyboard exposes a function with a signature like. What's the best way to expose that functionality to userspace?
I thought of a couple of possibilities, but none of them seem particularly good.
We could just add it as another member to the interface:
But that's not very satisfying. Most keyboards don't let you change the colour of their keys, so this doesn't seem like an essential part of a keyboard-driver interface. Besides which, what would that function do on a keyboard without fancy LEDs? (Probably, it would be a no-op, which is a bit dissatisfying.)
As you add more and more optional extensions, that interface gets very bloated.
Another option is to have 'extensions'. So you have something like this:
This is certainly more modular and flexible, but unsafe and encourages some uncomfortable habits. If an extension is almost universally supported (e.g., telling a hard drive to spin down, before SSDs and flash memory became widespread; or telling a game controller to vibrate), then people will be encouraged to write code that uses extensions without checking if they're available, and the software will crash on platforms where those extensions aren't provided.
Or we could bite the bullet, use OOP, and solve it with inheritance:
It takes more plumbing than I want to show in an example, but it's not difficult to ensure that code that wants to know about colourful keyboards only gets valid ColourfulKeyboardDriver instances along a one code path, and KeyboardDriver instances for other keyboards along a different codepath.
The problem with this is that it doesn't scale. Say I have one keyboard which explodes, another with colourful LEDs, and another that has both. Say my userspace app has separate codepaths for exploding keyboards, colourful keyboards, and boring ones. If I plug in the keyboard that has both, does it go along both codepaths? If not, which one?
Either you get into a nasty tangle of multiple inheritance, or you have a separate interface for each combination of possible extensions. Here, with only two extensions, you would need 4 interfaces: plain, colourful, exploding, and both.
If you had 5 extensions, you would 32 interfaces.
Is there something I'm missing? How have others solved this problem? Is it possible to usefully do ADT with a C FFI?
Code: Select all
struct KeyboardDriver {
function(void) init;
function((int *scancode) -> bool) poll;
}
KeyboardDriver usb_kb_driver = {usb_kb_init, usb_kb_poll};
// define those functions
We can easily define something similar for ps/2, bluetooth, etc.
Now, imagine I get a fancy 'gaming' keyboard with LEDs whose colours can be changed. The driver for that keyboard exposes a function with a signature like
Code: Select all
void change_colour(int x, int y, int r, int g, int b)
I thought of a couple of possibilities, but none of them seem particularly good.
We could just add it as another member to the
Code: Select all
KeyboardDriver
Code: Select all
struct KeyboardDriver {
function(void) init;
function((int *scancode) -> bool) poll;
function((int x, int y, int r, int g, int b) -> void) change_colour;
}
As you add more and more optional extensions, that interface gets very bloated.
Another option is to have 'extensions'. So you have something like this:
Code: Select all
struct KeyboardDriver {
function((KbExtension) -> void*) load_extension;
function(void) init;
function((int *scancode) -> bool) poll;
}
enum KbExtension {
ChangeColour,
Explode,
...
}
------------------------------------------
void *pfn = kbd_drv.load_extension(ChangeColour);
if (pfn) {
function((int x, int y, int r, int g, int b) -> void) fn = cast()pfn;
//change the colour!
} else {
//colour changing not supported :(
}
Or we could bite the bullet, use OOP, and solve it with inheritance:
Code: Select all
interface KeyboardDriver {
function(void) init;
function((int *scancode) -> bool) poll;
}
interface ColourfulKeyboardDriver: KeyboardDriver {
function((int x, int y, int r, int g, int b) -> void) change_colour;
}
interface ExplodingKeyboardDriver: KeyboardDriver {
function(void) explode;
}
struct KinesisFreestyleRgbUsbDriver implements ColourfulKeyboardDriver {
//...
}
The problem with this is that it doesn't scale. Say I have one keyboard which explodes, another with colourful LEDs, and another that has both. Say my userspace app has separate codepaths for exploding keyboards, colourful keyboards, and boring ones. If I plug in the keyboard that has both, does it go along both codepaths? If not, which one?
Either you get into a nasty tangle of multiple inheritance, or you have a separate interface for each combination of possible extensions. Here, with only two extensions, you would need 4 interfaces: plain, colourful, exploding, and both.
If you had 5 extensions, you would 32 interfaces.
Is there something I'm missing? How have others solved this problem? Is it possible to usefully do ADT with a C FFI?