Device Driver Model - suggestions

I was wondering if there were discussions about a generic driver model for FreeRTOS.
My idea would be something like a driver model plus, maybe a device tree to reuse the same driver on different hardware architectures.

Is there any interest in this? Are there discussions/directions around this topic?



I’m just going to provide some info, rather than state an opinion, as I’m interested in other’s thoughts and don’t want to prejudice with my own.

I intended the FreeRTOS+IO product to be a POSIX style IO library for FreeRTOS, rather than inventing anything new. It got interest, but ultimately wasn’t successful. That can probably be attributed to it being overly complex to port, which meant it didn’t often get ported. FreeRTOS+IO defines several different ‘transaction’ modes, from polling to interrupt driven, whereas I think it would have been better to settle on just one - probably just interrupt driven with a stream buffer to hold data as the most generic and flexible (not all devices have DMA and DMA can be a hinderance if you only want one data byte). POSIX is a little weird for I2C too.

More recently there are the common IO libraries. Here is a subset useful for doing things like providing examples for ExpressLink or celluar interfaces. - the full set is used by the Amazon Common Software project, all drivers in which conformance tests.

Also considered adopting something like the Arduino interface - although that is quite limited (and C++).

Richard, thanks for the input.

I think it’s not bad, since it covers many use cases. Maybe we can do something to simplify portability. The “transaction” modes have specific use cases and probably the most generic one is the one you mention. But users need to understand the tradeoffs. Like writing a driver which uses polling vs. interrupt-driven.
It’s interesting you mention POSIX and I2C. IMHO we are talking about the level of abstraction here. I mean if you connect an EEPROM over I2C and you want to use it as a file system, then I2C should not be visible to the application programmer IMHO.

I’ll think about it as well and am curious about input from other people as well. Is it something people care about?

Also, it would be interesting what level of abstraction people expect to see and what are the use cases.

It would be great to write a device driver for a certain IP block that works across different architectures.

Do we want to be able to have wrappers (I saw something with FreeRTOS and POSIX) so people could run their applications on a POSIX OS as well as on FreeRTOS?

It is “Arduino users” we would like to attract with FreeRTOS?

Can’t share actual headers, as they are my companies, but we use a very generic interface layer.

Serial ports have a put character, put string, and give/take access mutex (so a task can put together a string in pieces), as well as set baud (or other parameters). The input side tends to just be a get character with timeout that returns -1 on timeout.

I2C does basic read or white or write then read cycles to a provided I2C address (and the address sets upper bits to indicate what I2C interface the part is on.

Drivers for specific peripherals use this generic interface, and can be used on any platform. If moving to a new processor, just need to implement the base drivers and suddenly get all the peripheral library available.

I would say that a simple generic interface works well, and more complicated is rarely needed, and very hard to keep generic, so we don’t really try, just keep to the basics.

This topic comes up in discussion all of the time! Like Richard I will try not to express too much of my own opinions but am very interested in seeing what people need out there. In my experience abstractions that port between large numbers of devices brings you further away from the hardware and as a result dealing with I/O pins and I2C is very hard on operating systems like Linux and Windows. I would love to look at patterns people have applied or used in the past that solved this problem and explore this further.

I’ll be happy to provide my input. Here’s my background: In the 90s I worked for Microsoft where I had access to source code to all OSs (Win3/95+ series as well as Windows NT) and wrote tech articles about system development issues. Since then most of my work was in RTOS based systems, developing and deploying embedded systems mostly in security environments. Device drivers, concurrency and M2M communication (all ISO/OSI levels, all media) were always both my vocation and avocation. Currently my contracts focus on Embedded Linux based systems. I am thoroughly familiar with the I/O systems of all major operating systems as well as the “back end” (device driver development). So I can look back to 30+ years of in depth involvement (from architecting to deployment/support) in systems level software.

Now to my catalogue: I believe that the “top level API” exposed to consumers of an I/O system would be pretty much off-the-shelf, meaning Open/Close/Read/Write/IoCTL. That’s well established and covers almost all use cases even to this day.

The aspect where an I/O system DOES make a huge difference, even thpough it’s hopelessly underestimated, is asynchronous I/O. So my primary requirement for an I/O system from scratch would be: Do NOT even think about select() or something compatible, even though it is tempting. Instead (even though it raises a number of spine hairs), look into Windows NT’s asynchronous I/O architecture. The one area where a server OS can score big time over the 200 pound gorillas is with some kind of support for I/O completion ports or compatible architectures which require a well thought out asynchronous I/O architecture. I’d be willing to assist if need be.


1 Like

dealing with I/O pins and I2C is very hard on operating systems like Linux

I tend to disagree with that.

If you want to blink a LED on ARM you can assume the drivers are available and it’s just an entry in the flattened device tree and you get an entry in /sys/class/leds where you can change triggers in runtime.

I/O: (GPIO/Input)
If you want a GPIO as an input you can assume the drivers are available and it’s just an entry in the flattened device tree. You can use the input event subsystem.

You could create /dev/i2cX device nodes and access them as such, but this lacks the abstraction I would like to see (and has other issues as well)

Think about the use case of a GPIO expander. It could be connected via I2C, SPI or it could be some custom stuff with some FPGA. In the case of a LED the user should not care how this led is connected. It could be via a GPIO, via a GPIO expander (I2C/SPI), or via the custom FPGA stuff. With a device driver model, we can have classes and busses and connect stuff. So you can reuse the same top-level code independent of the underlying hardware. Or you can connect your LED in a different way without rewriting the top-level code which accesses it.

I don’t want my spine hairs to be raised even more so I looked more into Linux than this other OS. There is something relatively new in Linux called “io_uring”[1]. I think this might be what you are looking for. The issue with this with respect to FreeRTOS might be how useful it can be on small Embedded systems and how much impact it can have on determinism of the system. Certainly it should improve the overall I/O throughput.

[1] The rapid growth of io_uring []

Hi there Robert,

I finally got around to looking at the uring architecture, thanks for the pointer! It is definitely an improvement and a big step towards NTs I/O completion port architecture, yet there are a number of inherent shortcomings in it which are mostly indebted to Linux’s history. Windows NT was designed from scratch, and Dave Cutler certainly knew what he was doing, so Windows NTs architecture, imho, is way superior to urings, and, as an additional benefit, maps much more natural to FreeRTOS!

One of the things I do not like about urings is that the predefined opcodes map directly to functionalities such as read, write from files or recv and send from sockets(1). I do not see any need for that whatsoever. In NT, any kernel object is waitable, and any waitable object can be associated with a completion port, regardless of what the waiting reason is, so a completion port can generically and without limits serve any synchronisation object. Decoding on where the computation sits is entirely up to the processing thread via a freely definable context structure. In fact, IOCPs can also be used outside of any I/O architecture as the concept of “waiting” on something is much more generic than I/O.

In FreeRTOS, this could mean, for example, that any queue-derived object could be attached to a completion port, regardless of whether the queue implements a mutex or a sempahore used to implement, e.g. socket or file system notification or anything else that a queue can implement. No need to distinguish between high level functionalities or semantic interpretations of the wait/suspend reasons. This could easily be added via a compound context structure, but in my experience, I do not see a big need for it. By predefining (thus limiting) semantics, it is harder to add new ones without the need for a tedious and resource-and time consuming certication/review process.

I have more than once toyed with code that would implement “pure” IOCPs on FreeRTOS, and I do not believe it would be that hard to implement.

(1) From what I understand about Unix/Linux, the need for separate sets of I/O API functions for network and file I/O is rather arbitrary and has been debated heatedly over time, with several attempts to provide a unifying API over time. It is hard for me to understand why the uring API now goes back to that historical mess.