Device drivers
Device drivers
Hi,
One of the recent threads on drivers made me thinking. It would be nice to have common, reusable driver set for hobby OSes, but so far all such attempts failed. I was thinking about why, and how could we avoid their mistakes. This is what I got this far:
- BSD, Linux etc.: OpenSource true, but they are tied to their kernels way too much
- CDI, UDI: they are way too complex trying to solve everything and their structure not ideal for a microkernel
- Minix: as it has a separated message queue interface, this is the best candidate so far, but not suitable for monolithic kernels
- FUSE: is a good working example on how such a source compatible interface can be done
So what if we wouldn't want to solve all the issues (like UDI) instead we focus on the smallest common denominator and aim for K.I.S.S.? Could it be viable and would there be enough interest in such a project?
What I have in mind, is to define a source compatible interface in each and every driver. The spec would not care about how to name or organise your device files (or if you have a /dev file system at all), it would not care how you call those functions (directly from the FS layer or from a message dispatcher), and it would not care about how you detect the devices, those would be up to the kernel entirely.
Instead what it would do, is to provide the following functions in every compatible driver:
- init: checks if the device really exists and sets up internal data structures. Called on boot or when the device is attached
- open: called with a device index when the corresponding device file opened (if there's no /dev then it's the kernel's responsibility to call this hook with the appropriate device index before any reads or writes)
- read: called with a device index and buffer address, size. The driver is supposed to read size bytes into the buffer from the device indexed by the argument.
- write: similar to read, but for writes
- ioctl: set up or query device characteristics. Same arguments as read/write, the command buffer would be device type specific. For queries, the response would be placed in the command buffer.
- seek: set position for the given device index if applicable. Character devices (like uart) would simply do nothing in this function
- irq: called with the device index when irq is fired
- reset: reset the device to it's defaults.
- close: called when the corresponding device file closed. Could be an empty function (if there's no /dev. then the kernel should call this with the appropriate device index when reads or writes are done)
- fini: called when the device is removed or when the system is shutting down
The header would provide ANSI C function prototypes for these functions, gathered together in a struct typedef with a C++ wrapper (and maybe a C++ class template if there's interest in that). The struct typedefs for the ioctl commands would be defined here too. I'm planning to have common structs here, so not like ath5k_setmtu_struct_t and such, but instead command with two 64 bit arguments, command with a buffer and 3 32 bit arguments etc. For example all serial drivers would be expected to use the same, single command struct for setting baud, data bits, parity, stop bits etc. This header would define the error codes too (negative return values, like device timed out, buffer not big enough etc.)
On a typical UNIX-like system, the device index would be the device minor number. Other kernels must face the fact that there can be more devices of the same kind, handled by the same driver, so they must index those somehow. This index would be passed to every function as a first argument, and there it would select the proper device context from the internal data.
The spec would expect a compliant kernel to provide "register_irq", "malloc" and "free", but those could be macros.
Integration in a monolithic kernel: the FS layer should call driver functions directly when needed. The provided malloc should be a macro and should allocate kernel heap memory.
Integration in a microkernel: there's a need to create a dispatcher main() function to receive a message and call one of these functions accordingly (there should be no common wrapper, like fuse_main() for example, because it's up to every kernel how it implements messaging). The malloc could be a standard libc malloc implementation.
We could call this spec OSDev Drivers, or ODD in short (because it's odd to specify only the smallest subset in each and every driver, but if you think about it, helps portability a lot).
Any thoughts? Would you be interested in such a minimalistic approach?
bzt
One of the recent threads on drivers made me thinking. It would be nice to have common, reusable driver set for hobby OSes, but so far all such attempts failed. I was thinking about why, and how could we avoid their mistakes. This is what I got this far:
- BSD, Linux etc.: OpenSource true, but they are tied to their kernels way too much
- CDI, UDI: they are way too complex trying to solve everything and their structure not ideal for a microkernel
- Minix: as it has a separated message queue interface, this is the best candidate so far, but not suitable for monolithic kernels
- FUSE: is a good working example on how such a source compatible interface can be done
So what if we wouldn't want to solve all the issues (like UDI) instead we focus on the smallest common denominator and aim for K.I.S.S.? Could it be viable and would there be enough interest in such a project?
What I have in mind, is to define a source compatible interface in each and every driver. The spec would not care about how to name or organise your device files (or if you have a /dev file system at all), it would not care how you call those functions (directly from the FS layer or from a message dispatcher), and it would not care about how you detect the devices, those would be up to the kernel entirely.
Instead what it would do, is to provide the following functions in every compatible driver:
- init: checks if the device really exists and sets up internal data structures. Called on boot or when the device is attached
- open: called with a device index when the corresponding device file opened (if there's no /dev then it's the kernel's responsibility to call this hook with the appropriate device index before any reads or writes)
- read: called with a device index and buffer address, size. The driver is supposed to read size bytes into the buffer from the device indexed by the argument.
- write: similar to read, but for writes
- ioctl: set up or query device characteristics. Same arguments as read/write, the command buffer would be device type specific. For queries, the response would be placed in the command buffer.
- seek: set position for the given device index if applicable. Character devices (like uart) would simply do nothing in this function
- irq: called with the device index when irq is fired
- reset: reset the device to it's defaults.
- close: called when the corresponding device file closed. Could be an empty function (if there's no /dev. then the kernel should call this with the appropriate device index when reads or writes are done)
- fini: called when the device is removed or when the system is shutting down
The header would provide ANSI C function prototypes for these functions, gathered together in a struct typedef with a C++ wrapper (and maybe a C++ class template if there's interest in that). The struct typedefs for the ioctl commands would be defined here too. I'm planning to have common structs here, so not like ath5k_setmtu_struct_t and such, but instead command with two 64 bit arguments, command with a buffer and 3 32 bit arguments etc. For example all serial drivers would be expected to use the same, single command struct for setting baud, data bits, parity, stop bits etc. This header would define the error codes too (negative return values, like device timed out, buffer not big enough etc.)
On a typical UNIX-like system, the device index would be the device minor number. Other kernels must face the fact that there can be more devices of the same kind, handled by the same driver, so they must index those somehow. This index would be passed to every function as a first argument, and there it would select the proper device context from the internal data.
The spec would expect a compliant kernel to provide "register_irq", "malloc" and "free", but those could be macros.
Integration in a monolithic kernel: the FS layer should call driver functions directly when needed. The provided malloc should be a macro and should allocate kernel heap memory.
Integration in a microkernel: there's a need to create a dispatcher main() function to receive a message and call one of these functions accordingly (there should be no common wrapper, like fuse_main() for example, because it's up to every kernel how it implements messaging). The malloc could be a standard libc malloc implementation.
We could call this spec OSDev Drivers, or ODD in short (because it's odd to specify only the smallest subset in each and every driver, but if you think about it, helps portability a lot).
Any thoughts? Would you be interested in such a minimalistic approach?
bzt
Re: Device drivers
Do you consider the fact that a lot of folks have patchy understanding and handle of things like:
Or do you think those are non-issues? (If they weren't, well, everyone would have proper drivers and minimally functional OSes (and not just some crappy hello-world-like boot loaders) in a matter of months) What if they can't understand your code or satisfy its requirements because, well, they just aren't up to the task yet or they're doing something inherently incompatible with your code for one reason or another?
IOW, what's your idea as to why there is a problem and what it is? Are you sure you aren't trying to help with the wrong thing?
- memory management, especially with page translation
- exclusive/atomic accesses, race conditions, deadlocks
- multi-processor system issues
- interrupts
- device specifics
- format and protocol specifics
- C
- assembly
- error handling
- design (especially, well structured and testable)
- debugging
- testing
- tools
Or do you think those are non-issues? (If they weren't, well, everyone would have proper drivers and minimally functional OSes (and not just some crappy hello-world-like boot loaders) in a matter of months) What if they can't understand your code or satisfy its requirements because, well, they just aren't up to the task yet or they're doing something inherently incompatible with your code for one reason or another?
IOW, what's your idea as to why there is a problem and what it is? Are you sure you aren't trying to help with the wrong thing?
Re: Device drivers
One question that comes to mind is how to handle synchronous / asynchronous drivers? In contrast to synchronous drivers that just return from the entry point, asynchronous drivers probably want to complete operations by invoking a callback. Would that be out of scope?
In addition to that, there needs to be some way to access hardware (e.g., to map MMIO regions from PCI BARs and/or legacy HW).
In addition to that, there needs to be some way to access hardware (e.g., to map MMIO regions from PCI BARs and/or legacy HW).
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
-
- Member
- Posts: 71
- Joined: Fri Jun 28, 2013 1:48 am
- Contact:
Re: Device drivers
For simple devices like pit and ps2 keyboard, I'd rather write my own driver. Since those devices are tightly coupled with the kernel, and they are very easy.
ODD spec is only good for big and complex devices, like graphics adaptor, network card, sound card, etc. That also means we have to define a set of kernel API, just like ACPICA does.
ODD spec is only good for big and complex devices, like graphics adaptor, network card, sound card, etc. That also means we have to define a set of kernel API, just like ACPICA does.
Reinventing the Wheel, code: https://github.com/songziming/wheel
Re: Device drivers
Hi,
Thanks for the answers! I think there's a misunderstanding on the goal of this project, I'll try to explain below.
I hope this makes it clear what ODD is and isn't. To sum it up, it's a C/C++ header file to provide a standardized driver API and interface to make porting drivers among hobby OSes easier. Nothing more, nothing less.
Cheers,
bzt
Thanks for the answers! I think there's a misunderstanding on the goal of this project, I'll try to explain below.
No. I don't care what folks can't do. I'm interested in helping those who can. Developers with patchy understanding of those things wouldn't be able to write their OS, and no interface can help with that. They must learn and practice more, that's the only way. But beginners are not ODD's target audiance. It's aimed for advanced developers, who have implemented a lot already in their kernels, and now want to increase the number of supported devices.alexfru wrote:Do you consider the fact that a lot of folks have patchy understanding and handle of things like
No it is not. But easier than to get and read all hw specs and implement everything on your own. ODD does not provide ready-to-use building blocks btw, that's not the goal. It's just a recommendation on driver source interfaces. You definitely need to do some integration work in your kernel with that, but that's okay as ODD doesn't want to tell you how you should write your kernel or if you want async or sync calls or microkernel or monolithic kernel etc.alexfru wrote:Implementing the building blocks isn't easy. Putting a system together using those blocks isn't easy either
That's why I'm aiming at a minimalistic approach. ODD does not care about the components nor how they interact, except for the lowest layer of the driver, but only at source compatibility level.alexfru wrote:One needs to do that while really understanding what they're doing, how the components work and interact with one another, etc.
Then they won't be able to understand the hw specs and write their own drivers either. Simple as that. Again, ODD is not a complex code, just some definitions in a single C header. It's just an interface that encapsulates the lowest layer of the driver. By following it's recommendations and using it's typedefs, hobby OSes could exchange driver sources with minimal integration effort (but not with zero effort).alexfru wrote:What if they can't understand your code or satisfy its requirements because, well, they just aren't up to the task yet or they're doing something inherently incompatible with your code for one reason or another?
Positive. I'm not trying to design a complex API, or binary compatible drivers or anything like that. I'm just suggesting a minimalistic source driver interface with some kernel hooks to make porting device drivers among hobby OSes easier.alexfru wrote:IOW, what's your idea as to why there is a problem and what it is? Are you sure you aren't trying to help with the wrong thing?
Out of the scope. This spec is a lower layer than that. This only provides somewhat standardized way to read/write from a device, but it does not care when and how you call those. That's a higher abstraction level which I deliberatly don't want to address. The point is to create a simple interface for the drivers so that you can integrate them into your kernel more easily.Korona wrote:One question that comes to mind is how to handle synchronous / asynchronous drivers?
That's correct.Korona wrote:In addition to that, there needs to be some way to access hardware (e.g., to map MMIO regions from PCI BARs and/or legacy HW).
Exactly. Let's say you already have a networking stack but only one network card driver. With ODD, you can increase the number of supported network cards easily (but you have to have a networking stack, as ODD does not want to solve everything). Another example, you have implemented a GUI with shiny screen resolution chooser, but you have only one video card driver. With ODD, you can add more (but you have to have the GUI and screen resolution chooser as ODD does not provide those).songziming wrote:ODD spec is only good for big and complex devices, like graphics adaptor, network card, sound card, etc.
Definitely not. ACPICA tries to solve all aspects and create fully functional driver ecosystem and therefore is very complex. ODD just tries to somewhat standardize driver interfaces to ease portability. Fundamentally different goals. But it is true that it requires some hooks from the kernel, but I definitely want to keep that as small as possible (4-5 functions tops).songziming wrote:That also means we have to define a set of kernel API, just like ACPICA does.
I hope this makes it clear what ODD is and isn't. To sum it up, it's a C/C++ header file to provide a standardized driver API and interface to make porting drivers among hobby OSes easier. Nothing more, nothing less.
Cheers,
bzt
Re: Device drivers
init, open, close, read, write, lseek, ioctl?bzt wrote:I hope this makes it clear what ODD is and isn't. To sum it up, it's a C/C++ header file to provide a standardized driver API and interface to make porting drivers among hobby OSes easier. Nothing more, nothing less.
Re: Device drivers
Could be any other names. I've choosen those to help with understanding of their purpose. That's why I've also written a small description for each.alexfru wrote:init, open, close, read, write, lseek, ioctl?
But if you like, could be: attach, startop, endop, getdata, senddata, setpos, command (and deattach instead of fini).
Cheers,
bzt
Re: Device drivers
My point is that you've pretty much chosen the old (I'm not saying necessarily/universally bad) paradigm of everything being a file with a small hole for extensions to it and exceptions from it (ioctl, AKA Pandora's box ). It's been around for a long time and has become a practical standard.bzt wrote:Could be any other names.alexfru wrote:init, open, close, read, write, lseek, ioctl?
If the intended audience, as you say, is knowledgeable and experienced, couldn't they pull off something similar, write the same kind of familiar (UNIXish?) API?
Or is there an actual intent to supply the API with implementations? Are you gonna write them? If not, who? Whom and how are you going to convince to code their stuff to your API?
-
- Member
- Posts: 223
- Joined: Thu Jul 05, 2007 8:58 am
Re: Device drivers
As you currently suggest it, there are several problems with your proposed interface
First, the suggested interface really only works for a kernel implementation that "pulls" information out of its device drivers. For example, suppose you would have a network card driver adhering to this interface. Then you would need to have something (probably a thread) continously calling read and waiting for its results to inject packets into the rest of the network stack. This might be far from ideal for systems that are designed with the idea in mind that network card drivers "push" received packets up through the rest of the network stack. You claim this is out of scope, but this actually determines on the lowest level how a driver is structured.
Second, while the open/read/write/ioctl/seek/close interface works decent (although it definitely has its problems) for cramming direct device access through file systems, it is in my opinion a lot less suitable for an internal kernel device interface. The main issue is that for quite a number of devices, there will be a significant device-agnostic stack on top of it interacting with the device. For example, for a network interface, there is typically a complete tcp/ip stack handling the traffic of all the individual interfaces, which therefore would need to talk with individual network card drivers. To make this possible, you will need to specify a lot more specifically how a network card is to expose its functionality over this interface. Similar problems also come up with things like sound cards, serial ports, keyboards and displays.
Third, you are certainly going to need more than just "register_irq", "malloc" and "free" for the operating system interface. At a minimum, most drivers will need the ability to request access to: port ranges (port io), specific memory regions (memory mapped io). Beyond that, for things like dma, they will need some way to deal with physical adresses and physically contiguous buffers.
First, the suggested interface really only works for a kernel implementation that "pulls" information out of its device drivers. For example, suppose you would have a network card driver adhering to this interface. Then you would need to have something (probably a thread) continously calling read and waiting for its results to inject packets into the rest of the network stack. This might be far from ideal for systems that are designed with the idea in mind that network card drivers "push" received packets up through the rest of the network stack. You claim this is out of scope, but this actually determines on the lowest level how a driver is structured.
Second, while the open/read/write/ioctl/seek/close interface works decent (although it definitely has its problems) for cramming direct device access through file systems, it is in my opinion a lot less suitable for an internal kernel device interface. The main issue is that for quite a number of devices, there will be a significant device-agnostic stack on top of it interacting with the device. For example, for a network interface, there is typically a complete tcp/ip stack handling the traffic of all the individual interfaces, which therefore would need to talk with individual network card drivers. To make this possible, you will need to specify a lot more specifically how a network card is to expose its functionality over this interface. Similar problems also come up with things like sound cards, serial ports, keyboards and displays.
Third, you are certainly going to need more than just "register_irq", "malloc" and "free" for the operating system interface. At a minimum, most drivers will need the ability to request access to: port ranges (port io), specific memory regions (memory mapped io). Beyond that, for things like dma, they will need some way to deal with physical adresses and physically contiguous buffers.
Re: Device drivers
Hi,
Another example: a storage driver would allow you to read and write a sector, but it won't parse a partitioning table for you as that's a higher abstraction layer.
Okay, let's forget it. It seems the concept of a source-compatible, low level interface is beyond your comprehention, you are confused by similar names and lost in abstraction layers. That's a pity. (No offense meant, the answers made it clear people on this forum are not ready yet to collaborate on such a project.)
Cheers,
bzt
I have not. Where are the files in my proposed interface? You are biased by the names, but again, could be "attach, startop, endop, getdata, senddata, setpos, command (and deattach instead of fini)." As I've said, I've only used the names you are familiar with to help understanding of the purpose of each function. There's a good reason why I wrote the first argument is a device index (and not a file descriptor nor a FILE struct pointer), because there are no files involved at all.alexfru wrote:My point is that you've pretty much chosen the old (I'm not saying necessarily/universally bad) paradigm of everything being a file
There's no "pull" or "push" defined here. You are lost in the abstractions. There's only a function to get the data (the received packet) from the device, and a function to send data (packet to be sent) to the device that's all. ODD does not tell you when to call those and what to do with the data (that's a higher abstraction layer), you can push that received data up to your network stack if you like. Since you have mentioned the network stack, if I wrote that ODD driver is only responsible for communicating the ISO/OSI physical layer 1 with the device, but does not care how that data is used by layer 2 and above, would it make now sense to you?davidv1992 wrote:This might be far from ideal for systems that are designed with the idea in mind that network card drivers "push" received packets up through the rest of the network stack.
Another example: a storage driver would allow you to read and write a sector, but it won't parse a partitioning table for you as that's a higher abstraction layer.
No files or file systems at all. Just drivers and device indeces. For example you have a machine with two ne2k cards. Then it is up to your kernel how to access the driver; ODD only expects that the ne2k driver should consistently choose the same card for the index 0 and 1. Likewise, if you have 4 serial ports, then it is up to your kernel how you call the uart driver; ODD only expects that index 0-3 should consistently select the same port (which can be "COM1:", "/dev/ttyS1", used by a special CommOpen(1) API whatever, ODD doesn't care).davidv1992 wrote:device access through file systems
Okay, let's forget it. It seems the concept of a source-compatible, low level interface is beyond your comprehention, you are confused by similar names and lost in abstraction layers. That's a pity. (No offense meant, the answers made it clear people on this forum are not ready yet to collaborate on such a project.)
Cheers,
bzt
-
- Member
- Posts: 232
- Joined: Mon Jul 25, 2016 6:54 pm
- Location: Adelaide, Australia
Re: Device drivers
If you can't respond to pretty straight forward and obvious criticism about your ideas without getting huffy, maybe it's you who isn't ready to collaborate with others. If you brought something to the table beyond an idea people might offer something beyond critique of the idea in return.bzt wrote: Okay, let's forget it. It seems the concept of a source-compatible, low level interface is beyond your comprehention, you are confused by similar names and lost in abstraction layers. That's a pity. (No offense meant, the answers made it clear people on this forum are not ready yet to collaborate on such a project.)
Personally, I doubt such a simple interface would encapsulate meaningful functionality for anything but the simplest devices, so I don't see the point of it since it won't actually make the task of porting drivers any easier.
Re: Device drivers
The original post describes the standard driver interfaces found in commercial systems, although they are not source-compatible on their own, which is what is being sold.
The goal seems to be to write a framework (each OS will implement and maintain it in different ways) which provides a common interface so that a driver can be written under its specification, and be used anywhere that framework runs.
The cost of this framework has to be less than the cost it takes to write separate drivers for each of the participating systems. I doubt that that condition will be satisfied.
But, there are these limited to smaller subsystems:
Windows has storport port driver, to whose API specification the storage vendors write their miniport drivers. Windows also has a NDIS. Interestingly, Linux can run some NDIS drivers through a wrapper. I concede that they belong to a single OS (namely, Windows) but they are still (specialized) interfaces meant to reduce the effort spent by developers (vendors) writing the corresponding drivers.
An important point to note here is that, even though this is a single OS, NDIS and storport are two /different/ frameworks, each catering to devices possessing different properties.
The goal seems to be to write a framework (each OS will implement and maintain it in different ways) which provides a common interface so that a driver can be written under its specification, and be used anywhere that framework runs.
The cost of this framework has to be less than the cost it takes to write separate drivers for each of the participating systems. I doubt that that condition will be satisfied.
But, there are these limited to smaller subsystems:
Windows has storport port driver, to whose API specification the storage vendors write their miniport drivers. Windows also has a NDIS. Interestingly, Linux can run some NDIS drivers through a wrapper. I concede that they belong to a single OS (namely, Windows) but they are still (specialized) interfaces meant to reduce the effort spent by developers (vendors) writing the corresponding drivers.
An important point to note here is that, even though this is a single OS, NDIS and storport are two /different/ frameworks, each catering to devices possessing different properties.
A distinction without a difference. The index/fd/FILE all identify the device and help in locating/managing the device's state.bzt wrote: There's a good reason why I wrote the first argument is a device index (and not a file descriptor nor a FILE struct pointer), because there are no files involved at all.
Please don't lose hope on account of us pitiable folks. Take two OS (I suggest Linux and one of the BSDs), remove the built-in support for a device (for e.g. a usb host controller) in both, and build and publish the interfaces and the common driver.bzt wrote: Okay, let's forget it. It seems the concept of a source-compatible, low level interface is beyond your comprehention, you are confused by similar names and lost in abstraction layers. That's a pity. (No offense meant, the answers made it clear people on this forum are not ready yet to collaborate on such a project.)
Re: Device drivers
One thing that might make the effort more approachable is separating what the drivers provide from what driver demand. Both can be specified individually, possibly in two different documents. For example, in microkernels it could be reasonable for drivers to demand access to (part of) a standard C library. Hence, stuff like malloc() does not need to be specified in this case. Instead, it would be possible to first focus on what drivers provide, for example, the read()/write()/ioctl() interfaces.
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
Re: Device drivers
What if a driver interface looks completly different? For example my kernel doesn't have general read/write functions at all. There is only an open function that returns function pointers to the driver's read/write routines. How would this work with your idea?bzt wrote:Instead what it would do, is to provide the following functions in every compatible driver: open ... read ... write
Re: Device drivers
IOCTL will handle everything. It's specifically designed for abuse.Sturm wrote:What if a driver interface looks completly different? For example my kernel doesn't have general read/write functions at all. There is only an open function that returns function pointers to the driver's read/write routines. How would this work with your idea?bzt wrote:Instead what it would do, is to provide the following functions in every compatible driver: open ... read ... write