From de73b38aae01092879545829904440dd4a05a46f Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 13 Feb 2025 15:43:29 +0100 Subject: [PATCH] feat(core): introduce hibernate mode [no changelog] --- core/embed/projects/prodtest/README.md | 37 +++++++++++++++++++ .../projects/prodtest/cmd/prodtest_powerctl.c | 25 +++++++++++++ core/embed/projects/prodtest/main.c | 4 ++ core/embed/sys/powerctl/inc/sys/powerctl.h | 22 ++++++++++- core/embed/sys/powerctl/npm1300/npm1300.h | 16 +++++++- core/embed/sys/powerctl/stm32u5/powerctl.c | 24 +++++++++++- .../sys/syscall/stm32/syscall_dispatch.c | 9 +++++ .../embed/sys/syscall/stm32/syscall_numbers.h | 2 + core/embed/sys/syscall/stm32/syscall_stubs.c | 8 ++++ .../sys/syscall/stm32/syscall_verifiers.c | 22 +++++++++++ .../sys/syscall/stm32/syscall_verifiers.h | 10 +++++ 11 files changed, 175 insertions(+), 4 deletions(-) diff --git a/core/embed/projects/prodtest/README.md b/core/embed/projects/prodtest/README.md index 69972e68fc..c9ff44f7d2 100644 --- a/core/embed/projects/prodtest/README.md +++ b/core/embed/projects/prodtest/README.md @@ -547,3 +547,40 @@ Example: optiga-counter-read OK 0E ``` + +### powerctl-suspend +Enters low-power mode. + +In low-power mode, the CPU retains its state, including SRAM content. +The device can be woken by pressing the power button and will continue +operation from the point where it was suspended. + +Example: +``` +powerctl-suspend +# Suspending the device to low-power mode... +# Press the POWER button to resume. + +.... + +# Resumed to active mode. +OK +``` + +### powerctl-hibernate +Enters Hibernate mode. + +In Hibernate mode, the CPU is powered off, and only the VBAT domain remains +active. The device can be woken by pressing the power button, triggering +a full boot sequence. + +Hibernate mode can only be entered if the device is not connected to a USB or +wireless charger. + +Example: +``` +powerctl-hibernate +# Hibernating the the device... +# Device is powered externally, hibernation is not possible. +OK +``` diff --git a/core/embed/projects/prodtest/cmd/prodtest_powerctl.c b/core/embed/projects/prodtest/cmd/prodtest_powerctl.c index 03a6bc66ee..a91ac199f9 100644 --- a/core/embed/projects/prodtest/cmd/prodtest_powerctl.c +++ b/core/embed/projects/prodtest/cmd/prodtest_powerctl.c @@ -43,6 +43,23 @@ static void prodtest_powerctl_suspend(cli_t* cli) { cli_ok(cli, ""); } +static void prodtest_powerctl_hibernate(cli_t* cli) { + if (cli_arg_count(cli) > 0) { + cli_error_arg_count(cli); + return; + } + + cli_trace(cli, "Hibernating the the device..."); + + if (!powerctl_hibernate()) { + cli_error(cli, CLI_ERROR, "Failed to hibernate."); + return; + } + + cli_trace(cli, "Device is powered externally, hibernation is not possible."); + cli_ok(cli, ""); +} + // clang-format off PRODTEST_CLI_CMD( @@ -52,4 +69,12 @@ PRODTEST_CLI_CMD( .args = "" ); +PRODTEST_CLI_CMD( + .name = "powerctl-hibernate", + .func = prodtest_powerctl_hibernate, + .info = "Hibernate the device into a near power-off state", + .args = "" +); + + #endif // USE_POWERCTL diff --git a/core/embed/projects/prodtest/main.c b/core/embed/projects/prodtest/main.c index 3dc3546371..aebf310d5f 100644 --- a/core/embed/projects/prodtest/main.c +++ b/core/embed/projects/prodtest/main.c @@ -155,6 +155,10 @@ static void show_welcome_screen(void) { } static void drivers_init(void) { +#ifdef USE_POWERCTL + powerctl_init(); +#endif + display_init(DISPLAY_RESET_CONTENT); #ifdef USE_STORAGE_HWKEY diff --git a/core/embed/sys/powerctl/inc/sys/powerctl.h b/core/embed/sys/powerctl/inc/sys/powerctl.h index b4e00c5cb6..9bc21daf51 100644 --- a/core/embed/sys/powerctl/inc/sys/powerctl.h +++ b/core/embed/sys/powerctl/inc/sys/powerctl.h @@ -49,10 +49,30 @@ typedef struct { } powerctl_status_t; // Gets the current power status. -void powerctl_get_status(powerctl_status_t* status); +// +// Returns `true` if the status was successfully retrieved. +bool powerctl_get_status(powerctl_status_t* status); // Enters low-power mode // +// In low-power mode, the CPU retains its state, including SRAM content. +// The device can be woken by pressing the power button and will continue +// operation from the point where it was suspended. void powerctl_suspend(void); +// Enters Hibernate mode. +// +// In Hibernate mode, the CPU is powered off, and only the VBAT domain remains +// active. The device can be woken by pressing the power button, triggering +// a full boot sequence. +// +// Hibernate mode can only be entered if the device is not connected to a USB or +// wireless charger. If the device is charging, the function returns `true`, +// and the device state remains unchanged. If the function succeeds, it does +// not return. +// +// Returns `false` if the operation fails (likely due to uninitialized power +// management). +bool powerctl_hibernate(void); + #endif // TREZORHAL_POWERCTL_H diff --git a/core/embed/sys/powerctl/npm1300/npm1300.h b/core/embed/sys/powerctl/npm1300/npm1300.h index 7c46089923..8f4d9876ae 100644 --- a/core/embed/sys/powerctl/npm1300/npm1300.h +++ b/core/embed/sys/powerctl/npm1300/npm1300.h @@ -63,7 +63,21 @@ void npm1300_deinit(void); // Gets the cause of the last restart uint8_t npm1300_restart_cause(void); -// Switches the device to the ship mode +// Switches the device to ship mode. +// +// In tge ship mode, the CPU is powered off, and only the VBAT domain remains +// active. The device can be woken by pressing the power button, triggering +// a full boot sequence. +// +// Ship mode can only be entered if the device is not connected to a USB or +// wireless charger. If the device is charging, the function returns `true`, +// and the device state remains unchanged. +// +// If the function succeeds, the device will not be powered off immediately, +// but after some time (typically a few milliseconds). +// +// Returns `false` if the operation fails (likely due to uninitialized power +// management). bool npm1300_enter_shipmode(void); // Starts the asynchronous measurement diff --git a/core/embed/sys/powerctl/stm32u5/powerctl.c b/core/embed/sys/powerctl/stm32u5/powerctl.c index cdea214e1a..97e97e6567 100644 --- a/core/embed/sys/powerctl/stm32u5/powerctl.c +++ b/core/embed/sys/powerctl/stm32u5/powerctl.c @@ -21,6 +21,7 @@ #include #include +#include #include "../npm1300/npm1300.h" #include "../stwlc38/stwlc38.h" @@ -77,16 +78,35 @@ void powerctl_deinit(void) { drv->initialized = false; } -void powerctl_get_status(powerctl_status_t* status) { +bool powerctl_get_status(powerctl_status_t* status) { powerctl_driver_t* drv = &g_powerctl_driver; memset(status, 0, sizeof(powerctl_status_t)); if (!drv->initialized) { - return; + return false; } // TODO + + return true; +} + +bool powerctl_hibernate(void) { + powerctl_driver_t* drv = &g_powerctl_driver; + + if (!drv->initialized) { + return false; + } + + if (!npm1300_enter_shipmode()) { + return false; + } + + // Wait for the device to power off + systick_delay_ms(50); + + return true; } #endif // KERNEL_MODE diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c index 7843ea0e1a..233da3cf51 100644 --- a/core/embed/sys/syscall/stm32/syscall_dispatch.c +++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c @@ -726,6 +726,15 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args, case SYSCALL_POWERCTL_SUSPEND: { powerctl_suspend(); } break; + + case SYSCALL_POWERCTL_HIBERNATE: { + args[0] = powerctl_hibernate(); + } + + case SYSCALL_POWERCTL_GET_STATUS: { + powerctl_status_t *status = (powerctl_status_t *)args[0]; + args[0] = powerctl_get_status__verified(status); + } #endif #ifdef USE_HW_JPEG_DECODER diff --git a/core/embed/sys/syscall/stm32/syscall_numbers.h b/core/embed/sys/syscall/stm32/syscall_numbers.h index 11ad1a5748..226b0d250b 100644 --- a/core/embed/sys/syscall/stm32/syscall_numbers.h +++ b/core/embed/sys/syscall/stm32/syscall_numbers.h @@ -147,6 +147,8 @@ typedef enum { SYSCALL_BLE_READ, SYSCALL_POWERCTL_SUSPEND, + SYSCALL_POWERCTL_HIBERNATE, + SYSCALL_POWERCTL_GET_STATUS, SYSCALL_JPEGDEC_OPEN, SYSCALL_JPEGDEC_CLOSE, diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c index 1a02d3881c..35e427bf56 100644 --- a/core/embed/sys/syscall/stm32/syscall_stubs.c +++ b/core/embed/sys/syscall/stm32/syscall_stubs.c @@ -685,6 +685,14 @@ uint32_t ble_read(uint8_t *data, uint16_t len) { void powerctl_suspend(void) { syscall_invoke0(SYSCALL_POWERCTL_SUSPEND); } +bool powerctl_hibernate(void) { + return (bool)syscall_invoke0(SYSCALL_POWERCTL_HIBERNATE); +} + +bool powerctl_get_status(powerctl_status_t *status) { + return (bool)syscall_invoke1((uint32_t)status, SYSCALL_POWERCTL_GET_STATUS); +} + #endif // USE_POWERCTL // ============================================================================= diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c index 3909e2cab0..8303b0c483 100644 --- a/core/embed/sys/syscall/stm32/syscall_verifiers.c +++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c @@ -783,6 +783,28 @@ access_violation: // --------------------------------------------------------------------- +#ifdef USE_POWERCTL + +bool powerctl_get_status__verified(powerctl_status_t *status) { + if (!probe_write_access(status, sizeof(*status))) { + goto access_violation; + } + + powerctl_status_t status_copy = {0}; + bool retval = powerctl_get_status(&status_copy); + *status = status_copy; + + return retval; + +access_violation: + apptask_access_violation(); + return false; +} + +#endif + +// --------------------------------------------------------------------- + #ifdef USE_HW_JPEG_DECODER jpegdec_state_t jpegdec_process__verified(jpegdec_input_t *input) { diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.h b/core/embed/sys/syscall/stm32/syscall_verifiers.h index 7b9b31e71e..fe2c20e9cb 100644 --- a/core/embed/sys/syscall/stm32/syscall_verifiers.h +++ b/core/embed/sys/syscall/stm32/syscall_verifiers.h @@ -202,6 +202,16 @@ secbool ble_read__verified(uint8_t *data, size_t len); #endif +// --------------------------------------------------------------------- + +#ifdef USE_POWERCTL + +#include + +bool powerctl_get_status__verified(powerctl_status_t *status); + +#endif + // --------------------------------------------------------------------- #ifdef USE_HW_JPEG_DECODER