diff --git a/core/embed/io/display/fb_queue/fb_queue.c b/core/embed/io/display/fb_queue/fb_queue.c new file mode 100644 index 0000000000..8931d6f81b --- /dev/null +++ b/core/embed/io/display/fb_queue/fb_queue.c @@ -0,0 +1,99 @@ +#ifdef KERNEL_MODE + +#include + +#include + +#include "fb_queue.h" + +// Initializes the queue and make it empty +// Clear peeked flag +void fb_queue_reset(fb_queue_t* queue) { + memset(queue, 0, sizeof(fb_queue_t)); + for (int i = 0; i < FRAME_BUFFER_COUNT; i++) { + queue->entries[i].index = -1; + } +} + +// Inserts a new element to the tail of the queue +bool fb_queue_put(fb_queue_t* queue, int16_t index) { + irq_key_t irq_key = irq_lock(); + + // check if the queue is full + if (queue->entries[queue->wix].index != -1) { + irq_unlock(irq_key); + return false; + } + + queue->entries[queue->wix].index = index; + queue->wix = (queue->wix + 1) % FRAME_BUFFER_COUNT; + + irq_unlock(irq_key); + + return true; +} + +// Removes an element from the queue head, returns -1 if the queue is empty +// Clear peeked flag +int16_t fb_queue_take(fb_queue_t* queue) { + irq_key_t irq_key = irq_lock(); + + if (queue->entries[queue->rix].index == -1) { + irq_unlock(irq_key); + return -1; + } + + queue->peaked = false; + int16_t index = queue->entries[queue->rix].index; + queue->entries[queue->rix].index = -1; + queue->rix = (queue->rix + 1) % FRAME_BUFFER_COUNT; + + irq_unlock(irq_key); + return index; +} +// Returns true if the queue is empty +bool fb_queue_empty(fb_queue_t* queue) { + irq_key_t irq_key = irq_lock(); + + if (queue->entries[queue->rix].index == -1) { + irq_unlock(irq_key); + return true; + } + + irq_unlock(irq_key); + return false; +} + +// Waits until the queue is not empty +void fb_queue_wait(fb_queue_t* queue) { + while (fb_queue_empty(queue)) + ; +} + +// Returns the head of the queue (or -1 if the queue is empty) +// Set peeked flag if the queue is not empty +int16_t fb_queue_peek(fb_queue_t* queue) { + irq_key_t irq_key = irq_lock(); + + if (queue->entries[queue->rix].index == -1) { + irq_unlock(irq_key); + return -1; + } + + int16_t index = queue->entries[queue->rix].index; + queue->peaked = true; + + irq_unlock(irq_key); + + return index; +} + +// Return if the head was already peeked +bool fb_queue_peeked(fb_queue_t* queue) { + irq_key_t key = irq_lock(); + bool peeked = queue->peaked; + irq_unlock(key); + return peeked; +} + +#endif diff --git a/core/embed/io/display/fb_queue/fb_queue.h b/core/embed/io/display/fb_queue/fb_queue.h new file mode 100644 index 0000000000..5558eeb846 --- /dev/null +++ b/core/embed/io/display/fb_queue/fb_queue.h @@ -0,0 +1,71 @@ +/* + * 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 + +// Number of frame buffers used (1 or 2) +// If 1 buffer is selected, some animations may not +// be so smooth but the memory usage is lower. +#define FRAME_BUFFER_COUNT 2 + +// Each frame buffer can be in one of the following states: +typedef struct { + int16_t index; +} fb_queue_entry; + +typedef struct { + // Queue entries + fb_queue_entry entries[FRAME_BUFFER_COUNT]; + + // Read index + // (accessed & updated in the context of the interrupt handlers + uint8_t rix; + // Write index + // (accessed & updated in context of the main thread) + uint8_t wix; + + // Flag indicating that the head of the queue has been peaked + bool peaked; + +} fb_queue_t; + +// Initializes the queue and make it empty +// Clear peeked flag +void fb_queue_reset(fb_queue_t* queue); + +// Inserts a new element to the tail of the queue +bool fb_queue_put(fb_queue_t* queue, int16_t index); + +// Removes an element from the queue head, returns -1 if the queue is empty +// Clear peeked flag +int16_t fb_queue_take(fb_queue_t* queue); +// Returns true if the queue is empty +bool fb_queue_empty(fb_queue_t* queue); + +// Waits until the queue is not empty +void fb_queue_wait(fb_queue_t* queue); + +// Returns the head of the queue (or -1 if the queue is empty) +// Set peeked flag if the queue is not empty +int16_t fb_queue_peek(fb_queue_t* queue); + +// Return if the head was already peeked +bool fb_queue_peeked(fb_queue_t* queue); diff --git a/core/embed/io/display/st-7789/display_driver.c b/core/embed/io/display/st-7789/display_driver.c index 759e6bc2d6..c8165a727f 100644 --- a/core/embed/io/display/st-7789/display_driver.c +++ b/core/embed/io/display/st-7789/display_driver.c @@ -56,6 +56,10 @@ void display_init(display_content_mode_t mode) { memset(drv, 0, sizeof(display_driver_t)); +#ifdef FRAMEBUFFER + display_fb_init(); +#endif + if (mode == DISPLAY_RESET_CONTENT) { display_io_init_gpio(); display_io_init_fmc(); @@ -149,7 +153,7 @@ int display_set_orientation(int angle) { drv->orientation_angle = angle; #ifdef FRAMEBUFFER - display_physical_fb_clear(); + display_fb_clear(); #endif display_panel_set_window(0, 0, INTERNAL_FB_WIDTH - 1, diff --git a/core/embed/io/display/st-7789/display_fb.c b/core/embed/io/display/st-7789/display_fb.c index a286dc188f..538b5d569b 100644 --- a/core/embed/io/display/st-7789/display_fb.c +++ b/core/embed/io/display/st-7789/display_fb.c @@ -63,7 +63,7 @@ _Static_assert(FRAME_BUFFER_COUNT == 1 || FRAME_BUFFER_COUNT == 2); PHYSICAL_FRAME_BUFFER_ALIGNMENT) // Physical frame buffers in internal SRAM memory. -// Both frame buffers layes in the fixed addresses that +// Both frame buffers layers in the fixed addresses that // are shared between bootloaders and the firmware. static __attribute__((section(".fb1"), aligned(PHYSICAL_FRAME_BUFFER_ALIGNMENT))) @@ -91,6 +91,21 @@ void display_set_unpriv_access(bool unpriv) { } #endif // USE_TRUSTZONE +void display_fb_init(void) { + display_driver_t *drv = &g_display_driver; + + if (drv->initialized) { + return; + } + + fb_queue_reset(&drv->empty_frames); + fb_queue_reset(&drv->ready_frames); + + for (int16_t i = 0; i < FRAME_BUFFER_COUNT; i++) { + fb_queue_put(&drv->empty_frames, i); + } +} + // Returns the pointer to the physical frame buffer (0.. FRAME_BUFFER_COUNT-1) // Returns NULL if the framebuffer index is out of range. static uint8_t *get_fb_ptr(uint32_t index) { @@ -105,7 +120,7 @@ static uint8_t *get_fb_ptr(uint32_t index) { } } -void display_physical_fb_clear(void) { +void display_fb_clear(void) { for (int i = 0; i < FRAME_BUFFER_COUNT; i++) { mpu_set_active_fb(get_fb_ptr(i), PHYSICAL_FRAME_BUFFER_SIZE); memset(get_fb_ptr(i), 0, PHYSICAL_FRAME_BUFFER_SIZE); @@ -120,13 +135,7 @@ void display_physical_fb_clear(void) { static void bg_copy_callback(void) { display_driver_t *drv = &g_display_driver; - if (drv->queue.rix >= FRAME_BUFFER_COUNT) { - // This is an invalid state and we should never get here - return; - } - - drv->queue.entry[drv->queue.rix] = FB_STATE_EMPTY; - drv->queue.rix = (drv->queue.rix + 1) % FRAME_BUFFER_COUNT; + fb_queue_put(&drv->empty_frames, fb_queue_take(&drv->ready_frames)); } // Interrupt routing handling TE signal @@ -135,37 +144,15 @@ static void display_te_interrupt_handler(void) { __HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN); - if (drv->queue.rix >= FRAME_BUFFER_COUNT) { - // This is an invalid state and we should never get here - return; - } + if (!fb_queue_peeked(&drv->ready_frames)) { + int16_t fb_idx = fb_queue_peek(&drv->ready_frames); - switch (drv->queue.entry[drv->queue.rix]) { - case FB_STATE_EMPTY: - case FB_STATE_PREPARING: - // No new frame queued - break; - - case FB_STATE_COPYING: - // Currently we are copying a data to the display. - // We need to wait for the next TE interrupt. - break; - - case FB_STATE_READY: - // Now it's proper time to copy the data to the display - drv->queue.entry[drv->queue.rix] = FB_STATE_COPYING; + 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(drv->queue.rix), + bg_copy_start_const_out_8(get_fb_ptr(fb_idx), (uint8_t *)DISPLAY_DATA_ADDRESS, PHYSICAL_FRAME_BUFFER_SIZE, bg_copy_callback); - - // NOTE: when copying is done, this queue slot is marked empty - // (see bg_copy_callback()) - break; - - default: - // This is an invalid state and we should never get here - break; + } } } @@ -187,17 +174,10 @@ bool display_get_frame_buffer(display_fb_info_t *fb) { return false; } - frame_buffer_state_t state; + fb_queue_wait(&drv->empty_frames); + uint8_t fb_idx = fb_queue_peek(&drv->empty_frames); - // We have to wait if the buffer was passed for copying - // to the interrupt handler - do { - state = drv->queue.entry[drv->queue.wix]; - } while (state == FB_STATE_READY || state == FB_STATE_COPYING); - - drv->queue.entry[drv->queue.wix] = FB_STATE_PREPARING; - - fb->ptr = get_fb_ptr(drv->queue.wix); + fb->ptr = get_fb_ptr(fb_idx); fb->stride = DISPLAY_RESX * sizeof(uint16_t); // Enable access to the frame buffer from the unprivileged code mpu_set_active_fb(fb->ptr, PHYSICAL_FRAME_BUFFER_SIZE); @@ -205,6 +185,7 @@ bool display_get_frame_buffer(display_fb_info_t *fb) { return true; } +#ifdef BOARDLOADER // Copies the frame buffer with the given index to the display static void copy_fb_to_display(uint8_t index) { uint16_t *fb = (uint16_t *)get_fb_ptr(index); @@ -228,6 +209,7 @@ static void wait_for_te_signal(void) { while (GPIO_PIN_RESET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) { } } +#endif void display_refresh(void) { display_driver_t *drv = &g_display_driver; @@ -236,7 +218,7 @@ void display_refresh(void) { return; } - if (drv->queue.entry[drv->queue.wix] != FB_STATE_PREPARING) { + if (!fb_queue_peeked(&drv->empty_frames)) { // No refresh needed as the frame buffer is not in // the state to be copied to the display return; @@ -246,35 +228,16 @@ void display_refresh(void) { mpu_set_active_fb(NULL, 0); #ifndef BOARDLOADER - if (is_mode_exception()) { - // Disable scheduling of any new background copying - NVIC_DisableIRQ(DISPLAY_TE_INTERRUPT_NUM); - // Wait for next TE signal. During this time the - // display might be updated in the background - wait_for_te_signal(); - // Stop any background copying even if it is not finished yet - bg_copy_abort(); - // Copy the frame buffer to the display manually - copy_fb_to_display(drv->queue.wix); - // Reset the buffer queue so we can eventually continue - // safely in thread mode - drv->queue.wix = 0; - drv->queue.rix = 0; - for (int i = 0; i < FRAME_BUFFER_COUNT; i++) { - drv->queue.entry[i] = FB_STATE_EMPTY; - } - // Enable normal processing again - NVIC_EnableIRQ(DISPLAY_TE_INTERRUPT_NUM); - } else { - // Mark the buffer ready to switch to - drv->queue.entry[drv->queue.wix] = FB_STATE_READY; - drv->queue.wix = (drv->queue.wix + 1) % FRAME_BUFFER_COUNT; - } + // Mark the buffer ready to switch to + fb_queue_put(&drv->ready_frames, fb_queue_take(&drv->empty_frames)); #else // BOARDLOADER wait_for_te_signal(); - copy_fb_to_display(drv->queue.wix); - drv->queue.entry[drv->queue.wix] = FB_STATE_EMPTY; + int16_t fb_idx = fb_queue_take(&drv->empty_frames); + if (fb_idx >= 0) { + copy_fb_to_display(fb_idx); + fb_queue_put(&drv->empty_frames, fb_idx); + } #endif } @@ -293,14 +256,7 @@ void display_ensure_refreshed(void) { // so we can be sure there's not scheduled or pending // background copying do { - copy_pending = false; - for (int i = 0; i < FRAME_BUFFER_COUNT; i++) { - frame_buffer_state_t state = drv->queue.entry[i]; - if (state == FB_STATE_READY || state == FB_STATE_COPYING) { - copy_pending = true; - break; - } - } + copy_pending = !fb_queue_empty(&drv->ready_frames); __WFI(); } while (copy_pending); diff --git a/core/embed/io/display/st-7789/display_fb.h b/core/embed/io/display/st-7789/display_fb.h index 0dbc99c83d..c7d1fbe528 100644 --- a/core/embed/io/display/st-7789/display_fb.h +++ b/core/embed/io/display/st-7789/display_fb.h @@ -24,8 +24,10 @@ #ifdef FRAMEBUFFER +void display_fb_init(void); + // Clears both physical frame buffers -void display_physical_fb_clear(void); +void display_fb_clear(void); void display_ensure_refreshed(void); diff --git a/core/embed/io/display/st-7789/display_internal.h b/core/embed/io/display/st-7789/display_internal.h index 19a4398669..ae357001c5 100644 --- a/core/embed/io/display/st-7789/display_internal.h +++ b/core/embed/io/display/st-7789/display_internal.h @@ -6,37 +6,7 @@ #ifdef FRAMEBUFFER -// Number of frame buffers used (1 or 2) -// If 1 buffer is selected, some animations may not -// be so smooth but the memory usage is lower. -#define FRAME_BUFFER_COUNT 2 - -// Each frame buffer can be in one of the following states: -typedef enum { - // The frame buffer is empty and can be written to - FB_STATE_EMPTY = 0, - // The frame buffer pass passed to application - FB_STATE_PREPARING = 1, - // The frame buffer was written to and is ready - // to be copied to the display - FB_STATE_READY = 2, - // The frame buffer is currently being copied to - // the display - FB_STATE_COPYING = 3, - -} frame_buffer_state_t; - -typedef struct { - // Queue entries - volatile frame_buffer_state_t entry[FRAME_BUFFER_COUNT]; - // Read index - // (accessed & updated in the context of the interrupt handlers - uint8_t rix; - // Write index - // (accessed & updated in context of the main thread) - uint8_t wix; - -} frame_buffer_queue_t; +#include "../fb_queue/fb_queue.h" #endif // FRAMEBUFFER @@ -49,7 +19,8 @@ typedef struct { // Framebuffer queue // (accessed & updated in the context of the main thread // and the interrupt context) - volatile frame_buffer_queue_t queue; + fb_queue_t empty_frames; + fb_queue_t ready_frames; #endif // Current display orientation (0, 90, 180, 270) diff --git a/core/site_scons/models/T3T1/trezor_t3t1_revE.py b/core/site_scons/models/T3T1/trezor_t3t1_revE.py index 0f52a08689..5229e2379a 100644 --- a/core/site_scons/models/T3T1/trezor_t3t1_revE.py +++ b/core/site_scons/models/T3T1/trezor_t3t1_revE.py @@ -47,6 +47,7 @@ def configure( sources += ["embed/io/display/st-7789/display_io.c"] sources += ["embed/io/display/st-7789/display_panel.c"] sources += ["embed/io/display/st-7789/panels/lx154a2482.c"] + sources += ["embed/io/display/fb_queue/fb_queue.c"] paths += ["embed/io/display/inc"] sources += ["embed/io/display/backlight/stm32/backlight_pwm.c"]