Page 1 of 1

Generic driver interface, extensions?

Posted: Fri Apr 03, 2020 4:26 pm
by moonchild
Imagine a driver interface for keyboard (pseudocode):

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
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

Code: Select all

void change_colour(int x, int y, int r, int g, int b)
. 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

Code: Select all

KeyboardDriver
interface:

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;
}
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:

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 :(
}
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:

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 {
	//...
}
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?

Re: Generic driver interface, extensions?

Posted: Sun Apr 05, 2020 11:23 am
by kzinti
https://en.wikipedia.org/wiki/Ioctl

Code: Select all

DeviceIoControl(hKeyboard, DIOC_SET_COLOR, ...);
DeviceIoControl(hKeyboard, DIOC_EXPLODE, ...);

Re: Generic driver interface, extensions?

Posted: Sun Apr 05, 2020 2:09 pm
by moonchild
It's a better solution than any of mine, but it still doesn't give you safety. You can send an ioctl to a device that doesn't support it, and the type system won't protect you.

Re: Generic driver interface, extensions?

Posted: Sun Apr 05, 2020 10:10 pm
by kzinti
So what? The device will just ignore your request and/or return an error code to let you know it doesn't understand the ioctl.

You can always have compile-time type safety by using a function around the ioctl:

Code: Select all

int SetKeyboardColor(Color color)
{
    return DeviceIoControl(hKeyboard, DIOC_SET_COLOR, &color, sizeof(color));
}

...

    int status = SetKeyboardColor(color); // Type safe
    if (status < 0)
    {
        // handle error
    }
This is the same thing you would (should) do to wrap system calls. You can wrap different ioctl the same way.


Ultimately making a user -> kernel call (system call) is never going to be type safe without using such wrappers since the mechanism to call the kernel is not using your standard language ABI.

Re: Generic driver interface, extensions?

Posted: Sun Apr 05, 2020 10:29 pm
by nullplan
Separate concerns. The keyboard driver is for reading keys off a keyboard. You need another driver interface for the color changing LEDs. Since that is such a random thing, I would be tempted to just call it a "special" device. (Whether that is "special" in the "short bus" sense is anyone's guess). Then the gaming keyboard with color changing keys is just a multi-device, comprised of a normal keyboard and a special device. kzinti's tip about ioctls is actually a good one, since it allows you to pass arbitrary requests to kernel space.

So, this is what I'd do: Make your keyboard driver interface clean, and come up with a registry of special devices (there will often be a square peg that just doesn't fit any of the round holes you have available). Or to say it in your pseudo-code:

Code: Select all

interface KeyboardDriver {
    function(void) init;
    function((int *scancode) -> bool) poll;
}:

interface MiscDriver {
    function((int, void*) -> void) misc_request;
};

struct FancyKeyboardDriver implements KeyboardDriver, MiscDriver {
...
};
If color changing happens to you often enough that you want to make another driver category, go nuts. Make another interface.

So long as you can find a way to register all possible miscellaneous devices such that the requests all have stable numeric values across releases (so that a userspace application for changing the colors does not have to be recompiled), this should work beautifully. One way would be to have a giant list of all misc devices, and requests are then always 256 times the device number, plus whatever request you want to make of the device. On a 32-bit system, that would limit you to 256 distinct requests and 16 million misc devices. I think you can stay within those limits.

One more thing: Loose the init function from the interface. The driver should be initialized during device discovery, and thus be initialized from whatever you used to find the device. The PS/2 keyboard is just assumed to be there if ACPI says it is there, the USB keyboard is found on USB, and I don't know how you find the bluetooth keyboard. Therefore, an init function that takes no arguments is not really all that useful. The PS/2 keyboard needs no arguments (except maybe which port it is on), but the USB keyboard driver needs to know its HCI and the device number, at the very least. And the bluetooth keyboard driver will need to talk to the bluetooth interface, which in turn will likely need to talk to the PCI driver, etc.

Re: Generic driver interface, extensions?

Posted: Mon Apr 06, 2020 11:55 am
by moonchild
nullplan wrote:Separate concerns. You need another driver interface for the color changing LEDs. Since that is such a random thing, I would be tempted to just call it a "special" device.
That works for something like this, where you can control the extra functionality separately. But what about, e.g. a driver interface for a block storage device? If it's a spinny disc, then you should be able to tell it to spin down, but not so for an SSD. That's not separate from reading/writing the disc.

Re: Generic driver interface, extensions?

Posted: Mon Apr 06, 2020 12:06 pm
by moonchild
nullplan wrote:Separate concerns. You need another driver interface for the color changing LEDs. Since that is such a random thing, I would be tempted to just call it a "special" device.
Ahh, I see--you have the keyboard as a metadevice which owns a key-reading device and (maybe) a colour-changing device.

Re: Generic driver interface, extensions?

Posted: Mon Apr 06, 2020 1:55 pm
by nullplan
moonchild wrote:But what about, e.g. a driver interface for a block storage device? If it's a spinny disc, then you should be able to tell it to spin down, but not so for an SSD. That's not separate from reading/writing the disc.
Hoo boy. Since SSDs are about emulating hard disks with solid-state storage anyway, I'd be tempted to just make spinning the disk down or up a standard feature of block devices, in the case of SSDs that would be stubbed out. But honestly, I am not sure if this doesn't veer into power management, and then you might want to take a more holistic approach. Like having a block device driver interface that can read and write sectors, and a power management interface, that can standby and resume and recommend timings for such things.

Re: Generic driver interface, extensions?

Posted: Mon Apr 06, 2020 6:21 pm
by moonchild
nullplan wrote:Since SSDs are about emulating hard disks with solid-state storage anyway, I'd be tempted to just make spinning the disk down or up a standard feature of block devices, in the case of SSDs that would be stubbed out. But honestly, I am not sure if this doesn't veer into power management, and then you might want to take a more holistic approach.
Hmmm. I can see your point, but on the other hand, consider caching. That's generally implemented above in the file system layer, not in the block device driver. Granted, that generally needs more advanced logic, but it has a similar role: it's an optimization strategy which doesn't change functionality, but enhances usability. And especially since SSDs are replacing spinny discs for many applications (definitely not all, but many of them) as they get cheaper, the ability to spin down a disc becomes less and less useful.

Regardless, though, I think the broader point still stands: there can be 'optional' functionality which is really core functionality if it's there.