/* * 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 #include #include #include #include #include #include #include #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);