zephyr/drivers/console/native_posix_console.c
Gerard Marull-Paretas a5fd0d184a init: remove the need for a dummy device pointer in SYS_INIT functions
The init infrastructure, found in `init.h`, is currently used by:

- `SYS_INIT`: to call functions before `main`
- `DEVICE_*`: to initialize devices

They are all sorted according to an initialization level + a priority.
`SYS_INIT` calls are really orthogonal to devices, however, the required
function signature requires a `const struct device *dev` as a first
argument. The only reason for that is because the same init machinery is
used by devices, so we have something like:

```c
struct init_entry {
	int (*init)(const struct device *dev);
	/* only set by DEVICE_*, otherwise NULL */
	const struct device *dev;
}
```

As a result, we end up with such weird/ugly pattern:

```c
static int my_init(const struct device *dev)
{
	/* always NULL! add ARG_UNUSED to avoid compiler warning */
	ARG_UNUSED(dev);
	...
}
```

This is really a result of poor internals isolation. This patch proposes
a to make init entries more flexible so that they can accept sytem
initialization calls like this:

```c
static int my_init(void)
{
	...
}
```

This is achieved using a union:

```c
union init_function {
	/* for SYS_INIT, used when init_entry.dev == NULL */
	int (*sys)(void);
	/* for DEVICE*, used when init_entry.dev != NULL */
	int (*dev)(const struct device *dev);
};

struct init_entry {
	/* stores init function (either for SYS_INIT or DEVICE*)
	union init_function init_fn;
	/* stores device pointer for DEVICE*, NULL for SYS_INIT. Allows
	 * to know which union entry to call.
	 */
	const struct device *dev;
}
```

This solution **does not increase ROM usage**, and allows to offer clean
public APIs for both SYS_INIT and DEVICE*. Note that however, init
machinery keeps a coupling with devices.

**NOTE**: This is a breaking change! All `SYS_INIT` functions will need
to be converted to the new signature. See the script offered in the
following commit.

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>

init: convert SYS_INIT functions to the new signature

Conversion scripted using scripts/utils/migrate_sys_init.py.

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>

manifest: update projects for SYS_INIT changes

Update modules with updated SYS_INIT calls:

- hal_ti
- lvgl
- sof
- TraceRecorderSource

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>

tests: devicetree: devices: adjust test

Adjust test according to the recently introduced SYS_INIT
infrastructure.

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>

tests: kernel: threads: adjust SYS_INIT call

Adjust to the new signature: int (*init_fn)(void);

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
2023-04-12 14:28:07 +00:00

271 lines
6.0 KiB
C

/*
* Copyright (c) 2018 Oticon A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <ctype.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/console/console.h>
#include "posix_board_if.h"
#include <string.h>
#include <sys/time.h>
#include <sys/select.h>
#include <unistd.h>
#define DEBUG_ECHO 0
#if (DEBUG_ECHO)
#define ECHO(...) printf(__VA_ARGS__)
#else
#define ECHO(...)
#endif
#if defined(CONFIG_NATIVE_POSIX_STDOUT_CONSOLE)
/**
*
* @brief Initialize the driver that provides the printk output
*
*/
static void native_posix_stdout_init(void)
{
/* Let's ensure that even if we are redirecting to a file, we get stdout
* and stderr line buffered (default for console). Note that glibc
* ignores size. But just in case we set a reasonable number in case
* somebody tries to compile against a different library
*/
setvbuf(stdout, NULL, _IOLBF, 512);
setvbuf(stderr, NULL, _IOLBF, 512);
extern void __printk_hook_install(int (*fn)(int));
__printk_hook_install(putchar);
}
/**
* Ensure that whatever was written thru printk is displayed now
*/
void posix_flush_stdout(void)
{
fflush(stdout);
}
#endif /* CONFIG_NATIVE_POSIX_STDOUT_CONSOLE */
#if defined(CONFIG_NATIVE_POSIX_STDIN_CONSOLE)
#define VALID_DIRECTIVES \
"Valid native console driver directives:\n" \
" !wait %%u\n" \
" !quit\n"
static struct k_fifo *avail_queue;
static struct k_fifo *lines_queue;
static uint8_t (*completion_cb)(char *line, uint8_t len);
static bool stdin_is_tty;
static K_KERNEL_STACK_DEFINE(stack, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE);
static struct k_thread native_stdio_thread;
static inline void found_eof(void)
{
/*
* Once stdin is closed or the input file has ended,
* there is no need to try again
*/
ECHO("Got EOF\n");
k_thread_abort(&native_stdio_thread);
}
/*
* Check if the command is a directive the driver handles on its own
* and if it is, handle it.
* If not return 0 (so it can be passed to the shell)
*
* Inputs
* s Command string
* towait Pointer to the amount of time wait until attempting to receive
* the next command
*
* return 0 if it is not a directive
* return > 0 if it was a directive (command starts with '!')
* return 2 if the driver directive requires to pause processing input
*/
static int catch_directive(char *s, int32_t *towait)
{
while (*s != 0 && isspace(*s) != 0) {
s++;
}
if (*s != '!') {
return 0;
}
if (strncmp(s, "!wait", 5) == 0) {
int ret;
ret = sscanf(&s[5], "%i", towait);
if (ret != 1) {
posix_print_error_and_exit("%s(): '%s' not understood, "
"!wait syntax: !wait %%i\n",
__func__, s);
}
return 2;
} else if (strcmp(s, "!quit") == 0) {
posix_exit(0);
}
posix_print_warning("%s(): '%s' not understood\n" VALID_DIRECTIVES,
__func__, s);
return 1;
}
/**
* Check if there is data ready in stdin
*/
static int stdin_not_ready(void)
{
int ready;
fd_set readfds;
struct timeval timeout;
timeout.tv_usec = 0;
timeout.tv_sec = 0;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
ready = select(STDIN_FILENO+1, &readfds, NULL, NULL, &timeout);
if (ready == 0) {
return 1;
} else if (ready == -1) {
posix_print_error_and_exit("%s: Error on select ()\n",
__func__);
}
return 0;
}
/**
* Check if there is any line in the stdin buffer,
* if there is and we have available shell buffers feed it to the shell
*
* This function returns how long the thread should wait in ms,
* before checking again the stdin buffer
*/
static int32_t attempt_read_from_stdin(void)
{
static struct console_input *cmd;
int32_t towait = CONFIG_NATIVE_STDIN_POLL_PERIOD;
while (1) {
char *ret;
int last;
int is_directive;
if (feof(stdin)) {
found_eof();
}
/*
* If stdin comes from a terminal, we check if the user has
* input something, and if not we pause the process.
*
* If stdin is not coming from a terminal, but from a file or
* pipe, we always proceed to try to get data and block until
* we do
*/
if (stdin_is_tty && stdin_not_ready()) {
return towait;
}
/* Pick next available shell line buffer */
if (!cmd) {
cmd = k_fifo_get(avail_queue, K_NO_WAIT);
if (!cmd) {
return towait;
}
}
/*
* By default stdin is (_IOLBF) line buffered when connected to
* a terminal and fully buffered (_IOFBF) when connected to a
* pipe/file.
* If we got a terminal: we already checked for it to be ready
* and therefore a full line should be there for us.
*
* If we got a pipe or file we will block until we get a line,
* or we reach EOF
*/
ret = fgets(cmd->line, CONSOLE_MAX_LINE_LEN, stdin);
if (ret == NULL) {
if (feof(stdin)) {
found_eof();
}
/*
* Otherwise this was an unexpected error we do
* not try to handle
*/
return towait;
}
/* Remove a possible end of line and other trailing spaces */
last = (int)strlen(cmd->line) - 1;
while ((last >= 0) && isspace(cmd->line[last]) != 0) {
cmd->line[last--] = 0;
}
ECHO("Got: \"%s\"\n", cmd->line);
/*
* This console has a special set of directives which start with
* "!" which we capture here
*/
is_directive = catch_directive(cmd->line, &towait);
if (is_directive == 2) {
return towait;
} else if (is_directive > 0) {
continue;
}
/* Let's give it to the shell to handle */
k_fifo_put(lines_queue, cmd);
cmd = NULL;
}
return towait;
}
/**
* This thread will check if there is any new line in the stdin buffer
* every CONFIG_NATIVE_STDIN_POLL_PERIOD ms
*
* If there is, it will feed it to the shell
*/
static void native_stdio_runner(void *p1, void *p2, void *p3)
{
stdin_is_tty = isatty(STDIN_FILENO);
while (1) {
int32_t wait_time = attempt_read_from_stdin();
k_sleep(wait_time);
}
}
#endif /* CONFIG_NATIVE_POSIX_STDIN_CONSOLE */
static int native_posix_console_init(void)
{
#if defined(CONFIG_NATIVE_POSIX_STDOUT_CONSOLE)
native_posix_stdout_init();
#endif
return 0;
}
SYS_INIT(native_posix_console_init, PRE_KERNEL_1,
CONFIG_NATIVE_POSIX_CONSOLE_INIT_PRIORITY);