net: websocket: Add console support
Add console driver that allows console session to be transferred over a websocket connection. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
parent
4d4c7bf3ea
commit
ead9cd409c
7 changed files with 556 additions and 0 deletions
|
@ -8,3 +8,4 @@ zephyr_sources_if_kconfig(uart_pipe.c)
|
|||
zephyr_sources_if_kconfig(telnet_console.c)
|
||||
zephyr_sources_if_kconfig(xtensa_sim_console.c)
|
||||
zephyr_sources_if_kconfig(native_posix_console.c)
|
||||
zephyr_sources_if_kconfig(websocket_console.c)
|
||||
|
|
|
@ -312,4 +312,5 @@ config NATIVE_POSIX_CONSOLE_INIT_PRIORITY
|
|||
Device driver initialization priority.
|
||||
|
||||
source "drivers/console/Kconfig.telnet"
|
||||
source "drivers/console/Kconfig.ws"
|
||||
endif
|
||||
|
|
112
drivers/console/Kconfig.ws
Normal file
112
drivers/console/Kconfig.ws
Normal file
|
@ -0,0 +1,112 @@
|
|||
# Kconfig - console driver configuration options
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
menuconfig WEBSOCKET_CONSOLE
|
||||
bool "Enable websocket console service"
|
||||
default n
|
||||
select NETWORKING
|
||||
select NET_TCP
|
||||
select HTTP_PARSER
|
||||
select HTTP_SERVER
|
||||
select WEBSOCKET
|
||||
help
|
||||
This option enables console over a websocket link. Currently,
|
||||
it is basically just a redirection of the Zephyr console through
|
||||
websocket. It nicely works along with another console driver (like
|
||||
uart), twist being that it will take over the output only if a
|
||||
successful connection to its HTTP service is done.
|
||||
|
||||
if WEBSOCKET_CONSOLE
|
||||
|
||||
config WEBSOCKET_CONSOLE_LINE_BUF_SIZE
|
||||
int "WS console line buffer size"
|
||||
default 128
|
||||
help
|
||||
This option can be used to modify the size of the buffer storing
|
||||
console output line, prior to sending it through the network.
|
||||
Of course an output line can be longer than such size, it just
|
||||
means sending it will start as soon as it reaches this size.
|
||||
It really depends on what type of output is expected.
|
||||
If there is a lot of short lines, then lower this value. If there
|
||||
are longer lines, then raise this value.
|
||||
|
||||
config WEBSOCKET_CONSOLE_LINE_BUF_NUMBERS
|
||||
int "WS console line buffers"
|
||||
default 4
|
||||
help
|
||||
This option can be used to modify the amount of line buffers the
|
||||
driver can use. It really depends on how much output is meant to be
|
||||
sent, depending on the system load etc. You can play on both
|
||||
WEBSOCKET_CONSOLE_LINE_BUF_SIZE and this current option to get the
|
||||
best possible buffer settings you need.
|
||||
|
||||
config WEBSOCKET_CONSOLE_SEND_TIMEOUT
|
||||
int "WS console line send timeout"
|
||||
default 100
|
||||
help
|
||||
This option can be used to modify the duration of the timer that kick
|
||||
in when a line buffer is not empty but did not yet meet the line feed.
|
||||
|
||||
config WEBSOCKET_CONSOLE_SEND_THRESHOLD
|
||||
int "WS console line send threshold"
|
||||
default 5
|
||||
help
|
||||
This option can be used to modify the minimal amount of a line buffer
|
||||
that can be sent by the WS server when nothing has happened for
|
||||
a little while (see WEBSOCKET_CONSOLE_SEND_TIMEOUT) and when the line
|
||||
buffer did not meet the line feed yet.
|
||||
|
||||
config WEBSOCKET_CONSOLE_STACK_SIZE
|
||||
int "WS console inner thread stack size"
|
||||
default 1500
|
||||
help
|
||||
This option helps to fine-tune WS console inner thread stack size.
|
||||
|
||||
config WEBSOCKET_CONSOLE_PRIO
|
||||
int "WS console inner thread priority"
|
||||
default 7
|
||||
help
|
||||
This option helps to fine-tune WS console inner thread priority.
|
||||
|
||||
config SYS_LOG_WEBSOCKET_CONSOLE_LEVEL
|
||||
int "WS console log level"
|
||||
default 0
|
||||
depends on SYS_LOG
|
||||
help
|
||||
Sets log level for websocket console (for WS console dev only)
|
||||
|
||||
Levels are:
|
||||
|
||||
- 0 OFF, do not write
|
||||
|
||||
- 1 ERROR, only write SYS_LOG_ERR
|
||||
|
||||
- 2 WARNING, write SYS_LOG_WRN in addition to previous level
|
||||
|
||||
- 3 INFO, write SYS_LOG_INF in addition to previous levels
|
||||
|
||||
- 4 DEBUG, write SYS_LOG_DBG in addition to previous levels
|
||||
|
||||
config WEBSOCKET_CONSOLE_DEBUG_DEEP
|
||||
bool "Forward output to original console handler"
|
||||
depends on UART_CONSOLE
|
||||
default n
|
||||
help
|
||||
For WS console developers only, this will forward each output to
|
||||
original console handler. So if by chance WS console seems silent,
|
||||
at least things will be printed to original handler, usually
|
||||
UART console.
|
||||
|
||||
config WEBSOCKET_CONSOLE_INIT_PRIORITY
|
||||
int "WS console init priority"
|
||||
default 99
|
||||
help
|
||||
WS console driver initialization priority. Note that WS works
|
||||
on application level. Usually, you won't have to tweak this.
|
||||
|
||||
endif
|
339
drivers/console/websocket_console.c
Normal file
339
drivers/console/websocket_console.c
Normal file
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* 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);
|
37
include/drivers/console/websocket_console.h
Normal file
37
include/drivers/console/websocket_console.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef __WS_CONSOLE_H__
|
||||
#define __WS_CONSOLE_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <kernel.h>
|
||||
|
||||
/** @brief Register websocket input processing
|
||||
*
|
||||
* Input processing is started when string is received in WS server.
|
||||
* Carriage return is translated to NULL making string always NULL
|
||||
* terminated. Application before calling register function need to
|
||||
* initialize two fifo queues mentioned below.
|
||||
*
|
||||
* @param avail k_fifo queue keeping available input slots
|
||||
* @param lines k_fifo queue of entered lines which to be processed
|
||||
* in the application code.
|
||||
* @param completion callback for tab completion of entered commands
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
void ws_register_input(struct k_fifo *avail, struct k_fifo *lines,
|
||||
u8_t (*completion)(char *str, u8_t len));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __WS_CONSOLE_H__ */
|
60
include/net/websocket_console.h
Normal file
60
include/net/websocket_console.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef __WEBSOCKET_CONSOLE_H__
|
||||
#define __WEBSOCKET_CONSOLE_H__
|
||||
|
||||
#include <net/websocket.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Websocket console library
|
||||
* @defgroup websocket_console Websocket Console Library
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Enable websocket console.
|
||||
*
|
||||
* @details The console can be sent over websocket to browser.
|
||||
*
|
||||
* @param ctx HTTP context
|
||||
*
|
||||
* @return 0 if ok, <0 if error
|
||||
*/
|
||||
int ws_console_enable(struct http_ctx *ctx);
|
||||
|
||||
/**
|
||||
* @brief Disable websocket console.
|
||||
*
|
||||
* @param ctx HTTP context
|
||||
*
|
||||
* @return 0 if ok, <0 if error
|
||||
*/
|
||||
int ws_console_disable(struct http_ctx *ctx);
|
||||
|
||||
/**
|
||||
* @brief Receive data from outside system and feed it into Zephyr.
|
||||
*
|
||||
* @param ctx HTTP context
|
||||
* @param pkt Network packet containing the received data.
|
||||
*
|
||||
* @return 0 if ok, <0 if error
|
||||
*/
|
||||
int ws_console_recv(struct http_ctx *ctx, struct net_pkt *pkt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif /* __WEBSOCKET_CONSOLE_H__ */
|
|
@ -28,6 +28,9 @@
|
|||
#ifdef CONFIG_NATIVE_POSIX_CONSOLE
|
||||
#include <console/native_posix_console.h>
|
||||
#endif
|
||||
#ifdef CONFIG_WEBSOCKET_CONSOLE
|
||||
#include <console/websocket_console.h>
|
||||
#endif
|
||||
|
||||
#include <shell/shell.h>
|
||||
|
||||
|
@ -638,6 +641,9 @@ void shell_init(const char *str)
|
|||
#ifdef CONFIG_NATIVE_POSIX_STDIN_CONSOLE
|
||||
native_stdin_register_input(&avail_queue, &cmds_queue, completion);
|
||||
#endif
|
||||
#ifdef CONFIG_WEBSOCKET_CONSOLE
|
||||
ws_register_input(&avail_queue, &cmds_queue, completion);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** @brief Optionally register an app default cmd handler.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue