Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * 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)
{
	unsigned 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);