/* * Copyright (c) 2018 Oticon A/S * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "init.h" #include "kernel.h" #include "console/console.h" #include #include #include #include #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 u8_t (*completion_cb)(char *line, u8_t len); static bool stdin_is_tty; static K_THREAD_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, s32_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 s32_t attempt_read_from_stdin(void) { static struct console_input *cmd; s32_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) { s32_t wait_time = attempt_read_from_stdin(); k_sleep(wait_time); } } void native_stdin_register_input(struct k_fifo *avail, struct k_fifo *lines, u8_t (*completion)(char *str, u8_t len)) { avail_queue = avail; lines_queue = lines; completion_cb = completion; k_thread_create(&native_stdio_thread, stack, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE, native_stdio_runner, NULL, NULL, NULL, CONFIG_NATIVE_STDIN_PRIO, 0, K_NO_WAIT); } #endif /* CONFIG_NATIVE_POSIX_STDIN_CONSOLE */ static int native_posix_console_init(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);