feat(core): implementing reviews' findings and cleaning up before merging into main.
What changed, and why it matters
This commit is a cleanup and hardening pass on the backlight driver for Trezor hardware wallets. It improves error handling, removes debug code, fixes a buffer-index bug in a DMA safety check, and adds memory-protection reconfiguration around the interrupt handler. The changes are defensive and reduce the chance of driver misbehavior, but they do not appear to fix a known exploitable security vulnerability in user-facing software.
Treat as a routine hardening/refactoring commit. No immediate security response is warranted based solely on this diff. If this commit is part of a larger pre-release branch, review the full branch for related security fixes and ensure the DMA index fix and MPU wrapping are validated by hardware-in-the-loop tests.
Security signals we found
Hardened HAL error handling with centralized cleanup and deinitialization
Fixed DMA buffer bounds/index check (DMA_BUF_COUNT vs DMA_BUF_LENGTH)
Added MPU reconfiguration around DMA interrupt handler
Removed debug console output and disabled optimization pragma
Unconditional DMA security attribute configuration for TrustZone-enabled builds
Evidence from the diff
The patch refactors core/embed/io/backlight/stm32u5/tps61062.c. Key changes: (1) replaces bitwise-OR accumulation of HAL return codes with a HAL_ERR_CHECK macro that jumps to a cleanup label and calls a new backlight_deinit_ll() rollback function; (2) removes disabled #pragma GCC optimize("O0"), debug console usage, and unused callback stubs; (3) moves low-level deinitialization into backlight_deinit_ll() and calls it both on init failure and on BACKLIGHT_RESET; (4) fixes an apparent off-by-one/indexing bug where DMA_BUF_COUNT - 1 was used instead of DMA_BUF_LENGTH - 1 when validating the DMA current source address against the locked PWM buffer; (5) wraps GPDMA1_Channel3_IRQHandler with mpu_reconfig(MPU_MODE_DEFAULT) / mpu_restore(); (6) unconditionally configures DMA channel security attributes (previously gated by __ARM_FEATURE_CMSE); (7) simplifies duty-cycle math and removes unused ret variables. The commit message frames this as implementing review findings before merging into main, with no changelog.
Changed components
core/embed/io/backlight/stm32u5/tps61062.cTrezor Model Safe 3 / STM32U5 backlight driverGPDMA1_Channel3 interrupt handlerDMA-linked-list PWM backlight controlInspect captured patch +86 / −183
diff --git a/core/embed/io/backlight/stm32u5/tps61062.c b/core/embed/io/backlight/stm32u5/tps61062.c
index d32107e96..ff8adf765 100644
--- a/core/embed/io/backlight/stm32u5/tps61062.c
+++ b/core/embed/io/backlight/stm32u5/tps61062.c
@@ -17,25 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#if 0
-#pragma GCC optimize("O0")
-#endif
-
#ifdef KERNEL_MODE
#include <sys/irq.h>
+#include <sys/mpu.h>
#include <sys/systick.h>
#include <trezor_bsp.h>
#include <trezor_rtl.h>
#include <io/backlight.h>
-#ifdef USE_DBG_CONSOLE
-#include <sys/dbg_console.h>
-#else
-#define dbg_printf(...) ((void)0)
-#endif
-
#define BACKLIGHT_CONTROL_T_UP_US 30 // may be in range 1-75
#define BACKLIGHT_CONTROL_T_DOWN_US 198 // may be in range 180-300
@@ -70,6 +61,13 @@
#define DMA_BUF_COUNT 2 // 2 buffers for double buffering
+#define HAL_ERR_CHECK(ret) \
+ do { \
+ if (HAL_OK != (ret)) { \
+ goto cleanup; \
+ } \
+ } while (0)
+
typedef enum { BACKLIGHT_OFF = 0, BACKLIGHT_ON = 1 } backlight_state_t;
// Backlight driver state
@@ -121,18 +119,12 @@ static backlight_driver_t g_backlight_driver = {
static void backlight_control_up(uint16_t *data, int steps);
static void backlight_control_down(uint16_t *data, int steps);
static void backlight_shutdown(void);
+static void backlight_deinit_ll(void);
static void DMA_XferCpltCallback(DMA_HandleTypeDef *hdma);
-#if 0
-static void DMA_XferHalfCpltCallback(DMA_HandleTypeDef *hdma);
-static void DMA_XferErrorCallback(DMA_HandleTypeDef *hdma);
-static void DMA_XferAbortCallback(DMA_HandleTypeDef *hdma);
-static void DMA_XferSuspendCallback(DMA_HandleTypeDef *hdma);
-#endif
bool backlight_init(backlight_action_t action) {
backlight_driver_t *drv = &g_backlight_driver;
- HAL_StatusTypeDef ret = HAL_OK;
if (drv->initialized) {
return true;
@@ -166,7 +158,7 @@ bool backlight_init(backlight_action_t action) {
drv->tim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
drv->tim.Init.CounterMode = TIM_COUNTERMODE_UP;
drv->tim.Init.RepetitionCounter = 0;
- ret |= HAL_TIM_PWM_Init(&drv->tim);
+ HAL_ERR_CHECK(HAL_TIM_PWM_Init(&drv->tim));
TIM_OC_InitTypeDef TIM_OC_InitStructure = {0};
// Make ILED to log 1 (by TIM.CCR1 value >= TIM.ARR) =>
@@ -180,8 +172,8 @@ bool backlight_init(backlight_action_t action) {
TIM_OC_InitStructure.OCNPolarity = TIM_OCNPOLARITY_HIGH;
TIM_OC_InitStructure.OCIdleState = TIM_OCIDLESTATE_RESET;
TIM_OC_InitStructure.OCNIdleState = TIM_OCNIDLESTATE_RESET;
- ret |= HAL_TIM_PWM_ConfigChannel(&drv->tim, &TIM_OC_InitStructure,
- TIM_CHANNEL_1);
+ HAL_ERR_CHECK(HAL_TIM_PWM_ConfigChannel(&drv->tim, &TIM_OC_InitStructure,
+ TIM_CHANNEL_1));
// Initialize ILED GPIO
GPIO_InitTypeDef GPIO_ILED_InitStructure = {0};
@@ -192,9 +184,9 @@ bool backlight_init(backlight_action_t action) {
GPIO_ILED_InitStructure.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(TPS61062_ILED_PORT, &GPIO_ILED_InitStructure);
- // GPDMA init (circular linked list mode with 2 nodes forming double buffer 1
- // one buffer is at a time, the 2nd is prepared at DMA.TC event which occurs
- // after buffers gets transferred)
+ // GPDMA init (circular linked list mode with 2 nodes forming a double
+ // buffer: one buffer is used at a time, the 2nd is prepared at
+ // DMA.TC event which occurs after a buffer gets transferred)
__HAL_RCC_GPDMA1_CLK_ENABLE();
drv->dma.Instance = GPDMA1_Channel3;
@@ -203,12 +195,10 @@ bool backlight_init(backlight_action_t action) {
drv->dma.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT1;
drv->dma.InitLinkedList.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
drv->dma.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
- ret |= HAL_DMAEx_List_Init(&drv->dma);
-#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
- ret |= HAL_DMA_ConfigChannelAttributes(
+ HAL_ERR_CHECK(HAL_DMAEx_List_Init(&drv->dma));
+ HAL_ERR_CHECK(HAL_DMA_ConfigChannelAttributes(
&drv->dma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | DMA_CHANNEL_SRC_SEC |
- DMA_CHANNEL_DEST_SEC);
-#endif // defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
+ DMA_CHANNEL_DEST_SEC));
DMA_NodeConfTypeDef pNodeConfig;
@@ -229,65 +219,41 @@ bool backlight_init(backlight_action_t action) {
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
- pNodeConfig.SrcAddress = (uint32_t)drv->pwm_data[0];
pNodeConfig.DstAddress = (uint32_t)&drv->tim.Instance->CCR1;
- pNodeConfig.DataSize = sizeof(drv->pwm_data[0]);
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
pNodeConfig.SrcSecure = DMA_CHANNEL_SRC_SEC;
pNodeConfig.DestSecure = DMA_CHANNEL_DEST_SEC;
#endif // defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
- // Build dma_node Node
- ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &drv->dma_node[0]);
- memset(drv->pwm_data[0], UINT8_MAX, sizeof(drv->pwm_data[0]));
-
- // Insert dma_node to Queue
- ret |= HAL_DMAEx_List_InsertNode_Tail(&drv->dma_queue, &drv->dma_node[0]);
+ for (int i = 0; i < DMA_BUF_COUNT; i++) {
+ pNodeConfig.SrcAddress = (uint32_t)drv->pwm_data[i];
+ pNodeConfig.DataSize = sizeof(drv->pwm_data[i]);
- // Prepare second node for regular operation
- pNodeConfig.SrcAddress = (uint32_t)drv->pwm_data[1];
- pNodeConfig.DataSize = sizeof(drv->pwm_data[1]);
+ // Build dma_node Node
+ HAL_ERR_CHECK(HAL_DMAEx_List_BuildNode(&pNodeConfig, &drv->dma_node[i]));
+ memset(drv->pwm_data[i], UINT8_MAX, sizeof(drv->pwm_data[i]));
- // Build dma_node Node
- ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &drv->dma_node[1]);
- memset(drv->pwm_data[1], UINT8_MAX, sizeof(drv->pwm_data[1]));
-
- // Insert dma_node to Queue
- ret |= HAL_DMAEx_List_InsertNode_Tail(&drv->dma_queue, &drv->dma_node[1]);
+ // Insert dma_node to Queue
+ HAL_ERR_CHECK(
+ HAL_DMAEx_List_InsertNode_Tail(&drv->dma_queue, &drv->dma_node[i]));
+ }
// Set circular mode
- ret |= HAL_DMAEx_List_SetCircularMode(&drv->dma_queue);
+ HAL_ERR_CHECK(HAL_DMAEx_List_SetCircularMode(&drv->dma_queue));
// Link the Queue to the DMA channel
- ret |= HAL_DMAEx_List_LinkQ(&drv->dma, &drv->dma_queue);
+ HAL_ERR_CHECK(HAL_DMAEx_List_LinkQ(&drv->dma, &drv->dma_queue));
// Enable TIM DMA requests
__HAL_TIM_ENABLE_DMA(&drv->tim, TIM_DMA_UPDATE);
// Start TIM
- ret |= HAL_TIM_Base_Start(&drv->tim);
- ret |= HAL_TIM_PWM_Start(&drv->tim, TIM_CHANNEL_1);
+ HAL_ERR_CHECK(HAL_TIM_Base_Start(&drv->tim));
+ HAL_ERR_CHECK(HAL_TIM_PWM_Start(&drv->tim, TIM_CHANNEL_1));
// Register DMA callbacks
- ret |= HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_CPLT_CB_ID,
- &DMA_XferCpltCallback);
-#if 0
- ret |= HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_HALFCPLT_CB_ID,
- &DMA_XferHalfCpltCallback);
- ret |= HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_ERROR_CB_ID,
- &DMA_XferErrorCallback);
- ret |= HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_ABORT_CB_ID,
- &DMA_XferAbortCallback);
- ret |= HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_SUSPEND_CB_ID,
- &DMA_XferSuspendCallback);
-#endif
-
- if (HAL_OK != ret) {
- // Failure
- // TODO: ++low level deinit function not requiring drv->initialized == true
- // as the backlight_deinit() function does.
- return false;
- }
+ HAL_ERR_CHECK(HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_CPLT_CB_ID,
+ &DMA_XferCpltCallback));
// Configure and enable DMA IRQ
NVIC_SetPriority(GPDMA1_Channel3_IRQn, IRQ_PRI_NORMAL);
@@ -303,10 +269,12 @@ bool backlight_init(backlight_action_t action) {
drv->initialized = true;
- dbg_printf("%s:%d\n", __FILE_NAME__, __LINE__);
- dbg_printf("%s:%s(..) executed\n", __FILE_NAME__, __func__);
-
return true;
+
+cleanup:
+ // Failure
+ backlight_deinit_ll();
+ return false;
}
void backlight_deinit(backlight_action_t action) {
@@ -317,50 +285,7 @@ void backlight_deinit(backlight_action_t action) {
}
if (action == BACKLIGHT_RESET) {
- HAL_StatusTypeDef ret = HAL_OK;
-
- irq_key_t key = irq_lock();
-
- // Abort the DMA. It's unclear what last data is transferred to TIM_CCR
- // register and in what state the respective GPIO pin will be left in. CCR
- // register is set to UINT16_MAX value inside "backlight_shutdown()"
- // function.
- if (drv->dma.State == HAL_DMA_STATE_BUSY) {
- ret |= HAL_DMA_Abort(
- &drv->dma); // TODO: could be replaced with interrupt based variant
- }
-
- irq_unlock(key);
-
- backlight_shutdown();
-
- NVIC_DisableIRQ(GPDMA1_Channel3_IRQn);
-
- ret |= HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_CPLT_CB_ID);
-#if 0
- ret |= HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_HALFCPLT_CB_ID);
- ret |= HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_ERROR_CB_ID);
- ret |= HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_ABORT_CB_ID);
- ret |= HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_SUSPEND_CB_ID);
-#endif
-
- ret |= HAL_DMAEx_List_UnLinkQ(&drv->dma);
- ret |= HAL_DMAEx_List_DeInit(&drv->dma);
-
- if (HAL_OK != ret) {
- // Failure
- // TODO: should we react anyhow?
- }
-
- HAL_GPIO_DeInit(TPS61062_ILED_PORT, TPS61062_ILED_PIN);
- HAL_GPIO_DeInit(TPS61062_EN_PORT, TPS61062_EN_PIN);
-
- __HAL_RCC_TIM3_FORCE_RESET();
- __HAL_RCC_TIM3_RELEASE_RESET();
- __HAL_RCC_TIM3_CLK_DISABLE();
-
- // Move the state to OFF
- drv->state = BACKLIGHT_OFF;
+ backlight_deinit_ll();
}
drv->initialized = false;
@@ -368,7 +293,6 @@ void backlight_deinit(backlight_action_t action) {
bool backlight_set(uint8_t val) {
backlight_driver_t *drv = &g_backlight_driver;
- HAL_StatusTypeDef ret = HAL_OK;
if (!drv->initialized) {
return false;
@@ -385,9 +309,6 @@ bool backlight_set(uint8_t val) {
return true;
}
- dbg_printf("%s:%d:: val = %d, level, : %d\n", __FILE_NAME__, __LINE__, val,
- drv->requested_level);
-
irq_key_t key = irq_lock();
// Save the new value into the shared variable so that it can be used inside
@@ -395,28 +316,21 @@ bool backlight_set(uint8_t val) {
drv->requested_level_limited = requested_level_limited;
// Calculate the mapping of requested level to steps (quotient)
- drv->requested_step =
- (MAX(drv->requested_level_limited, LEVEL_OFFSET) - LEVEL_OFFSET) /
- LEVEL_STEPS_RATIO;
+ uint8_t requested_step_precalc =
+ MAX(drv->requested_level_limited, LEVEL_OFFSET) - LEVEL_OFFSET;
+ drv->requested_step = requested_step_precalc / LEVEL_STEPS_RATIO;
// Calculate the mapping of requested level to steps (remainder => duty cycle
// of PWM regulation of the step)
drv->requested_step_duty_cycle =
- (MAX(drv->requested_level_limited, LEVEL_OFFSET) - LEVEL_OFFSET) %
+ ((requested_step_precalc % LEVEL_STEPS_RATIO) * DMA_BUF_LENGTH) /
LEVEL_STEPS_RATIO;
- drv->requested_step_duty_cycle =
- (drv->requested_step_duty_cycle * DMA_BUF_LENGTH) / LEVEL_STEPS_RATIO;
// Requested level is below LEVEL_OFFSET => shutdown backlight
if (drv->requested_level_limited < LEVEL_OFFSET) {
if (drv->dma.State == HAL_DMA_STATE_BUSY) {
- ret |= HAL_DMA_Abort(
+ HAL_DMA_Abort(
&drv->dma); // TODO: could be replaced with interrupt based variant
-
- if (HAL_OK != ret) {
- // Failure
- // TODO: should we react anyhow?
- }
}
irq_unlock(key);
@@ -509,23 +423,16 @@ bool backlight_set(uint8_t val) {
HAL_GPIO_WritePin(TPS61062_EN_PORT, TPS61062_EN_PIN, GPIO_PIN_SET);
// Start the DMA
- ret |= HAL_DMAEx_List_Start_IT(&drv->dma);
-
- if (HAL_OK != ret) {
- // Failure
- // TODO: should we react anyhow?
- }
+ HAL_DMAEx_List_Start_IT(&drv->dma);
// Move the state to ON
drv->state = BACKLIGHT_ON;
} else {
- // Some serious problem occured - DMA is not in READY state.
+ // Some serious problem occurred - DMA is not in READY state.
// TODO: how to react?
}
}
- UNUSED(ret);
-
return true;
}
@@ -575,21 +482,46 @@ static void backlight_shutdown(void) {
HAL_GPIO_WritePin(TPS61062_EN_PORT, TPS61062_EN_PIN, GPIO_PIN_RESET);
}
-// Transfer complete callback
-static void DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) {
+static void backlight_deinit_ll(void) {
backlight_driver_t *drv = &g_backlight_driver;
- uint32_t dma_CSAR_tmp, dma_BNDT_tmp;
- uint64_t tdiff;
- static uint64_t tmax = 0;
+ irq_key_t key = irq_lock();
- tdiff = systick_us();
+ // Abort the DMA. It's unclear what last data is transferred to TIM_CCR
+ // register and in what state the respective GPIO pin will be left in. CCR
+ // register is set to UINT16_MAX value inside "backlight_shutdown()"
+ // function.
+ if (drv->dma.State == HAL_DMA_STATE_BUSY) {
+ HAL_DMA_Abort(
+ &drv->dma); // TODO: could be replaced with interrupt based variant
+ }
-#if 0
- dbg_printf("%s:%d:: dma_CSAR_tmp = %08x", __FILE_NAME__, __LINE__, dma_CSAR_tmp);
- dbg_printf(", &pwm_data[drv->locked_buf_idx][0] = %08x", (uint32_t)&drv->pwm_data[drv->locked_buf_idx][0]);
- dbg_printf(", &pwm_data[drv->locked_buf_idx][49] = %08x\n", (uint32_t)&drv->pwm_data[drv->locked_buf_idx][DMA_BUF_COUNT - 1]);
-#endif
+ irq_unlock(key);
+
+ backlight_shutdown();
+
+ NVIC_DisableIRQ(GPDMA1_Channel3_IRQn);
+
+ HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_CPLT_CB_ID);
+
+ HAL_DMAEx_List_UnLinkQ(&drv->dma);
+ HAL_DMAEx_List_DeInit(&drv->dma);
+
+ HAL_GPIO_DeInit(TPS61062_ILED_PORT, TPS61062_ILED_PIN);
+ HAL_GPIO_DeInit(TPS61062_EN_PORT, TPS61062_EN_PIN);
+
+ __HAL_RCC_TIM3_FORCE_RESET();
+ __HAL_RCC_TIM3_RELEASE_RESET();
+ __HAL_RCC_TIM3_CLK_DISABLE();
+
+ // Move the state to OFF
+ drv->state = BACKLIGHT_OFF;
+}
+
+// Transfer complete callback
+static void DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) {
+ backlight_driver_t *drv = &g_backlight_driver;
+ uint32_t dma_CSAR_tmp, dma_BNDT_tmp;
// There is a possibility of entering the ISR late e.g. just before
// DMA finishes another transfer. Such case needs to be handled properly.
@@ -608,7 +540,7 @@ static void DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) {
// have been switched off for too long and the DMA buffers' track has been
// lost)
if ((uint32_t)&drv->pwm_data[drv->locked_buf_idx][0] <= dma_CSAR_tmp &&
- (uint32_t)&drv->pwm_data[drv->locked_buf_idx][DMA_BUF_COUNT - 1] >=
+ (uint32_t)&drv->pwm_data[drv->locked_buf_idx][DMA_BUF_LENGTH - 1] >=
dma_CSAR_tmp) {
// The CSAR points to the wrong buffer => we need to switch the buffers
// Locked buffer is the one which has just been transferred
@@ -686,47 +618,18 @@ static void DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) {
drv->requested_step_duty_cycle;
}
- tdiff = systick_us() - tdiff;
- tmax = MAX(tmax, tdiff);
- dbg_printf("execution time = %d us, max execution time = %d us\n",
- (uint32_t)tdiff, (uint32_t)tmax);
-
- UNUSED(hdma);
-}
-
-#if 0
-// Half transfer complete callback
-static void DMA_XferHalfCpltCallback(DMA_HandleTypeDef *hdma)
-{
UNUSED(hdma);
}
-// Error callback
-static void DMA_XferErrorCallback(DMA_HandleTypeDef *hdma)
-{
- UNUSED(hdma);
-}
-
-// Abort callback
-static void DMA_XferAbortCallback(DMA_HandleTypeDef *hdma)
-{
- UNUSED(hdma);
-}
-
-// Suspend callback
-static void DMA_XferSuspendCallback(DMA_HandleTypeDef *hdma)
-{
- UNUSED(hdma);
-}
-#endif
-
void GPDMA1_Channel3_IRQHandler(void) {
IRQ_LOG_ENTER();
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DEFAULT);
backlight_driver_t *drv = &g_backlight_driver;
HAL_DMA_IRQHandler(&drv->dma);
+ mpu_restore(mpu_mode);
IRQ_LOG_EXIT();
}
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.