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 providing stdio_uart for debugging (in case stdio_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
Useful links
Related issues, PRs, and discussions:
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.ethosorslipdevto 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_uartinethos'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()andstdio_write(), these can be implemented by astdio_*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.
ethos.stdio_cdc_acmas main interface for user interaction but also providingstdio_uartfor debugging (in casestdio_cdc_acmis unable to be initialized).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).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 astdio_tobject that is provided via configuration.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):
Use case 3 still would be a NOP module, but we could use some compile-time config to make at least
stdio_read()andstdio_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
STDIO_MODULES) for STDIO modules (Makefile.dep: disable stdio_% modules before they are included #13650 (comment))Useful links
Related issues, PRs, and discussions: