Hello Oleg,
Hi Kévin!
I began to use function pointers in the 'radio_driver.h' when trying to
create a unified model for radio drivers. I guess it went over later to the
whole netdev effort.
Yes, I remember that, but why can't this be implemented in a similar way we
did for most of the peripheral drivers like SPI? Instead of calling a function
pointer, we simply pass a struct describing the low-level device (driver) that
should be used.
This is more or less how Contiki actually works -- and what I planned to use in the 'radio_driver.h' mechanism : you use structs... But these structs ultimately contains function pointers. Of course, if this struct is 'const' -- and it *should* be -- it actually translates into a single indirect call in assembler (as I said before).
My idea then was to have a "fast", predictable call chain from the MAC layer
to the physical radio transceiver functions: most MAC protocols expect to
run with precise delays, and a mechanism relying on message between threads
(like 'transceiver') did not fit that need.
I completely agree on the point that we need to have direct function calls for
MAC protocols (see also http://arxiv.org/abs/1502.01968).
The model of driver as structs containing function pointers is the one used
in Contiki: it seemed quite a natural way to handle the problem to me... In
their code, the needed drivers are aliased as 'NETSTACK_xxx' macros, then
functions are called by invoking 'NETSTACK_xxx.func()'.
I think it is indeed one of the two natural solutions - the other one would be
MACROing everything. I made experiences with both versions while developing on
firmware for older sensor board platforms. And both had their drawbacks. Thus,
when I started to work on what now is known as RIOT, I wanted to avoid Macros
and a function pointer based abstraction layer model as much as possible. Of
course, there are cases where either the one or the other will be the best
solution, but I think we have to make sure to check if there are no
alternative solutions that might work better on the long run.
The const struct of function pointers seems actually cleaner and easier to understand IMHO. Macros in C can quickly turn into a mess if they are used too "liberally". The only macros used in Contiki's netstack model is for choosing the used driver for a given layer. This seems clear enough to me; not to mention that Contiki is known for its efficiency to use constrained devices.
As P. Nikander said, since you are supposed to know your hardware at compile
time, these pointers are actually constants that should be resolved at
compile (linking) time -- even with GCC. Theoretically, it should be
translated by standard indirect calls at the assembler level.
You name it: If everything is known at link time, then there is also always a
way around function pointers. As I've written in the previous mail: I'd rather
tell the compiler (or linker) how to do the stuff in an efficient way than
hoping for the toolchain being smarter than me.
Explicitly mentioning which drivers to use (thanks to simple macros like NETSTACK_xxx), in the board definition for the radio PḦY layer, and in the application Makefile for higher layers, seems quite simple and efficient IMHO...
Could you tell us more about your previous experiments? It seems quite
strange to me that such a mechanism (that seems simple to me) could impose
speed and debugging penalties.
I cannot disclose any code here, because it was proprietary code for my old
company, but the problem was a layered one. We had a highly abstracted driver
model, similar to Linux' one with char and block devices, abstracted serial
line devices and the like. Thus, an application or any high level code
accessing low-level drivers would be de-referenced multiple times. This made
it incredible hard to debug (because you had to follow all the pointer
de-referencing) and made it almost impossible to hold some strict deadlines
for TDMA on our old MSP430F1612.
OK I get the point: your former boss(es) wanted to have a high-level abstraction model, like in Linux. Well, this is like allocating memory at runtime for everything: a good practice on powerful hardware that (more importantly) includes a VMMU -- like our PCs --, but it becomes a chore on less powerful, MMU-less microcontrollers...
As said before, I didn't think to variable function pointers allocated at runtime, but to the "driver as a const struct of function pointers" model.
The other question is how could we implement the network stack? Using a
mechanism like 'transceiver', that relies on message-passing between
threads, implies that there is no predictable delay for executing network
functions (one has to wait for the scheduler to switch to the thread(s) that
execute(s) the feature(s) you want to use). At some levels -- like between
MAC and PHY -- this can be a huge disadvantage.
I think we can live with the overhead of IPC calls on all layers except the
MAC protocol. However, even message-passing has a predictable delay in RIOT -
just the concrete numbers might be to high for some low latency MAC protocols.
But again, I'm definitely not arguing against function calls for these tasks,
just wondering if function pointers are the smartest solution here. (And I'm
not saying that they're not...)
Indeed, the most "sensible" link in the netdev chain is probably between the PHY and MAC layers...
Well, if we go the high-level way, we could indeed almost revert to message-passing-only API for the all of the netstack; except for the lowest PHY layer, that could use a "const struct" API usable by the MAC/RDC layer.
But that actually means reverting all of the netdev effort made by Martine, Hauke and al. during weeks!
The other point I see is that if we use the IPC/message model for the whole netstack, all of its layers will need to be implemented as independent threads, and of course, each of these threads will need their own stack.
And here comes another big drawback: this will considerably increase the RAM needed to run the netstack. With a complete stack that goes from PHY up to the app layer, we will certainly eliminate all of the 8 and 16-bit microcontrollers that barely have more than 8 Kb of RAM. In fact, we may even exhaust the MCU that have 32 Kb of RAM... which includes the Cortex-M0+ of the SAMD21/SAMR21!
IMHO, one of the main advantage of the "const struct of function pointers" model, with the faster response time, is the lessening of RAM requirements by diminishing the number of created threads. That's not something negligible...
Cheers,
Oleg
Best regards,