| /* |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * @brief Websocket console |
| * |
| * |
| * Websocket console driver. The console is provided over |
| * a websocket connection. |
| */ |
| |
| #define SYS_LOG_LEVEL CONFIG_SYS_LOG_WEBSOCKET_CONSOLE_LEVEL |
| #define SYS_LOG_DOMAIN "ws/console" |
| #include <logging/sys_log.h> |
| |
| #include <zephyr.h> |
| #include <init.h> |
| #include <misc/printk.h> |
| |
| #include <console/console.h> |
| #include <net/buf.h> |
| #include <net/net_pkt.h> |
| #include <net/websocket_console.h> |
| |
| #define NVT_NUL 0 |
| #define NVT_LF 10 |
| #define NVT_CR 13 |
| |
| #define WS_CONSOLE_STACK_SIZE CONFIG_WEBSOCKET_CONSOLE_STACK_SIZE |
| #define WS_CONSOLE_PRIORITY CONFIG_WEBSOCKET_CONSOLE_PRIO |
| #define WS_CONSOLE_TIMEOUT K_MSEC(CONFIG_WEBSOCKET_CONSOLE_SEND_TIMEOUT) |
| #define WS_CONSOLE_LINES CONFIG_WEBSOCKET_CONSOLE_LINE_BUF_NUMBERS |
| #define WS_CONSOLE_LINE_SIZE CONFIG_WEBSOCKET_CONSOLE_LINE_BUF_SIZE |
| #define WS_CONSOLE_TIMEOUT K_MSEC(CONFIG_WEBSOCKET_CONSOLE_SEND_TIMEOUT) |
| #define WS_CONSOLE_THRESHOLD CONFIG_WEBSOCKET_CONSOLE_SEND_THRESHOLD |
| |
| #define WS_CONSOLE_MIN_MSG 2 |
| |
| /* These 2 structures below are used to store the console output |
| * before sending it to the client. This is done to keep some |
| * reactivity: the ring buffer is non-protected, if first line has |
| * not been sent yet, and if next line is reaching the same index in rb, |
| * the first one will be replaced. In a perfect world, this should |
| * not happen. However on a loaded system with a lot of debug output |
| * this is bound to happen eventualy, moreover if it does not have |
| * the luxury to bufferize as much as it wants to. Just raise |
| * CONFIG_WEBSOCKET_CONSOLE_LINE_BUF_NUMBERS if possible. |
| */ |
| struct line_buf { |
| char buf[WS_CONSOLE_LINE_SIZE]; |
| u16_t len; |
| }; |
| |
| struct line_buf_rb { |
| struct line_buf l_bufs[WS_CONSOLE_LINES]; |
| u16_t line_in; |
| u16_t line_out; |
| }; |
| |
| static struct line_buf_rb ws_rb; |
| |
| NET_STACK_DEFINE(WS_CONSOLE, ws_console_stack, |
| WS_CONSOLE_STACK_SIZE, WS_CONSOLE_STACK_SIZE); |
| static struct k_thread ws_thread_data; |
| static K_SEM_DEFINE(send_lock, 0, UINT_MAX); |
| |
| /* The timer is used to send non-lf terminated output that has |
| * been around for "tool long". This will prove to be useful |
| * to send the shell prompt for instance. |
| * ToDo: raise the time, incrementaly, when no output is coming |
| * so the timer will kick in less and less. |
| */ |
| static void ws_send_prematurely(struct k_timer *timer); |
| static K_TIMER_DEFINE(send_timer, ws_send_prematurely, NULL); |
| static int (*orig_printk_hook)(int); |
| |
| static struct k_fifo *avail_queue; |
| static struct k_fifo *input_queue; |
| |
| /* Websocket context that this console is related to */ |
| static struct http_ctx *ws_console; |
| |
| extern void __printk_hook_install(int (*fn)(int)); |
| extern void *__printk_get_hook(void); |
| |
| void ws_register_input(struct k_fifo *avail, struct k_fifo *lines, |
| u8_t (*completion)(char *str, u8_t len)) |
| { |
| ARG_UNUSED(completion); |
| |
| avail_queue = avail; |
| input_queue = lines; |
| } |
| |
| static void ws_rb_init(void) |
| { |
| int i; |
| |
| ws_rb.line_in = 0; |
| ws_rb.line_out = 0; |
| |
| for (i = 0; i < WS_CONSOLE_LINES; i++) { |
| ws_rb.l_bufs[i].len = 0; |
| } |
| } |
| |
| static void ws_end_client_connection(struct http_ctx *console) |
| { |
| __printk_hook_install(orig_printk_hook); |
| orig_printk_hook = NULL; |
| |
| k_timer_stop(&send_timer); |
| |
| ws_send_msg(console, NULL, 0, WS_OPCODE_CLOSE, false, true, |
| NULL, NULL); |
| |
| ws_rb_init(); |
| } |
| |
| static void ws_rb_switch(void) |
| { |
| ws_rb.line_in++; |
| |
| if (ws_rb.line_in == WS_CONSOLE_LINES) { |
| ws_rb.line_in = 0; |
| } |
| |
| ws_rb.l_bufs[ws_rb.line_in].len = 0; |
| |
| /* Unfortunately, we don't have enough line buffer, |
| * so we eat the next to be sent. |
| */ |
| if (ws_rb.line_in == ws_rb.line_out) { |
| ws_rb.line_out++; |
| if (ws_rb.line_out == WS_CONSOLE_LINES) { |
| ws_rb.line_out = 0; |
| } |
| } |
| |
| k_timer_start(&send_timer, WS_CONSOLE_TIMEOUT, WS_CONSOLE_TIMEOUT); |
| k_sem_give(&send_lock); |
| } |
| |
| static inline struct line_buf *ws_rb_get_line_out(void) |
| { |
| u16_t out = ws_rb.line_out; |
| |
| ws_rb.line_out++; |
| if (ws_rb.line_out == WS_CONSOLE_LINES) { |
| ws_rb.line_out = 0; |
| } |
| |
| if (!ws_rb.l_bufs[out].len) { |
| return NULL; |
| } |
| |
| return &ws_rb.l_bufs[out]; |
| } |
| |
| static inline struct line_buf *ws_rb_get_line_in(void) |
| { |
| return &ws_rb.l_bufs[ws_rb.line_in]; |
| } |
| |
| /* The actual printk hook */ |
| static int ws_console_out(int c) |
| { |
| int key = irq_lock(); |
| struct line_buf *lb = ws_rb_get_line_in(); |
| bool yield = false; |
| |
| lb->buf[lb->len++] = (char)c; |
| |
| if (c == '\n' || lb->len == WS_CONSOLE_LINE_SIZE - 1) { |
| lb->buf[lb->len-1] = NVT_CR; |
| lb->buf[lb->len++] = NVT_LF; |
| ws_rb_switch(); |
| yield = true; |
| } |
| |
| irq_unlock(key); |
| |
| #ifdef CONFIG_WEBSOCKET_CONSOLE_DEBUG_DEEP |
| /* This is ugly, but if one wants to debug websocket console, it |
| * will also output the character to original console |
| */ |
| orig_printk_hook(c); |
| #endif |
| |
| if (yield) { |
| k_yield(); |
| } |
| |
| return c; |
| } |
| |
| static void ws_send_prematurely(struct k_timer *timer) |
| { |
| struct line_buf *lb = ws_rb_get_line_in(); |
| |
| if (lb->len >= WS_CONSOLE_THRESHOLD) { |
| ws_rb_switch(); |
| } |
| } |
| |
| static inline void ws_handle_input(struct net_pkt *pkt) |
| { |
| struct console_input *input; |
| u16_t len, offset, pos; |
| |
| len = net_pkt_appdatalen(pkt); |
| if (len > CONSOLE_MAX_LINE_LEN || len < WS_CONSOLE_MIN_MSG) { |
| return; |
| } |
| |
| if (!avail_queue || !input_queue) { |
| return; |
| } |
| |
| input = k_fifo_get(avail_queue, K_NO_WAIT); |
| if (!input) { |
| return; |
| } |
| |
| offset = net_pkt_get_len(pkt) - len; |
| net_frag_read(pkt->frags, offset, &pos, len, (u8_t *)input->line); |
| |
| /* The data from websocket does not contain \n or NUL, so insert |
| * it here. |
| */ |
| input->line[len] = NVT_NUL; |
| |
| /* LF/CR will be removed if only the line is not NUL terminated */ |
| if (input->line[len-1] != NVT_NUL) { |
| if (input->line[len-1] == NVT_LF) { |
| input->line[len-1] = NVT_NUL; |
| } |
| |
| if (input->line[len-2] == NVT_CR) { |
| input->line[len-2] = NVT_NUL; |
| } |
| } |
| |
| k_fifo_put(input_queue, input); |
| } |
| |
| /* The data is coming from outside system and going into zephyr */ |
| int ws_console_recv(struct http_ctx *ctx, struct net_pkt *pkt) |
| { |
| if (ctx != ws_console) { |
| return -ENOENT; |
| } |
| |
| ws_handle_input(pkt); |
| |
| net_pkt_unref(pkt); |
| |
| return 0; |
| } |
| |
| /* This is for transferring data from zephyr to outside system */ |
| static bool ws_console_send(struct http_ctx *console) |
| { |
| struct line_buf *lb = ws_rb_get_line_out(); |
| |
| if (lb) { |
| (void)ws_send_msg(console, (u8_t *)lb->buf, lb->len, |
| WS_OPCODE_DATA_TEXT, false, true, |
| NULL, NULL); |
| |
| /* We reinitialize the line buffer */ |
| lb->len = 0; |
| } |
| |
| return true; |
| } |
| |
| /* WS console loop, used to send buffered output in the RB */ |
| static void ws_console_run(void) |
| { |
| while (true) { |
| k_sem_take(&send_lock, K_FOREVER); |
| |
| if (!ws_console_send(ws_console)) { |
| ws_end_client_connection(ws_console); |
| } |
| } |
| } |
| |
| int ws_console_enable(struct http_ctx *ctx) |
| { |
| orig_printk_hook = __printk_get_hook(); |
| __printk_hook_install(ws_console_out); |
| |
| k_timer_start(&send_timer, WS_CONSOLE_TIMEOUT, WS_CONSOLE_TIMEOUT); |
| |
| ws_console = ctx; |
| |
| return 0; |
| } |
| |
| int ws_console_disable(struct http_ctx *ctx) |
| { |
| if (!ws_console) { |
| return 0; |
| } |
| |
| if (ws_console != ctx) { |
| return -ENOENT; |
| } |
| |
| ws_end_client_connection(ws_console); |
| |
| ws_console = NULL; |
| |
| return 0; |
| } |
| |
| static int ws_console_init(struct device *arg) |
| { |
| k_thread_create(&ws_thread_data, ws_console_stack, |
| K_THREAD_STACK_SIZEOF(ws_console_stack), |
| (k_thread_entry_t)ws_console_run, |
| NULL, NULL, NULL, |
| K_PRIO_COOP(WS_CONSOLE_PRIORITY), 0, K_MSEC(10)); |
| |
| SYS_LOG_INF("Websocket console initialized"); |
| |
| return 0; |
| } |
| |
| /* Websocket console is initialized as an application directly, as it requires |
| * the whole network stack to be ready. |
| */ |
| SYS_INIT(ws_console_init, APPLICATION, CONFIG_WEBSOCKET_CONSOLE_INIT_PRIORITY); |