Linux Audio

Check our new training course

Embedded Linux Audio

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

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2015 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file
 * @brief Console handler implementation of shell.h API
 */


#include <zephyr.h>
#include <stdio.h>
#include <string.h>

#include <console/uart_console.h>
#include <misc/printk.h>
#include <misc/util.h>

#include <misc/shell.h>

/* maximum number of command parameters */
#define ARGC_MAX 10

static const struct shell_cmd *commands;

static const char *prompt;

#define STACKSIZE CONFIG_CONSOLE_HANDLER_SHELL_STACKSIZE
static char __stack stack[STACKSIZE];

#define MAX_CMD_QUEUED 3
static struct uart_console_input buf[MAX_CMD_QUEUED];

static struct nano_fifo avail_queue;
static struct nano_fifo cmds_queue;

static shell_cmd_function_t app_cmd_handler;
static shell_prompt_function_t app_prompt_handler;

static const char *get_prompt(void)
{
	if (app_prompt_handler) {
		const char *str;

		str = app_prompt_handler();
		if (str) {
			return str;
		}
	}

	return prompt;
}

static void line_queue_init(void)
{
	int i;

	for (i = 0; i < MAX_CMD_QUEUED; i++) {
		nano_fifo_put(&avail_queue, &buf[i]);
	}
}

static size_t line2argv(char *str, char *argv[], size_t size)
{
	size_t argc = 0;

	if (!strlen(str)) {
		return 0;
	}

	while (*str && *str == ' ') {
		str++;
	}

	if (!*str) {
		return 0;
	}

	argv[argc++] = str;

	while ((str = strchr(str, ' '))) {
		*str++ = '\0';

		while (*str && *str == ' ') {
			str++;
		}

		if (!*str) {
			break;
		}

		argv[argc++] = str;

		if (argc == size) {
			printk("Too many parameters (max %u)\n", size - 1);
			return 0;
		}
	}

	/* keep it POSIX style where argv[argc] is required to be NULL */
	argv[argc] = NULL;

	return argc;
}

static int show_cmd_help(int argc, char *argv[])
{
	int i;

	if (!argv[0] || argv[0][0] == '\0') {
		goto done;
	}

	for (i = 0; commands[i].cmd_name; i++) {
		if (!strcmp(argv[0], commands[i].cmd_name)) {
			printk("%s %s\n", commands[i].cmd_name,
			       commands[i].help ? commands[i].help : "");
			return 0;
		}
	}

done:
	printk("Unrecognized command: %s\n", argv[0]);
	return 0;
}

static int show_help(int argc, char *argv[])
{
	int i;

	if (argc > 1) {
		return show_cmd_help(--argc, &argv[1]);
	}

	printk("Available commands:\n");
	printk("help\n");

	for (i = 0; commands[i].cmd_name; i++) {
		printk("%s\n", commands[i].cmd_name);
	}

	return 0;
}

static shell_cmd_function_t get_cb(const char *string)
{
	int i;

	if (!string || string[0] == '\0') {
		return NULL;
	}

	if (!strcmp(string, "help")) {
		return show_help;
	}

	for (i = 0; commands[i].cmd_name; i++) {
		if (!strcmp(string, commands[i].cmd_name)) {
			return commands[i].cb;
		}
	}

	return NULL;
}

static void shell(int arg1, int arg2)
{
	char *argv[ARGC_MAX + 1];
	size_t argc;

	while (1) {
		struct uart_console_input *cmd;
		shell_cmd_function_t cb;

		printk("%s", get_prompt());

		cmd = nano_fiber_fifo_get(&cmds_queue, TICKS_UNLIMITED);

		argc = line2argv(cmd->line, argv, ARRAY_SIZE(argv));
		if (!argc) {
			nano_fiber_fifo_put(&avail_queue, cmd);
			continue;
		}

		cb = get_cb(argv[0]);
		if (!cb) {
			if (app_cmd_handler != NULL) {
				cb = app_cmd_handler;
			} else {
				printk("Unrecognized command: %s\n", argv[0]);
				printk("Type 'help' for list of available commands\n");
				nano_fiber_fifo_put(&avail_queue, cmd);
				continue;
			}
		}

		/* Execute callback with arguments */
		if (cb(argc, argv) < 0) {
			show_cmd_help(argc, argv);
		}

		nano_fiber_fifo_put(&avail_queue, cmd);
	}
}

static uint8_t completion(char *line, uint8_t len)
{
	char cmds[MAX_LINE_LEN];
	int common_chars = -1;
	size_t cmds_len = 0;
	int i;

	for (i = 0; commands[i].cmd_name; i++) {
		int name_len, j;

		if (strncmp(line, commands[i].cmd_name, len)) {
			continue;
		}

		name_len = strlen(commands[i].cmd_name);
		if (name_len > (MAX_LINE_LEN - (cmds_len + 1))) {
			break;
		}

		memcpy(cmds + cmds_len, commands[i].cmd_name, name_len);
		cmds_len += name_len;
		cmds[cmds_len++] = '\n';

		/* first match */
		if (common_chars < 0) {
			common_chars = name_len;
			continue;
		}

		/* cut common part of matching names */
		for (j = 0; j < common_chars; j++) {
			if (cmds[j] != commands[i].cmd_name[j]) {
				break;
			}
		}

		common_chars = j;
	}

	/* no match */
	if (common_chars < 0) {
		return 0;
	}

	/* alter line with common part of commands */
	memcpy(line + len, cmds + len, common_chars - len);

	if (common_chars < cmds_len - 1) {
		/*
		 * more than one match, print matching commands, restore prompt
		 * and print common part of matched commands
		 */
		cmds[cmds_len] = '\0';
		printk("\n%s", cmds);

		/* restore prompt */
		printk("%s", get_prompt());

		/* print common part after prompt */
		for (i = 0; i < common_chars; i++) {
			printk("%c", line[i]);
		}
	} else {
		/* only one match, complete command name */
		for (i = len; i < common_chars; i++) {
			printk("%c", line[i]);
		}

		/* for convenience add space after command */
		printk(" ");
		line[common_chars] = ' ';
		common_chars++;
	}

	return common_chars - len;
}

void shell_init(const char *str, const struct shell_cmd *cmds)
{
	nano_fifo_init(&cmds_queue);
	nano_fifo_init(&avail_queue);

	commands = cmds;

	line_queue_init();

	prompt = str ? str : "";

	task_fiber_start(stack, STACKSIZE, shell, 0, 0, 7, 0);

	/* Register serial console handler */
	uart_register_input(&avail_queue, &cmds_queue, completion);
}

/** @brief Optionally register an app default cmd handler.
 *
 *  @param handler To be called if no cmd found in cmds registered with shell_init.
 */
void shell_register_app_cmd_handler(shell_cmd_function_t handler)
{
	app_cmd_handler = handler;
}

void shell_register_prompt_handler(shell_prompt_function_t handler)
{
	app_prompt_handler = handler;
}