diff --git a/core/embed/projects/prodtest/cmd/prodtest_rgbled.c b/core/embed/projects/prodtest/cmd/prodtest_rgbled.c index 56ac01ef66..e14ee341e0 100644 --- a/core/embed/projects/prodtest/cmd/prodtest_rgbled.c +++ b/core/embed/projects/prodtest/cmd/prodtest_rgbled.c @@ -23,6 +23,29 @@ #include #include +#include + +static bool g_prodtest_rgbled_start = false; + +static void prodtest_rgbled_timer_cb(void* context) { + if (g_prodtest_rgbled_start) { + rgb_led_set_color(0); + g_prodtest_rgbled_start = false; + } +} + +void prodtest_rgbled_init(void) { + static systimer_t* timer = NULL; + + timer = systimer_create(prodtest_rgbled_timer_cb, NULL); + + if (timer == NULL) { + return; + } + systimer_set(timer, 1000); + rgb_led_set_color(0xFF00); + g_prodtest_rgbled_start = true; +} static void prodtest_rgbled_set(cli_t* cli) { uint32_t r = 0; @@ -53,6 +76,7 @@ static void prodtest_rgbled_set(cli_t* cli) { uint32_t rgb = (r << 16) | (g << 8) | b; + g_prodtest_rgbled_start = false; rgb_led_set_color(rgb); cli_ok(cli, ""); diff --git a/core/embed/projects/prodtest/cmd/prodtest_rgbled.h b/core/embed/projects/prodtest/cmd/prodtest_rgbled.h new file mode 100644 index 0000000000..5992f9cfb3 --- /dev/null +++ b/core/embed/projects/prodtest/cmd/prodtest_rgbled.h @@ -0,0 +1,24 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +void prodtest_rgbled_init(void); diff --git a/core/embed/projects/prodtest/main.c b/core/embed/projects/prodtest/main.c index c929d22504..2693b197bb 100644 --- a/core/embed/projects/prodtest/main.c +++ b/core/embed/projects/prodtest/main.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,7 @@ #ifdef USE_RGB_LED #include +#include "cmd/prodtest_rgbled.h" #endif #ifdef USE_HASH_PROCESSOR @@ -105,6 +107,10 @@ cli_t g_cli = {0}; #define VCP_IFACE 0 +static bool console_can_read(void *context) { + return usb_vcp_can_read(VCP_IFACE); +} + static size_t console_read(void *context, char *buf, size_t size) { return usb_vcp_read_blocking(VCP_IFACE, (uint8_t *)buf, size, -1); } @@ -209,6 +215,7 @@ static void drivers_init(void) { #endif #ifdef USE_RGB_LED rgb_led_init(); + prodtest_rgbled_init(); #endif #ifdef USE_BLE unit_properties_init(); @@ -233,7 +240,7 @@ int main(void) { show_welcome_screen(); // Initialize command line interface - cli_init(&g_cli, console_read, console_write, NULL); + cli_init(&g_cli, console_can_read, console_read, console_write, NULL); extern cli_command_t _prodtest_cli_cmd_section_start; extern cli_command_t _prodtest_cli_cmd_section_end; @@ -248,7 +255,47 @@ int main(void) { pair_optiga(&g_cli); #endif - cli_run_loop(&g_cli); +#if defined USE_BUTTON && defined USE_POWERCTL + uint32_t btn_deadline = 0; +#endif + + while (true) { + cli_event_t cli_event = {0}; + + bool cli_event_received = cli_get_event(&g_cli, &cli_event); + + if (cli_event_received) { + if (cli_event.type == CLI_EVENT_COMMAND_RECEIVED) { + cli_process_command(&g_cli, cli_event.data.command_received.command); + } + } + +#if defined USE_BUTTON && defined USE_POWERCTL + button_event_t btn_event = {0}; + bool btn = button_get_event(&btn_event); + + if (btn) { + if (btn_event.button == BTN_POWER && + btn_event.event_type == BTN_EVENT_DOWN) { + btn_deadline = ticks_timeout(1000); + } + if (btn_event.button == BTN_POWER && + btn_event.event_type == BTN_EVENT_UP) { + if (ticks_expired(systick_ms())) { + powerctl_hibernate(); + rgb_led_set_color(0xffff00); + systick_delay_ms(1000); + rgb_led_set_color(0); + } + } + } + + if (button_is_down(BTN_POWER) && ticks_expired(btn_deadline)) { + rgb_led_set_color(0xff0000); + } + +#endif + } return 0; } diff --git a/core/embed/rtl/cli.c b/core/embed/rtl/cli.c index f513fd5011..7a0348c0d1 100644 --- a/core/embed/rtl/cli.c +++ b/core/embed/rtl/cli.c @@ -11,9 +11,10 @@ #define ESC_COLOR_GRAY "\e[37m" #define ESC_COLOR_RESET "\e[39m" -bool cli_init(cli_t* cli, cli_read_cb_t read, cli_write_cb_t write, - void* callback_context) { +bool cli_init(cli_t* cli, cli_can_read_cb_t can_read, cli_read_cb_t read, + cli_write_cb_t write, void* callback_context) { memset(cli, 0, sizeof(cli_t)); + cli->can_read = can_read; cli->read = read; cli->write = write; cli->callback_context = callback_context; @@ -331,163 +332,170 @@ static char cli_autocomplete(cli_t* cli, const char* prefix) { return next_char; } -// Reads a line from the console input and stores it in the `cli->line` buffer +// Reads next char and processes it. // -// Returns false if the input line is too long -static bool cli_readln(cli_t* cli) { +// Returns 1 if the input line is complete, +// returns 0 if more characters are needed, +// returns negative value if the input line is too long +static int cli_process_char(cli_t* cli) { char* buf = cli->line_buffer; - int len = 0; // number of characters in the buffer (excluding '\0') - int cursor = 0; // cursor position in the buffer - int hist_idx = 0; - int hist_prefix = 0; + int ch = cli_readch(cli); - buf[0] = '\0'; - - for (;;) { - int ch = cli_readch(cli); - - switch (ch) { - case ESC_SEQ('A'): // ESC[A - // Up arrow - search history backwards - if (hist_idx == 0) { - hist_prefix = len; + switch (ch) { + case ESC_SEQ('A'): // ESC[A + // Up arrow - search history backwards + if (cli->hist_idx == 0) { + cli->hist_prefix = cli->len; + } + const char* hist_line = + cli_history_rev(cli, &cli->hist_idx, buf, cli->hist_prefix); + if (hist_line != NULL) { + if (cli->cursor > 0) { + // Move the cursor to the beginning of the line + cli_printf(cli, "\e[%dD", cli->cursor); } + // Replace original text + strcpy(buf, hist_line); + cli->len = cli->cursor = strlen(buf); + cli_printf(cli, "%s\e[K", buf); + } + return 0; + + case ESC_SEQ('B'): // ESC[B + // Down arrow - search history forwards + if (cli->hist_idx > 0) { const char* hist_line = - cli_history_rev(cli, &hist_idx, buf, hist_prefix); + cli_history_fwd(cli, &cli->hist_idx, buf, cli->hist_prefix); if (hist_line != NULL) { - if (cursor > 0) { + if (cli->cursor > 0) { // Move the cursor to the beginning of the line - cli_printf(cli, "\e[%dD", cursor); + cli_printf(cli, "\e[%dD", cli->cursor); } // Replace original text strcpy(buf, hist_line); - len = cursor = strlen(buf); + cli->len = cli->cursor = strlen(buf); cli_printf(cli, "%s\e[K", buf); - } - continue; - - case ESC_SEQ('B'): // ESC[B - // Down arrow - search history forwards - if (hist_idx > 0) { - const char* hist_line = - cli_history_fwd(cli, &hist_idx, buf, hist_prefix); - if (hist_line != NULL) { - if (cursor > 0) { - // Move the cursor to the beginning of the line - cli_printf(cli, "\e[%dD", cursor); - } - // Replace original text - strcpy(buf, hist_line); - len = cursor = strlen(buf); - cli_printf(cli, "%s\e[K", buf); - } else { - if (cursor > hist_prefix) { - cli_printf(cli, "\e[%dD", cursor - hist_prefix); - } - cli_printf(cli, "\e[K"); - len = cursor = hist_prefix; - buf[len] = '\0'; + } else { + if (cli->cursor > cli->hist_prefix) { + cli_printf(cli, "\e[%dD", cli->cursor - cli->hist_prefix); } + cli_printf(cli, "\e[K"); + cli->len = cli->cursor = cli->hist_prefix; + buf[cli->len] = '\0'; } - continue; - } + } + return 0; + } - // Reset the history index, if the user types something else - hist_idx = 0; + // Reset the history index, if the user types something else + cli->hist_idx = 0; - switch (ch) { - case ESC_SEQ('C'): // ESC[C - // Right arrow - if (cursor < len) { - if (cli->interactive) { - cli_printf(cli, "\e[C"); - } - cursor++; - } - break; - - case ESC_SEQ('D'): // ESC[D - // Left arrow - if (cursor > 0) { - if (cli->interactive) { - cli_printf(cli, "\e[D"); - } - cursor--; - } - break; - - case '\b': - case 0x7F: - // backspace => delete last character - if (cursor == 0) break; + switch (ch) { + case ESC_SEQ('C'): // ESC[C + // Right arrow + if (cli->cursor < cli->len) { + if (cli->interactive) { + cli_printf(cli, "\e[C"); + } + cli->cursor++; + } + break; + + case ESC_SEQ('D'): // ESC[D + // Left arrow + if (cli->cursor > 0) { if (cli->interactive) { - // Move the cursor left cli_printf(cli, "\e[D"); } - --cursor; - // do not break, fall through + cli->cursor--; + } + break; - case ESC_SEQ(3): // ESC[3~ - // Delete - if (cursor < len) { - // Delete the character at the cursor - memmove(&buf[cursor], &buf[cursor + 1], len - cursor); - --len; - if (cli->interactive) { - // Print the rest of the line and move the cursor back - cli_printf(cli, "%s \b", &buf[cursor]); - if (cursor < len) { - cli_printf(cli, "\e[%dD", len - cursor); - } - } - } - break; + case '\b': + case 0x7F: + // backspace => delete last character + if (cli->cursor == 0) break; + if (cli->interactive) { + // Move the cursor left + cli_printf(cli, "\e[D"); + } + --cli->cursor; + // do not break, fall through - case '\r': - case '\n': - // end of line + case ESC_SEQ(3): // ESC[3~ + // Delete + if (cli->cursor < cli->len) { + // Delete the character at the cursor + memmove(&buf[cli->cursor], &buf[cli->cursor + 1], + cli->len - cli->cursor); + --cli->len; if (cli->interactive) { - cli_printf(cli, "\r\n"); - } - return len < CLI_LINE_BUFFER_SIZE; - - case '\t': - // tab => autocomplete - if (cli->interactive && len == cursor) { - char ch; - while ((ch = cli_autocomplete(cli, buf)) != '\0') { - if (len < CLI_LINE_BUFFER_SIZE - 1) { - cli_printf(cli, "%c", ch); - buf[len++] = ch; - buf[len] = '\0'; - cursor++; - } + // Print the rest of the line and move the cursor back + cli_printf(cli, "%s \b", &buf[cli->cursor]); + if (cli->cursor < cli->len) { + cli_printf(cli, "\e[%dD", cli->len - cli->cursor); } } - break; + } + break; - default: - if (ch >= 0x20 && ch <= 0x7E) { - // Printable character - if (len < CLI_LINE_BUFFER_SIZE - 1) { - // Insert the character at the cursor - ++len; - memmove(&buf[cursor + 1], &buf[cursor], len - cursor); - buf[cursor] = ch; - // Print new character and the rest of the line - if (cli->interactive) { - cli_printf(cli, "%s", &buf[cursor]); - } - ++cursor; - if (cli->interactive && cursor < len) { - // Move the cursor back - cli_printf(cli, "\e[%dD", len - cursor); - } + case '\r': + case '\n': + // end of line + if (cli->interactive) { + cli_printf(cli, "\r\n"); + } + if (cli->len < CLI_LINE_BUFFER_SIZE) { + return 1; + } + return -1; + + case '\t': + // tab => autocomplete + if (cli->interactive && cli->len == cli->cursor) { + char ch; + while ((ch = cli_autocomplete(cli, buf)) != '\0') { + if (cli->len < CLI_LINE_BUFFER_SIZE - 1) { + cli_printf(cli, "%c", ch); + buf[cli->len++] = ch; + buf[cli->len] = '\0'; + cli->cursor++; } } - } + } + break; + + default: + if (ch >= 0x20 && ch <= 0x7E) { + // Printable character + if (cli->len < CLI_LINE_BUFFER_SIZE - 1) { + // Insert the character at the cursor + ++cli->len; + memmove(&buf[cli->cursor + 1], &buf[cli->cursor], + cli->len - cli->cursor); + buf[cli->cursor] = ch; + // Print new character and the rest of the line + if (cli->interactive) { + cli_printf(cli, "%s", &buf[cli->cursor]); + } + ++cli->cursor; + if (cli->interactive && cli->cursor < cli->len) { + // Move the cursor back + cli_printf(cli, "\e[%dD", cli->len - cli->cursor); + } + } + } } + return 0; +} + +static void cli_clear_line(cli_t* cli) { + cli->len = 0; + cli->cursor = 0; + cli->hist_idx = 0; + cli->hist_prefix = 0; + memset(cli->line_buffer, 0, sizeof(cli->line_buffer)); } // Splits the command line into arguments @@ -526,80 +534,111 @@ static bool cli_split_args(cli_t* cli) { return *cstr_skip_whitespace(buf) == '\0'; } -void cli_run_loop(cli_t* cli) { - while (true) { - if (cli->interactive) { - if (cli->final_status) { - // Finalize the last command with an empty line - cli_printf(cli, "\r\n"); +bool cli_get_event(cli_t* cli, cli_event_t* event) { + memset(event, 0, sizeof(cli_event_t)); + + if (!cli->can_read(cli->callback_context)) { + return false; + } + + if (cli->cmd_active) { + // Some command is already active or waiting for processing + return false; + } + + // Read the next line + int res = cli_process_char(cli); + + if (res < 0) { + cli_error(cli, CLI_ERROR_FATAL, "Input line too long."); + goto cleanup; + } + + if (res == 0) { + return false; + } + + cli_history_add(cli, cli->line_buffer); + + // Split command line into arguments + if (!cli_split_args(cli)) { + cli_error(cli, CLI_ERROR_FATAL, "Too many arguments."); + goto cleanup; + } + + // Empty line? + if (*cli->cmd_name == '\0') { + // Switch to interactive mode if two empty lines are entered + if (++cli->empty_lines >= 2 && !cli->interactive) { + cli->interactive = true; + // Print the welcome message + const cli_command_t* cmd = cli_find_command(cli, "$intro"); + if (cmd != NULL) { + cmd->func(cli); } - // Print the prompt + cli_printf(cli, "> "); + } else if (cli->interactive) { cli_printf(cli, "> "); } - - cli->final_status = false; - cli->aborted = false; - - // Read the next line - if (!cli_readln(cli)) { - cli_error(cli, CLI_ERROR_FATAL, "Input line too long."); - continue; - } - - cli_history_add(cli, cli->line_buffer); - - // Split command line into arguments - if (!cli_split_args(cli)) { - cli_error(cli, CLI_ERROR_FATAL, "Too many arguments."); - continue; - } - - // Empty line? - if (*cli->cmd_name == '\0') { - // Switch to interactive mode if two empty lines are entered - if (++cli->empty_lines >= 2 && !cli->interactive) { - cli->interactive = true; - // Print the welcome message - const cli_command_t* cmd = cli_find_command(cli, "$intro"); - if (cmd != NULL) { - cmd->func(cli); - } - } - continue; - } - cli->empty_lines = 0; - - // Quit interactive mode on `.+ENTER` - if ((strcmp(cli->cmd_name, ".") == 0)) { - if (cli->interactive) { - cli->interactive = false; - cli_trace(cli, "Exiting interactive mode..."); - } - continue; - } - - // Find the command handler - cli->current_cmd = cli_find_command(cli, cli->cmd_name); - - if (cli->current_cmd == NULL) { - cli_error(cli, CLI_ERROR_INVALID_CMD, "Invalid command '%s', try 'help'.", - cli->cmd_name); - continue; - } - - // Call the command handler - cli->current_cmd->func(cli); - - if (!cli->final_status) { - // Command handler hasn't send final status - if (cli->aborted) { - cli_error(cli, CLI_ERROR_ABORT, ""); - } else { - cli_error(cli, CLI_ERROR_FATAL, - "Command handler didn't finish properly."); - } - } + goto cleanup; } + cli->empty_lines = 0; + + // Quit interactive mode on `.+ENTER` + if ((strcmp(cli->cmd_name, ".") == 0)) { + if (cli->interactive) { + cli->interactive = false; + cli_trace(cli, "Exiting interactive mode..."); + } + goto cleanup; + } + + // Find the command handler + const cli_command_t* cmd = cli_find_command(cli, cli->cmd_name); + + if (cmd == NULL) { + cli_error(cli, CLI_ERROR_INVALID_CMD, "Invalid command '%s', try 'help'.", + cli->cmd_name); + goto cleanup; + } + + event->type = CLI_EVENT_COMMAND_RECEIVED; + event->data.command_received.command = cmd; + cli->cmd_active = true; + return true; + +cleanup: + cli_clear_line(cli); + return false; +} + +void cli_process_command(cli_t* cli, const cli_command_t* cmd) { + cli->current_cmd = cmd; + cli->final_status = false; + cli->aborted = false; + + // Call the command handler + cmd->func(cli); + + if (!cli->final_status) { + // Command handler hasn't send final status + if (cli->aborted) { + cli_error(cli, CLI_ERROR_ABORT, ""); + } else { + cli_error(cli, CLI_ERROR_FATAL, + "Command handler didn't finish properly."); + } + } else { + // Finalize the last command with an empty line + cli_printf(cli, "\r\n"); + } + if (cli->interactive) { + // Print the prompt + cli_printf(cli, "> "); + } + + cli->cmd_active = false; + cli_clear_line(cli); } // Return position of the argument with the given name in diff --git a/core/embed/rtl/inc/rtl/cli.h b/core/embed/rtl/inc/rtl/cli.h index af0f420bc0..1796c01318 100644 --- a/core/embed/rtl/inc/rtl/cli.h +++ b/core/embed/rtl/inc/rtl/cli.h @@ -77,9 +77,12 @@ typedef struct { typedef size_t (*cli_write_cb_t)(void* ctx, const char* buf, size_t len); // Callback for reading characters from console input typedef size_t (*cli_read_cb_t)(void* ctx, char* buf, size_t len); +// Callback for can read assessment +typedef bool (*cli_can_read_cb_t)(void* ctx); struct cli { // I/O callbacks + cli_can_read_cb_t can_read; cli_read_cb_t read; cli_write_cb_t write; void* callback_context; @@ -90,6 +93,14 @@ struct cli { // Current line buffer char line_buffer[CLI_LINE_BUFFER_SIZE]; + int len; // number of characters in the buffer (excluding '\0') + int cursor; // cursor position in the buffer + int hist_idx; + int hist_prefix; + + // Command active flag + bool cmd_active; // true if the command is pending or being processed + // Command name (pointer to the line buffer) const char* cmd_name; // Number of parsed arguments @@ -115,16 +126,32 @@ struct cli { volatile bool aborted; }; +typedef enum { + CLI_EVENT_COMMAND_RECEIVED, +} cli_event_type_t; + +typedef struct { + cli_event_type_t type; + union { + struct { + const cli_command_t* command; + } command_received; + } data; +} cli_event_t; + // Initializes the command line structure -bool cli_init(cli_t* cli, cli_read_cb_t read, cli_write_cb_t write, - void* callback_context); +bool cli_init(cli_t* cli, cli_can_read_cb_t can_read, cli_read_cb_t read, + cli_write_cb_t write, void* callback_context); // Registers the command handlers void cli_set_commands(cli_t* cli, const cli_command_t* cmd_array, size_t cmd_count); -// Runs the CLI command loop -void cli_run_loop(cli_t* cli); +// Polls the CLI for events +bool cli_get_event(cli_t* cli, cli_event_t* event); + +// Processes the command line +void cli_process_command(cli_t* cli, const cli_command_t* cmd); // Returne the number of arguments in the command line size_t cli_arg_count(cli_t* cli);