Files
acrn-hypervisor/hypervisor/debug/shell.c
Fei Li 712568f949 shell: split arch special code
Now moved all the guest related (risc-v doesn't support VMs) and x86 specific
commands to x86_shell.c except shell_dump_host_mem.

common for guest.
> -static int32_t shell_list_vm(__unused int32_t argc, __unused char **argv);
> -static int32_t shell_list_vcpu(__unused int32_t argc, __unused char **argv);
> -static int32_t shell_to_vm_console(int32_t argc, char **argv);
  depends on how we map guest memory.
> -static int32_t shell_dump_guest_mem(int32_t argc, char **argv);

common concept but arch special implementation.
> -static int32_t shell_vcpu_dumpreg(int32_t argc, char **argv);
> -static int32_t shell_show_cpu_int(__unused int32_t argc, __unused char **argv);
> -static int32_t shell_show_ptdev_info(__unused int32_t argc, __unused char **argv);
> -static int32_t shell_reboot(int32_t argc, char **argv);

x86 special
> -static int32_t shell_show_vioapic_info(int32_t argc, char **argv);
> -static int32_t shell_show_ioapic_info(__unused int32_t argc, __unused char **argv);
> -static int32_t shell_cpuid(int32_t argc, char **argv);
> -static int32_t shell_rdmsr(int32_t argc, char **argv);
> -static int32_t shell_wrmsr(int32_t argc, char **argv);

common but not added for now.
> -static int32_t shell_dump_host_mem(int32_t argc, char **argv);

Tracked-On: #8805
Signed-off-by: Fei Li <fei1.li@intel.com>
2025-10-14 14:45:12 +08:00

668 lines
17 KiB
C

/*
* Copyright (C) 2018-2025 Intel Corporation.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <types.h>
#include <errno.h>
#include <bits.h>
#include "shell_priv.h"
#include <console.h>
#include <per_cpu.h>
#include <sprintf.h>
#include <logmsg.h>
#include <version.h>
#include <shell.h>
#include <cpu.h>
#define SHELL_PROMPT_STR "ACRN:\\>"
char shell_log_buf[SHELL_LOG_BUF_SIZE];
extern struct shell_cmd arch_shell_cmds[];
extern uint32_t arch_shell_cmds_sz;
/* Input Line Other - Switch to the "other" input line (there are only two
* input lines total).
*/
static int32_t shell_cmd_help(__unused int32_t argc, __unused char **argv);
static int32_t shell_version(__unused int32_t argc, __unused char **argv);
static int32_t shell_loglevel(int32_t argc, char **argv);
static struct shell_cmd shell_cmds[] = {
{
.str = SHELL_CMD_HELP,
.cmd_param = SHELL_CMD_HELP_PARAM,
.help_str = SHELL_CMD_HELP_HELP,
.fcn = shell_cmd_help,
},
{
.str = SHELL_CMD_VERSION,
.cmd_param = SHELL_CMD_VERSION_PARAM,
.help_str = SHELL_CMD_VERSION_HELP,
.fcn = shell_version,
},
{
.str = SHELL_CMD_LOG_LVL,
.cmd_param = SHELL_CMD_LOG_LVL_PARAM,
.help_str = SHELL_CMD_LOG_LVL_HELP,
.fcn = shell_loglevel,
},
};
/* for function key: up/down/right/left/home/end and delete key */
enum function_key {
KEY_NONE,
KEY_DELETE = 0x5B33,
KEY_UP = 0x5B41,
KEY_DOWN = 0x5B42,
KEY_RIGHT = 0x5B43,
KEY_LEFT = 0x5B44,
KEY_END = 0x5B46,
KEY_HOME = 0x5B48,
};
extern uint16_t mem_loglevel;
extern uint16_t console_loglevel;
extern uint16_t npk_loglevel;
static struct shell hv_shell;
static struct shell *p_shell = &hv_shell;
static int32_t string_to_argv(char *argv_str, void *p_argv_mem,
__unused uint32_t argv_mem_size,
uint32_t *p_argc, char ***p_argv)
{
uint32_t argc;
char **argv;
char *p_ch;
/* Setup initial argument values. */
argc = 0U;
argv = NULL;
/* Ensure there are arguments to be processed. */
if (argv_str == NULL) {
*p_argc = argc;
*p_argv = argv;
return -EINVAL;
}
/* Process the argument string (there is at least one element). */
argv = (char **)p_argv_mem;
p_ch = argv_str;
/* Remove all spaces at the beginning of cmd*/
while (*p_ch == ' ') {
p_ch++;
}
while (*p_ch != 0) {
/* Add argument (string) pointer to the vector. */
argv[argc] = p_ch;
/* Move past the vector entry argument string (in the
* argument string).
*/
while ((*p_ch != ' ') && (*p_ch != ',') && (*p_ch != 0)) {
p_ch++;
}
/* Count the argument just processed. */
argc++;
/* Check for the end of the argument string. */
if (*p_ch != 0) {
/* Terminate the vector entry argument string
* and move to the next.
*/
*p_ch = 0;
/* Remove all space in middile of cmdline */
p_ch++;
while (*p_ch == ' ') {
p_ch++;
}
}
}
/* Update return parameters */
*p_argc = argc;
*p_argv = argv;
return 0;
}
static struct shell_cmd *shell_find_cmd(const char *cmd_str)
{
uint32_t i;
struct shell_cmd *p_cmd = NULL;
for (i = 0U; i < p_shell->cmd_count; i++) {
p_cmd = &p_shell->cmds[i];
if (strcmp(p_cmd->str, cmd_str) == 0) {
return p_cmd;
}
}
for (i = 0U; i < p_shell->arch_cmd_count; i++) {
p_cmd = &p_shell->arch_cmds[i];
if (strcmp(p_cmd->str, cmd_str) == 0) {
return p_cmd;
}
}
return NULL;
}
static char shell_getc(void)
{
return console_getc();
}
void shell_puts(const char *string_ptr)
{
/* Output the string */
(void)console_write(string_ptr, strnlen_s(string_ptr,
SHELL_STRING_MAX_LEN));
}
static void clear_input_line(uint32_t len)
{
while (len > 0) {
len--;
shell_puts("\b");
shell_puts(" \b");
}
}
static void set_cursor_pos(uint32_t left_offset)
{
while (left_offset > 0) {
left_offset--;
shell_puts("\b");
}
}
static void handle_delete_key(void)
{
if (p_shell->cursor_offset < p_shell->input_line_len) {
uint32_t delta = p_shell->input_line_len - p_shell->cursor_offset - 1;
/* Send a space + backspace sequence to delete character */
shell_puts(" \b");
/* display the left input chars and remove former last one */
shell_puts(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset + 1);
shell_puts(" \b");
set_cursor_pos(delta);
memcpy(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset,
p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset + 1, delta);
/* Null terminate the last character to erase it */
p_shell->buffered_line[p_shell->input_line_active][p_shell->input_line_len - 1] = 0;
/* Reduce the length of the string by one */
p_shell->input_line_len--;
}
}
static void handle_updown_key(enum function_key key_value)
{
int32_t to_select, current_select = p_shell->to_select_index;
/* update current_select and p_shell->to_select_index as up/down key */
if (key_value == KEY_UP) {
/* if the ring buffer not full, just decrease one until to 0; if full, need handle overflow case */
to_select = p_shell->to_select_index - 1;
if (to_select < 0) {
to_select += MAX_BUFFERED_CMDS;
}
if (p_shell->buffered_line[to_select][0] != '\0') {
current_select = to_select;
}
} else {
/* if down key and current is active line, not need update */
if (p_shell->to_select_index != p_shell->input_line_active) {
current_select = (p_shell->to_select_index + 1) % MAX_BUFFERED_CMDS;
}
}
/* go up/down until first buffered cmd or current input line: user will know it is end to select */
if (current_select != p_shell->input_line_active) {
p_shell->to_select_index = current_select;
}
if (strcmp(p_shell->buffered_line[current_select], p_shell->buffered_line[p_shell->input_line_active]) != 0) {
/* reset cursor pos and clear current input line first, then output selected cmd */
if (p_shell->cursor_offset < p_shell->input_line_len) {
shell_puts(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset);
}
clear_input_line(p_shell->input_line_len);
shell_puts(p_shell->buffered_line[current_select]);
size_t len = strnlen_s(p_shell->buffered_line[current_select], SHELL_CMD_MAX_LEN);
memcpy_s(p_shell->buffered_line[p_shell->input_line_active], SHELL_CMD_MAX_LEN,
p_shell->buffered_line[current_select], len + 1);
p_shell->input_line_len = len;
p_shell->cursor_offset = len;
}
}
static void shell_handle_special_char(char ch)
{
enum function_key key_value = KEY_NONE;
switch (ch) {
/* original function key value: ESC + key (2/3 bytes), so consume the next 2/3 characters */
case 0x1b:
key_value = (shell_getc() << 8) | shell_getc();
if (key_value == KEY_DELETE) {
(void)shell_getc(); /* delete key has one more byte */
}
switch (key_value) {
case KEY_DELETE:
handle_delete_key();
break;
case KEY_UP:
case KEY_DOWN:
handle_updown_key(key_value);
break;
case KEY_RIGHT:
if (p_shell->cursor_offset < p_shell->input_line_len) {
shell_puts(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset);
p_shell->cursor_offset++;
set_cursor_pos(p_shell->input_line_len - p_shell->cursor_offset);
}
break;
case KEY_LEFT:
if (p_shell->cursor_offset > 0) {
p_shell->cursor_offset--;
shell_puts("\b");
}
break;
case KEY_END:
if (p_shell->cursor_offset < p_shell->input_line_len) {
shell_puts(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset);
p_shell->cursor_offset = p_shell->input_line_len;
}
break;
case KEY_HOME:
if (p_shell->cursor_offset > 0) {
set_cursor_pos(p_shell->cursor_offset);
p_shell->cursor_offset = 0;
}
break;
default:
break;
}
break;
default:
/*
* Only the Escape character is treated as special character.
* All the other characters have been handled properly in
* shell_input_line, so they will not be handled in this API.
* Gracefully return if prior case clauses have not been met.
*/
break;
}
}
static void handle_backspace_key(void)
{
/* Ensure length is not 0 */
if (p_shell->cursor_offset > 0U) {
/* Echo backspace */
shell_puts("\b");
/* Send a space + backspace sequence to delete character */
shell_puts(" \b");
if (p_shell->cursor_offset < p_shell->input_line_len) {
uint32_t delta = p_shell->input_line_len - p_shell->cursor_offset;
/* display the left input-chars and remove the former last one */
shell_puts(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset);
shell_puts(" \b");
set_cursor_pos(delta);
memcpy(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset - 1,
p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset, delta);
}
/* Null terminate the last character to erase it */
p_shell->buffered_line[p_shell->input_line_active][p_shell->input_line_len - 1] = 0;
/* Reduce the length of the string by one */
p_shell->input_line_len--;
p_shell->cursor_offset--;
}
}
static void handle_input_char(char ch)
{
uint32_t delta = p_shell->input_line_len - p_shell->cursor_offset;
/* move the input from cursor offset back first */
if (delta > 0) {
memcpy_backwards(p_shell->buffered_line[p_shell->input_line_active] + p_shell->input_line_len,
p_shell->buffered_line[p_shell->input_line_active] + p_shell->input_line_len - 1, delta);
}
p_shell->buffered_line[p_shell->input_line_active][p_shell->cursor_offset] = ch;
/* Echo back the input */
shell_puts(p_shell->buffered_line[p_shell->input_line_active] + p_shell->cursor_offset);
set_cursor_pos(delta);
/* Move to next character in string */
p_shell->input_line_len++;
p_shell->cursor_offset++;
}
static bool shell_input_line(void)
{
bool done = false;
char ch;
ch = shell_getc();
/* Check character */
switch (ch) {
/* Backspace */
case '\b':
handle_backspace_key();
break;
/* Carriage-return */
case '\r':
/* Echo carriage return / line feed */
shell_puts("\r\n");
/* Set flag showing line input done */
done = true;
/* Reset command length for next command processing */
p_shell->input_line_len = 0U;
p_shell->cursor_offset = 0U;
break;
/* Line feed */
case '\n':
/* Do nothing */
break;
/* All other characters */
default:
/* Ensure data doesn't exceed full terminal width */
if (p_shell->input_line_len < SHELL_CMD_MAX_LEN) {
/* See if a "standard" prINTable ASCII character received */
if ((ch >= 32) && (ch <= 126)) {
handle_input_char(ch);
} else {
/* call special character handler */
shell_handle_special_char(ch);
}
} else {
/* Echo carriage return / line feed */
shell_puts("\r\n");
/* Set flag showing line input done */
done = true;
/* Reset command length for next command processing */
p_shell->input_line_len = 0U;
p_shell->cursor_offset = 0U;
}
break;
}
return done;
}
static int32_t shell_process_cmd(const char *p_input_line)
{
int32_t status = -EINVAL;
struct shell_cmd *p_cmd;
char cmd_argv_str[SHELL_CMD_MAX_LEN + 1U];
int32_t cmd_argv_mem[sizeof(char *) * ((SHELL_CMD_MAX_LEN + 1U) >> 1U)];
int32_t cmd_argc;
char **cmd_argv;
/* Copy the input line INTo an argument string to become part of the
* argument vector.
*/
(void)strncpy_s(&cmd_argv_str[0], SHELL_CMD_MAX_LEN + 1U, p_input_line, SHELL_CMD_MAX_LEN);
cmd_argv_str[SHELL_CMD_MAX_LEN] = 0;
/* Build the argv vector from the string. The first argument in the
* resulting vector will be the command string itself.
*/
/* NOTE: This process is destructive to the argument string! */
(void) string_to_argv(&cmd_argv_str[0],
(void *) &cmd_argv_mem[0],
sizeof(cmd_argv_mem), (void *)&cmd_argc, &cmd_argv);
/* Determine if there is a command to process. */
if (cmd_argc != 0) {
/* See if command is in cmds supported */
p_cmd = shell_find_cmd(cmd_argv[0]);
if (p_cmd == NULL) {
shell_puts("\r\nError: Invalid command.\r\n");
return -EINVAL;
}
status = p_cmd->fcn(cmd_argc, &cmd_argv[0]);
if (status == -EINVAL) {
shell_puts("\r\nError: Invalid parameters.\r\n");
} else if (status != 0) {
shell_puts("\r\nCommand launch failed.\r\n");
} else {
/* No other state currently, do nothing */
}
}
return status;
}
static int32_t shell_process(void)
{
int32_t status, former_index;
char *p_input_line;
/* Process current command (using active input line). */
p_input_line = p_shell->buffered_line[p_shell->input_line_active];
former_index = (p_shell->input_line_active + MAX_BUFFERED_CMDS - 1) % MAX_BUFFERED_CMDS;
/* just buffer current cmd if current is not empty and not same with last buffered one */
if ((strnlen_s(p_input_line, SHELL_CMD_MAX_LEN) > 0) &&
(strcmp(p_input_line, p_shell->buffered_line[former_index]) != 0)) {
p_shell->input_line_active = (p_shell->input_line_active + 1) % MAX_BUFFERED_CMDS;
}
p_shell->to_select_index = p_shell->input_line_active;
/* Process command */
status = shell_process_cmd(p_input_line);
/* Now that the command is processed, zero fill the input buffer */
(void)memset(p_shell->buffered_line[p_shell->input_line_active], 0, SHELL_CMD_MAX_LEN + 1U);
/* Process command and return result to caller */
return status;
}
void shell_kick(void)
{
static bool is_cmd_cmplt = true;
/* At any given instance, UART may be owned by the HV
* OR by the guest that has enabled the vUart.
* Show HV shell prompt ONLY when HV owns the
* serial port.
*/
/* Prompt the user for a selection. */
if (is_cmd_cmplt) {
shell_puts(SHELL_PROMPT_STR);
}
/* Get user's input */
is_cmd_cmplt = shell_input_line();
/* If user has pressed the ENTER then process
* the command
*/
if (is_cmd_cmplt) {
/* Process current input line. */
(void)shell_process();
}
}
void shell_init(void)
{
p_shell->cmds = shell_cmds;
p_shell->cmd_count = ARRAY_SIZE(shell_cmds);
p_shell->arch_cmds = arch_shell_cmds;
p_shell->arch_cmd_count = arch_shell_cmds_sz;
p_shell->to_select_index = 0;
/* Zero fill the input buffer */
(void)memset(p_shell->buffered_line[p_shell->input_line_active], 0U, SHELL_CMD_MAX_LEN + 1U);
}
#define SHELL_ROWS 30
#define MAX_OUTPUT_LEN 80
static int32_t shell_cmd_help(__unused int32_t argc, __unused char **argv)
{
struct shell_cmd *p_cmd = NULL;
char str[MAX_STR_SIZE];
char* help_str;
uint32_t cmd_cnt = p_shell->cmd_count + p_shell->arch_cmd_count;
/* Print title */
shell_puts("\r\nRegistered Commands:\r\n\r\n");
pr_dbg("shell: Number of registered commands = %u in %s\n",
cmd_cnt, __func__);
/* Proceed based on the number of registered commands. */
if (cmd_cnt == 0U) {
/* No registered commands */
shell_puts("NONE\r\n");
} else {
int32_t i = 0;
uint32_t j;
for (j = 0U; j < cmd_cnt; j++) {
if (j < p_shell->cmd_count) {
p_cmd = &p_shell->cmds[j];
} else {
p_cmd = &p_shell->arch_cmds[j - p_shell->cmd_count];
}
/* Check if we've filled the screen with info */
/* i + 1 used to avoid 0%SHELL_ROWS=0 */
if (((i + 1) % SHELL_ROWS) == 0) {
/* Pause before we continue on to the next
* page.
*/
/* Print message to the user. */
shell_puts("<*** Hit any key to continue ***>");
/* Wait for a character from user (NOT USED) */
(void)shell_getc();
/* Print a new line after the key is hit. */
shell_puts("\r\n");
}
i++;
if (p_cmd->cmd_param == NULL)
p_cmd->cmd_param = " ";
(void)memset(str, ' ', sizeof(str));
/* Output the command & parameter string */
snprintf(str, MAX_OUTPUT_LEN, " %-15s%-64s",
p_cmd->str, p_cmd->cmd_param);
shell_puts(str);
shell_puts("\r\n");
help_str = p_cmd->help_str;
while (strnlen_s(help_str, MAX_OUTPUT_LEN > 0)) {
(void)memset(str, ' ', sizeof(str));
if (strnlen_s(help_str, MAX_OUTPUT_LEN) > 65) {
snprintf(str, MAX_OUTPUT_LEN, " %-s", help_str);
shell_puts(str);
shell_puts("\r\n");
help_str = help_str + 65;
} else {
snprintf(str, MAX_OUTPUT_LEN, " %-s", help_str);
shell_puts(str);
shell_puts("\r\n");
break;
}
}
}
}
shell_puts("\r\n");
return 0;
}
static int32_t shell_version(__unused int32_t argc, __unused char **argv)
{
char temp_str[MAX_STR_SIZE];
snprintf(temp_str, MAX_STR_SIZE, "HV: %s-%s-%s %s%s%s%s %s@%s build by %s %s\r\n",
HV_BRANCH_VERSION, HV_COMMIT_TIME, HV_COMMIT_DIRTY, HV_BUILD_TYPE,
(sizeof(HV_COMMIT_TAGS) > 1) ? "(tag: " : "", HV_COMMIT_TAGS,
(sizeof(HV_COMMIT_TAGS) > 1) ? ")" : "",
HV_BUILD_SCENARIO, HV_BUILD_BOARD, HV_BUILD_USER, HV_BUILD_TIME);
shell_puts(temp_str);
return 0;
}
static int32_t shell_loglevel(int32_t argc, char **argv)
{
char str[MAX_STR_SIZE] = {0};
switch (argc) {
case 4:
npk_loglevel = (uint16_t)strtol_deci(argv[3]);
/* falls through */
case 3:
mem_loglevel = (uint16_t)strtol_deci(argv[2]);
/* falls through */
case 2:
console_loglevel = (uint16_t)strtol_deci(argv[1]);
break;
case 1:
snprintf(str, MAX_STR_SIZE, "console_loglevel: %u, "
"mem_loglevel: %u, npk_loglevel: %u\r\n",
console_loglevel, mem_loglevel, npk_loglevel);
shell_puts(str);
break;
default:
return -EINVAL;
}
return 0;
}