refactor(core): expand configurability of st-7789 display driver
What changed, and why it matters
This commit rewrites the ST-7789 display driver so that pin assignments, reset, power, and tearing-effect interrupt are controlled by board-specific macros instead of being hard-coded. It also adds a fallback code path for boards that do not have a tearing-effect (TE) signal, chaining frame-buffer copies directly instead of waiting for a display interrupt. There is no indication in the commit that this fixes a security bug; it reads as a hardware-portability refactor.
No security action required. Review as normal hardware-abstraction refactor; verify that the new non-TE frame-buffer path behaves correctly on boards without a tearing-effect signal.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change moves the driver from fixed GPIOC/GPIOD pin assumptions to board-header-defined macros (DISPLAY_RST_PORT/PIN, DISPLAY_DC_PORT/PIN, DISPLAY_MEMORY_PIN, optional DISPLAY_PWR_PIN and DISPLAY_TE_PIN). It wraps TE-interrupt code in #ifdef DISPLAY_TE_PIN / #ifdef DISPLAY_TE_INTERRUPT_NUM and provides a non-TE copy path that calls start_fb_copy() from display_refresh() and chains copies in bg_copy_callback(). display_io_init_te_interrupt() is now always defined but becomes a no-op when the interrupt is not configured. The T2T1 and T3T1 board headers gain the required macro definitions so existing behavior is preserved.
Changed components
core/embed/io/display/st-7789/display_driver.ccore/embed/io/display/st-7789/display_fb.ccore/embed/io/display/st-7789/display_io.ccore/embed/io/display/st-7789/display_io.hcore/embed/io/display/st-7789/display_panel.ccore/embed/models/T2T1/boards/trezor_t.hcore/embed/models/T3T1/boards/trezor_t3t1_revE.hInspect captured patch +94 / −24
diff --git a/core/embed/io/display/st-7789/display_driver.c b/core/embed/io/display/st-7789/display_driver.c
index 8ea3c457..643a1816 100644
--- a/core/embed/io/display/st-7789/display_driver.c
+++ b/core/embed/io/display/st-7789/display_driver.c
@@ -105,7 +105,7 @@ void display_deinit(display_content_mode_t mode) {
// Ensure that the ready frame buffer is transferred to
// the display controller
display_ensure_refreshed();
-#ifdef FRAMEBUFFER
+#if defined(FRAMEBUFFER) && defined(DISPLAY_TE_INTERRUPT_NUM)
// Disable periodical interrupt
NVIC_DisableIRQ(DISPLAY_TE_INTERRUPT_NUM);
#endif
diff --git a/core/embed/io/display/st-7789/display_fb.c b/core/embed/io/display/st-7789/display_fb.c
index b0b73b1e..0f07ca0b 100644
--- a/core/embed/io/display/st-7789/display_fb.c
+++ b/core/embed/io/display/st-7789/display_fb.c
@@ -126,16 +126,43 @@ void display_fb_clear(void) {
#ifndef BOARDLOADER
+static void bg_copy_callback(void);
+
+// Starts copying the next ready frame buffer to the display, if one is queued
+// and no copy is already in progress. Called from an IRQ context or with
+// interrupts locked.
+static void start_fb_copy(void) {
+ display_driver_t *drv = &g_display_driver;
+
+ if (!fb_queue_peeked(&drv->ready_frames)) {
+ int16_t fb_idx = fb_queue_peek(&drv->ready_frames);
+
+ if (fb_idx >= 0) {
+ display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
+ bg_copy_start_const_out_8(get_fb_ptr(fb_idx),
+ (uint8_t *)DISPLAY_DATA_ADDRESS,
+ PHYSICAL_FRAME_BUFFER_SIZE, bg_copy_callback);
+ }
+ }
+}
+
// Callback called when the background copying is done
// It's called from the IRQ context
static void bg_copy_callback(void) {
display_driver_t *drv = &g_display_driver;
- drv->update_pending = 2;
-
fb_queue_put(&drv->empty_frames, fb_queue_take(&drv->ready_frames));
+
+#ifdef DISPLAY_TE_PIN
+ drv->update_pending = 2;
+#else
+ // Without a tearing-effect signal, copies are not retriggered by the TE
+ // interrupt, so chain directly to the next queued frame (if any).
+ start_fb_copy();
+#endif
}
+#ifdef DISPLAY_TE_PIN
// Interrupt routing handling TE signal
static void display_te_interrupt_handler(void) {
display_driver_t *drv = &g_display_driver;
@@ -146,16 +173,7 @@ static void display_te_interrupt_handler(void) {
drv->update_pending--;
}
- if (!fb_queue_peeked(&drv->ready_frames)) {
- int16_t fb_idx = fb_queue_peek(&drv->ready_frames);
-
- if (fb_idx >= 0) {
- display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
- bg_copy_start_const_out_8(get_fb_ptr(fb_idx),
- (uint8_t *)DISPLAY_DATA_ADDRESS,
- PHYSICAL_FRAME_BUFFER_SIZE, bg_copy_callback);
- }
- }
+ start_fb_copy();
}
void DISPLAY_TE_INTERRUPT_HANDLER(void) {
@@ -166,6 +184,7 @@ void DISPLAY_TE_INTERRUPT_HANDLER(void) {
IRQ_LOG_EXIT();
}
#endif
+#endif
bool display_get_frame_buffer(display_fb_info_t *fb) {
display_driver_t *drv = &g_display_driver;
@@ -205,6 +224,7 @@ static void copy_fb_to_display(uint8_t index) {
mpu_set_active_fb(NULL, 0);
}
+#ifdef DISPLAY_TE_PIN
static void wait_for_te_signal(void) {
// sync with the panel refresh
while (GPIO_PIN_SET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) {
@@ -213,6 +233,7 @@ static void wait_for_te_signal(void) {
}
}
#endif
+#endif
void display_refresh(void) {
display_driver_t *drv = &g_display_driver;
@@ -234,8 +255,18 @@ void display_refresh(void) {
// Mark the buffer ready to switch to
fb_queue_put(&drv->ready_frames, fb_queue_take(&drv->empty_frames));
+#ifndef DISPLAY_TE_PIN
+ // Without a tearing-effect signal there is no interrupt to trigger the
+ // copy, so kick it off here (no-op if a copy is already in progress).
+ irq_key_t irq_key = irq_lock();
+ start_fb_copy();
+ irq_unlock(irq_key);
+#endif
+
#else // BOARDLOADER
+#ifdef DISPLAY_TE_PIN
wait_for_te_signal();
+#endif
int16_t fb_idx = fb_queue_take(&drv->empty_frames);
if (fb_idx >= 0) {
copy_fb_to_display(fb_idx);
diff --git a/core/embed/io/display/st-7789/display_io.c b/core/embed/io/display/st-7789/display_io.c
index e8109fcc..7259cb31 100644
--- a/core/embed/io/display/st-7789/display_io.c
+++ b/core/embed/io/display/st-7789/display_io.c
@@ -38,18 +38,31 @@ void display_io_init_gpio(void) {
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
+ __HAL_RCC_GPIOF_CLK_ENABLE();
+ __HAL_RCC_GPIOG_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStructure;
- // LCD_RST/PC14
+#ifdef DISPLAY_PWR_PIN
+ // Enable the display power supply (load switch) before reset
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStructure.Alternate = 0;
- GPIO_InitStructure.Pin = GPIO_PIN_14;
+ GPIO_InitStructure.Pin = DISPLAY_PWR_PIN;
+ HAL_GPIO_WritePin(DISPLAY_PWR_PORT, DISPLAY_PWR_PIN, GPIO_PIN_SET);
+ HAL_GPIO_Init(DISPLAY_PWR_PORT, &GPIO_InitStructure);
+#endif
+
+ // LCD_RST
+ GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
+ GPIO_InitStructure.Pull = GPIO_NOPULL;
+ GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
+ GPIO_InitStructure.Alternate = 0;
+ GPIO_InitStructure.Pin = DISPLAY_RST_PIN;
// default to keeping display in reset
- HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET);
- HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);
+ HAL_GPIO_WritePin(DISPLAY_RST_PORT, DISPLAY_RST_PIN, GPIO_PIN_RESET);
+ HAL_GPIO_Init(DISPLAY_RST_PORT, &GPIO_InitStructure);
#ifdef DISPLAY_TE_PIN
// LCD_FMARK (tearing effect)
@@ -65,8 +78,11 @@ void display_io_init_gpio(void) {
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = GPIO_AF12_FMC;
- // LCD_CS/PD7 LCD_RS/PD11 LCD_RD/PD4 LCD_WR/PD5
- GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_4 | GPIO_PIN_5;
+ // LCD_RS / D/C
+ GPIO_InitStructure.Pin = DISPLAY_DC_PIN;
+ HAL_GPIO_Init(DISPLAY_DC_PORT, &GPIO_InitStructure);
+ // LCD_CS/PD7 LCD_RD/PD4 LCD_WR/PD5
+ GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_4 | GPIO_PIN_5;
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
// LCD_D0/PD14 LCD_D1/PD15 LCD_D2/PD0 LCD_D3/PD1
GPIO_InitStructure.Pin = GPIO_PIN_14 | GPIO_PIN_15 | GPIO_PIN_0 | GPIO_PIN_1;
@@ -135,8 +151,8 @@ void display_io_init_fmc(void) {
mpu_restore(mpu_mode);
}
-#ifdef DISPLAY_TE_INTERRUPT_HANDLER
void display_io_init_te_interrupt(void) {
+#ifdef DISPLAY_TE_INTERRUPT_HANDLER
EXTI_HandleTypeDef EXTI_Handle = {0};
EXTI_ConfigTypeDef EXTI_Config = {0};
EXTI_Config.GPIOSel = DISPLAY_TE_INTERRUPT_GPIOSEL;
@@ -148,7 +164,7 @@ void display_io_init_te_interrupt(void) {
// setup interrupt for tearing effect pin
NVIC_SetPriority(DISPLAY_TE_INTERRUPT_NUM, IRQ_PRI_NORMAL);
NVIC_EnableIRQ(DISPLAY_TE_INTERRUPT_NUM);
-}
#endif
+}
#endif // KERNEL_MODE
diff --git a/core/embed/io/display/st-7789/display_io.h b/core/embed/io/display/st-7789/display_io.h
index e50d8c91..a272196a 100644
--- a/core/embed/io/display/st-7789/display_io.h
+++ b/core/embed/io/display/st-7789/display_io.h
@@ -31,8 +31,17 @@ void display_io_init_te_interrupt(void);
#define FMC_BANK1 0x60000000U
#endif
+// The board header must provide the display reset and data/command (RS) pins.
+// DISPLAY_DC_PORT/PIN is the GPIO routed to an FMC address line and
+// DISPLAY_MEMORY_PIN selects which external FMC address line (A<n>) drives D/C.
+#if !defined(DISPLAY_RST_PORT) || !defined(DISPLAY_RST_PIN) || \
+ !defined(DISPLAY_DC_PORT) || !defined(DISPLAY_DC_PIN) || \
+ !defined(DISPLAY_MEMORY_PIN)
+#error \
+ "Board header must define DISPLAY_RST_PORT/PIN, DISPLAY_DC_PORT/PIN and DISPLAY_MEMORY_PIN"
+#endif
+
#define DISPLAY_MEMORY_BASE FMC_BANK1
-#define DISPLAY_MEMORY_PIN 16
#ifdef DISPLAY_I8080_16BIT_DW
#define DISPLAY_ADDR_SHIFT 2
diff --git a/core/embed/io/display/st-7789/display_panel.c b/core/embed/io/display/st-7789/display_panel.c
index 283bdc7a..fc7b37f2 100644
--- a/core/embed/io/display/st-7789/display_panel.c
+++ b/core/embed/io/display/st-7789/display_panel.c
@@ -192,13 +192,15 @@ void display_panel_preserve_inversion(void) { t2t1_preserve_inversion(); }
#endif
void display_panel_init(void) {
- HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); // LCD_RST/PC14
+ HAL_GPIO_WritePin(DISPLAY_RST_PORT, DISPLAY_RST_PIN,
+ GPIO_PIN_RESET); // LCD_RST
// wait 10 milliseconds. only needs to be low for 10 microseconds.
// my dev display module ties display reset and touch panel reset together.
// keeping this low for max(display_reset_time, ctpm_reset_time) aids
// development and does not hurt.
HAL_Delay(10);
- HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET); // LCD_RST/PC14
+ HAL_GPIO_WritePin(DISPLAY_RST_PORT, DISPLAY_RST_PIN,
+ GPIO_PIN_SET); // LCD_RST
// max wait time for hardware reset is 120 milliseconds
// (experienced display flakiness using only 5ms wait before sending commands)
HAL_Delay(120);
diff --git a/core/embed/models/T2T1/boards/trezor_t.h b/core/embed/models/T2T1/boards/trezor_t.h
index d3e91778..a0d6b278 100644
--- a/core/embed/models/T2T1/boards/trezor_t.h
+++ b/core/embed/models/T2T1/boards/trezor_t.h
@@ -6,6 +6,12 @@
#define DISPLAY_TE_PIN GPIO_PIN_12
#define DISPLAY_I8080_8BIT_DW 1
+#define DISPLAY_RST_PORT GPIOC
+#define DISPLAY_RST_PIN GPIO_PIN_14
+#define DISPLAY_DC_PORT GPIOD
+#define DISPLAY_DC_PIN GPIO_PIN_11 // FMC_A16
+#define DISPLAY_MEMORY_PIN 16
+
#define TPS61043_FREQ 10000
#define TPS61043_TIM TIM1
#define TPS61043_TIM_CLK_EN __HAL_RCC_TIM1_CLK_ENABLE
diff --git a/core/embed/models/T3T1/boards/trezor_t3t1_revE.h b/core/embed/models/T3T1/boards/trezor_t3t1_revE.h
index f5e586fe..6a57defe 100644
--- a/core/embed/models/T3T1/boards/trezor_t3t1_revE.h
+++ b/core/embed/models/T3T1/boards/trezor_t3t1_revE.h
@@ -13,6 +13,12 @@
#define DISPLAY_TE_INTERRUPT_GPIOSEL EXTI_GPIOD
#define DISPLAY_TE_INTERRUPT_EXTI_LINE EXTI_LINE_12
+#define DISPLAY_RST_PORT GPIOC
+#define DISPLAY_RST_PIN GPIO_PIN_14
+#define DISPLAY_DC_PORT GPIOD
+#define DISPLAY_DC_PIN GPIO_PIN_11 // FMC_A16
+#define DISPLAY_MEMORY_PIN 16
+
#define TPS61043_FREQ 12500
#define TPS61043_TIM TIM17
#define TPS61043_TIM_CLK_EN __HAL_RCC_TIM17_CLK_ENABLE
Why this scored 13/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.