From caacadc3d03a13dd4dfe1eefb2626faa2d3bbbe3 Mon Sep 17 00:00:00 2001
From: tychovrahe <brunam@seznam.cz>
Date: Wed, 12 Mar 2025 02:26:28 +0100
Subject: [PATCH] button hibernation prodtest

---
 .../projects/prodtest/cmd/prodtest_rgbled.c   |  24 +++
 .../projects/prodtest/cmd/prodtest_rgbled.h   |  24 +++
 core/embed/projects/prodtest/main.c           |  39 ++++-
 core/embed/rtl/cli.c                          | 137 +++++++++---------
 core/embed/rtl/inc/rtl/cli.h                  |   7 +-
 5 files changed, 160 insertions(+), 71 deletions(-)
 create mode 100644 core/embed/projects/prodtest/cmd/prodtest_rgbled.h

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 <io/rgb_led.h>
 #include <rtl/cli.h>
+#include <sys/systimer.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+void prodtest_rgbled_init(void);
diff --git a/core/embed/projects/prodtest/main.c b/core/embed/projects/prodtest/main.c
index 7e4abee60a..526f01d24c 100644
--- a/core/embed/projects/prodtest/main.c
+++ b/core/embed/projects/prodtest/main.c
@@ -24,6 +24,7 @@
 #include <io/usb.h>
 #include <rtl/cli.h>
 #include <sys/system.h>
+#include <sys/systick.h>
 #include <util/flash_otp.h>
 #include <util/rsod.h>
 #include <util/unit_properties.h>
@@ -66,6 +67,7 @@
 
 #ifdef USE_RGB_LED
 #include <io/rgb_led.h>
+#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);
 }
@@ -208,6 +214,7 @@ static void drivers_init(void) {
 #endif
 #ifdef USE_RGB_LED
   rgb_led_init();
+  prodtest_rgbled_init();
 #endif
 #ifdef USE_BLE
   unit_properties_init();
@@ -232,7 +239,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;
@@ -247,7 +254,35 @@ int main(void) {
   pair_optiga(&g_cli);
 #endif
 
-  cli_run_loop(&g_cli);
+  while (true) {
+    cli_run_loop(&g_cli);
+
+    button_event_t btn_event = {0};
+
+    bool btn = button_get_event(&btn_event);
+
+    if (btn) {
+      bool hibernate = true;
+      uint32_t deadline = ticks_timeout(1000);
+      while (!ticks_expired(deadline)) {
+        button_get_event(&btn_event);
+        if (!button_is_down(BTN_POWER)) {
+          hibernate = false;
+          break;
+        }
+      }
+
+      if (hibernate) {
+        // todo only
+        rgb_led_set_color(0xff0000);
+        systick_delay_ms(1000);
+        powerctl_hibernate();
+        rgb_led_set_color(0xffff00);
+        systick_delay_ms(1000);
+        rgb_led_set_color(0);
+      }
+    }
+  }
 
   return 0;
 }
diff --git a/core/embed/rtl/cli.c b/core/embed/rtl/cli.c
index f513fd5011..900b38259b 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;
@@ -527,77 +528,79 @@ static bool cli_split_args(cli_t* cli) {
 }
 
 void cli_run_loop(cli_t* cli) {
-  while (true) {
+  if (!cli->can_read(cli->callback_context)) {
+    return;
+  }
+
+  if (cli->interactive) {
+    if (cli->final_status) {
+      // Finalize the last command with an empty line
+      cli_printf(cli, "\r\n");
+    }
+    // Print the prompt
+    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.");
+    return;
+  }
+
+  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.");
+    return;
+  }
+
+  // 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);
+      }
+    }
+    return;
+  }
+  cli->empty_lines = 0;
+
+  // Quit interactive mode on `.+ENTER`
+  if ((strcmp(cli->cmd_name, ".") == 0)) {
     if (cli->interactive) {
-      if (cli->final_status) {
-        // Finalize the last command with an empty line
-        cli_printf(cli, "\r\n");
-      }
-      // Print the prompt
-      cli_printf(cli, "> ");
+      cli->interactive = false;
+      cli_trace(cli, "Exiting interactive mode...");
     }
+    return;
+  }
 
-    cli->final_status = false;
-    cli->aborted = false;
+  // Find the command handler
+  cli->current_cmd = cli_find_command(cli, cli->cmd_name);
 
-    // Read the next line
-    if (!cli_readln(cli)) {
-      cli_error(cli, CLI_ERROR_FATAL, "Input line too long.");
-      continue;
-    }
+  if (cli->current_cmd == NULL) {
+    cli_error(cli, CLI_ERROR_INVALID_CMD, "Invalid command '%s', try 'help'.",
+              cli->cmd_name);
+    return;
+  }
 
-    cli_history_add(cli, cli->line_buffer);
+  // Call the command handler
+  cli->current_cmd->func(cli);
 
-    // 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.");
-      }
+  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.");
     }
   }
 }
diff --git a/core/embed/rtl/inc/rtl/cli.h b/core/embed/rtl/inc/rtl/cli.h
index af0f420bc0..abd12e594a 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;
@@ -116,8 +119,8 @@ struct cli {
 };
 
 // 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,