You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
6.0 KiB
271 lines
6.0 KiB
/* |
|
* Copyright (c) 2018 Oticon A/S |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <stdio.h> |
|
#include <ctype.h> |
|
#include "init.h" |
|
#include "kernel.h" |
|
#include "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)) { |
|
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])) { |
|
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(const struct device *arg) |
|
{ |
|
ARG_UNUSED(arg); |
|
|
|
#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);
|
|
|