Description
Description
We need to rethink our STDIO model! With new methods for STDIO emerging (stdio_cdc_acm
, stdio_nimble
, ...) and usage of modules that multiplex STDIOs with other functionality (e.g. ethos
or slipdev
to multiplex network connections via serial) getting more and more complicated to configure, while only being able to just talk to one specific lower-end implementation of STDIO (stdio_uart
in ethos
's case) this becomes clearer and clearer. After some brainstorming with @haukepetersen and @kaspar030 I decided to write down our initial ideas to be build upon and maybe to find someone™ to implement it in the end.
Problems with the current model
The current model maps STDIO system calls to the functions stdio_read()
and stdio_write()
, these can be implemented by a stdio_*
module to provide the desired functionality. The problem is that this only allows for exactly one STDIO implementation at a time. This leads to problems if this use case isn't given anymore.
Use cases
There are several use cases that call for a new model.
- Mapping a more advanced STDIO on top of a more basic STDIO: e.g. multiplexing STDIO with other functionality such as in
ethos
. - Having multiple STDIO interfaces: e.g.
stdio_cdc_acm
as main interface for user interaction but also providingstdio_uart
for debugging (in casestdio_cdc_acm
is unable to be initialized). - Having no STDIO: This is currently modeled as a module with empty operations called
stdio_null
. However, maybe we find a way to not necessitate it at all.
Model proposal
My initial gut / keep-it-simple feeling would call for a driver-based approach. That means that a new STDIO implementation would provide a struct with function pointers similar as we did it elsewhere (e.g. in netdev
).
typedef struct {
ssize_t (*read)(void *buffer, size_t max_len);
ssize_t (*write)(const void *buffer, size_t len);
} stdio_t;
Additionally a module dependent init-function is provided.
A top-level implementation (under the current model that would be the equivalent to stdio_read()
/stdio_write()
) would then call a stdio_t
object that is provided via configuration.
static const stdio_t *_stdio;
void stdio_init(const stdio_t *stdio)
{
_stdio = stdio;
}
ssize_t stdio_read(void *buffer, size_t max_len)
{
return _stdio->read(buffer, max_len)
}
ssize_t stdio_write(const void *buffer, size_t len)
{
return _stdio->write(buffer, max_len)
}
This should IMHO cover at least use case 1 and 2 (please understand the code snippets more as pseudo-code than actual implementation ideas, I don't think it is as easy as I make it seem here):
- Initialize the more advanced STDIO with the more basic STDIO and use the advanced STDIO in the top-level implementation, e.g.
const stdio_t *stdio = stdio_uart_init(uart_config, baudrate); stdio = ethos_init(stdio, ...); stdio_init(stdio);
- Provide a multiplexer STDIO to call multiple other STDIOs
const stdio_t *stdios[] = { uart, cdc_acm, NULL, } const stdio_t *stdio = stdio_multiplex_init(stdios); stdio_init(stdio);
Use case 3 still would be a NOP module, but we could use some compile-time config to make at least stdio_read()
and stdio_write()
also NOPs in case no other STDIO module is present.
For the more graphically minded people here is a somewhat thrown together UML diagram of what I am thinking of (showing both the cases shown in 1. and 2. as a model)
More considerations
- Provide support for different STDIOs to different file descriptors / module-wise STDIO? (stdio: a layered / multiheaded approach to I/O #13469 (comment))
- Provide a separate module list (e.g.
STDIO_MODULES
) for STDIO modules (Makefile.dep: disable stdio_% modules before they are included #13650 (comment)) - Have there no stdio module if stdio is not needed (Makefile.dep: disable stdio_% modules before they are included #13650 (comment))
Useful links
Related issues, PRs, and discussions:
- sys/stdio_nimble: add new stdio module using nimble #12012
- boards/arduino-mkr: feather-m0: sodaq-*: provide stdio over USB and setup automatic flash with bossa #12304
- stdio_semihosting: Initial include of Semihosting-based STDIO #13320
- Makefile.dep: DEFAULT_MODULE += stdio_rtt will always load dependencies - even if other stdio is selected #13460
- Makefile.dep: disable stdio_% modules before they are included #13650
- sys/net/application_layer: add telnet server module & example #16723
- UART-ish-over-CoAP protocol proposal in the forum