What changed, and why it matters
This commit is a pure code reorganization: it moves the graphics (gfx) subsystem from the `embed/gfx/` directory into `embed/io/gfx/`, updates all build scripts and include paths accordingly, and renames some internal subdirectories (for example, `fonts/` becomes `terminal/`). The actual graphics logic, functions, and behavior appear unchanged. There is no indication this fixes or introduces a security issue.
No security action required. Treat as routine maintenance/refactor. Verify the build still compiles and tests pass after the path changes.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change is a large-scale refactor that relocates the Trezor core graphics/bitblt/JPEG/terminal sources under the embed/io/ hierarchy. The diff shows file deletions from core/embed/gfx/ and corresponding additions under core/embed/io/gfx/, with build scripts (SConscript.*) and include paths updated from embed/gfx/inc to embed/io/gfx/inc. The code content of the moved files is substantially identical (e.g., bitblt implementations, DMA2D driver, color helpers, JPEG decoder, terminal). No functional or security-relevant modifications are visible in the supplied diff.
Changed components
core/embed/gfx -> core/embed/io/gfx directory movecore/SConscript.* build scriptscore/site_scons/models/*core/embed/projects/boardloader/sd_update.ccore/embed/projects/bootloader_ci/*core/embed/projects/kernel/main.ccore/embed/rust/build.rscore/embed/sys/syscall/stm32/*core/embed/util/rsod/rsod.cInspect captured patch +3913 / −3916
diff --git a/core/SConscript.boardloader b/core/SConscript.boardloader
index a885a847..8a1d83e7 100644
--- a/core/SConscript.boardloader
+++ b/core/SConscript.boardloader
@@ -84,15 +84,15 @@ CPPPATH_MOD += [
'vendor/micropython/lib/uzlib',
]
SOURCE_MOD += [
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
'embed/rtl/error_handling.c',
@@ -148,8 +148,8 @@ env.Replace(
'embed/rtl/inc',
'embed/models',
'embed/upymod/modtrezorui',
- 'embed/gfx/inc',
'embed/io/button/inc',
+ 'embed/io/gfx/inc',
'embed/io/haptic/inc',
'embed/io/touch/inc',
'embed/io/usb/inc',
diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index 87291349..8c082b06 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -109,15 +109,15 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
'embed/rtl/error_handling.c',
@@ -206,7 +206,7 @@ ALLPATHS = [
'embed/projects/bootloader/protob',
'embed/rtl/inc',
'embed/models',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sec/image/inc',
'embed/sys/bsp/inc',
diff --git a/core/SConscript.bootloader_ci b/core/SConscript.bootloader_ci
index 0817a925..a72294e2 100644
--- a/core/SConscript.bootloader_ci
+++ b/core/SConscript.bootloader_ci
@@ -88,15 +88,15 @@ CPPPATH_MOD += [
'vendor/micropython/lib/uzlib',
]
SOURCE_MOD += [
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
'embed/rtl/error_handling.c',
@@ -167,7 +167,7 @@ env.Replace(
'embed/projects/bootloader_ci/protob',
'embed/rtl/inc',
'embed/models',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/sec/image/inc',
'embed/sys/bsp/inc',
'embed/util/rsod/inc',
diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu
index 21c610bc..f00c6fa3 100644
--- a/core/SConscript.bootloader_emu
+++ b/core/SConscript.bootloader_emu
@@ -83,15 +83,15 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
'embed/rtl/error_handling.c',
@@ -180,7 +180,7 @@ ALLPATHS = ['embed/rust',
'embed/models',
'embed/projects/unix',
'embed/upymod/modtrezorui',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sec/image/inc',
'embed/util/rsod/inc',
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 3bccaac5..5f119c58 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -251,15 +251,15 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/upymod/modtrezorui/modtrezorui.c',
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/io/translations/translations.c',
'embed/util/rsod/rsod.c',
'embed/rtl/error_handling.c',
@@ -488,7 +488,7 @@ ALLPATHS = [
'embed/projects/firmware',
'embed/rtl/inc',
'embed/models',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sys/bsp/inc',
'embed/sec/image/inc',
diff --git a/core/SConscript.kernel b/core/SConscript.kernel
index ab53bab7..f9cd0654 100644
--- a/core/SConscript.kernel
+++ b/core/SConscript.kernel
@@ -209,14 +209,14 @@ if FEATURE_FLAGS["SECP256K1_ZKP"]:
]
SOURCE_MOD += [
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/io/translations/translations.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
@@ -313,7 +313,7 @@ ALLPATHS = [
'embed/projects/firmware',
'embed/rtl/inc',
'embed/models',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sec/image/inc',
'embed/sys/bsp/inc',
diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest
index 2613f37b..d60585f0 100644
--- a/core/SConscript.prodtest
+++ b/core/SConscript.prodtest
@@ -161,15 +161,15 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
'embed/rtl/cli.c',
@@ -254,7 +254,7 @@ ALLPATHS = [
'embed/projects/prodtest',
'embed/rtl/inc',
'embed/models',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sys/bsp/inc',
'embed/sec/image/inc',
diff --git a/core/SConscript.prodtest_emu b/core/SConscript.prodtest_emu
index 288f1660..184e1ffb 100644
--- a/core/SConscript.prodtest_emu
+++ b/core/SConscript.prodtest_emu
@@ -127,15 +127,15 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
'embed/rtl/cli.c',
@@ -220,7 +220,7 @@ ALLPATHS = ['embed/rust',
'embed/rtl/inc',
'embed/models',
'embed/projects/unix',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sec/image/inc',
'embed/util/rsod/inc',
diff --git a/core/SConscript.secmon b/core/SConscript.secmon
index bb5843d3..bd1370da 100644
--- a/core/SConscript.secmon
+++ b/core/SConscript.secmon
@@ -307,7 +307,7 @@ ALLPATHS = [
'embed/projects/firmware',
'embed/rtl/inc',
'embed/models',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sec/image/inc',
'embed/sys/bsp/inc',
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 3e5463b8..55ad2188 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -234,15 +234,15 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/upymod/modtrezorui/modtrezorui.c',
- 'embed/gfx/bitblt/gfx_bitblt.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
- 'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
- 'embed/gfx/bitblt/gfx_bitblt_mono8.c',
- 'embed/gfx/fonts/font_bitmap.c',
- 'embed/gfx/gfx_color.c',
- 'embed/gfx/gfx_draw.c',
- 'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgb565.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c',
+ 'embed/io/gfx/bitblt/gfx_bitblt_mono8.c',
+ 'embed/io/gfx/gfx_color.c',
+ 'embed/io/gfx/gfx_draw.c',
+ 'embed/io/gfx/terminal/font_bitmap.c',
+ 'embed/io/gfx/terminal/terminal.c',
'embed/io/translations/translations.c',
'embed/util/rsod/rsod.c',
'embed/util/rsod/rsod_special.c',
@@ -497,7 +497,7 @@ ALLPATHS=['.',
'embed/models',
'embed/projects/unix',
'embed/upymod/modtrezorui',
- 'embed/gfx/inc',
+ 'embed/io/gfx/inc',
'embed/io/translations/inc',
'embed/sec/image/inc',
'embed/util/rsod/inc',
diff --git a/core/embed/gfx/bitblt/gfx_bitblt.c b/core/embed/gfx/bitblt/gfx_bitblt.c
deleted file mode 100644
index 85ae1820..00000000
--- a/core/embed/gfx/bitblt/gfx_bitblt.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <gfx/dma2d_bitblt.h>
-
-#ifdef KERNEL_MODE
-
-void gfx_bitblt_init(void) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- dma2d_init();
-#endif
-}
-
-void gfx_bitblt_deinit(void) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- dma2d_deinit();
-#endif
-}
-
-#endif // KERNEL_MODE
-
-void gfx_bitblt_wait(void) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- dma2d_wait();
-#endif
-}
diff --git a/core/embed/gfx/bitblt/gfx_bitblt_mono8.c b/core/embed/gfx/bitblt/gfx_bitblt_mono8.c
deleted file mode 100644
index 55a4fd5a..00000000
--- a/core/embed/gfx/bitblt/gfx_bitblt_mono8.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <gfx/gfx_bitblt.h>
-
-void gfx_mono8_fill(const gfx_bitblt_t* bb) {
- uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
- uint16_t height = bb->height;
-
- uint8_t fg = gfx_color_lum(bb->src_fg);
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = fg;
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- }
-}
-
-void gfx_mono8_copy_mono1p(const gfx_bitblt_t* bb) {
- uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
- uint8_t* src = (uint8_t*)bb->src_row;
- uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
- uint16_t height = bb->height;
-
- uint8_t fg = gfx_color_lum(bb->src_fg);
- uint8_t bg = gfx_color_lum(bb->src_bg);
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
- uint8_t data = src[(src_ofs + x) / 8];
- dst_ptr[x] = (data & mask) ? fg : bg;
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ofs += bb->src_stride;
- }
-}
-
-void gfx_mono8_copy_mono4(const gfx_bitblt_t* bb) {
- uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_row = (uint8_t*)bb->src_row;
- uint16_t height = bb->height;
-
- uint8_t fg = gfx_color_lum(bb->src_fg);
- uint8_t bg = gfx_color_lum(bb->src_bg);
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t src_data = src_row[(x + bb->src_x) / 2];
- uint8_t src_lum = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0xF;
- dst_ptr[x] = (fg * src_lum + bg * (15 - src_lum)) / 15;
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_row += bb->src_stride / sizeof(*src_row);
- }
-}
-
-void gfx_mono8_blend_mono1p(const gfx_bitblt_t* bb) {
- uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
- uint8_t* src = (uint8_t*)bb->src_row;
- uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
- uint16_t height = bb->height;
-
- uint8_t fg = gfx_color_lum(bb->src_fg);
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
- uint8_t data = src[(src_ofs + x) / 8];
- dst_ptr[x] = (data & mask) ? fg : dst_ptr[x];
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ofs += bb->src_stride;
- }
-}
-
-void gfx_mono8_blend_mono4(const gfx_bitblt_t* bb) {
- uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_row = (uint8_t*)bb->src_row;
- uint16_t height = bb->height;
-
- uint8_t fg = gfx_color_lum(bb->src_fg);
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t src_data = src_row[(x + bb->src_x) / 2];
- uint8_t src_alpha = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0x0F;
- src_alpha = src_alpha * bb->src_alpha / 15;
- dst_ptr[x] = (fg * src_alpha + dst_ptr[x] * (255 - src_alpha)) / 255;
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_row += bb->src_stride / sizeof(*src_row);
- }
-}
diff --git a/core/embed/gfx/bitblt/gfx_bitblt_rgb565.c b/core/embed/gfx/bitblt/gfx_bitblt_rgb565.c
deleted file mode 100644
index feea2c16..00000000
--- a/core/embed/gfx/bitblt/gfx_bitblt_rgb565.c
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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/>.
- */
-
-// Turning off the stack protector for this file improves
-// the performance of drawing operations when called frequently.
-#ifndef TREZOR_EMULATOR
-#pragma GCC optimize("no-stack-protector")
-#endif
-
-#include <gfx/gfx_bitblt.h>
-
-#if USE_DMA2D
-#include <gfx/dma2d_bitblt.h>
-#endif
-
-volatile uint64_t g_gfx_cycles;
-
-void gfx_rgb565_fill(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgb565_fill(bb))
-#endif
- {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint16_t height = bb->height;
-
- if (bb->src_alpha == 255) {
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = bb->src_fg;
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- }
- } else {
- uint8_t alpha = bb->src_alpha;
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = gfx_color16_blend_a8(bb->src_fg, dst_ptr[x], alpha);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- }
- }
- }
-}
-
-void gfx_rgb565_copy_mono1p(const gfx_bitblt_t* bb) {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint8_t* src = (uint8_t*)bb->src_row;
- uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
- uint16_t height = bb->height;
-
- uint16_t fg = gfx_color_to_color16(bb->src_fg);
- uint16_t bg = gfx_color_to_color16(bb->src_bg);
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
- uint8_t data = src[(src_ofs + x) / 8];
- dst_ptr[x] = (data & mask) ? fg : bg;
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ofs += bb->src_stride;
- }
-}
-
-void gfx_rgb565_copy_mono4(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgb565_copy_mono4(bb))
-#endif
- {
- const gfx_color16_t* gradient =
- gfx_color16_gradient_a4(bb->src_fg, bb->src_bg);
-
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_row = (uint8_t*)bb->src_row;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t fg_data = src_row[(x + bb->src_x) / 2];
- uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
- dst_ptr[x] = gradient[fg_lum];
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_row += bb->src_stride / sizeof(*src_row);
- }
- }
-}
-
-void gfx_rgb565_copy_rgb565(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgb565_copy_rgb565(bb))
-#endif
- {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = src_ptr[x];
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
- }
-}
-
-void gfx_rgb565_blend_mono4(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgb565_blend_mono4(bb))
-#endif
- {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_row = (uint8_t*)bb->src_row;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t fg_data = src_row[(x + bb->src_x) / 2];
- uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
- fg_alpha = fg_alpha * bb->src_alpha / 15;
- dst_ptr[x] = gfx_color16_blend_a8(
- bb->src_fg, gfx_color16_to_color(dst_ptr[x]), fg_alpha);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_row += bb->src_stride / sizeof(*src_row);
- }
- }
-}
-
-void gfx_rgb565_blend_mono8(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgb565_blend_mono8(bb))
-#endif
- {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t fg_alpha = src_ptr[x];
- dst_ptr[x] = gfx_color16_blend_a8(
- bb->src_fg, gfx_color16_to_color(dst_ptr[x]), fg_alpha);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
- }
-}
diff --git a/core/embed/gfx/bitblt/gfx_bitblt_rgba8888.c b/core/embed/gfx/bitblt/gfx_bitblt_rgba8888.c
deleted file mode 100644
index 63299463..00000000
--- a/core/embed/gfx/bitblt/gfx_bitblt_rgba8888.c
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <gfx/gfx_bitblt.h>
-
-#if USE_DMA2D
-#include <gfx/dma2d_bitblt.h>
-#endif
-
-void gfx_rgba8888_fill(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgba8888_fill(bb))
-#endif
- {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint16_t height = bb->height;
-
- if (bb->src_alpha == 255) {
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = gfx_color_to_color32(bb->src_fg);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- }
- } else {
- uint8_t alpha = bb->src_alpha;
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = gfx_color32_blend_a8(
- bb->src_fg, gfx_color32_to_color(dst_ptr[x]), alpha);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- }
- }
- }
-}
-
-void gfx_rgba8888_copy_mono1p(const gfx_bitblt_t* bb) {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint8_t* src = (uint8_t*)bb->src_row;
- uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
- uint16_t height = bb->height;
-
- uint32_t fg = gfx_color_to_color32(bb->src_fg);
- uint32_t bg = gfx_color_to_color32(bb->src_bg);
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
- uint8_t data = src[(src_ofs + x) / 8];
- dst_ptr[x] = (data & mask) ? fg : bg;
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ofs += bb->src_stride;
- }
-}
-
-void gfx_rgba8888_copy_mono4(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgba8888_copy_mono4(bb))
-#endif
- {
- const gfx_color32_t* gradient =
- gfx_color32_gradient_a4(bb->src_fg, bb->src_bg);
-
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_row = (uint8_t*)bb->src_row;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t fg_data = src_row[(x + bb->src_x) / 2];
- uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
- dst_ptr[x] = gradient[fg_lum];
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_row += bb->src_stride / sizeof(*src_row);
- }
- }
-}
-
-void gfx_rgba8888_copy_rgb565(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgba8888_copy_rgb565(bb))
-#endif
- {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = gfx_color16_to_color32(src_ptr[x]);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
- }
-}
-
-void gfx_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgba8888_copy_rgba8888(bb))
-#endif
- {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint32_t* src_ptr = (uint32_t*)bb->src_row + bb->src_x;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- dst_ptr[x] = src_ptr[x << bb->src_downscale];
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += (bb->src_stride / sizeof(*src_ptr)) << bb->src_downscale;
- }
- }
-}
-
-void gfx_rgba8888_blend_mono4(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgba8888_blend_mono4(bb))
-#endif
- {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_row = (uint8_t*)bb->src_row;
- uint16_t height = bb->height;
-
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t fg_data = src_row[(x + bb->src_x) / 2];
- uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
- fg_alpha = fg_alpha * bb->src_alpha / 15;
- dst_ptr[x] = gfx_color32_blend_a8(
- bb->src_fg, gfx_color32_to_color(dst_ptr[x]), fg_alpha);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_row += bb->src_stride / sizeof(*src_row);
- }
- }
-}
-
-void gfx_rgba8888_blend_mono8(const gfx_bitblt_t* bb) {
-#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
- if (!dma2d_rgba8888_blend_mono8(bb))
-#endif
- {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x;
- uint16_t height = bb->height;
-
- if (bb->src_alpha == 255) {
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t fg_alpha = src_ptr[x];
- dst_ptr[x] = gfx_color32_blend_a8(
- bb->src_fg, gfx_color32_to_color(dst_ptr[x]), fg_alpha);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
- } else {
- while (height-- > 0) {
- for (int x = 0; x < bb->width; x++) {
- uint8_t fg_alpha = (src_ptr[x] * bb->src_alpha) / 255;
- dst_ptr[x] = gfx_color32_blend_a8(
- bb->src_fg, gfx_color32_to_color(dst_ptr[x]), fg_alpha);
- }
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
- }
- }
-}
diff --git a/core/embed/gfx/bitblt/stm32/dma2d_bitblt.c b/core/embed/gfx/bitblt/stm32/dma2d_bitblt.c
deleted file mode 100644
index 7379ca0f..00000000
--- a/core/embed/gfx/bitblt/stm32/dma2d_bitblt.c
+++ /dev/null
@@ -1,976 +0,0 @@
-/*
- * 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/>.
- */
-
-// Turning off the stack protector for this file improves
-// the performance of drawing operations when called frequently.
-#pragma GCC optimize("no-stack-protector")
-
-#ifdef KERNEL_MODE
-
-#include <trezor_bsp.h>
-#include <trezor_rtl.h>
-
-#include <gfx/dma2d_bitblt.h>
-#include <gfx/gfx_color.h>
-
-// Number of DMA2D layers - background (0) and foreground (1)
-#define DMA2D_LAYER_COUNT 2
-
-typedef struct {
- // Set if the driver is initialized
- bool initialized;
- // ST DMA2D driver handle
- DMA2D_HandleTypeDef handle;
- // CLUT cache
- struct {
- gfx_color32_t c_fg;
- gfx_color32_t c_bg;
- } cache[DMA2D_LAYER_COUNT];
-
- // CLUT is configured according to the cache
- bool clut_valid;
-
-} dma2d_driver_t;
-
-static dma2d_driver_t g_dma2d_driver = {
- .initialized = false,
-};
-
-// Returns `true` if the specified address is accessible by DMA2D
-// and can be used by any of the following functions
-static inline bool dma2d_accessible(const void* ptr) {
-#ifdef STM32F4
- const void* ccm_start = (const void*)0x10000000;
- const void* ccm_end = (const void*)0x1000FFFF;
- return !(ptr >= ccm_start && ptr <= ccm_end);
-#else
- return true;
-#endif
-}
-
-void dma2d_init(void) {
- dma2d_driver_t* drv = &g_dma2d_driver;
- if (drv->initialized) {
- return;
- }
- memset(drv, 0, sizeof(dma2d_driver_t));
- drv->handle.Instance = DMA2D;
-
-#ifdef KERNEL_MODE
- __HAL_RCC_DMA2D_FORCE_RESET();
- __HAL_RCC_DMA2D_RELEASE_RESET();
- __HAL_RCC_DMA2D_CLK_ENABLE();
-#endif
- drv->initialized = true;
-}
-
-void dma2d_deinit(void) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
-#ifdef KERNEL_MODE
- __HAL_RCC_DMA2D_CLK_DISABLE();
- __HAL_RCC_DMA2D_FORCE_RESET();
- __HAL_RCC_DMA2D_RELEASE_RESET();
-#endif
- memset(drv, 0, sizeof(dma2d_driver_t));
-}
-
-void dma2d_wait(void) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return;
- }
-
- while (HAL_DMA2D_PollForTransfer(&drv->handle, 10) != HAL_OK)
- ;
-}
-
-bool dma2d_rgb565_fill(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 16)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row)) {
- return false;
- }
-
- if (bb->src_alpha == 255) {
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
- drv->handle.Init.Mode = DMA2D_R2M;
- drv->handle.Init.OutputOffset =
- bb->dst_stride / sizeof(uint16_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- HAL_DMA2D_Start(&drv->handle, gfx_color_to_color32(bb->src_fg),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
- bb->width, bb->height);
- } else {
-#ifdef STM32U5
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
- drv->handle.Init.Mode = DMA2D_M2M_BLEND_FG;
- drv->handle.Init.OutputOffset =
- bb->dst_stride / sizeof(uint16_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
- drv->handle.LayerCfg[1].InputOffset = 0;
- drv->handle.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
- drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
- drv->handle.LayerCfg[0].InputOffset =
- bb->dst_stride / sizeof(uint16_t) - bb->width;
- drv->handle.LayerCfg[0].AlphaMode = 0;
- drv->handle.LayerCfg[0].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 0);
-
- HAL_DMA2D_BlendingStart(
- &drv->handle, gfx_color_to_color32(bb->src_fg),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), bb->width,
- bb->height);
-#else
- // STM32F4 can not accelerate blending with the fixed color
- return false;
-#endif
- }
-
- return true;
-}
-
-static void dma2d_config_clut(uint32_t layer, gfx_color32_t fg,
- gfx_color32_t bg) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
-#define GRADIENT_STEPS 16
-
- if (layer >= ARRAY_LENGTH(drv->cache)) {
- return;
- }
-
- volatile uint32_t* clut =
- layer ? drv->handle.Instance->FGCLUT : drv->handle.Instance->BGCLUT;
-
- if (fg != drv->cache[layer].c_fg || bg != drv->cache[layer].c_bg ||
- !drv->clut_valid) {
- drv->cache[layer].c_fg = fg;
- drv->cache[layer].c_bg = bg;
- drv->clut_valid = true;
-
- for (int step = 0; step < GRADIENT_STEPS; step++) {
- clut[step] = gfx_color32_rgba(
- a4_lerp(gfx_color32_to_r(fg), gfx_color32_to_r(bg), step),
- a4_lerp(gfx_color32_to_g(fg), gfx_color32_to_g(bg), step),
- a4_lerp(gfx_color32_to_b(fg), gfx_color32_to_b(bg), step),
- a4_lerp(gfx_color32_to_a(fg), gfx_color32_to_a(bg), step));
- }
-
- DMA2D_CLUTCfgTypeDef clut_def = {0};
- clut_def.CLUTColorMode = DMA2D_CCM_ARGB8888;
- clut_def.Size = GRADIENT_STEPS - 1;
- clut_def.pCLUT = 0; // ???
-
- HAL_DMA2D_ConfigCLUT(&drv->handle, clut_def, layer);
- }
-}
-
-static void dma2d_rgb565_copy_mono4_first_col(gfx_bitblt_t* bb,
- const gfx_color16_t* gradient) {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_lum = src_ptr[0] >> 4;
- dst_ptr[0] = gradient[fg_lum];
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-static void dma2d_rgb565_copy_mono4_last_col(gfx_bitblt_t* bb,
- const gfx_color16_t* gradient) {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
- uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_lum = src_ptr[0] & 0x0F;
- dst_ptr[0] = gradient[fg_lum];
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-bool dma2d_rgb565_copy_mono4(const gfx_bitblt_t* params) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(params, 16) ||
- !gfx_bitblt_check_src_x(params, 4)) {
- return false;
- }
-
- const gfx_color16_t* src_gradient = NULL;
-
- gfx_bitblt_t bb_copy = *params;
- gfx_bitblt_t* bb = &bb_copy;
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- if (bb->src_x & 1) {
- // First column of mono4 bitmap is odd
- // Use the CPU to draw the first column
- src_gradient = gfx_color16_gradient_a4(bb->src_fg, bb->src_bg);
- dma2d_rgb565_copy_mono4_first_col(bb, src_gradient);
- bb->dst_x += 1;
- bb->src_x += 1;
- bb->width -= 1;
- }
-
- if (bb->width > 0 && bb->width & 1) {
- // The width is odd
- // Use the CPU to draw the last column
- if (src_gradient == NULL) {
- src_gradient = gfx_color16_gradient_a4(bb->src_fg, bb->src_bg);
- }
- dma2d_rgb565_copy_mono4_last_col(bb, src_gradient);
- bb->width -= 1;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
- drv->handle.Init.Mode = DMA2D_M2M_PFC;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint16_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
- drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- dma2d_config_clut(1, gfx_color_to_color32(bb->src_fg),
- gfx_color_to_color32(bb->src_bg));
-
- HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
- bb->width, bb->height);
- return true;
-}
-
-bool dma2d_rgb565_copy_rgb565(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 16) || !gfx_bitblt_check_src_x(bb, 16)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
- drv->handle.Init.Mode = DMA2D_M2M_PFC;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint16_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
- drv->handle.LayerCfg[1].InputOffset =
- bb->src_stride / sizeof(uint16_t) - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- HAL_DMA2D_Start(&drv->handle,
- (uint32_t)bb->src_row + bb->src_x * sizeof(uint16_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
- bb->width, bb->height);
- return true;
-}
-
-static void dma2d_rgb565_blend_mono4_first_col(const gfx_bitblt_t* bb) {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_alpha = src_ptr[0] >> 4;
- fg_alpha = (fg_alpha * bb->src_alpha) / 15;
- dst_ptr[0] = gfx_color16_blend_a8(
- bb->src_fg, gfx_color16_to_color(dst_ptr[0]), fg_alpha);
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-static void dma2d_rgb565_blend_mono4_last_col(const gfx_bitblt_t* bb) {
- uint16_t* dst_ptr = (uint16_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
- uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_alpha = src_ptr[0] & 0x0F;
- fg_alpha = (fg_alpha * bb->src_alpha) / 15;
- dst_ptr[0] = gfx_color16_blend_a8(
- bb->src_fg, gfx_color16_to_color(dst_ptr[0]), fg_alpha);
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-bool dma2d_rgb565_blend_mono4(const gfx_bitblt_t* params) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(params, 16) ||
- !gfx_bitblt_check_src_x(params, 4)) {
- return false;
- }
-
- dma2d_wait();
-
- gfx_bitblt_t bb_copy = *params;
- gfx_bitblt_t* bb = &bb_copy;
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- if (bb->src_x & 1) {
- // First column of mono4 bitmap is odd
- // Use the CPU to draw the first column
- dma2d_rgb565_blend_mono4_first_col(bb);
- bb->dst_x += 1;
- bb->src_x += 1;
- bb->width -= 1;
- }
-
- if (bb->width > 0 && bb->width & 1) {
- // The width is odd
- // Use the CPU to draw the last column
- dma2d_rgb565_blend_mono4_last_col(bb);
- bb->width -= 1;
- }
-
- if (bb->width > 0) {
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
- drv->handle.Init.Mode = DMA2D_M2M_BLEND;
- drv->handle.Init.OutputOffset =
- bb->dst_stride / sizeof(uint16_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
- drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA;
- drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- dma2d_config_clut(
- 1, gfx_color_to_color32(bb->src_fg),
- gfx_color32_set_alpha(gfx_color_to_color32(bb->src_fg), 0));
-
- drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
- drv->handle.LayerCfg[0].InputOffset =
- bb->dst_stride / sizeof(uint16_t) - bb->width;
- drv->handle.LayerCfg[0].AlphaMode = 0;
- drv->handle.LayerCfg[0].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 0);
-
- HAL_DMA2D_BlendingStart(
- &drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), bb->width,
- bb->height);
- }
-
- return true;
-}
-
-bool dma2d_rgb565_blend_mono8(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 16) || !gfx_bitblt_check_src_x(bb, 8)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
- drv->handle.Init.Mode = DMA2D_M2M_BLEND;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint16_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;
- drv->handle.LayerCfg[1].InputOffset = bb->src_stride - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = gfx_color_to_color32(bb->src_fg);
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
- drv->handle.LayerCfg[0].InputOffset =
- bb->dst_stride / sizeof(uint16_t) - bb->width;
- drv->handle.LayerCfg[0].AlphaMode = 0;
- drv->handle.LayerCfg[0].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 0);
-
- HAL_DMA2D_BlendingStart(&drv->handle, (uint32_t)bb->src_row + bb->src_x,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
- bb->width, bb->height);
-
- return true;
-}
-
-bool dma2d_rgba8888_fill(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 32)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row)) {
- return false;
- }
-
- if (bb->src_alpha == 255) {
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_R2M;
- drv->handle.Init.OutputOffset =
- bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- HAL_DMA2D_Start(&drv->handle, gfx_color_to_color32(bb->src_fg),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- bb->width, bb->height);
- } else {
-#ifdef STM32U5
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_BLEND_FG;
- drv->handle.Init.OutputOffset =
- bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;
- drv->handle.LayerCfg[1].InputOffset = 0;
- drv->handle.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
- drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
- drv->handle.LayerCfg[0].InputOffset =
- bb->dst_stride / sizeof(uint32_t) - bb->width;
- drv->handle.LayerCfg[0].AlphaMode = 0;
- drv->handle.LayerCfg[0].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 0);
-
- HAL_DMA2D_BlendingStart(
- &drv->handle, gfx_color_to_color32(bb->src_fg),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), bb->width,
- bb->height);
-#else
- // STM32F4 can not accelerate blending with the fixed color
- return false;
-#endif
- }
- return true;
-}
-
-static void dma2d_rgba8888_copy_mono4_first_col(gfx_bitblt_t* bb,
- const gfx_color32_t* gradient) {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_lum = src_ptr[0] >> 4;
- dst_ptr[0] = gradient[fg_lum];
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-static void dma2d_rgba8888_copy_mono4_last_col(gfx_bitblt_t* bb,
- const gfx_color32_t* gradient) {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
- uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_lum = src_ptr[0] & 0x0F;
- dst_ptr[0] = gradient[fg_lum];
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-bool dma2d_rgba8888_copy_mono4(const gfx_bitblt_t* params) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(params, 32) ||
- !gfx_bitblt_check_src_x(params, 4)) {
- return false;
- }
-
- dma2d_wait();
-
- const gfx_color32_t* src_gradient = NULL;
- gfx_bitblt_t bb_copy = *params;
- gfx_bitblt_t* bb = &bb_copy;
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- if (bb->src_x & 1) {
- // First column of mono4 bitmap is odd
- // Use the CPU to draw the first column
- src_gradient = gfx_color32_gradient_a4(bb->src_fg, bb->src_bg);
- dma2d_rgba8888_copy_mono4_first_col(bb, src_gradient);
- bb->dst_x += 1;
- bb->src_x += 1;
- bb->width -= 1;
- }
-
- if (bb->width > 0 && bb->width & 1) {
- // The width is odd
- // Use the CPU to draw the last column
- if (src_gradient == NULL) {
- src_gradient = gfx_color32_gradient_a4(bb->src_fg, bb->src_bg);
- }
- dma2d_rgba8888_copy_mono4_last_col(bb, src_gradient);
- bb->width -= 1;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_PFC;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
- drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- dma2d_config_clut(1, gfx_color_to_color32(bb->src_fg),
- gfx_color_to_color32(bb->src_bg));
-
- HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- bb->width, bb->height);
- return true;
-}
-
-bool dma2d_rgba8888_copy_rgb565(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 16)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_PFC;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
- drv->handle.LayerCfg[1].InputOffset =
- bb->src_stride / sizeof(uint16_t) - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- HAL_DMA2D_Start(&drv->handle,
- (uint32_t)bb->src_row + bb->src_x * sizeof(uint16_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- bb->width, bb->height);
- return true;
-}
-
-static void dma2d_rgba8888_blend_mono4_first_col(const gfx_bitblt_t* bb) {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
- uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_alpha = src_ptr[0] >> 4;
- fg_alpha = (fg_alpha * bb->src_alpha) / 15;
- dst_ptr[0] = gfx_color32_blend_a8(
- bb->src_fg, gfx_color32_to_color(dst_ptr[0]), fg_alpha);
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-static void dma2d_rgba8888_blend_mono4_last_col(const gfx_bitblt_t* bb) {
- uint32_t* dst_ptr = (uint32_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
- uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
-
- int height = bb->height;
-
- while (height-- > 0) {
- uint8_t fg_alpha = src_ptr[0] & 0x0F;
- fg_alpha = (fg_alpha * bb->src_alpha) / 15;
- dst_ptr[0] = gfx_color32_blend_a8(
- bb->src_fg, gfx_color32_to_color(dst_ptr[0]), fg_alpha);
- dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
- src_ptr += bb->src_stride / sizeof(*src_ptr);
- }
-}
-
-bool dma2d_rgba8888_blend_mono4(const gfx_bitblt_t* params) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(params, 32) ||
- !gfx_bitblt_check_src_x(params, 4)) {
- return false;
- }
-
- dma2d_wait();
-
- gfx_bitblt_t bb_copy = *params;
- gfx_bitblt_t* bb = &bb_copy;
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- if (bb->src_x & 1) {
- // First column of mono4 bitmap is odd
- // Use the CPU to draw the first column
- dma2d_rgba8888_blend_mono4_first_col(bb);
- bb->dst_x += 1;
- bb->src_x += 1;
- bb->width -= 1;
- }
-
- if (bb->width > 0 && bb->width & 1) {
- // The width is odd
- // Use the CPU to draw the last column
- dma2d_rgba8888_blend_mono4_last_col(bb);
- bb->width -= 1;
- }
-
- if (bb->width > 0) {
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_BLEND;
- drv->handle.Init.OutputOffset =
- bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
- drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA;
- drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- dma2d_config_clut(
- 1, gfx_color_to_color32(bb->src_fg),
- gfx_color32_set_alpha(gfx_color_to_color32(bb->src_fg), 0));
-
- drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
- drv->handle.LayerCfg[0].InputOffset =
- bb->dst_stride / sizeof(uint32_t) - bb->width;
- drv->handle.LayerCfg[0].AlphaMode = 0;
- drv->handle.LayerCfg[0].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 0);
-
- HAL_DMA2D_BlendingStart(
- &drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), bb->width,
- bb->height);
- }
-
- return true;
-}
-
-bool dma2d_rgba8888_blend_mono8(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 8)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- uint32_t src_fg =
- gfx_color32_replace_a(gfx_color_to_color32(bb->src_fg), bb->src_alpha);
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_BLEND;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;
- drv->handle.LayerCfg[1].InputOffset = bb->src_stride - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA;
- drv->handle.LayerCfg[1].InputAlpha = src_fg;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
- drv->handle.LayerCfg[0].InputOffset =
- bb->dst_stride / sizeof(uint32_t) - bb->width;
- drv->handle.LayerCfg[0].AlphaMode = 0;
- drv->handle.LayerCfg[0].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 0);
-
- HAL_DMA2D_BlendingStart(&drv->handle, (uint32_t)bb->src_row + bb->src_x,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- bb->width, bb->height);
-
- return true;
-}
-
-bool dma2d_rgba8888_copy_mono8(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 8)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_PFC;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;
- drv->handle.LayerCfg[1].InputOffset = bb->src_stride - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = gfx_color_to_color32(bb->src_fg);
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row + bb->src_x,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- bb->width, bb->height);
-
- return true;
-}
-
-bool dma2d_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 32)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- if (bb->src_downscale > 0) {
- return false;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_PFC;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;
- drv->handle.LayerCfg[1].InputOffset =
- bb->src_stride / sizeof(uint32_t) - bb->width;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- HAL_DMA2D_Start(&drv->handle,
- (uint32_t)bb->src_row + bb->src_x * sizeof(uint32_t),
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- bb->width, bb->height);
- return true;
-}
-
-#ifdef USE_HW_JPEG_DECODER
-static bool dma2d_rgba8888_copy_ycbcr(const gfx_bitblt_t* bb, uint32_t css) {
- dma2d_driver_t* drv = &g_dma2d_driver;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!gfx_bitblt_check_dst_x(bb, 32)) {
- return false;
- }
-
- dma2d_wait();
-
- if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
- return false;
- }
-
- drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- drv->handle.Init.Mode = DMA2D_M2M_PFC;
- drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
- HAL_DMA2D_Init(&drv->handle);
-
- drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_YCBCR;
- drv->handle.LayerCfg[1].InputOffset = 0;
- drv->handle.LayerCfg[1].ChromaSubSampling = css;
- drv->handle.LayerCfg[1].AlphaMode = 0;
- drv->handle.LayerCfg[1].InputAlpha = 0;
- HAL_DMA2D_ConfigLayer(&drv->handle, 1);
-
- HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row,
- (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
- bb->width, bb->height);
-
- // DMA2D overwrites CLUT during YCbCr conversion
- // (seems to be a bug or an undocumented feature)
- drv->clut_valid = false;
-
- return true;
-}
-
-bool dma2d_rgba8888_copy_ycbcr420(const gfx_bitblt_t* bb) {
- return dma2d_rgba8888_copy_ycbcr(bb, DMA2D_CSS_420);
-}
-
-bool dma2d_rgba8888_copy_ycbcr422(const gfx_bitblt_t* bb) {
- return dma2d_rgba8888_copy_ycbcr(bb, DMA2D_CSS_422);
-}
-
-bool dma2d_rgba8888_copy_ycbcr444(const gfx_bitblt_t* bb) {
- return dma2d_rgba8888_copy_ycbcr(bb, DMA2D_NO_CSS);
-}
-
-bool dma2d_rgba8888_copy_y(const gfx_bitblt_t* bb) {
- gfx_bitblt_t bb_copy = *bb;
-
- if (bb->height % 8 != 0 || bb->width % 8 != 0) {
- return false;
- }
-
- // src cotains only Y channel organized in 8x8 blocks
-
- bb_copy.height = 8;
- bb_copy.width = 8;
- bb_copy.src_stride = 8;
- bb_copy.src_fg = gfx_color_rgb(255, 255, 255);
-
- for (uint16_t y = 0; y < bb->height; y += 8) {
- bb_copy.dst_x = 0;
- for (uint16_t x = 0; x < bb->width; x += 8) {
- if (!dma2d_rgba8888_copy_mono8(&bb_copy)) {
- return false;
- }
- bb_copy.dst_x += 8;
- bb_copy.src_row = (uint8_t*)bb_copy.src_row + 64;
- }
- bb_copy.dst_y += 8;
- }
- return true;
-}
-
-#endif // USE_HW_JPEG_DECODER
-
-#endif // KERNEL_MODE
diff --git a/core/embed/gfx/fonts/font_bitmap.c b/core/embed/gfx/fonts/font_bitmap.c
deleted file mode 100644
index ab017f66..00000000
--- a/core/embed/gfx/fonts/font_bitmap.c
+++ /dev/null
@@ -1,101 +0,0 @@
-#include "font_bitmap.h"
-
-// clang-format off
-
-const uint8_t * const Font_Bitmap = (const uint8_t * const)
- "\x00\x00\x00\x00\x00"
- "\x00\x00\x5f\x00\x00"
- "\x00\x07\x00\x07\x00"
- "\x14\x7f\x14\x7f\x14"
- "\x24\x2a\x7f\x2a\x12"
- "\x23\x13\x08\x64\x62"
- "\x36\x49\x56\x20\x50"
- "\x00\x08\x07\x03\x00"
- "\x00\x1c\x22\x41\x00"
- "\x00\x41\x22\x1c\x00"
- "\x2a\x1c\x7f\x1c\x2a"
- "\x08\x08\x3e\x08\x08"
- "\x00\x80\x70\x30\x00"
- "\x08\x08\x08\x08\x08"
- "\x00\x00\x60\x60\x00"
- "\x20\x10\x08\x04\x02"
- "\x3e\x51\x49\x45\x3e"
- "\x00\x42\x7f\x40\x00"
- "\x72\x49\x49\x49\x46"
- "\x21\x41\x49\x4d\x33"
- "\x18\x14\x12\x7f\x10"
- "\x27\x45\x45\x45\x39"
- "\x3c\x4a\x49\x49\x31"
- "\x41\x21\x11\x09\x07"
- "\x36\x49\x49\x49\x36"
- "\x46\x49\x49\x29\x1e"
- "\x00\x00\x14\x00\x00"
- "\x00\x40\x34\x00\x00"
- "\x00\x08\x14\x22\x41"
- "\x14\x14\x14\x14\x14"
- "\x00\x41\x22\x14\x08"
- "\x02\x01\x59\x09\x06"
- "\x3e\x41\x5d\x59\x4e"
- "\x7c\x12\x11\x12\x7c"
- "\x7f\x49\x49\x49\x36"
- "\x3e\x41\x41\x41\x22"
- "\x7f\x41\x41\x41\x3e"
- "\x7f\x49\x49\x49\x41"
- "\x7f\x09\x09\x09\x01"
- "\x3e\x41\x41\x51\x73"
- "\x7f\x08\x08\x08\x7f"
- "\x00\x41\x7f\x41\x00"
- "\x20\x40\x41\x3f\x01"
- "\x7f\x08\x14\x22\x41"
- "\x7f\x40\x40\x40\x40"
- "\x7f\x02\x1c\x02\x7f"
- "\x7f\x04\x08\x10\x7f"
- "\x3e\x41\x41\x41\x3e"
- "\x7f\x09\x09\x09\x06"
- "\x3e\x41\x51\x21\x5e"
- "\x7f\x09\x19\x29\x46"
- "\x26\x49\x49\x49\x32"
- "\x03\x01\x7f\x01\x03"
- "\x3f\x40\x40\x40\x3f"
- "\x1f\x20\x40\x20\x1f"
- "\x3f\x40\x38\x40\x3f"
- "\x63\x14\x08\x14\x63"
- "\x03\x04\x78\x04\x03"
- "\x61\x59\x49\x4d\x43"
- "\x00\x7f\x41\x41\x41"
- "\x02\x04\x08\x10\x20"
- "\x00\x41\x41\x41\x7f"
- "\x04\x02\x01\x02\x04"
- "\x40\x40\x40\x40\x40"
- "\x00\x03\x07\x08\x00"
- "\x20\x54\x54\x78\x40"
- "\x7f\x28\x44\x44\x38"
- "\x38\x44\x44\x44\x28"
- "\x38\x44\x44\x28\x7f"
- "\x38\x54\x54\x54\x18"
- "\x00\x08\x7e\x09\x02"
- "\x18\xa4\xa4\x9c\x78"
- "\x7f\x08\x04\x04\x78"
- "\x00\x44\x7d\x40\x00"
- "\x20\x40\x40\x3d\x00"
- "\x7f\x10\x28\x44\x00"
- "\x00\x41\x7f\x40\x00"
- "\x7c\x04\x78\x04\x78"
- "\x7c\x08\x04\x04\x78"
- "\x38\x44\x44\x44\x38"
- "\xfc\x18\x24\x24\x18"
- "\x18\x24\x24\x18\xfc"
- "\x7c\x08\x04\x04\x08"
- "\x48\x54\x54\x54\x24"
- "\x04\x04\x3f\x44\x24"
- "\x3c\x40\x40\x20\x7c"
- "\x1c\x20\x40\x20\x1c"
- "\x3c\x40\x30\x40\x3c"
- "\x44\x28\x10\x28\x44"
- "\x4c\x90\x90\x90\x7c"
- "\x44\x64\x54\x4c\x44"
- "\x00\x08\x36\x41\x00"
- "\x00\x00\x77\x00\x00"
- "\x00\x41\x36\x08\x00"
- "\x02\x01\x02\x04\x02"
- "\x3c\x26\x23\x26\x3c";
diff --git a/core/embed/gfx/fonts/font_bitmap.h b/core/embed/gfx/fonts/font_bitmap.h
deleted file mode 100644
index f8ab8e38..00000000
--- a/core/embed/gfx/fonts/font_bitmap.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#include <trezor_types.h>
-
-extern const uint8_t* const Font_Bitmap;
diff --git a/core/embed/gfx/gfx_color.c b/core/embed/gfx/gfx_color.c
deleted file mode 100644
index a96f038e..00000000
--- a/core/embed/gfx/gfx_color.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <gfx/gfx_color.h>
-
-const gfx_color16_t* gfx_color16_gradient_a4(gfx_color_t fg_color,
- gfx_color_t bg_color) {
- static gfx_color16_t cache[16] = {0};
-
- if (gfx_color_to_color16(bg_color) != cache[0] ||
- gfx_color_to_color16(fg_color) != cache[15]) {
- for (int alpha = 0; alpha < 16; alpha++) {
- cache[alpha] = gfx_color16_blend_a4(fg_color, bg_color, alpha);
- }
- }
-
- return cache;
-}
-
-const gfx_color32_t* gfx_color32_gradient_a4(gfx_color_t fg_color,
- gfx_color_t bg_color) {
- static gfx_color32_t cache[16] = {0};
-
- if (bg_color != gfx_color32_to_color(cache[0]) ||
- fg_color != gfx_color32_to_color(cache[15])) {
- for (int alpha = 0; alpha < 16; alpha++) {
- cache[alpha] = gfx_color32_blend_a4(fg_color, bg_color, alpha);
- }
- }
-
- return cache;
-}
diff --git a/core/embed/gfx/gfx_draw.c b/core/embed/gfx/gfx_draw.c
deleted file mode 100644
index 7cc5358d..00000000
--- a/core/embed/gfx/gfx_draw.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <trezor_model.h>
-#include <trezor_rtl.h>
-
-#include <gfx/gfx_draw.h>
-#include <io/display.h>
-
-typedef struct {
- int16_t dst_x;
- int16_t dst_y;
- int16_t src_x;
- int16_t src_y;
- int16_t width;
- int16_t height;
-} gfx_clip_t;
-
-static inline gfx_clip_t gfx_clip(gfx_rect_t dst) {
- int16_t dst_x = dst.x0;
- int16_t dst_y = dst.y0;
-
- int16_t src_x = 0;
- int16_t src_y = 0;
-
- // Normalize negative top-left of destination rectangle
- if (dst_x < 0) {
- src_x -= dst_x;
- dst_x = 0;
- }
-
- if (dst_y < 0) {
- src_y -= dst_y;
- dst_y = 0;
- }
-
- // Calculate dimension of effective rectangle
- int16_t width = MIN(DISPLAY_RESX, dst.x1) - dst_x;
- int16_t height = MIN(DISPLAY_RESY, dst.y1) - dst_y;
-
- gfx_clip_t clip = {
- .dst_x = dst_x,
- .dst_y = dst_y,
- .src_x = src_x,
- .src_y = src_y,
- .width = width,
- .height = height,
- };
-
- return clip;
-}
-
-void gfx_clear(void) {
- gfx_bitblt_t bb = {
- // Destination bitmap
- .height = DISPLAY_RESY,
- .width = DISPLAY_RESX,
- .dst_row = NULL,
- .dst_x = 0,
- .dst_y = 0,
- .dst_stride = 0,
-
- // Source bitmap
- .src_fg = 0,
- .src_alpha = 255,
- };
-
- display_fill(&bb);
-}
-
-void gfx_draw_bar(gfx_rect_t rect, gfx_color_t color) {
- gfx_clip_t clip = gfx_clip(rect);
-
- if (clip.width <= 0 || clip.height <= 0) {
- return;
- }
-
- gfx_bitblt_t bb = {
- // Destination bitmap
- .height = clip.height,
- .width = clip.width,
- .dst_row = NULL,
- .dst_x = clip.dst_x,
- .dst_y = clip.dst_y,
- .dst_stride = 0,
-
- // Source bitmap
- .src_fg = color,
- .src_alpha = 255,
- };
-
- display_fill(&bb);
-}
diff --git a/core/embed/gfx/inc/gfx/dma2d_bitblt.h b/core/embed/gfx/inc/gfx/dma2d_bitblt.h
deleted file mode 100644
index bef6e221..00000000
--- a/core/embed/gfx/inc/gfx/dma2d_bitblt.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 <gfx/gfx_bitblt.h>
-
-#ifdef KERNEL_MODE
-
-// Initializes DMA2D peripheral
-void dma2d_init(void);
-
-// Deinitializes DMA2D peripheral
-void dma2d_deinit(void);
-
-#endif
-
-// Waits until any pending DMA2D operation is finished
-void dma2d_wait(void);
-
-// Following functions are hardware (DMA2D) accelerated versions
-// of `gfx_rgb565_xxx()` and `gfx_rgba8888_xxx()` function from `gfx_bitblt.h`
-
-// These functions may return `false`, indicating that the accelerated
-// operation cannot be performed and must be implemented in software
-
-bool dma2d_rgb565_fill(const gfx_bitblt_t* bb);
-bool dma2d_rgb565_copy_mono4(const gfx_bitblt_t* bb);
-bool dma2d_rgb565_copy_rgb565(const gfx_bitblt_t* bb);
-bool dma2d_rgb565_blend_mono4(const gfx_bitblt_t* bb);
-bool dma2d_rgb565_blend_mono8(const gfx_bitblt_t* bb);
-
-bool dma2d_rgba8888_fill(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_copy_mono4(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_copy_mono8(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_copy_rgb565(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_blend_mono4(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_blend_mono8(const gfx_bitblt_t* bb);
-
-#ifdef USE_HW_JPEG_DECODER
-bool dma2d_rgba8888_copy_ycbcr420(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_copy_ycbcr422(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_copy_ycbcr444(const gfx_bitblt_t* bb);
-bool dma2d_rgba8888_copy_y(const gfx_bitblt_t* bb);
-#endif
diff --git a/core/embed/gfx/inc/gfx/gfx_bitblt.h b/core/embed/gfx/inc/gfx/gfx_bitblt.h
deleted file mode 100644
index f1cc17b0..00000000
--- a/core/embed/gfx/inc/gfx/gfx_bitblt.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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>
-
-#include <gfx/gfx_color.h>
-
-// These module provides low-level bit block transfer (bitblt)
-// operations on different bitmap/framebuffer types.
-//
-// `fill` - fills a rectangle with a solid color (with an optional
-// alpha, allowing color blending).
-//
-// `copy` - copies a bitmap or part of it to the destination bitmap.
-//
-// `blend` - blends a bitmap with a 1- or 4-bit alpha channel to the
-// destination using background and foreground colors.
-//
-// These operations might be accelerated using DMA2D (ChromART accelerator)
-// on the STM32 platform.
-
-// Represents a set of parameters for a bit block transfer operation.
-typedef struct {
- // Pointer to the destination bitmap's first row
- void* dst_row;
- // Number of bytes per line in the destination bitmap
- uint16_t dst_stride;
- // X-coordinate of the top-left corner inside the destination
- uint16_t dst_x;
- // Y-coordinate of the top-left corner inside the destination
- uint16_t dst_y;
- // Height of the filled/copied/blended area
- uint16_t height;
- // Width of the filled/copied/blended area
- uint16_t width;
-
- // Pointer to the source bitmap's first row
- // (unused for fill operations)
- void* src_row;
- // Number of bytes per line in the source bitmap
- // (unused for fill operations)
- uint16_t src_stride;
- // X-coordinate of the origin in the source bitmap
- // (unused for fill operations)
- uint16_t src_x;
- // Y-coordinate of the origin in the source bitmap
- // (unused for fill operations)
- uint16_t src_y;
-
- // Foreground color used when copying/blending/filling
- gfx_color_t src_fg;
- // Background color used when copying mono bitmaps
- gfx_color_t src_bg;
- // Alpha value for fill operation (255 => normal fill, 0 => noop)
- uint8_t src_alpha;
- // Downscaling for the source bitmap
- // (0 => no downscaling, 1 => 1/2, 2 => 1/4, 3 => 1/8)
- uint8_t src_downscale;
-
-} gfx_bitblt_t;
-
-#ifdef KERNEL_MODE
-
-// Initializes bitblt operations
-void gfx_bitblt_init(void);
-
-// Deinitializes bitblt operations
-void gfx_bitblt_deinit(void);
-
-#endif // KERNEL_MODE
-
-// Checks if src_x and width are within the bounds of the source bitmap
-static inline bool gfx_bitblt_check_src_x(const gfx_bitblt_t* bb,
- size_t pixel_bits) {
- return (bb->src_x + bb->width >= bb->src_x) && // overflow check
- (((bb->src_x + bb->width) * pixel_bits + 7) / 8 <= bb->src_stride);
-}
-
-// Checks if dst_x and width are within the bounds of the destination bitmap
-static inline bool gfx_bitblt_check_dst_x(const gfx_bitblt_t* bb,
- size_t pixel_bits) {
- return (bb->dst_x + bb->width >= bb->dst_x) && // overflow check
- (((bb->dst_x + bb->width) * pixel_bits + 7) / 8 <= bb->dst_stride);
-}
-
-// Checks if dst_y and height are within the bounds of the destination bitmap
-static inline bool gfx_bitblt_check_dst_y(const gfx_bitblt_t* bb,
- size_t fb_size) {
- return (bb->dst_y + bb->height >= bb->dst_y) && // overflow check
- (bb->dst_y + bb->height) * bb->dst_stride <= fb_size;
-}
-
-// If the bitblt operation is asynchronous, waits until it's finished
-void gfx_bitblt_wait(void);
-
-// Functions for RGB565 bitmap/framebuffer
-
-// Fills a rectangle with a solid color
-void gfx_rgb565_fill(const gfx_bitblt_t* bb);
-// Copies a mono bitmap (with 1-bit alpha channel)
-void gfx_rgb565_copy_mono1p(const gfx_bitblt_t* bb);
-// Copies a mono bitmap (with 4-bit alpha channel)
-void gfx_rgb565_copy_mono4(const gfx_bitblt_t* bb);
-// Copies an RGB565 bitmap
-void gfx_rgb565_copy_rgb565(const gfx_bitblt_t* bb);
-// Blends a mono bitmap (with 4-bit alpha channel)
-// with the destination bitmap
-void gfx_rgb565_blend_mono4(const gfx_bitblt_t* bb);
-// Blends a mono bitmap (with 8-bit alpha channel)
-// with the destination bitmap
-void gfx_rgb565_blend_mono8(const gfx_bitblt_t* bb);
-
-// Functions for RGBA8888 bitmap/framebuffer
-void gfx_rgba8888_fill(const gfx_bitblt_t* bb);
-// Copies a mono bitmap (with 1-bit alpha channel)
-void gfx_rgba8888_copy_mono1p(const gfx_bitblt_t* bb);
-// Copies a mono bitmap (with 4-bit alpha channel)
-void gfx_rgba8888_copy_mono4(const gfx_bitblt_t* bb);
-// Copies an RGB565 bitmap
-void gfx_rgba8888_copy_rgb565(const gfx_bitblt_t* bb);
-// Copies an RGBA8888 bitmap
-void gfx_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb);
-// Blends a mono bitmap (with 4-bit alpha channel)
-// with the destination bitmap
-void gfx_rgba8888_blend_mono4(const gfx_bitblt_t* bb);
-// Blends a mono bitmap (with 8-bit alpha channel)
-// with the destination bitmap
-void gfx_rgba8888_blend_mono8(const gfx_bitblt_t* bb);
-
-// Functions for Mono8 bitmap/framebuffer
-void gfx_mono8_fill(const gfx_bitblt_t* bb);
-// Copies a mono bitmap (with 1-bit alpha channel)
-void gfx_mono8_copy_mono1p(const gfx_bitblt_t* bb);
-// Copies a mono bitmap (with 4-bit alpha channel)
-void gfx_mono8_copy_mono4(const gfx_bitblt_t* bb);
-// Blends a mono bitmap (with 1-bit alpha channel)
-// with the destination bitmap
-void gfx_mono8_blend_mono1p(const gfx_bitblt_t* bb);
-// Blends a mono bitmap (with 4-bit alpha channel)
-// with the destination bitmap
-void gfx_mono8_blend_mono4(const gfx_bitblt_t* bb);
diff --git a/core/embed/gfx/inc/gfx/gfx_color.h b/core/embed/gfx/inc/gfx/gfx_color.h
deleted file mode 100644
index 235eecee..00000000
--- a/core/embed/gfx/inc/gfx/gfx_color.h
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * 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/>.
- */
-
-#ifndef GFX_COLOR_H
-#define GFX_COLOR_H
-
-#include <trezor_types.h>
-
-#ifdef UI_COLOR_32BIT
-#define GFX_COLOR_32BIT
-#else
-#define GFX_COLOR_16BIT
-#endif
-
-// Color in RGB565 format
-//
-// |15 8 | 7 0|
-// |---------------------------------|
-// |r r r r r g g g | g g g b b b b b|
-// |---------------------------------|
-
-typedef uint16_t gfx_color16_t;
-
-// Color in RGBA8888 format
-//
-// |31 24 |23 16 |15 8 | 7 0 |
-// |----------------------------------------------------------------------|
-// |a a a a a a a a | r r r r r r r r | g g g g g g g g | b b b b b b b b |
-// |----------------------------------------------------------------------|
-//
-
-typedef uint32_t gfx_color32_t;
-
-#ifdef GFX_COLOR_16BIT
-#define gfx_color_t gfx_color16_t
-#define gfx_color_to_color16(c) (c)
-#define gfx_color16_to_color(c) (c)
-#define gfx_color_to_color32(c) (gfx_color16_to_color32(c))
-#define gfx_color32_to_color(c) (gfx_color32_to_color16(c))
-#define gfx_color_lum(c) (gfx_color16_lum(c))
-#define gfx_color_rgb(r, g, b) gfx_color16_rgb(r, g, b)
-#define COLOR_WHITE 0xFFFF
-#define COLOR_BLACK 0x0000
-#elif defined GFX_COLOR_32BIT
-#define gfx_color_t gfx_color32_t
-#define gfx_color_to_color16(c) (gfx_color32_to_color16(c))
-#define gfx_color16_to_color(c) (gfx_color16_to_color32(c))
-#define gfx_color_to_color32(c) (c)
-#define gfx_color32_to_color(c) (c)
-#define gfx_color_lum(c) (gfx_color32_lum(c))
-#define gfx_color_rgb(r, g, b) gfx_color32_rgb(r, g, b)
-#define COLOR_WHITE 0xFFFFFFFF
-#define COLOR_BLACK 0xFF000000
-#else
-#error "GFX_COLOR_16BIT/32BIT not specified"
-#endif
-
-// Extracts red component from gfx_color16_t and converts it to 8-bit value
-#define gfx_color16_to_r(c) ((((c) & 0xF800) >> 8) | (((c) & 0xF800) >> 13))
-// Extracts green component from gfx_color16_t and converts it to 8-bit value
-#define gfx_color16_to_g(c) ((((c) & 0x07E0) >> 3) | (((c) & 0x07E0) >> 9))
-// Extracts blue component from gfx_color16_t and converts it to 8-bit value
-#define gfx_color16_to_b(c) ((((c) & 0x001F) << 3) | (((c) & 0x001F) >> 2))
-
-// Extracts red component from gfx_color32_t
-#define gfx_color32_to_r(c) (((c) & 0x00FF0000) >> 16)
-// Extracts green component from gfx_color32_t
-#define gfx_color32_to_g(c) (((c) & 0x0000FF00) >> 8)
-// Extracts blue component from gfx_color32_t
-#define gfx_color32_to_b(c) (((c) & 0x000000FF) >> 0)
-// Extracts alpha component from gfx_color32_t
-#define gfx_color32_to_a(c) (((c) & 0xFF000000) >> 24)
-// Sets alpha component of gfx_color32_t
-#define gfx_color32_replace_a(c, a) (((c) & 0x00FFFFFF) | ((a) << 24))
-
-// 4-bit linear interpolation between `fg` and `bg`
-#define a4_lerp(fg, bg, alpha) (((fg) * (alpha) + ((bg) * (15 - (alpha)))) / 15)
-// 8-bit linear interpolation between `fg` and `bg`
-#define a8_lerp(fg, bg, alpha) \
- (((fg) * (alpha) + ((bg) * (255 - (alpha)))) / 255)
-
-// Constructs a 16-bit color from the given red (r),
-// green (g), and blue (b) values in the range 0..255
-static inline gfx_color16_t gfx_color16_rgb(uint8_t r, uint8_t g, uint8_t b) {
- return ((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | ((b & 0xF8U) >> 3);
-}
-
-// Constructs a 32-bit color from the given red (r),
-// green (g), and blue (b) values in the range 0..255.
-// Alpha is set to 255.
-static inline gfx_color32_t gfx_color32_rgb(uint8_t r, uint8_t g, uint8_t b) {
- return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
-}
-
-// Constructs a 32-bit color from the given red (r),
-// green (g), blue (b) and alhpa (a) values in the range 0..255.
-static inline gfx_color32_t gfx_color32_rgba(uint8_t r, uint8_t g, uint8_t b,
- uint8_t a) {
- return (a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
-}
-
-// Converts a 16-bit color to a 32-bit color; alpha is set to 255
-static inline gfx_color32_t gfx_color16_to_color32(gfx_color16_t color) {
- uint32_t r = gfx_color16_to_r(color);
- uint32_t g = gfx_color16_to_g(color);
- uint32_t b = gfx_color16_to_b(color);
-
- return gfx_color32_rgb(r, g, b);
-}
-
-// Converts 32-bit color to 16-bit color, alpha is ignored
-static inline gfx_color16_t gfx_color32_to_color16(gfx_color32_t color) {
- uint16_t r = (color & 0x00F80000) >> 8;
- uint16_t g = (color & 0x0000FC00) >> 5;
- uint16_t b = (color & 0x000000F8) >> 3;
-
- return r | g | b;
-}
-
-// Converts 16-bit color into luminance (ranging from 0 to 255)
-static inline uint8_t gfx_color16_lum(gfx_color16_t color) {
- uint32_t r = gfx_color16_to_r(color);
- uint32_t g = gfx_color16_to_g(color);
- uint32_t b = gfx_color16_to_b(color);
-
- return (r + g + b) / 3;
-}
-
-// Converts 32-bit color into luminance (ranging from 0 to 255)
-static inline uint8_t gfx_color32_lum(gfx_color32_t color) {
- uint32_t r = gfx_color32_to_r(color);
- uint32_t g = gfx_color32_to_g(color);
- uint32_t b = gfx_color32_to_b(color);
-
- return (r + g + b) / 3;
-}
-
-#ifdef GFX_COLOR_16BIT
-// Blends foreground and background colors with 4-bit alpha
-//
-// Returns a color in 16-bit format
-//
-// If `alpha` is 0, the function returns the background color
-// If `alpha` is 15, the function returns the foreground color
-static inline gfx_color16_t gfx_color16_blend_a4(gfx_color16_t fg,
- gfx_color16_t bg,
- uint8_t alpha) {
- uint16_t fg_r = (fg & 0xF800) >> 11;
- uint16_t bg_r = (bg & 0xF800) >> 11;
- uint16_t r = a4_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = (fg & 0x07E0) >> 5;
- uint16_t bg_g = (bg & 0x07E0) >> 5;
- uint16_t g = a4_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = (fg & 0x001F) >> 0;
- uint16_t bg_b = (bg & 0x001F) >> 0;
- uint16_t b = a4_lerp(fg_b, bg_b, alpha);
-
- return (r << 11) | (g << 5) | b;
-}
-
-// Blends foreground and background colors with 8-bit alpha
-//
-// Returns a color in 16-bit format
-//
-// If `alpha` is 0, the function returns the background color
-// If `alpha` is 15, the function returns the foreground color
-static inline gfx_color16_t gfx_color16_blend_a8(gfx_color16_t fg,
- gfx_color16_t bg,
- uint8_t alpha) {
- uint16_t fg_r = (fg & 0xF800) >> 11;
- uint16_t bg_r = (bg & 0xF800) >> 11;
- uint16_t r = a8_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = (fg & 0x07E0) >> 5;
- uint16_t bg_g = (bg & 0x07E0) >> 5;
- uint16_t g = a8_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = (fg & 0x001F) >> 0;
- uint16_t bg_b = (bg & 0x001F) >> 0;
- uint16_t b = a8_lerp(fg_b, bg_b, alpha);
-
- return (r << 11) | (g << 5) | b;
-}
-
-// Blends foreground and background colors with 4-bit alpha
-//
-// Returns a color in 32-bit format
-//
-// If alpha is 0, the function returns the background color
-// If alpha is 15, the function returns the foreground color
-static inline gfx_color32_t gfx_color32_blend_a4(gfx_color16_t fg,
- gfx_color16_t bg,
- uint8_t alpha) {
- uint16_t fg_r = gfx_color16_to_r(fg);
- uint16_t bg_r = gfx_color16_to_r(bg);
- uint16_t r = a4_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = gfx_color16_to_g(fg);
- uint16_t bg_g = gfx_color16_to_g(bg);
- uint16_t g = a4_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = gfx_color16_to_b(fg);
- uint16_t bg_b = gfx_color16_to_b(bg);
- uint16_t b = a4_lerp(fg_b, bg_b, alpha);
-
- return gfx_color32_rgb(r, g, b);
-}
-
-// Blends foreground and background colors with 8-bit alpha
-//
-// Returns a color in 32-bit format
-//
-// If `alpha` is 0, the function returns the background color
-// If `alpha` is 255, the function returns the foreground color
-static inline gfx_color32_t gfx_color32_blend_a8(gfx_color16_t fg,
- gfx_color16_t bg,
- uint8_t alpha) {
- uint16_t fg_r = gfx_color16_to_r(fg);
- uint16_t bg_r = gfx_color16_to_r(bg);
- uint16_t r = a8_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = gfx_color16_to_g(fg);
- uint16_t bg_g = gfx_color16_to_g(bg);
- uint16_t g = a8_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = gfx_color16_to_b(fg);
- uint16_t bg_b = gfx_color16_to_b(bg);
- uint16_t b = a8_lerp(fg_b, bg_b, alpha);
-
- return gfx_color32_rgb(r, g, b);
-}
-
-#elif defined GFX_COLOR_32BIT
-
-// Blends foreground and background colors with 4-bit alpha
-//
-// Returns a color in 16-bit format
-//
-// If `alpha` is 0, the function returns the background color
-// If `alpha` is 15, the function returns the foreground color
-static inline gfx_color16_t gfx_color16_blend_a4(gfx_color32_t fg,
- gfx_color32_t bg,
- uint8_t alpha) {
- uint16_t fg_r = gfx_color32_to_r(fg);
- uint16_t bg_r = gfx_color32_to_r(bg);
- uint16_t r = a4_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = gfx_color32_to_g(fg);
- uint16_t bg_g = gfx_color32_to_g(bg);
- uint16_t g = a4_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = gfx_color32_to_b(fg);
- uint16_t bg_b = gfx_color32_to_b(bg);
- uint16_t b = a4_lerp(fg_b, bg_b, alpha);
-
- return gfx_color16_rgb(r, g, b);
-}
-
-// Blends foreground and background colors with 8-bit alpha
-//
-// Returns a color in 16-bit format
-//
-// If `alpha` is 0, the function returns the background color
-// If `alpha` is 255, the function returns the foreground color
-static inline gfx_color16_t gfx_color16_blend_a8(gfx_color32_t fg,
- gfx_color32_t bg,
- uint8_t alpha) {
- uint16_t fg_r = gfx_color32_to_r(fg);
- uint16_t bg_r = gfx_color32_to_r(bg);
- uint16_t r = a8_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = gfx_color32_to_g(fg);
- uint16_t bg_g = gfx_color32_to_g(bg);
- uint16_t g = a8_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = gfx_color32_to_b(fg);
- uint16_t bg_b = gfx_color32_to_b(bg);
- uint16_t b = g = a8_lerp(fg_b, bg_b, alpha);
-
- return gfx_color16_rgb(r, g, b);
-}
-
-// Blends foreground and background colors with 4-bit alpha
-//
-// Returns a color in 32-bit format
-//
-// If `alpha` is 0, the function returns the background color
-// If `alpha` is 15, the function returns the foreground color
-static inline gfx_color32_t gfx_color32_blend_a4(gfx_color32_t fg,
- gfx_color32_t bg,
- uint8_t alpha) {
- uint16_t fg_r = gfx_color32_to_r(fg);
- uint16_t bg_r = gfx_color32_to_r(bg);
- uint16_t r = a4_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = gfx_color32_to_g(fg);
- uint16_t bg_g = gfx_color32_to_g(bg);
- uint16_t g = a4_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = gfx_color32_to_b(fg);
- uint16_t bg_b = gfx_color32_to_b(bg);
- uint16_t b = a4_lerp(fg_b, bg_b, alpha);
-
- return gfx_color32_rgb(r, g, b);
-}
-
-// Blends foreground and background colors with 8-bit alpha
-//
-// Returns a color in 32-bit format
-//
-// If `alpha` is 0, the function returns the background color
-// If `alpha` is 15, the function returns the foreground color
-static inline gfx_color32_t gfx_color32_blend_a8(gfx_color32_t fg,
- gfx_color32_t bg,
- uint8_t alpha) {
- uint16_t fg_r = gfx_color32_to_r(fg);
- uint16_t bg_r = gfx_color32_to_r(bg);
- uint16_t r = a8_lerp(fg_r, bg_r, alpha);
-
- uint16_t fg_g = gfx_color32_to_g(fg);
- uint16_t bg_g = gfx_color32_to_g(bg);
- uint16_t g = a8_lerp(fg_g, bg_g, alpha);
-
- uint16_t fg_b = gfx_color32_to_b(fg);
- uint16_t bg_b = gfx_color32_to_b(bg);
- uint16_t b = a8_lerp(fg_b, bg_b, alpha);
-
- return gfx_color32_rgb(r, g, b);
-}
-
-#else
-#error "GFX_COLOR_16BIT/32BIT not specified"
-#endif
-
-// Returns a gradient as an array of 16 consecutive 16-bit colors
-//
-// Each element in the array represents a color, with `retval[0]` being
-// the background (`bg`) color and `retval[15]` the foreground (`fg`) color
-const gfx_color16_t* gfx_color16_gradient_a4(gfx_color_t fg, gfx_color_t bg);
-
-// Returns a gradient as an array of 16 consecutive 32-bit colors
-//
-// Each element in the array represents a color, with `retval[0]` being
-// the background (`bg`) color and `retval[15]` the foreground (`fg`) color
-const gfx_color32_t* gfx_color32_gradient_a4(gfx_color_t fg, gfx_color_t bg);
-
-// Returns a color with alpha channel set
-//
-// The original color is not modified
-static inline gfx_color32_t gfx_color32_set_alpha(gfx_color32_t c,
- uint8_t alpha) {
- return (c & 0xFFFFFF) | (alpha << 24);
-}
-
-#endif // GFX_COLOR_H
diff --git a/core/embed/gfx/inc/gfx/gfx_draw.h b/core/embed/gfx/inc/gfx/gfx_draw.h
deleted file mode 100644
index 68d0cce5..00000000
--- a/core/embed/gfx/inc/gfx/gfx_draw.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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 <gfx/gfx_color.h>
-
-// 2D rectangle coordinates
-//
-// `x0`, `y0` - top-left coordinates
-// `x1`, `y1` - bottom-right coordinates point (not included)
-typedef struct {
- int16_t x0;
- int16_t y0;
- int16_t x1;
- int16_t y1;
-} gfx_rect_t;
-
-// Builds a rectangle (`gfx_rect_t`) from top-left coordinates and dimensions
-static inline gfx_rect_t gfx_rect_wh(int16_t x, int16_t y, int16_t w,
- int16_t h) {
- gfx_rect_t rect = {
- .x0 = x,
- .y0 = y,
- .x1 = x + w,
- .y1 = y + h,
- };
-
- return rect;
-}
-
-// Builds a rectangle (`gfx_rect_t`) from top-left and bottom-right coordinates
-static inline gfx_rect_t gfx_rect(int16_t x0, int16_t y0, int16_t x1,
- int16_t y1) {
- gfx_rect_t rect = {
- .x0 = x0,
- .y0 = y0,
- .x1 = x1,
- .y1 = y1,
- };
-
- return rect;
-}
-
-// 2D offset/ coordinates
-typedef struct {
- int16_t x;
- int16_t y;
-} gfx_offset_t;
-
-// Builds a `gfx_offset_t` structure
-static inline gfx_offset_t gfx_offset(int16_t x, int16_t y) {
- gfx_offset_t offset = {
- .x = x,
- .y = y,
- };
-
- return offset;
-}
-
-// 2D size in pixels
-typedef struct {
- int16_t x;
- int16_t y;
-} gfx_size_t;
-
-// Builds a `gfx_size_t` structure
-static inline gfx_size_t gfx_size(int16_t x, int16_t y) {
- gfx_size_t size = {
- .x = x,
- .y = y,
- };
-
- return size;
-}
-
-// Format of pixels in a bitmap
-typedef enum {
- GFX_FORMAT_UNKNOWN, //
- GFX_FORMAT_MONO1P, // 1-bpp per pixel (packed)
- GFX_FORMAT_MONO4, // 4-bpp per pixel
- GFX_FORMAT_RGB565, // 16-bpp per pixel
- GFX_FORMAT_RGBA8888, // 32-bpp
-} gfx_format_t;
-
-// Clears the display with a black color.
-void gfx_clear(void);
-
-// Fills a rectangle with a specified color.
-void gfx_draw_bar(gfx_rect_t rect, gfx_color_t color);
diff --git a/core/embed/gfx/inc/gfx/jpegdec.h b/core/embed/gfx/inc/gfx/jpegdec.h
deleted file mode 100644
index cf5a12f3..00000000
--- a/core/embed/gfx/inc/gfx/jpegdec.h
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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>
-
-// Maximum number of blocks (8x8) in a slice.
-// The more blocks we use, the decodeer is faster.
-// Minimum value is 4 to support 4:2:0 subsampling (MCU is 16x16).
-#define JPEGDEC_MAX_SLICE_BLOCKS 16
-
-// Size of Y/YCbCr data buffer
-// The worst case is 192 bytes per block (8x8 pixels) for 4:4:4 subsampling
-#define JPEGDEC_YCBCR_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8 * 3)
-
-// Maximum size of the RGBA8888 buffer for a slice.
-#define JPEGDEC_RGBA8888_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8 * 4)
-
-// Maximum size of the MONO8 buffer for a slice
-#define JPEGDEC_MONO8_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8)
-
-typedef struct jpegdec jpegdec_t;
-
-typedef enum {
- // Decoder needs more data
- // (jpegdec_process should be called with more data)
- JPEGDEC_STATE_NEED_DATA,
- // Image info is ready
- // (jpegdec_get_info can be called to get the image info)
- JPEGDEC_STATE_INFO_READY,
- // Decoded slice is ready
- // (jpegdec_get_slice_xxx can be called to get the slice data)
- JPEGDEC_STATE_SLICE_READY,
- // Decoding is finished
- JPEGDEC_STATE_FINISHED,
- // Error occurred, decoding is stopped
- JPEGDEC_STATE_ERROR,
-} jpegdec_state_t;
-
-typedef enum {
- JPEGDEC_IMAGE_GRAYSCALE, // Gray scale image
- JPEGDEC_IMAGE_YCBCR420, // Color image with 4:2:0 subsampling
- JPEGDEC_IMAGE_YCBCR422, // Color image with 4:2:2 subsampling
- JPEGDEC_IMAGE_YCBCR444, // Color image with 4:4:4 subsampling
-} jpegdec_image_format_t;
-
-typedef struct {
- // Pointer to the data
- const uint8_t* data;
- // Size of the data in bytes
- size_t size;
- // Current offset in the data
- size_t offset;
- // Set to true when no more data is available
- bool last_chunk;
-} jpegdec_input_t;
-
-typedef struct {
- // Image format
- jpegdec_image_format_t format;
- // Image width in pixels
- int16_t width;
- // Image height in pixels
- int16_t height;
-} jpegdec_image_t;
-
-typedef struct {
- // Slice x-coordinate
- int16_t x;
- // Slice y-coordinate
- int16_t y;
- // Slice width
- int16_t width;
- // Slice height
- int16_t height;
-} jpegdec_slice_t;
-
-// Initialize and reset the decoder internal state
-bool jpegdec_open(void);
-
-// Release the decoder and free resources
-void jpegdec_close(void);
-
-// Process all or part of the input buffer and advances the `input->offset`
-//
-// `input->offset` must be aligned to 4 bytes.
-// `input->size` must be aligned to 4 bytes except for the last chunk.
-// `input->last_chunk` must be set to true when no more data is available.
-//
-// Returns the current state of the decoder:
-// - `JPEGDEC_STATE_NEED_DATA` - more data is needed
-// - `JPEGDEC_STATE_INFO_READY` - the image info is ready
-// - `JPEGDEC_STATE_SLICE_READY` - a decoded slice is ready
-// - `JPEGDEC_STATE_FINISHED` - the decoding is finished
-// - `JPEGDEC_STATE_ERROR` - an error occurred
-jpegdec_state_t jpegdec_process(jpegdec_input_t* input);
-
-// Get the decoded image info
-//
-// Can be called anytimer if the decoder went through the
-// `JPEGDEC_STATE_INFO_READY` state.
-//
-// Returns true if the info is available
-bool jpegdec_get_info(jpegdec_image_t* info);
-
-// Copy the last decoded slice to the buffer
-//
-// `rgba8888` must be a buffer of at least `JPEGDEC_RGBA8888_BUFFER_SIZE`
-// bytes and must be aligned to 4 bytes.
-//
-// Can be called immediately after `jpegdec_process` returns
-// `JPEGDEC_STATE_SLICE_READY`.
-bool jpegdec_get_slice_rgba8888(uint32_t* rgba8888, jpegdec_slice_t* slice);
-
-// Copy the last decoded slice to the buffer
-//
-// `mono8` must be a buffer of at least `JPEGDEC_MONO8_BUFFER_SIZE`
-// bytes and must be aligned to 4 bytes.
-//
-// Can be called immediately after `jpegdec_process` returns
-// `JPEGDEC_STATE_SLICE_READY`.
-bool jpegdec_get_slice_mono8(uint32_t* mono8, jpegdec_slice_t* slice);
diff --git a/core/embed/gfx/inc/gfx/terminal.h b/core/embed/gfx/inc/gfx/terminal.h
deleted file mode 100644
index e1a6d09b..00000000
--- a/core/embed/gfx/inc/gfx/terminal.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 <gfx/gfx_color.h>
-
-/**
- * Sets foreground and background colors for terminal text.
- *
- * Note: The current implementation does not support per-character colors.
- * It only supports setting global foreground and background colors, which
- * may be used before printing any text to the terminal.
- *
- * @param fgcolor Foreground color.
- * @param bgcolor Background color.
- */
-void term_set_color(gfx_color_t fgcolor, gfx_color_t bgcolor);
-
-/**
- * Prints a text of given length to the terminal.
- *
- * @param text Text to print.
- * @param textlen Number of characters to print from the text.
- */
-void term_nprint(const char *text, int textlen);
-
-/**
- * Prints null-terminated text to the terminal.
- *
- * @param text Text to print.
- */
-void term_print(const char *text);
-
-/**
- * Prints a 32-bit integer in decimal format to the terminal.
- *
- * @param value Integer value to print.
- */
-void term_print_int32(int32_t value);
-
-/**
- * Prints printf-style formatted text to the terminal.
- *
- * The function internally uses `vsnprintf_` to format the text.
- *
- * @param fmt Format string (printf-style).
- * @param ... Additional arguments for formatting.
- */
-void term_printf(const char *fmt, ...)
- __attribute__((__format__(__printf__, 1, 2)));
diff --git a/core/embed/gfx/jpegdec/stm32u5/jpegdec.c b/core/embed/gfx/jpegdec/stm32u5/jpegdec.c
deleted file mode 100644
index 0bc2675c..00000000
--- a/core/embed/gfx/jpegdec/stm32u5/jpegdec.c
+++ /dev/null
@@ -1,580 +0,0 @@
-/*
- * 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/>.
- */
-
-#ifdef KERNEL_MODE
-
-#include <trezor_bsp.h>
-#include <trezor_rtl.h>
-
-#include <gfx/dma2d_bitblt.h>
-#include <gfx/jpegdec.h>
-#include <rtl/sizedefs.h>
-#include <sys/systick.h>
-
-// Fixes of STMicro bugs in cmsis-device-u5
-#undef JPEG_BASE_S
-#define JPEG_BASE_S (AHB1PERIPH_BASE_S + 0x0A000UL)
-#undef JPEG_CFR_CEOCF_Pos
-#define JPEG_CFR_CEOCF_Pos (5U)
-#undef JPEG_CFR_CHPDF_Pos
-#define JPEG_CFR_CHPDF_Pos (6U)
-
-// JPEG decoder processing timeout in microseconds.
-// The timeout must be selected to be long enough to process a single slice.
-// 100us @ 160MHZ CPU clock speed => 8000 CPU cycles
-// JPEG decoder issues 1pixel/1cycles => 125 8x8 blocks
-#define JPEGDEC_PROCESSING_TIMEOUT_US 100
-
-// JPEG decoder state
-struct jpegdec {
- // Set if the decoder is in use
- bool inuse;
-
- // DMA channel for JPEG data output
- DMA_HandleTypeDef hdma;
-
- // Current state of the FSM
- jpegdec_state_t state;
- // Decoded image parameters
- jpegdec_image_t image;
-
- // Decoded image MCU width
- int16_t mcu_width;
- // Decoded image MCU height
- int16_t mcu_height;
- // Decoded image MCU size in bytes
- size_t mcu_size;
-
- // Decoded YCbCr data for the current slice
- uint32_t ycbcr_buffer[JPEGDEC_YCBCR_BUFFER_SIZE / sizeof(uint32_t)];
-
- // Current slice x-coordinate
- int16_t slice_x;
- // Current slice y-coordinate
- int16_t slice_y;
- // Current slice width
- int16_t slice_width;
- // Current slice height
- int16_t slice_height;
-};
-
-// JPEG decoder instance
-jpegdec_t g_jpegdec = {
- .inuse = false,
-};
-
-bool jpegdec_open(void) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (dec->inuse) {
- return false;
- }
-
- memset(dec, 0, sizeof(jpegdec_t));
- dec->inuse = true;
-
- __HAL_RCC_JPEG_FORCE_RESET();
- __HAL_RCC_JPEG_RELEASE_RESET();
- __HAL_RCC_JPEG_CLK_ENABLE();
-
- // Configure JPEG codec for decoding and header parsing
- JPEG->CR |= JPEG_CR_JCEN;
- JPEG->CONFR1 |= JPEG_CONFR1_DE;
- JPEG->CONFR1 |= JPEG_CONFR1_HDR;
- JPEG->CONFR0 |= JPEG_CONFR0_START;
- JPEG->CR |= JPEG_CR_OFF | JPEG_CR_IFF;
-
- // Configure DMA channel for JPEG data output
- __HAL_RCC_GPDMA1_CLK_ENABLE();
- dec->hdma.Instance = GPDMA1_Channel4;
- dec->hdma.Init.Request = GPDMA1_REQUEST_JPEG_TX;
- dec->hdma.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
- dec->hdma.Init.Direction = DMA_PERIPH_TO_MEMORY;
- dec->hdma.Init.SrcInc = DMA_SINC_FIXED;
- dec->hdma.Init.DestInc = DMA_DINC_INCREMENTED;
- dec->hdma.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
- dec->hdma.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
- dec->hdma.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
- dec->hdma.Init.SrcBurstLength = 8;
- dec->hdma.Init.DestBurstLength = 8;
- dec->hdma.Init.TransferAllocatedPort =
- DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0;
- dec->hdma.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
- dec->hdma.Init.Mode = DMA_NORMAL;
-
- if (HAL_DMA_Init(&dec->hdma) != HAL_OK) {
- dec->hdma.Instance = NULL;
- goto cleanup;
- }
-
- if (HAL_DMA_ConfigChannelAttributes(
- &dec->hdma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | DMA_CHANNEL_SRC_SEC |
- DMA_CHANNEL_DEST_SEC) != HAL_OK) {
- goto cleanup;
- }
-
- return true;
-
-cleanup:
- jpegdec_close();
- return false;
-}
-
-void jpegdec_close(void) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (dec->hdma.Instance != NULL) {
- HAL_DMA_Abort(&dec->hdma);
- HAL_DMA_DeInit(&dec->hdma);
- }
-
- __HAL_RCC_JPEG_CLK_DISABLE();
- __HAL_RCC_JPEG_FORCE_RESET();
- __HAL_RCC_JPEG_RELEASE_RESET();
-
- memset(dec, 0, sizeof(jpegdec_t));
-}
-
-#define READ_REG_FIELD(reg, field) (((reg) & field##_Msk) >> field##_Pos)
-
-// Extracts image parameters from the JPEG codec registers
-// and set `dec->image` and `dec->mcu_xxx` fields
-static bool jpegdec_extract_header_info(jpegdec_t *dec) {
- jpegdec_image_t image = {0};
- size_t mcu_size = 64; // Grayscale, 8x8 blocks
- int16_t mcu_width = 8;
- int16_t mcu_height = 8;
-
- image.height = READ_REG_FIELD(JPEG->CONFR1, JPEG_CONFR1_YSIZE);
- image.width = READ_REG_FIELD(JPEG->CONFR3, JPEG_CONFR3_XSIZE);
-
- if (image.height == 0 || image.width == 0) {
- // Image size is zero, invalid header
- return false;
- }
-
- if (image.height > 32767 || image.width > 32767) {
- // Image is too large
- return false;
- }
-
- // Number of quantization tables
- int n_qt = 1 + READ_REG_FIELD(JPEG->CONFR1, JPEG_CONFR1_NF);
-
- if (n_qt == 1) {
- // 1 quantization table => Grayscale
- image.format = JPEGDEC_IMAGE_GRAYSCALE;
- } else if (n_qt == 3) {
- // 3 quantization table => YCbCr
- int y_blocks = 1 + READ_REG_FIELD(JPEG->CONFR4, JPEG_CONFR4_NB);
- int cb_blocks = 1 + READ_REG_FIELD(JPEG->CONFR5, JPEG_CONFR5_NB);
- int cr_blocks = 1 + READ_REG_FIELD(JPEG->CONFR6, JPEG_CONFR6_NB);
-
- mcu_size = (y_blocks + cb_blocks + cr_blocks) * 64;
- mcu_width = (y_blocks == 1) ? 8 : 16;
- mcu_height = (y_blocks == 4) ? 16 : 8;
-
- if (y_blocks == 2 && cb_blocks == 1 && cr_blocks == 1) {
- // 4:2:2 subsampling
- image.format = JPEGDEC_IMAGE_YCBCR422;
- } else if (y_blocks == 4 && cb_blocks == 1 && cr_blocks == 1) {
- // 4:2:0 subsampling
- image.format = JPEGDEC_IMAGE_YCBCR420;
- } else if (y_blocks == 1 && cb_blocks == 1 && cr_blocks == 1) {
- // 4:4:4 subsampling
- image.format = JPEGDEC_IMAGE_YCBCR444;
- } else {
- // Unsupported subsampling
- return false;
- }
- } else {
- // 2 or 4 quantization tables are not supported
- return false;
- }
-
- dec->image = image;
- dec->mcu_size = mcu_size;
- dec->mcu_width = mcu_width;
- dec->mcu_height = mcu_height;
- return true;
-}
-
-// Starts DMA transfer of the decoded YCbCr data for the current slice
-static bool jpegdec_start_dma_transfer(jpegdec_t *dec) {
- // Number ofs MCU that fit into the YCbCr buffer
- int n_ycbcr = sizeof(dec->ycbcr_buffer) / dec->mcu_size;
- // Number ofs MCUs that fit into the RGB buffer
- int n_rgb =
- JPEGDEC_MAX_SLICE_BLOCKS / ((dec->mcu_width * dec->mcu_height) / 64);
- // Number of remaining MCUs in the current row
- int n_row =
- (dec->image.width - dec->slice_x + dec->mcu_width - 1) / dec->mcu_width;
- // Number of MCUs to decode in the current slice
- int mcu_count = MIN(MIN(n_ycbcr, n_rgb), n_row);
-
- dec->slice_width = dec->mcu_width * mcu_count;
- dec->slice_height = dec->mcu_height;
-
- if (HAL_DMA_Start(&dec->hdma, (uint32_t)&JPEG->DOR,
- (uint32_t)dec->ycbcr_buffer,
- dec->mcu_size * mcu_count) != HAL_OK) {
- return false;
- }
-
- JPEG->CR |= JPEG_CR_ODMAEN;
- return true;
-}
-
-// Feeds the input FIFO with the data from the input buffer.
-// Returns `true` if at least one word was written to the FIFO.
-static inline bool jpegdec_feed_fifo(jpegdec_t *dec, jpegdec_input_t *inp) {
- // Input FIFO needs data
- uint32_t *ptr = (uint32_t *)&inp->data[inp->offset];
- if (inp->offset + 16 <= inp->size) {
- // Feed the FIFO with 16 bytes
- JPEG->DIR = ptr[0];
- JPEG->DIR = ptr[1];
- JPEG->DIR = ptr[2];
- JPEG->DIR = ptr[3];
- inp->offset += 16;
- return true;
- } else if (inp->offset < inp->size) {
- // Feed the FIFO with the remaining data
- while (inp->offset + 4 < inp->size) {
- JPEG->DIR = *ptr++;
- inp->offset += 4;
- }
- if (inp->offset < inp->size) {
- size_t bits = (inp->size - inp->offset) * 8;
- JPEG->DIR = *ptr & (0xFFFFFFFF >> (32 - bits));
- inp->offset = inp->size;
- }
- return true;
- }
-
- return false;
-}
-
-// Advances the slice coordinates to the next slice.
-// Returns `true` if the decoding is complete.
-static inline bool jpegdec_advance_slice_coordinates(jpegdec_t *dec) {
- dec->slice_x += dec->slice_width;
- if (dec->slice_x >= dec->image.width) {
- dec->slice_x = 0;
- dec->slice_y += dec->slice_height;
- }
- return dec->slice_y >= dec->image.height;
-}
-
-jpegdec_state_t jpegdec_process(jpegdec_input_t *inp) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return JPEGDEC_STATE_ERROR;
- }
-
- // Check input buffer alignment
- if (inp->offset < inp->size) {
- if (!IS_ALIGNED(inp->offset, 4) ||
- (!IS_ALIGNED(inp->size, 4) && !inp->last_chunk)) {
- return JPEGDEC_STATE_ERROR;
- }
- }
-
- switch (dec->state) {
- case JPEGDEC_STATE_ERROR:
- case JPEGDEC_STATE_FINISHED:
- return dec->state;
-
- case JPEGDEC_STATE_SLICE_READY:
- if (jpegdec_advance_slice_coordinates(dec)) {
- dec->state = JPEGDEC_STATE_FINISHED;
- return dec->state;
- }
- // pass through
- case JPEGDEC_STATE_INFO_READY:
- if (!jpegdec_start_dma_transfer(dec)) {
- dec->state = JPEGDEC_STATE_ERROR;
- return dec->state;
- }
- break;
-
- default:
- break;
- }
-
- uint64_t expire_time = 0; // = 0 => not active
- bool timed_out = false;
- uint8_t poll_counter = 0;
-
- for (;;) {
- uint32_t sr = JPEG->SR;
-
- if ((sr & JPEG_SR_IFTF) != 0) {
- if (jpegdec_feed_fifo(dec, inp)) {
- expire_time = 0;
- continue; // Feed the FIFO as fast as possible
- } else if (!inp->last_chunk) {
- dec->state = JPEGDEC_STATE_NEED_DATA;
- break;
- }
- }
-
- if (__HAL_DMA_GET_FLAG(&dec->hdma, DMA_FLAG_TC)) {
- // Clear status flags and prepare for the next transfer
- HAL_DMA_PollForTransfer(&dec->hdma, HAL_DMA_FULL_TRANSFER, 0);
- dec->state = JPEGDEC_STATE_SLICE_READY;
- break;
- }
-
- if ((sr & JPEG_SR_HPDF) != 0) {
- // Header parsing is complete
- // Clear the HPDF flag
- JPEG->CFR |= JPEG_CFR_CHPDF;
- bool unexpected_header = dec->image.width > 0;
- if (unexpected_header || !jpegdec_extract_header_info(dec)) {
- dec->state = JPEGDEC_STATE_ERROR;
- } else {
- dec->state = JPEGDEC_STATE_INFO_READY;
- }
- break;
- }
-
- // Timeout processing (especially `systick_us()`) is quite expensive
- // and therefore it is done only every 16 passes.
- if (poll_counter-- == 0) {
- poll_counter = 16;
- if (expire_time == 0) {
- // The timeout handles two situations:
- // 1) Invalid input data that causes the JPEG codec not produce
- // any output and the processing is stuck.
- // 2) Unexpected JPEG codec stuck in the processing state.
- expire_time = systick_us() + JPEGDEC_PROCESSING_TIMEOUT_US;
- } else if (timed_out) {
- dec->state = JPEGDEC_STATE_ERROR;
- break;
- } else {
- // `timed_out` flag is checked in the next pass
- timed_out = systick_us() > expire_time;
- }
- }
- }
-
- if (dec->state == JPEGDEC_STATE_ERROR ||
- dec->state == JPEGDEC_STATE_FINISHED) {
- JPEG->CR &= ~JPEG_CR_JCEN;
- HAL_DMA_Abort(&dec->hdma);
- }
-
- return dec->state;
-}
-
-bool jpegdec_get_info(jpegdec_image_t *image) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return false;
- }
-
- if (dec->image.width == 0 || dec->image.height == 0) {
- return false;
- }
-
- *image = dec->image;
- return true;
-}
-
-bool jpegdec_get_slice_rgba8888(uint32_t *rgba8888, jpegdec_slice_t *slice) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return false;
- }
-
- if (dec->state != JPEGDEC_STATE_SLICE_READY) {
- return false;
- }
-
- if (!IS_ALIGNED((uint32_t)rgba8888, 4)) {
- return false;
- }
-
- slice->width = dec->slice_width;
- slice->height = dec->slice_height;
- slice->x = dec->slice_x;
- slice->y = dec->slice_y;
-
- gfx_bitblt_t bb = {
- .height = dec->slice_height,
- .width = dec->slice_width,
- .dst_row = rgba8888,
- .dst_stride = dec->slice_width * 4,
- .dst_x = 0,
- .dst_y = 0,
- .src_row = dec->ycbcr_buffer,
- .src_stride = 0,
- .src_x = 0,
- .src_y = 0,
- .src_fg = 0,
- .src_bg = 0,
- .src_alpha = 255,
- };
-
- bool result = false;
-
- switch (dec->image.format) {
- case JPEGDEC_IMAGE_YCBCR420:
- result = dma2d_rgba8888_copy_ycbcr420(&bb);
- break;
- case JPEGDEC_IMAGE_YCBCR422:
- result = dma2d_rgba8888_copy_ycbcr422(&bb);
- break;
- case JPEGDEC_IMAGE_YCBCR444:
- result = dma2d_rgba8888_copy_ycbcr444(&bb);
- break;
- case JPEGDEC_IMAGE_GRAYSCALE:
- result = dma2d_rgba8888_copy_y(&bb);
- break;
- default:
- result = false;
- break;
- }
-
- // Wait until the DMA transfer is complete so that the caller can use
- // data in the `rgba8888` buffer immediately.
- dma2d_wait();
-
- return result;
-}
-
-// Initialize DMA base copy for fast copying of 8x8 blocks
-//
-// 'dst_stride' is the number of bytes between the start of two consecutive
-// rows in the destination buffer.
-static void fast_copy_init(DMA_HandleTypeDef *hdma, size_t dst_stride) {
- hdma->Instance = GPDMA1_Channel13;
- hdma->Init.Request = GPDMA1_REQUEST_HASH_IN;
- hdma->Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
- hdma->Init.Direction = DMA_MEMORY_TO_MEMORY;
- hdma->Init.SrcInc = DMA_SINC_INCREMENTED;
- hdma->Init.DestInc = DMA_DINC_INCREMENTED;
- hdma->Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
- hdma->Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
- hdma->Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
- hdma->Init.SrcBurstLength = 2;
- hdma->Init.DestBurstLength = 2;
- hdma->Init.TransferAllocatedPort =
- DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0;
- hdma->Init.TransferEventMode = DMA_TCEM_REPEATED_BLOCK_TRANSFER;
- hdma->Init.Mode = DMA_NORMAL;
- HAL_DMA_Init(hdma);
- HAL_DMA_ConfigChannelAttributes(hdma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC |
- DMA_CHANNEL_SRC_SEC |
- DMA_CHANNEL_DEST_SEC);
-
- DMA_RepeatBlockConfTypeDef rep = {0};
-
- rep.DestAddrOffset = dst_stride - 8;
- rep.RepeatCount = 1;
- HAL_DMAEx_ConfigRepeatBlock(hdma, &rep);
-}
-
-// Initiate a fast copy of an 8x8 block from 'src' to 'dst'
-//
-// `src` is expected to be a pointer to the start of an 8x8 block.
-// `dst` is expected to be a pointer to destination bitmap buffer
-static inline void fast_copy_block(DMA_HandleTypeDef *hdma, uint8_t *dst,
- uint8_t *src) {
- while ((hdma->Instance->CSR & DMA_FLAG_IDLE) == 0)
- ;
-
- hdma->Lock = 0;
- hdma->State = HAL_DMA_STATE_READY;
-
- HAL_DMA_Start(hdma, (uint32_t)src, (uint32_t)dst, 64);
-}
-
-// Deinitialize the DMA base copy
-static inline void fast_copy_deinit(DMA_HandleTypeDef *hdma) {
- while ((hdma->Instance->CSR & DMA_FLAG_IDLE) == 0)
- ;
-
- hdma->Lock = 0;
- hdma->State = HAL_DMA_STATE_READY;
-
- HAL_DMA_DeInit(hdma);
-}
-
-bool jpegdec_get_slice_mono8(uint32_t *mono8, jpegdec_slice_t *slice) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return false;
- }
-
- if (dec->state != JPEGDEC_STATE_SLICE_READY) {
- return false;
- }
-
- if (!IS_ALIGNED((uint32_t)mono8, 4)) {
- return false;
- }
-
- slice->width = dec->slice_width;
- slice->height = dec->slice_height;
- slice->x = dec->slice_x;
- slice->y = dec->slice_y;
-
- bool result = false;
-
- switch (dec->image.format) {
- case JPEGDEC_IMAGE_YCBCR420:
- // Not implemented
- break;
- case JPEGDEC_IMAGE_YCBCR422:
- // Not implemented
- break;
- case JPEGDEC_IMAGE_YCBCR444:
- // Not implemented
- break;
- case JPEGDEC_IMAGE_GRAYSCALE: {
- static DMA_HandleTypeDef hdma = {0};
- fast_copy_init(&hdma, dec->slice_width);
- uint8_t *src = (uint8_t *)dec->ycbcr_buffer;
- for (int y = 0; y < dec->slice_height; y += 8) {
- for (int x = 0; x < dec->slice_width; x += 8) {
- uint8_t *dst = (uint8_t *)mono8 + y * dec->slice_width + x;
- fast_copy_block(&hdma, dst, src);
- src += 64;
- }
- }
-
- fast_copy_deinit(&hdma);
- result = true;
- } break;
-
- default:
- result = false;
- break;
- }
-
- return result;
-}
-
-#endif // KERNEL_MODE
diff --git a/core/embed/gfx/jpegdec/unix/jpegdec.c b/core/embed/gfx/jpegdec/unix/jpegdec.c
deleted file mode 100644
index 0b28d67b..00000000
--- a/core/embed/gfx/jpegdec/unix/jpegdec.c
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <trezor_rtl.h>
-
-#include <gfx/gfx_color.h>
-#include <gfx/jpegdec.h>
-
-#include <jpeglib.h>
-
-#define MAX_SLICE_HEIGHT 16
-#define MAX_SLICE_WIDTH \
- (JPEGDEC_RGBA8888_BUFFER_SIZE / (MAX_SLICE_HEIGHT * sizeof(uint32_t)))
-
-// Custom source manager structure
-typedef struct {
- struct jpeg_source_mgr pub;
- uint8_t buffer[4096];
- jpegdec_input_t *input;
-
-} custom_source_mgr_t;
-
-// Our internal decoder state.
-struct jpegdec {
- bool inuse;
-
- // jpeglib context
- struct jpeg_decompress_struct cinfo;
- struct jpeg_error_mgr jerr;
-
- // Our custom source manager
- custom_source_mgr_t source_mgr;
-
- // Last decoder state
- jpegdec_state_t state;
- // Image info
- jpegdec_image_t image;
-
- // Up to MAX_SLICE_HEIGHT lines of decoded data in RGBA8888 format
- JSAMPARRAY slice_buffer;
-
- // Current slice coordinates
- int16_t slice_x;
- int16_t slice_y;
-};
-
-// JPEG decoder instance
-jpegdec_t g_jpegdec = {
- .inuse = false,
-};
-
-//---------------------------------------------------------------------
-// Custom source manager functions
-//---------------------------------------------------------------------
-
-static void init_source(j_decompress_ptr cinfo) {
- // No special initialization is needed.
- UNUSED(cinfo);
-}
-
-static boolean fill_input_buffer(j_decompress_ptr cinfo) {
- custom_source_mgr_t *src = (custom_source_mgr_t *)cinfo->src;
- jpegdec_input_t *input = src->input;
-
- if (input->offset < input->size) {
- size_t nbytes = MIN(input->size - input->offset, sizeof(src->buffer));
- memcpy(src->buffer, input->data + input->offset, nbytes);
- input->offset += nbytes;
- src->pub.next_input_byte = src->buffer;
- src->pub.bytes_in_buffer = nbytes;
- return TRUE;
- }
-
- // If no more data is available and this is the last chunk,
- // supply a fake EOI marker.
-
- if (input->last_chunk) {
- src->buffer[0] = 0xFF;
- src->buffer[1] = JPEG_EOI;
- src->pub.next_input_byte = src->buffer;
- src->pub.bytes_in_buffer = 2;
- return TRUE;
- }
-
- // No data available, but not the last chunk: suspend input
- return FALSE;
-}
-
-static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
- custom_source_mgr_t *src = (custom_source_mgr_t *)cinfo->src;
-
- if (num_bytes > 0) {
- while (num_bytes > (long)src->pub.bytes_in_buffer) {
- num_bytes -= (long)src->pub.bytes_in_buffer;
- fill_input_buffer(cinfo);
- }
- src->pub.next_input_byte += num_bytes;
- src->pub.bytes_in_buffer -= num_bytes;
- }
-}
-
-static void term_source(j_decompress_ptr cinfo) {
- // No cleanup necessary
- UNUSED(cinfo);
-}
-
-//---------------------------------------------------------------------
-// JPEG decoder functions
-//---------------------------------------------------------------------
-
-bool jpegdec_open(void) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (dec->inuse) {
- return false;
- }
-
- memset(dec, 0, sizeof(jpegdec_t));
- dec->inuse = true;
-
- // Set up the JPEG decompression object with default error handling
- dec->cinfo.err = jpeg_std_error(&dec->jerr);
- jpeg_create_decompress(&dec->cinfo);
-
- // Init our custom source manager
- custom_source_mgr_t *src = &dec->source_mgr;
- src->pub.init_source = init_source;
- src->pub.fill_input_buffer = fill_input_buffer;
- src->pub.skip_input_data = skip_input_data;
- src->pub.resync_to_restart = jpeg_resync_to_restart;
- src->pub.term_source = term_source;
- src->pub.bytes_in_buffer = 0;
- src->pub.next_input_byte = NULL;
- src->input = NULL;
- dec->cinfo.src = (struct jpeg_source_mgr *)src;
-
- dec->state = JPEGDEC_STATE_NEED_DATA;
-
- return true;
-}
-
-jpegdec_state_t jpegdec_process(jpegdec_input_t *input) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return JPEGDEC_STATE_ERROR;
- }
-
- dec->source_mgr.input = input;
-
- if (dec->state == JPEGDEC_STATE_SLICE_READY) {
- dec->slice_x += MAX_SLICE_WIDTH;
- if (dec->slice_x < dec->image.width) {
- // There is more data ready in the slice buffer
- return JPEGDEC_STATE_SLICE_READY;
- }
- dec->slice_x = 0;
- dec->slice_y = MIN(dec->image.height, dec->slice_y + MAX_SLICE_HEIGHT);
-
- if (dec->slice_y >= dec->image.height) {
- // The image is fully decoded
- dec->state = JPEGDEC_STATE_FINISHED;
- return dec->state;
- }
- }
-
- if (dec->state == JPEGDEC_STATE_FINISHED ||
- dec->state == JPEGDEC_STATE_ERROR) {
- // Do nothing if the decoder is finished or in error state
- } else if (dec->image.width == 0 && dec->image.height == 0) {
- // Decode jpeg headers and get image parameters
- int ret = jpeg_consume_input(&dec->cinfo);
-
- switch (ret) {
- case JPEG_SUSPENDED:
- dec->state = JPEGDEC_STATE_NEED_DATA;
- break;
-
- case JPEG_REACHED_SOS:
- dec->image.width = (int16_t)dec->cinfo.image_width;
- dec->image.height = (int16_t)dec->cinfo.image_height;
-
- if (dec->cinfo.num_components == 1) {
- dec->image.format = JPEGDEC_IMAGE_GRAYSCALE;
- } else if (dec->cinfo.num_components == 3) {
- int h = dec->cinfo.comp_info[0].h_samp_factor;
- int v = dec->cinfo.comp_info[0].v_samp_factor;
- if (h == 2 && v == 2)
- dec->image.format = JPEGDEC_IMAGE_YCBCR420;
- else if (h == 2 && v == 1)
- dec->image.format = JPEGDEC_IMAGE_YCBCR422;
- else
- dec->image.format = JPEGDEC_IMAGE_YCBCR444;
- } else {
- dec->state = JPEGDEC_STATE_ERROR;
- break;
- }
-
- dec->slice_x = 0;
- dec->slice_y = 0;
-
- // Set the output color space
- // (need to be set before jpeg_start_decompress)
- dec->cinfo.out_color_space = JCS_EXT_BGRA;
-
- jpeg_start_decompress(&dec->cinfo);
-
- // Allocate the output buffer
- dec->slice_buffer = (*dec->cinfo.mem->alloc_sarray)(
- (j_common_ptr)&dec->cinfo, JPOOL_IMAGE,
- dec->cinfo.output_width * dec->cinfo.output_components,
- MAX_SLICE_HEIGHT);
-
- dec->state = JPEGDEC_STATE_INFO_READY;
- break;
-
- default:
- dec->state = JPEGDEC_STATE_ERROR;
- break;
- }
- } else {
- // Image headers where decoded, now we can decode the image data
- // Read scanlines until we have a full slice
-
- for (;;) {
- // Row in slice buffer
- size_t row = dec->cinfo.output_scanline - dec->slice_y;
-
- if (row >= MAX_SLICE_HEIGHT) {
- dec->slice_y = dec->cinfo.output_scanline;
- row = 0;
- }
-
- int lines_read =
- jpeg_read_scanlines(&dec->cinfo, &dec->slice_buffer[row], 1);
-
- if (lines_read == 0) {
- dec->state = JPEGDEC_STATE_NEED_DATA;
- break;
- }
-
- if (row == MAX_SLICE_HEIGHT - 1 ||
- dec->cinfo.output_scanline >= dec->cinfo.output_height) {
- dec->state = JPEGDEC_STATE_SLICE_READY;
- break;
- }
- }
- }
-
- return dec->state;
-}
-
-bool jpegdec_get_info(jpegdec_image_t *image) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return false;
- }
-
- if (dec->image.width == 0 || dec->image.height == 0) {
- return false;
- }
-
- *image = dec->image;
- return true;
-}
-
-bool jpegdec_get_slice_rgba8888(uint32_t *rgba8888, jpegdec_slice_t *slice) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return false;
- }
-
- if (dec->state != JPEGDEC_STATE_SLICE_READY) {
- return false;
- }
-
- slice->x = dec->slice_x;
- slice->y = dec->slice_y;
- slice->width = MIN(dec->image.width - dec->slice_x, MAX_SLICE_WIDTH);
- slice->height = MIN(dec->image.height - dec->slice_y, MAX_SLICE_HEIGHT);
-
- for (int y = 0; y < slice->height; y++) {
- void *src = &((uint32_t *)dec->slice_buffer[y])[slice->x];
- void *dst = rgba8888 + y * slice->width;
- memcpy(dst, src, slice->width * sizeof(uint32_t));
- }
-
- return true;
-}
-
-bool jpegdec_get_slice_mono8(uint32_t *mono8, jpegdec_slice_t *slice) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (!dec->inuse) {
- return false;
- }
-
- if (dec->state != JPEGDEC_STATE_SLICE_READY) {
- return false;
- }
-
- slice->x = dec->slice_x;
- slice->y = dec->slice_y;
- slice->width = MIN(dec->image.width - dec->slice_x, MAX_SLICE_WIDTH);
- slice->height = MIN(dec->image.height - dec->slice_y, MAX_SLICE_HEIGHT);
-
- uint8_t *dst = (uint8_t *)mono8;
-
- for (int y = 0; y < slice->height; y++) {
- for (int x = 0; x < slice->width; x++) {
- gfx_color32_t color = ((uint32_t *)dec->slice_buffer[y])[slice->x + x];
- dst[y * slice->width + x] = gfx_color32_lum(color);
- }
- }
-
- return true;
-}
-
-void jpegdec_close(void) {
- jpegdec_t *dec = &g_jpegdec;
-
- if (dec->inuse) {
- jpeg_destroy_decompress(&dec->cinfo);
-
- memset(dec, 0, sizeof(jpegdec_t));
- }
-}
diff --git a/core/embed/gfx/terminal.c b/core/embed/gfx/terminal.c
deleted file mode 100644
index 6ecada55..00000000
--- a/core/embed/gfx/terminal.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <trezor_model.h>
-#include <trezor_rtl.h>
-
-#include <gfx/gfx_draw.h>
-#include <gfx/terminal.h>
-#include <io/display.h>
-#include <rtl/printf.h>
-#include <rtl/strutils.h>
-
-#include "fonts/font_bitmap.h"
-
-#ifndef TERMINAL_FONT_SCALE
-#define TERMINAL_FONT_SCALE 1
-#endif
-
-#ifndef TERMINAL_X_PADDING
-#define TERMINAL_X_PADDING 0
-#endif
-
-#ifndef TERMINAL_Y_PADDING
-#define TERMINAL_Y_PADDING 0
-#endif
-
-#define TERMINAL_GLYPH_WIDTH (6 * TERMINAL_FONT_SCALE)
-#define TERMINAL_GLYPH_HEIGHT (8 * TERMINAL_FONT_SCALE)
-
-#define TERMINAL_COLS \
- ((DISPLAY_RESX - 2 * TERMINAL_X_PADDING) / TERMINAL_GLYPH_WIDTH)
-#define TERMINAL_ROWS \
- ((DISPLAY_RESY - 2 * TERMINAL_Y_PADDING) / TERMINAL_GLYPH_HEIGHT)
-
-static char terminal_fb[TERMINAL_ROWS][TERMINAL_COLS];
-static gfx_color_t terminal_fgcolor = COLOR_WHITE;
-static gfx_color_t terminal_bgcolor = COLOR_BLACK;
-
-// set colors for display_print function
-void term_set_color(gfx_color_t fgcolor, gfx_color_t bgcolor) {
- terminal_fgcolor = fgcolor;
- terminal_bgcolor = bgcolor;
-}
-
-typedef struct {
-#if (TERMINAL_FONT_SCALE == 1)
- // 6x8 bitmap
- uint8_t bytes[TERMINAL_GLYPH_HEIGHT];
-#elif (TERMINAL_FONT_SCALE == 2)
- // 12x16 bitmap
- uint16_t words[TERMINAL_GLYPH_HEIGHT];
-#endif
-} term_glyph_bits_t;
-
-// Font_Bitmap contains 96 (0x20 - 0x7F) 5x7 glyphs
-// Each glyph consists of 5 bytes (each byte represents one column)
-//
-// This function converts the glyph into the format compatible
-// with `display_copy_mono1p()` functions.
-
-#if (TERMINAL_FONT_SCALE == 1)
-static term_glyph_bits_t term_glyph_bits(char ch) {
- term_glyph_bits_t result = {0};
-
- if (ch > 32 && (uint8_t)ch < 128) {
- const uint8_t *b = &Font_Bitmap[(ch - ' ') * 5];
-
- for (int y = 0; y < 7; y++) {
- uint8_t mask = 1 << y;
- result.bytes[y] |= ((b[0] & mask) ? 128 : 0) + ((b[1] & mask) ? 64 : 0) +
- ((b[2] & mask) ? 32 : 0) + ((b[3] & mask) ? 16 : 0) +
- ((b[4] & mask) ? 8 : 0);
- }
- }
- return result;
-}
-#endif
-
-#if (TERMINAL_FONT_SCALE == 2)
-static term_glyph_bits_t term_glyph_bits(char ch) {
- term_glyph_bits_t result = {0};
-
- if (ch > 32 && (uint8_t)ch < 128) {
- const uint8_t *b = &Font_Bitmap[(ch - ' ') * 5];
-
- for (int y = 0; y < 7; y++) {
- uint8_t mask = 1 << y;
-
- result.words[2 * y] |= ((b[0] & mask) ? 0b11000000 << 0 : 0) +
- ((b[1] & mask) ? 0b00110000 << 0 : 0) +
- ((b[2] & mask) ? 0b00001100 << 0 : 0) +
- ((b[3] & mask) ? 0b00000011 << 0 : 0) +
- ((b[4] & mask) ? 0b11000000 << 8 : 0);
- }
-
- // Duplicate rows
- for (int y = 0; y < 7; y++) {
- result.words[2 * y + 1] = result.words[2 * y];
- }
- }
- return result;
-}
-#endif
-
-// Redraws specified rows to the display
-static void term_redraw_rows(int start_row, int row_count) {
- term_glyph_bits_t glyph_bits = {0};
- gfx_bitblt_t bb = {
- .height = TERMINAL_GLYPH_HEIGHT,
- .width = TERMINAL_GLYPH_WIDTH,
- .dst_row = NULL,
- .dst_x = 0,
- .dst_y = 0,
- .dst_stride = 0,
-
- .src_row = &glyph_bits,
- .src_x = 0,
- .src_y = 0,
- .src_stride = ((TERMINAL_GLYPH_WIDTH - 1) | 0x07) + 1,
- .src_fg = terminal_fgcolor,
- .src_bg = terminal_bgcolor,
- .src_alpha = 255,
- };
-
- for (int y = start_row; y < start_row + row_count; y++) {
- bb.dst_y = y * TERMINAL_GLYPH_HEIGHT + TERMINAL_Y_PADDING;
- for (int x = 0; x < TERMINAL_COLS; x++) {
- glyph_bits = term_glyph_bits(terminal_fb[y][x]);
- bb.dst_x = x * TERMINAL_GLYPH_WIDTH + TERMINAL_X_PADDING;
- display_copy_mono1p(&bb);
- }
- }
-}
-
-// display text using bitmap font
-void term_nprint(const char *text, int textlen) {
- static uint8_t row = 0, col = 0;
-
- // print characters to internal buffer (terminal_fb)
- for (int i = 0; i < textlen; i++) {
- switch (text[i]) {
- case '\r':
- break;
- case '\n':
- row++;
- col = 0;
- break;
- default:
- terminal_fb[row][col] = text[i];
- col++;
- break;
- }
-
- if (col >= TERMINAL_COLS) {
- col = 0;
- row++;
- }
-
- if (row >= TERMINAL_ROWS) {
- for (int j = 0; j < TERMINAL_ROWS - 1; j++) {
- memcpy(terminal_fb[j], terminal_fb[j + 1], TERMINAL_COLS);
- }
- memset(terminal_fb[TERMINAL_ROWS - 1], 0, TERMINAL_COLS);
- row = TERMINAL_ROWS - 1;
- }
- }
-
- term_redraw_rows(0, TERMINAL_ROWS);
-
- gfx_rect_t r;
-
- // redraw residual area of the display top side
- r = gfx_rect_wh(0, 0, DISPLAY_RESX, TERMINAL_Y_PADDING);
- gfx_draw_bar(r, terminal_bgcolor);
-
- // redraw residual area of the display bottom side
- r.y0 += TERMINAL_ROWS * TERMINAL_GLYPH_HEIGHT;
- r.y1 = DISPLAY_RESY;
- gfx_draw_bar(r, terminal_bgcolor);
-
- // redraw residual area of the display left side
- r = gfx_rect_wh(0, 0, TERMINAL_X_PADDING, DISPLAY_RESY);
- gfx_draw_bar(r, terminal_bgcolor);
-
- // redraw residual area of the display right side
- r.x0 += TERMINAL_COLS * TERMINAL_GLYPH_WIDTH;
- r.x1 = DISPLAY_RESX;
- gfx_draw_bar(r, terminal_bgcolor);
-
- display_refresh();
-}
-
-void term_print(const char *text) { term_nprint(text, strlen(text)); }
-
-void term_print_int32(int32_t value) {
- char buf[12] = "";
- cstr_append_int32(buf, sizeof(buf), value);
- term_print(buf);
-}
-
-void term_printf(const char *fmt, ...) {
- va_list va;
- va_start(va, fmt);
- char buf[256] = {0};
- int len = vsnprintf_(buf, sizeof(buf), fmt, va);
- term_nprint(buf, len);
- va_end(va);
-}
diff --git a/core/embed/io/display/inc/io/display.h b/core/embed/io/display/inc/io/display.h
index fb04fba8..1edac7d6 100644
--- a/core/embed/io/display/inc/io/display.h
+++ b/core/embed/io/display/inc/io/display.h
@@ -21,7 +21,7 @@
#include <trezor_types.h>
-#include <gfx/gfx_bitblt.h>
+#include <io/gfx_bitblt.h>
// This is a universal API for controlling different types of display
// controllers.
diff --git a/core/embed/io/display/ltdc_dsi/display_fb_rgb565.c b/core/embed/io/display/ltdc_dsi/display_fb_rgb565.c
index f0dc3913..91c70d7d 100644
--- a/core/embed/io/display/ltdc_dsi/display_fb_rgb565.c
+++ b/core/embed/io/display/ltdc_dsi/display_fb_rgb565.c
@@ -1,8 +1,8 @@
#include <trezor_bsp.h>
#include <trezor_rtl.h>
-#include <gfx/gfx_bitblt.h>
#include <io/display.h>
+#include <io/gfx_bitblt.h>
#ifdef KERNEL_MODE
void display_copy_rgb565(const gfx_bitblt_t *bb) {
diff --git a/core/embed/io/display/ltdc_dsi/display_fb_rgb888.c b/core/embed/io/display/ltdc_dsi/display_fb_rgb888.c
index 3b9620f6..44f5a8df 100644
--- a/core/embed/io/display/ltdc_dsi/display_fb_rgb888.c
+++ b/core/embed/io/display/ltdc_dsi/display_fb_rgb888.c
@@ -4,8 +4,8 @@
#include <trezor_bsp.h>
#include <trezor_model.h>
-#include <gfx/gfx_bitblt.h>
#include <io/display.h>
+#include <io/gfx_bitblt.h>
void display_fill(const gfx_bitblt_t *bb) {
display_fb_info_t fb;
diff --git a/core/embed/io/display/st-7789/display_fb.c b/core/embed/io/display/st-7789/display_fb.c
index 2565aaa9..24b90d62 100644
--- a/core/embed/io/display/st-7789/display_fb.c
+++ b/core/embed/io/display/st-7789/display_fb.c
@@ -21,8 +21,8 @@
#include <trezor_model.h>
#include <trezor_rtl.h>
-#include <gfx/gfx_bitblt.h>
#include <io/display.h>
+#include <io/gfx_bitblt.h>
#include <sys/irq.h>
#include <sys/mpu.h>
diff --git a/core/embed/io/gfx/bitblt/gfx_bitblt.c b/core/embed/io/gfx/bitblt/gfx_bitblt.c
new file mode 100644
index 00000000..98eab5c4
--- /dev/null
+++ b/core/embed/io/gfx/bitblt/gfx_bitblt.c
@@ -0,0 +1,42 @@
+/*
+ * 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/>.
+ */
+
+#include <io/dma2d_bitblt.h>
+
+#ifdef KERNEL_MODE
+
+void gfx_bitblt_init(void) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ dma2d_init();
+#endif
+}
+
+void gfx_bitblt_deinit(void) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ dma2d_deinit();
+#endif
+}
+
+#endif // KERNEL_MODE
+
+void gfx_bitblt_wait(void) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ dma2d_wait();
+#endif
+}
diff --git a/core/embed/io/gfx/bitblt/gfx_bitblt_mono8.c b/core/embed/io/gfx/bitblt/gfx_bitblt_mono8.c
new file mode 100644
index 00000000..b8e265ad
--- /dev/null
+++ b/core/embed/io/gfx/bitblt/gfx_bitblt_mono8.c
@@ -0,0 +1,111 @@
+/*
+ * 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/>.
+ */
+
+#include <io/gfx_bitblt.h>
+
+void gfx_mono8_fill(const gfx_bitblt_t* bb) {
+ uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
+ uint16_t height = bb->height;
+
+ uint8_t fg = gfx_color_lum(bb->src_fg);
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = fg;
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ }
+}
+
+void gfx_mono8_copy_mono1p(const gfx_bitblt_t* bb) {
+ uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src = (uint8_t*)bb->src_row;
+ uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
+ uint16_t height = bb->height;
+
+ uint8_t fg = gfx_color_lum(bb->src_fg);
+ uint8_t bg = gfx_color_lum(bb->src_bg);
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
+ uint8_t data = src[(src_ofs + x) / 8];
+ dst_ptr[x] = (data & mask) ? fg : bg;
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ofs += bb->src_stride;
+ }
+}
+
+void gfx_mono8_copy_mono4(const gfx_bitblt_t* bb) {
+ uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_row = (uint8_t*)bb->src_row;
+ uint16_t height = bb->height;
+
+ uint8_t fg = gfx_color_lum(bb->src_fg);
+ uint8_t bg = gfx_color_lum(bb->src_bg);
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t src_data = src_row[(x + bb->src_x) / 2];
+ uint8_t src_lum = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0xF;
+ dst_ptr[x] = (fg * src_lum + bg * (15 - src_lum)) / 15;
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_row += bb->src_stride / sizeof(*src_row);
+ }
+}
+
+void gfx_mono8_blend_mono1p(const gfx_bitblt_t* bb) {
+ uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src = (uint8_t*)bb->src_row;
+ uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
+ uint16_t height = bb->height;
+
+ uint8_t fg = gfx_color_lum(bb->src_fg);
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
+ uint8_t data = src[(src_ofs + x) / 8];
+ dst_ptr[x] = (data & mask) ? fg : dst_ptr[x];
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ofs += bb->src_stride;
+ }
+}
+
+void gfx_mono8_blend_mono4(const gfx_bitblt_t* bb) {
+ uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_row = (uint8_t*)bb->src_row;
+ uint16_t height = bb->height;
+
+ uint8_t fg = gfx_color_lum(bb->src_fg);
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t src_data = src_row[(x + bb->src_x) / 2];
+ uint8_t src_alpha = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0x0F;
+ src_alpha = src_alpha * bb->src_alpha / 15;
+ dst_ptr[x] = (fg * src_alpha + dst_ptr[x] * (255 - src_alpha)) / 255;
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_row += bb->src_stride / sizeof(*src_row);
+ }
+}
diff --git a/core/embed/io/gfx/bitblt/gfx_bitblt_rgb565.c b/core/embed/io/gfx/bitblt/gfx_bitblt_rgb565.c
new file mode 100644
index 00000000..10d6827b
--- /dev/null
+++ b/core/embed/io/gfx/bitblt/gfx_bitblt_rgb565.c
@@ -0,0 +1,166 @@
+/*
+ * 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/>.
+ */
+
+// Turning off the stack protector for this file improves
+// the performance of drawing operations when called frequently.
+#ifndef TREZOR_EMULATOR
+#pragma GCC optimize("no-stack-protector")
+#endif
+
+#include <io/gfx_bitblt.h>
+
+#if USE_DMA2D
+#include <io/dma2d_bitblt.h>
+#endif
+
+volatile uint64_t g_gfx_cycles;
+
+void gfx_rgb565_fill(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgb565_fill(bb))
+#endif
+ {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint16_t height = bb->height;
+
+ if (bb->src_alpha == 255) {
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = bb->src_fg;
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ }
+ } else {
+ uint8_t alpha = bb->src_alpha;
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = gfx_color16_blend_a8(bb->src_fg, dst_ptr[x], alpha);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ }
+ }
+ }
+}
+
+void gfx_rgb565_copy_mono1p(const gfx_bitblt_t* bb) {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src = (uint8_t*)bb->src_row;
+ uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
+ uint16_t height = bb->height;
+
+ uint16_t fg = gfx_color_to_color16(bb->src_fg);
+ uint16_t bg = gfx_color_to_color16(bb->src_bg);
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
+ uint8_t data = src[(src_ofs + x) / 8];
+ dst_ptr[x] = (data & mask) ? fg : bg;
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ofs += bb->src_stride;
+ }
+}
+
+void gfx_rgb565_copy_mono4(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgb565_copy_mono4(bb))
+#endif
+ {
+ const gfx_color16_t* gradient =
+ gfx_color16_gradient_a4(bb->src_fg, bb->src_bg);
+
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_row = (uint8_t*)bb->src_row;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t fg_data = src_row[(x + bb->src_x) / 2];
+ uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
+ dst_ptr[x] = gradient[fg_lum];
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_row += bb->src_stride / sizeof(*src_row);
+ }
+ }
+}
+
+void gfx_rgb565_copy_rgb565(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgb565_copy_rgb565(bb))
+#endif
+ {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = src_ptr[x];
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+ }
+}
+
+void gfx_rgb565_blend_mono4(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgb565_blend_mono4(bb))
+#endif
+ {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_row = (uint8_t*)bb->src_row;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t fg_data = src_row[(x + bb->src_x) / 2];
+ uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
+ fg_alpha = fg_alpha * bb->src_alpha / 15;
+ dst_ptr[x] = gfx_color16_blend_a8(
+ bb->src_fg, gfx_color16_to_color(dst_ptr[x]), fg_alpha);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_row += bb->src_stride / sizeof(*src_row);
+ }
+ }
+}
+
+void gfx_rgb565_blend_mono8(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgb565_blend_mono8(bb))
+#endif
+ {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t fg_alpha = src_ptr[x];
+ dst_ptr[x] = gfx_color16_blend_a8(
+ bb->src_fg, gfx_color16_to_color(dst_ptr[x]), fg_alpha);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+ }
+}
diff --git a/core/embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c b/core/embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c
new file mode 100644
index 00000000..41a3263d
--- /dev/null
+++ b/core/embed/io/gfx/bitblt/gfx_bitblt_rgba8888.c
@@ -0,0 +1,190 @@
+/*
+ * 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/>.
+ */
+
+#include <io/gfx_bitblt.h>
+
+#if USE_DMA2D
+#include <io/dma2d_bitblt.h>
+#endif
+
+void gfx_rgba8888_fill(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgba8888_fill(bb))
+#endif
+ {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint16_t height = bb->height;
+
+ if (bb->src_alpha == 255) {
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = gfx_color_to_color32(bb->src_fg);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ }
+ } else {
+ uint8_t alpha = bb->src_alpha;
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = gfx_color32_blend_a8(
+ bb->src_fg, gfx_color32_to_color(dst_ptr[x]), alpha);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ }
+ }
+ }
+}
+
+void gfx_rgba8888_copy_mono1p(const gfx_bitblt_t* bb) {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src = (uint8_t*)bb->src_row;
+ uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
+ uint16_t height = bb->height;
+
+ uint32_t fg = gfx_color_to_color32(bb->src_fg);
+ uint32_t bg = gfx_color_to_color32(bb->src_bg);
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
+ uint8_t data = src[(src_ofs + x) / 8];
+ dst_ptr[x] = (data & mask) ? fg : bg;
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ofs += bb->src_stride;
+ }
+}
+
+void gfx_rgba8888_copy_mono4(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgba8888_copy_mono4(bb))
+#endif
+ {
+ const gfx_color32_t* gradient =
+ gfx_color32_gradient_a4(bb->src_fg, bb->src_bg);
+
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_row = (uint8_t*)bb->src_row;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t fg_data = src_row[(x + bb->src_x) / 2];
+ uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
+ dst_ptr[x] = gradient[fg_lum];
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_row += bb->src_stride / sizeof(*src_row);
+ }
+ }
+}
+
+void gfx_rgba8888_copy_rgb565(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgba8888_copy_rgb565(bb))
+#endif
+ {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = gfx_color16_to_color32(src_ptr[x]);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+ }
+}
+
+void gfx_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgba8888_copy_rgba8888(bb))
+#endif
+ {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint32_t* src_ptr = (uint32_t*)bb->src_row + bb->src_x;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ dst_ptr[x] = src_ptr[x << bb->src_downscale];
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += (bb->src_stride / sizeof(*src_ptr)) << bb->src_downscale;
+ }
+ }
+}
+
+void gfx_rgba8888_blend_mono4(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgba8888_blend_mono4(bb))
+#endif
+ {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_row = (uint8_t*)bb->src_row;
+ uint16_t height = bb->height;
+
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t fg_data = src_row[(x + bb->src_x) / 2];
+ uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
+ fg_alpha = fg_alpha * bb->src_alpha / 15;
+ dst_ptr[x] = gfx_color32_blend_a8(
+ bb->src_fg, gfx_color32_to_color(dst_ptr[x]), fg_alpha);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_row += bb->src_stride / sizeof(*src_row);
+ }
+ }
+}
+
+void gfx_rgba8888_blend_mono8(const gfx_bitblt_t* bb) {
+#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
+ if (!dma2d_rgba8888_blend_mono8(bb))
+#endif
+ {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x;
+ uint16_t height = bb->height;
+
+ if (bb->src_alpha == 255) {
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t fg_alpha = src_ptr[x];
+ dst_ptr[x] = gfx_color32_blend_a8(
+ bb->src_fg, gfx_color32_to_color(dst_ptr[x]), fg_alpha);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+ } else {
+ while (height-- > 0) {
+ for (int x = 0; x < bb->width; x++) {
+ uint8_t fg_alpha = (src_ptr[x] * bb->src_alpha) / 255;
+ dst_ptr[x] = gfx_color32_blend_a8(
+ bb->src_fg, gfx_color32_to_color(dst_ptr[x]), fg_alpha);
+ }
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+ }
+ }
+}
diff --git a/core/embed/io/gfx/bitblt/stm32/dma2d_bitblt.c b/core/embed/io/gfx/bitblt/stm32/dma2d_bitblt.c
new file mode 100644
index 00000000..f89549a3
--- /dev/null
+++ b/core/embed/io/gfx/bitblt/stm32/dma2d_bitblt.c
@@ -0,0 +1,976 @@
+/*
+ * 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/>.
+ */
+
+// Turning off the stack protector for this file improves
+// the performance of drawing operations when called frequently.
+#pragma GCC optimize("no-stack-protector")
+
+#ifdef KERNEL_MODE
+
+#include <trezor_bsp.h>
+#include <trezor_rtl.h>
+
+#include <io/dma2d_bitblt.h>
+#include <io/gfx_color.h>
+
+// Number of DMA2D layers - background (0) and foreground (1)
+#define DMA2D_LAYER_COUNT 2
+
+typedef struct {
+ // Set if the driver is initialized
+ bool initialized;
+ // ST DMA2D driver handle
+ DMA2D_HandleTypeDef handle;
+ // CLUT cache
+ struct {
+ gfx_color32_t c_fg;
+ gfx_color32_t c_bg;
+ } cache[DMA2D_LAYER_COUNT];
+
+ // CLUT is configured according to the cache
+ bool clut_valid;
+
+} dma2d_driver_t;
+
+static dma2d_driver_t g_dma2d_driver = {
+ .initialized = false,
+};
+
+// Returns `true` if the specified address is accessible by DMA2D
+// and can be used by any of the following functions
+static inline bool dma2d_accessible(const void* ptr) {
+#ifdef STM32F4
+ const void* ccm_start = (const void*)0x10000000;
+ const void* ccm_end = (const void*)0x1000FFFF;
+ return !(ptr >= ccm_start && ptr <= ccm_end);
+#else
+ return true;
+#endif
+}
+
+void dma2d_init(void) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+ if (drv->initialized) {
+ return;
+ }
+ memset(drv, 0, sizeof(dma2d_driver_t));
+ drv->handle.Instance = DMA2D;
+
+#ifdef KERNEL_MODE
+ __HAL_RCC_DMA2D_FORCE_RESET();
+ __HAL_RCC_DMA2D_RELEASE_RESET();
+ __HAL_RCC_DMA2D_CLK_ENABLE();
+#endif
+ drv->initialized = true;
+}
+
+void dma2d_deinit(void) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+#ifdef KERNEL_MODE
+ __HAL_RCC_DMA2D_CLK_DISABLE();
+ __HAL_RCC_DMA2D_FORCE_RESET();
+ __HAL_RCC_DMA2D_RELEASE_RESET();
+#endif
+ memset(drv, 0, sizeof(dma2d_driver_t));
+}
+
+void dma2d_wait(void) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return;
+ }
+
+ while (HAL_DMA2D_PollForTransfer(&drv->handle, 10) != HAL_OK)
+ ;
+}
+
+bool dma2d_rgb565_fill(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 16)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row)) {
+ return false;
+ }
+
+ if (bb->src_alpha == 255) {
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
+ drv->handle.Init.Mode = DMA2D_R2M;
+ drv->handle.Init.OutputOffset =
+ bb->dst_stride / sizeof(uint16_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ HAL_DMA2D_Start(&drv->handle, gfx_color_to_color32(bb->src_fg),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
+ bb->width, bb->height);
+ } else {
+#ifdef STM32U5
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
+ drv->handle.Init.Mode = DMA2D_M2M_BLEND_FG;
+ drv->handle.Init.OutputOffset =
+ bb->dst_stride / sizeof(uint16_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
+ drv->handle.LayerCfg[1].InputOffset = 0;
+ drv->handle.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
+ drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
+ drv->handle.LayerCfg[0].InputOffset =
+ bb->dst_stride / sizeof(uint16_t) - bb->width;
+ drv->handle.LayerCfg[0].AlphaMode = 0;
+ drv->handle.LayerCfg[0].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 0);
+
+ HAL_DMA2D_BlendingStart(
+ &drv->handle, gfx_color_to_color32(bb->src_fg),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), bb->width,
+ bb->height);
+#else
+ // STM32F4 can not accelerate blending with the fixed color
+ return false;
+#endif
+ }
+
+ return true;
+}
+
+static void dma2d_config_clut(uint32_t layer, gfx_color32_t fg,
+ gfx_color32_t bg) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+#define GRADIENT_STEPS 16
+
+ if (layer >= ARRAY_LENGTH(drv->cache)) {
+ return;
+ }
+
+ volatile uint32_t* clut =
+ layer ? drv->handle.Instance->FGCLUT : drv->handle.Instance->BGCLUT;
+
+ if (fg != drv->cache[layer].c_fg || bg != drv->cache[layer].c_bg ||
+ !drv->clut_valid) {
+ drv->cache[layer].c_fg = fg;
+ drv->cache[layer].c_bg = bg;
+ drv->clut_valid = true;
+
+ for (int step = 0; step < GRADIENT_STEPS; step++) {
+ clut[step] = gfx_color32_rgba(
+ a4_lerp(gfx_color32_to_r(fg), gfx_color32_to_r(bg), step),
+ a4_lerp(gfx_color32_to_g(fg), gfx_color32_to_g(bg), step),
+ a4_lerp(gfx_color32_to_b(fg), gfx_color32_to_b(bg), step),
+ a4_lerp(gfx_color32_to_a(fg), gfx_color32_to_a(bg), step));
+ }
+
+ DMA2D_CLUTCfgTypeDef clut_def = {0};
+ clut_def.CLUTColorMode = DMA2D_CCM_ARGB8888;
+ clut_def.Size = GRADIENT_STEPS - 1;
+ clut_def.pCLUT = 0; // ???
+
+ HAL_DMA2D_ConfigCLUT(&drv->handle, clut_def, layer);
+ }
+}
+
+static void dma2d_rgb565_copy_mono4_first_col(gfx_bitblt_t* bb,
+ const gfx_color16_t* gradient) {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_lum = src_ptr[0] >> 4;
+ dst_ptr[0] = gradient[fg_lum];
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+static void dma2d_rgb565_copy_mono4_last_col(gfx_bitblt_t* bb,
+ const gfx_color16_t* gradient) {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_lum = src_ptr[0] & 0x0F;
+ dst_ptr[0] = gradient[fg_lum];
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+bool dma2d_rgb565_copy_mono4(const gfx_bitblt_t* params) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(params, 16) ||
+ !gfx_bitblt_check_src_x(params, 4)) {
+ return false;
+ }
+
+ const gfx_color16_t* src_gradient = NULL;
+
+ gfx_bitblt_t bb_copy = *params;
+ gfx_bitblt_t* bb = &bb_copy;
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ if (bb->src_x & 1) {
+ // First column of mono4 bitmap is odd
+ // Use the CPU to draw the first column
+ src_gradient = gfx_color16_gradient_a4(bb->src_fg, bb->src_bg);
+ dma2d_rgb565_copy_mono4_first_col(bb, src_gradient);
+ bb->dst_x += 1;
+ bb->src_x += 1;
+ bb->width -= 1;
+ }
+
+ if (bb->width > 0 && bb->width & 1) {
+ // The width is odd
+ // Use the CPU to draw the last column
+ if (src_gradient == NULL) {
+ src_gradient = gfx_color16_gradient_a4(bb->src_fg, bb->src_bg);
+ }
+ dma2d_rgb565_copy_mono4_last_col(bb, src_gradient);
+ bb->width -= 1;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
+ drv->handle.Init.Mode = DMA2D_M2M_PFC;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint16_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
+ drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ dma2d_config_clut(1, gfx_color_to_color32(bb->src_fg),
+ gfx_color_to_color32(bb->src_bg));
+
+ HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
+ bb->width, bb->height);
+ return true;
+}
+
+bool dma2d_rgb565_copy_rgb565(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 16) || !gfx_bitblt_check_src_x(bb, 16)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
+ drv->handle.Init.Mode = DMA2D_M2M_PFC;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint16_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
+ drv->handle.LayerCfg[1].InputOffset =
+ bb->src_stride / sizeof(uint16_t) - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ HAL_DMA2D_Start(&drv->handle,
+ (uint32_t)bb->src_row + bb->src_x * sizeof(uint16_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
+ bb->width, bb->height);
+ return true;
+}
+
+static void dma2d_rgb565_blend_mono4_first_col(const gfx_bitblt_t* bb) {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_alpha = src_ptr[0] >> 4;
+ fg_alpha = (fg_alpha * bb->src_alpha) / 15;
+ dst_ptr[0] = gfx_color16_blend_a8(
+ bb->src_fg, gfx_color16_to_color(dst_ptr[0]), fg_alpha);
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+static void dma2d_rgb565_blend_mono4_last_col(const gfx_bitblt_t* bb) {
+ uint16_t* dst_ptr = (uint16_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_alpha = src_ptr[0] & 0x0F;
+ fg_alpha = (fg_alpha * bb->src_alpha) / 15;
+ dst_ptr[0] = gfx_color16_blend_a8(
+ bb->src_fg, gfx_color16_to_color(dst_ptr[0]), fg_alpha);
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+bool dma2d_rgb565_blend_mono4(const gfx_bitblt_t* params) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(params, 16) ||
+ !gfx_bitblt_check_src_x(params, 4)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ gfx_bitblt_t bb_copy = *params;
+ gfx_bitblt_t* bb = &bb_copy;
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ if (bb->src_x & 1) {
+ // First column of mono4 bitmap is odd
+ // Use the CPU to draw the first column
+ dma2d_rgb565_blend_mono4_first_col(bb);
+ bb->dst_x += 1;
+ bb->src_x += 1;
+ bb->width -= 1;
+ }
+
+ if (bb->width > 0 && bb->width & 1) {
+ // The width is odd
+ // Use the CPU to draw the last column
+ dma2d_rgb565_blend_mono4_last_col(bb);
+ bb->width -= 1;
+ }
+
+ if (bb->width > 0) {
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
+ drv->handle.Init.Mode = DMA2D_M2M_BLEND;
+ drv->handle.Init.OutputOffset =
+ bb->dst_stride / sizeof(uint16_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
+ drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA;
+ drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ dma2d_config_clut(
+ 1, gfx_color_to_color32(bb->src_fg),
+ gfx_color32_set_alpha(gfx_color_to_color32(bb->src_fg), 0));
+
+ drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
+ drv->handle.LayerCfg[0].InputOffset =
+ bb->dst_stride / sizeof(uint16_t) - bb->width;
+ drv->handle.LayerCfg[0].AlphaMode = 0;
+ drv->handle.LayerCfg[0].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 0);
+
+ HAL_DMA2D_BlendingStart(
+ &drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), bb->width,
+ bb->height);
+ }
+
+ return true;
+}
+
+bool dma2d_rgb565_blend_mono8(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 16) || !gfx_bitblt_check_src_x(bb, 8)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
+ drv->handle.Init.Mode = DMA2D_M2M_BLEND;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint16_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;
+ drv->handle.LayerCfg[1].InputOffset = bb->src_stride - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = gfx_color_to_color32(bb->src_fg);
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
+ drv->handle.LayerCfg[0].InputOffset =
+ bb->dst_stride / sizeof(uint16_t) - bb->width;
+ drv->handle.LayerCfg[0].AlphaMode = 0;
+ drv->handle.LayerCfg[0].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 0);
+
+ HAL_DMA2D_BlendingStart(&drv->handle, (uint32_t)bb->src_row + bb->src_x,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t),
+ bb->width, bb->height);
+
+ return true;
+}
+
+bool dma2d_rgba8888_fill(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 32)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row)) {
+ return false;
+ }
+
+ if (bb->src_alpha == 255) {
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_R2M;
+ drv->handle.Init.OutputOffset =
+ bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ HAL_DMA2D_Start(&drv->handle, gfx_color_to_color32(bb->src_fg),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ bb->width, bb->height);
+ } else {
+#ifdef STM32U5
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_BLEND_FG;
+ drv->handle.Init.OutputOffset =
+ bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;
+ drv->handle.LayerCfg[1].InputOffset = 0;
+ drv->handle.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
+ drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
+ drv->handle.LayerCfg[0].InputOffset =
+ bb->dst_stride / sizeof(uint32_t) - bb->width;
+ drv->handle.LayerCfg[0].AlphaMode = 0;
+ drv->handle.LayerCfg[0].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 0);
+
+ HAL_DMA2D_BlendingStart(
+ &drv->handle, gfx_color_to_color32(bb->src_fg),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), bb->width,
+ bb->height);
+#else
+ // STM32F4 can not accelerate blending with the fixed color
+ return false;
+#endif
+ }
+ return true;
+}
+
+static void dma2d_rgba8888_copy_mono4_first_col(gfx_bitblt_t* bb,
+ const gfx_color32_t* gradient) {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_lum = src_ptr[0] >> 4;
+ dst_ptr[0] = gradient[fg_lum];
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+static void dma2d_rgba8888_copy_mono4_last_col(gfx_bitblt_t* bb,
+ const gfx_color32_t* gradient) {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_lum = src_ptr[0] & 0x0F;
+ dst_ptr[0] = gradient[fg_lum];
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+bool dma2d_rgba8888_copy_mono4(const gfx_bitblt_t* params) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(params, 32) ||
+ !gfx_bitblt_check_src_x(params, 4)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ const gfx_color32_t* src_gradient = NULL;
+ gfx_bitblt_t bb_copy = *params;
+ gfx_bitblt_t* bb = &bb_copy;
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ if (bb->src_x & 1) {
+ // First column of mono4 bitmap is odd
+ // Use the CPU to draw the first column
+ src_gradient = gfx_color32_gradient_a4(bb->src_fg, bb->src_bg);
+ dma2d_rgba8888_copy_mono4_first_col(bb, src_gradient);
+ bb->dst_x += 1;
+ bb->src_x += 1;
+ bb->width -= 1;
+ }
+
+ if (bb->width > 0 && bb->width & 1) {
+ // The width is odd
+ // Use the CPU to draw the last column
+ if (src_gradient == NULL) {
+ src_gradient = gfx_color32_gradient_a4(bb->src_fg, bb->src_bg);
+ }
+ dma2d_rgba8888_copy_mono4_last_col(bb, src_gradient);
+ bb->width -= 1;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_PFC;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
+ drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ dma2d_config_clut(1, gfx_color_to_color32(bb->src_fg),
+ gfx_color_to_color32(bb->src_bg));
+
+ HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ bb->width, bb->height);
+ return true;
+}
+
+bool dma2d_rgba8888_copy_rgb565(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 16)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_PFC;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
+ drv->handle.LayerCfg[1].InputOffset =
+ bb->src_stride / sizeof(uint16_t) - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ HAL_DMA2D_Start(&drv->handle,
+ (uint32_t)bb->src_row + bb->src_x * sizeof(uint16_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ bb->width, bb->height);
+ return true;
+}
+
+static void dma2d_rgba8888_blend_mono4_first_col(const gfx_bitblt_t* bb) {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_alpha = src_ptr[0] >> 4;
+ fg_alpha = (fg_alpha * bb->src_alpha) / 15;
+ dst_ptr[0] = gfx_color32_blend_a8(
+ bb->src_fg, gfx_color32_to_color(dst_ptr[0]), fg_alpha);
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+static void dma2d_rgba8888_blend_mono4_last_col(const gfx_bitblt_t* bb) {
+ uint32_t* dst_ptr = (uint32_t*)bb->dst_row + (bb->dst_x + bb->width - 1);
+ uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2;
+
+ int height = bb->height;
+
+ while (height-- > 0) {
+ uint8_t fg_alpha = src_ptr[0] & 0x0F;
+ fg_alpha = (fg_alpha * bb->src_alpha) / 15;
+ dst_ptr[0] = gfx_color32_blend_a8(
+ bb->src_fg, gfx_color32_to_color(dst_ptr[0]), fg_alpha);
+ dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
+ src_ptr += bb->src_stride / sizeof(*src_ptr);
+ }
+}
+
+bool dma2d_rgba8888_blend_mono4(const gfx_bitblt_t* params) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(params, 32) ||
+ !gfx_bitblt_check_src_x(params, 4)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ gfx_bitblt_t bb_copy = *params;
+ gfx_bitblt_t* bb = &bb_copy;
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ if (bb->src_x & 1) {
+ // First column of mono4 bitmap is odd
+ // Use the CPU to draw the first column
+ dma2d_rgba8888_blend_mono4_first_col(bb);
+ bb->dst_x += 1;
+ bb->src_x += 1;
+ bb->width -= 1;
+ }
+
+ if (bb->width > 0 && bb->width & 1) {
+ // The width is odd
+ // Use the CPU to draw the last column
+ dma2d_rgba8888_blend_mono4_last_col(bb);
+ bb->width -= 1;
+ }
+
+ if (bb->width > 0) {
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_BLEND;
+ drv->handle.Init.OutputOffset =
+ bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
+ drv->handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA;
+ drv->handle.LayerCfg[1].InputAlpha = bb->src_alpha;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ dma2d_config_clut(
+ 1, gfx_color_to_color32(bb->src_fg),
+ gfx_color32_set_alpha(gfx_color_to_color32(bb->src_fg), 0));
+
+ drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
+ drv->handle.LayerCfg[0].InputOffset =
+ bb->dst_stride / sizeof(uint32_t) - bb->width;
+ drv->handle.LayerCfg[0].AlphaMode = 0;
+ drv->handle.LayerCfg[0].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 0);
+
+ HAL_DMA2D_BlendingStart(
+ &drv->handle, (uint32_t)bb->src_row + bb->src_x / 2,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), bb->width,
+ bb->height);
+ }
+
+ return true;
+}
+
+bool dma2d_rgba8888_blend_mono8(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 8)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ uint32_t src_fg =
+ gfx_color32_replace_a(gfx_color_to_color32(bb->src_fg), bb->src_alpha);
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_BLEND;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;
+ drv->handle.LayerCfg[1].InputOffset = bb->src_stride - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA;
+ drv->handle.LayerCfg[1].InputAlpha = src_fg;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ drv->handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
+ drv->handle.LayerCfg[0].InputOffset =
+ bb->dst_stride / sizeof(uint32_t) - bb->width;
+ drv->handle.LayerCfg[0].AlphaMode = 0;
+ drv->handle.LayerCfg[0].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 0);
+
+ HAL_DMA2D_BlendingStart(&drv->handle, (uint32_t)bb->src_row + bb->src_x,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ bb->width, bb->height);
+
+ return true;
+}
+
+bool dma2d_rgba8888_copy_mono8(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 8)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_PFC;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;
+ drv->handle.LayerCfg[1].InputOffset = bb->src_stride - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = gfx_color_to_color32(bb->src_fg);
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row + bb->src_x,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ bb->width, bb->height);
+
+ return true;
+}
+
+bool dma2d_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 32) || !gfx_bitblt_check_src_x(bb, 32)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ if (bb->src_downscale > 0) {
+ return false;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_PFC;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;
+ drv->handle.LayerCfg[1].InputOffset =
+ bb->src_stride / sizeof(uint32_t) - bb->width;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ HAL_DMA2D_Start(&drv->handle,
+ (uint32_t)bb->src_row + bb->src_x * sizeof(uint32_t),
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ bb->width, bb->height);
+ return true;
+}
+
+#ifdef USE_HW_JPEG_DECODER
+static bool dma2d_rgba8888_copy_ycbcr(const gfx_bitblt_t* bb, uint32_t css) {
+ dma2d_driver_t* drv = &g_dma2d_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!gfx_bitblt_check_dst_x(bb, 32)) {
+ return false;
+ }
+
+ dma2d_wait();
+
+ if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) {
+ return false;
+ }
+
+ drv->handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
+ drv->handle.Init.Mode = DMA2D_M2M_PFC;
+ drv->handle.Init.OutputOffset = bb->dst_stride / sizeof(uint32_t) - bb->width;
+ HAL_DMA2D_Init(&drv->handle);
+
+ drv->handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_YCBCR;
+ drv->handle.LayerCfg[1].InputOffset = 0;
+ drv->handle.LayerCfg[1].ChromaSubSampling = css;
+ drv->handle.LayerCfg[1].AlphaMode = 0;
+ drv->handle.LayerCfg[1].InputAlpha = 0;
+ HAL_DMA2D_ConfigLayer(&drv->handle, 1);
+
+ HAL_DMA2D_Start(&drv->handle, (uint32_t)bb->src_row,
+ (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t),
+ bb->width, bb->height);
+
+ // DMA2D overwrites CLUT during YCbCr conversion
+ // (seems to be a bug or an undocumented feature)
+ drv->clut_valid = false;
+
+ return true;
+}
+
+bool dma2d_rgba8888_copy_ycbcr420(const gfx_bitblt_t* bb) {
+ return dma2d_rgba8888_copy_ycbcr(bb, DMA2D_CSS_420);
+}
+
+bool dma2d_rgba8888_copy_ycbcr422(const gfx_bitblt_t* bb) {
+ return dma2d_rgba8888_copy_ycbcr(bb, DMA2D_CSS_422);
+}
+
+bool dma2d_rgba8888_copy_ycbcr444(const gfx_bitblt_t* bb) {
+ return dma2d_rgba8888_copy_ycbcr(bb, DMA2D_NO_CSS);
+}
+
+bool dma2d_rgba8888_copy_y(const gfx_bitblt_t* bb) {
+ gfx_bitblt_t bb_copy = *bb;
+
+ if (bb->height % 8 != 0 || bb->width % 8 != 0) {
+ return false;
+ }
+
+ // src cotains only Y channel organized in 8x8 blocks
+
+ bb_copy.height = 8;
+ bb_copy.width = 8;
+ bb_copy.src_stride = 8;
+ bb_copy.src_fg = gfx_color_rgb(255, 255, 255);
+
+ for (uint16_t y = 0; y < bb->height; y += 8) {
+ bb_copy.dst_x = 0;
+ for (uint16_t x = 0; x < bb->width; x += 8) {
+ if (!dma2d_rgba8888_copy_mono8(&bb_copy)) {
+ return false;
+ }
+ bb_copy.dst_x += 8;
+ bb_copy.src_row = (uint8_t*)bb_copy.src_row + 64;
+ }
+ bb_copy.dst_y += 8;
+ }
+ return true;
+}
+
+#endif // USE_HW_JPEG_DECODER
+
+#endif // KERNEL_MODE
diff --git a/core/embed/io/gfx/gfx_color.c b/core/embed/io/gfx/gfx_color.c
new file mode 100644
index 00000000..4b0f641d
--- /dev/null
+++ b/core/embed/io/gfx/gfx_color.c
@@ -0,0 +1,48 @@
+/*
+ * 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/>.
+ */
+
+#include <io/gfx_color.h>
+
+const gfx_color16_t* gfx_color16_gradient_a4(gfx_color_t fg_color,
+ gfx_color_t bg_color) {
+ static gfx_color16_t cache[16] = {0};
+
+ if (gfx_color_to_color16(bg_color) != cache[0] ||
+ gfx_color_to_color16(fg_color) != cache[15]) {
+ for (int alpha = 0; alpha < 16; alpha++) {
+ cache[alpha] = gfx_color16_blend_a4(fg_color, bg_color, alpha);
+ }
+ }
+
+ return cache;
+}
+
+const gfx_color32_t* gfx_color32_gradient_a4(gfx_color_t fg_color,
+ gfx_color_t bg_color) {
+ static gfx_color32_t cache[16] = {0};
+
+ if (bg_color != gfx_color32_to_color(cache[0]) ||
+ fg_color != gfx_color32_to_color(cache[15])) {
+ for (int alpha = 0; alpha < 16; alpha++) {
+ cache[alpha] = gfx_color32_blend_a4(fg_color, bg_color, alpha);
+ }
+ }
+
+ return cache;
+}
diff --git a/core/embed/io/gfx/gfx_draw.c b/core/embed/io/gfx/gfx_draw.c
new file mode 100644
index 00000000..223ff372
--- /dev/null
+++ b/core/embed/io/gfx/gfx_draw.c
@@ -0,0 +1,109 @@
+/*
+ * 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/>.
+ */
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include <io/display.h>
+#include <io/gfx_draw.h>
+
+typedef struct {
+ int16_t dst_x;
+ int16_t dst_y;
+ int16_t src_x;
+ int16_t src_y;
+ int16_t width;
+ int16_t height;
+} gfx_clip_t;
+
+static inline gfx_clip_t gfx_clip(gfx_rect_t dst) {
+ int16_t dst_x = dst.x0;
+ int16_t dst_y = dst.y0;
+
+ int16_t src_x = 0;
+ int16_t src_y = 0;
+
+ // Normalize negative top-left of destination rectangle
+ if (dst_x < 0) {
+ src_x -= dst_x;
+ dst_x = 0;
+ }
+
+ if (dst_y < 0) {
+ src_y -= dst_y;
+ dst_y = 0;
+ }
+
+ // Calculate dimension of effective rectangle
+ int16_t width = MIN(DISPLAY_RESX, dst.x1) - dst_x;
+ int16_t height = MIN(DISPLAY_RESY, dst.y1) - dst_y;
+
+ gfx_clip_t clip = {
+ .dst_x = dst_x,
+ .dst_y = dst_y,
+ .src_x = src_x,
+ .src_y = src_y,
+ .width = width,
+ .height = height,
+ };
+
+ return clip;
+}
+
+void gfx_clear(void) {
+ gfx_bitblt_t bb = {
+ // Destination bitmap
+ .height = DISPLAY_RESY,
+ .width = DISPLAY_RESX,
+ .dst_row = NULL,
+ .dst_x = 0,
+ .dst_y = 0,
+ .dst_stride = 0,
+
+ // Source bitmap
+ .src_fg = 0,
+ .src_alpha = 255,
+ };
+
+ display_fill(&bb);
+}
+
+void gfx_draw_bar(gfx_rect_t rect, gfx_color_t color) {
+ gfx_clip_t clip = gfx_clip(rect);
+
+ if (clip.width <= 0 || clip.height <= 0) {
+ return;
+ }
+
+ gfx_bitblt_t bb = {
+ // Destination bitmap
+ .height = clip.height,
+ .width = clip.width,
+ .dst_row = NULL,
+ .dst_x = clip.dst_x,
+ .dst_y = clip.dst_y,
+ .dst_stride = 0,
+
+ // Source bitmap
+ .src_fg = color,
+ .src_alpha = 255,
+ };
+
+ display_fill(&bb);
+}
diff --git a/core/embed/io/gfx/inc/io/dma2d_bitblt.h b/core/embed/io/gfx/inc/io/dma2d_bitblt.h
new file mode 100644
index 00000000..0f36fe43
--- /dev/null
+++ b/core/embed/io/gfx/inc/io/dma2d_bitblt.h
@@ -0,0 +1,62 @@
+/*
+ * 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 <io/gfx_bitblt.h>
+
+#ifdef KERNEL_MODE
+
+// Initializes DMA2D peripheral
+void dma2d_init(void);
+
+// Deinitializes DMA2D peripheral
+void dma2d_deinit(void);
+
+#endif
+
+// Waits until any pending DMA2D operation is finished
+void dma2d_wait(void);
+
+// Following functions are hardware (DMA2D) accelerated versions
+// of `gfx_rgb565_xxx()` and `gfx_rgba8888_xxx()` function from `gfx_bitblt.h`
+
+// These functions may return `false`, indicating that the accelerated
+// operation cannot be performed and must be implemented in software
+
+bool dma2d_rgb565_fill(const gfx_bitblt_t* bb);
+bool dma2d_rgb565_copy_mono4(const gfx_bitblt_t* bb);
+bool dma2d_rgb565_copy_rgb565(const gfx_bitblt_t* bb);
+bool dma2d_rgb565_blend_mono4(const gfx_bitblt_t* bb);
+bool dma2d_rgb565_blend_mono8(const gfx_bitblt_t* bb);
+
+bool dma2d_rgba8888_fill(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_copy_mono4(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_copy_mono8(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_copy_rgb565(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_blend_mono4(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_blend_mono8(const gfx_bitblt_t* bb);
+
+#ifdef USE_HW_JPEG_DECODER
+bool dma2d_rgba8888_copy_ycbcr420(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_copy_ycbcr422(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_copy_ycbcr444(const gfx_bitblt_t* bb);
+bool dma2d_rgba8888_copy_y(const gfx_bitblt_t* bb);
+#endif
diff --git a/core/embed/io/gfx/inc/io/gfx_bitblt.h b/core/embed/io/gfx/inc/io/gfx_bitblt.h
new file mode 100644
index 00000000..6fd9496f
--- /dev/null
+++ b/core/embed/io/gfx/inc/io/gfx_bitblt.h
@@ -0,0 +1,159 @@
+/*
+ * 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>
+
+#include <io/gfx_color.h>
+
+// These module provides low-level bit block transfer (bitblt)
+// operations on different bitmap/framebuffer types.
+//
+// `fill` - fills a rectangle with a solid color (with an optional
+// alpha, allowing color blending).
+//
+// `copy` - copies a bitmap or part of it to the destination bitmap.
+//
+// `blend` - blends a bitmap with a 1- or 4-bit alpha channel to the
+// destination using background and foreground colors.
+//
+// These operations might be accelerated using DMA2D (ChromART accelerator)
+// on the STM32 platform.
+
+// Represents a set of parameters for a bit block transfer operation.
+typedef struct {
+ // Pointer to the destination bitmap's first row
+ void* dst_row;
+ // Number of bytes per line in the destination bitmap
+ uint16_t dst_stride;
+ // X-coordinate of the top-left corner inside the destination
+ uint16_t dst_x;
+ // Y-coordinate of the top-left corner inside the destination
+ uint16_t dst_y;
+ // Height of the filled/copied/blended area
+ uint16_t height;
+ // Width of the filled/copied/blended area
+ uint16_t width;
+
+ // Pointer to the source bitmap's first row
+ // (unused for fill operations)
+ void* src_row;
+ // Number of bytes per line in the source bitmap
+ // (unused for fill operations)
+ uint16_t src_stride;
+ // X-coordinate of the origin in the source bitmap
+ // (unused for fill operations)
+ uint16_t src_x;
+ // Y-coordinate of the origin in the source bitmap
+ // (unused for fill operations)
+ uint16_t src_y;
+
+ // Foreground color used when copying/blending/filling
+ gfx_color_t src_fg;
+ // Background color used when copying mono bitmaps
+ gfx_color_t src_bg;
+ // Alpha value for fill operation (255 => normal fill, 0 => noop)
+ uint8_t src_alpha;
+ // Downscaling for the source bitmap
+ // (0 => no downscaling, 1 => 1/2, 2 => 1/4, 3 => 1/8)
+ uint8_t src_downscale;
+
+} gfx_bitblt_t;
+
+#ifdef KERNEL_MODE
+
+// Initializes bitblt operations
+void gfx_bitblt_init(void);
+
+// Deinitializes bitblt operations
+void gfx_bitblt_deinit(void);
+
+#endif // KERNEL_MODE
+
+// Checks if src_x and width are within the bounds of the source bitmap
+static inline bool gfx_bitblt_check_src_x(const gfx_bitblt_t* bb,
+ size_t pixel_bits) {
+ return (bb->src_x + bb->width >= bb->src_x) && // overflow check
+ (((bb->src_x + bb->width) * pixel_bits + 7) / 8 <= bb->src_stride);
+}
+
+// Checks if dst_x and width are within the bounds of the destination bitmap
+static inline bool gfx_bitblt_check_dst_x(const gfx_bitblt_t* bb,
+ size_t pixel_bits) {
+ return (bb->dst_x + bb->width >= bb->dst_x) && // overflow check
+ (((bb->dst_x + bb->width) * pixel_bits + 7) / 8 <= bb->dst_stride);
+}
+
+// Checks if dst_y and height are within the bounds of the destination bitmap
+static inline bool gfx_bitblt_check_dst_y(const gfx_bitblt_t* bb,
+ size_t fb_size) {
+ return (bb->dst_y + bb->height >= bb->dst_y) && // overflow check
+ (bb->dst_y + bb->height) * bb->dst_stride <= fb_size;
+}
+
+// If the bitblt operation is asynchronous, waits until it's finished
+void gfx_bitblt_wait(void);
+
+// Functions for RGB565 bitmap/framebuffer
+
+// Fills a rectangle with a solid color
+void gfx_rgb565_fill(const gfx_bitblt_t* bb);
+// Copies a mono bitmap (with 1-bit alpha channel)
+void gfx_rgb565_copy_mono1p(const gfx_bitblt_t* bb);
+// Copies a mono bitmap (with 4-bit alpha channel)
+void gfx_rgb565_copy_mono4(const gfx_bitblt_t* bb);
+// Copies an RGB565 bitmap
+void gfx_rgb565_copy_rgb565(const gfx_bitblt_t* bb);
+// Blends a mono bitmap (with 4-bit alpha channel)
+// with the destination bitmap
+void gfx_rgb565_blend_mono4(const gfx_bitblt_t* bb);
+// Blends a mono bitmap (with 8-bit alpha channel)
+// with the destination bitmap
+void gfx_rgb565_blend_mono8(const gfx_bitblt_t* bb);
+
+// Functions for RGBA8888 bitmap/framebuffer
+void gfx_rgba8888_fill(const gfx_bitblt_t* bb);
+// Copies a mono bitmap (with 1-bit alpha channel)
+void gfx_rgba8888_copy_mono1p(const gfx_bitblt_t* bb);
+// Copies a mono bitmap (with 4-bit alpha channel)
+void gfx_rgba8888_copy_mono4(const gfx_bitblt_t* bb);
+// Copies an RGB565 bitmap
+void gfx_rgba8888_copy_rgb565(const gfx_bitblt_t* bb);
+// Copies an RGBA8888 bitmap
+void gfx_rgba8888_copy_rgba8888(const gfx_bitblt_t* bb);
+// Blends a mono bitmap (with 4-bit alpha channel)
+// with the destination bitmap
+void gfx_rgba8888_blend_mono4(const gfx_bitblt_t* bb);
+// Blends a mono bitmap (with 8-bit alpha channel)
+// with the destination bitmap
+void gfx_rgba8888_blend_mono8(const gfx_bitblt_t* bb);
+
+// Functions for Mono8 bitmap/framebuffer
+void gfx_mono8_fill(const gfx_bitblt_t* bb);
+// Copies a mono bitmap (with 1-bit alpha channel)
+void gfx_mono8_copy_mono1p(const gfx_bitblt_t* bb);
+// Copies a mono bitmap (with 4-bit alpha channel)
+void gfx_mono8_copy_mono4(const gfx_bitblt_t* bb);
+// Blends a mono bitmap (with 1-bit alpha channel)
+// with the destination bitmap
+void gfx_mono8_blend_mono1p(const gfx_bitblt_t* bb);
+// Blends a mono bitmap (with 4-bit alpha channel)
+// with the destination bitmap
+void gfx_mono8_blend_mono4(const gfx_bitblt_t* bb);
diff --git a/core/embed/io/gfx/inc/io/gfx_color.h b/core/embed/io/gfx/inc/io/gfx_color.h
new file mode 100644
index 00000000..235eecee
--- /dev/null
+++ b/core/embed/io/gfx/inc/io/gfx_color.h
@@ -0,0 +1,373 @@
+/*
+ * 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/>.
+ */
+
+#ifndef GFX_COLOR_H
+#define GFX_COLOR_H
+
+#include <trezor_types.h>
+
+#ifdef UI_COLOR_32BIT
+#define GFX_COLOR_32BIT
+#else
+#define GFX_COLOR_16BIT
+#endif
+
+// Color in RGB565 format
+//
+// |15 8 | 7 0|
+// |---------------------------------|
+// |r r r r r g g g | g g g b b b b b|
+// |---------------------------------|
+
+typedef uint16_t gfx_color16_t;
+
+// Color in RGBA8888 format
+//
+// |31 24 |23 16 |15 8 | 7 0 |
+// |----------------------------------------------------------------------|
+// |a a a a a a a a | r r r r r r r r | g g g g g g g g | b b b b b b b b |
+// |----------------------------------------------------------------------|
+//
+
+typedef uint32_t gfx_color32_t;
+
+#ifdef GFX_COLOR_16BIT
+#define gfx_color_t gfx_color16_t
+#define gfx_color_to_color16(c) (c)
+#define gfx_color16_to_color(c) (c)
+#define gfx_color_to_color32(c) (gfx_color16_to_color32(c))
+#define gfx_color32_to_color(c) (gfx_color32_to_color16(c))
+#define gfx_color_lum(c) (gfx_color16_lum(c))
+#define gfx_color_rgb(r, g, b) gfx_color16_rgb(r, g, b)
+#define COLOR_WHITE 0xFFFF
+#define COLOR_BLACK 0x0000
+#elif defined GFX_COLOR_32BIT
+#define gfx_color_t gfx_color32_t
+#define gfx_color_to_color16(c) (gfx_color32_to_color16(c))
+#define gfx_color16_to_color(c) (gfx_color16_to_color32(c))
+#define gfx_color_to_color32(c) (c)
+#define gfx_color32_to_color(c) (c)
+#define gfx_color_lum(c) (gfx_color32_lum(c))
+#define gfx_color_rgb(r, g, b) gfx_color32_rgb(r, g, b)
+#define COLOR_WHITE 0xFFFFFFFF
+#define COLOR_BLACK 0xFF000000
+#else
+#error "GFX_COLOR_16BIT/32BIT not specified"
+#endif
+
+// Extracts red component from gfx_color16_t and converts it to 8-bit value
+#define gfx_color16_to_r(c) ((((c) & 0xF800) >> 8) | (((c) & 0xF800) >> 13))
+// Extracts green component from gfx_color16_t and converts it to 8-bit value
+#define gfx_color16_to_g(c) ((((c) & 0x07E0) >> 3) | (((c) & 0x07E0) >> 9))
+// Extracts blue component from gfx_color16_t and converts it to 8-bit value
+#define gfx_color16_to_b(c) ((((c) & 0x001F) << 3) | (((c) & 0x001F) >> 2))
+
+// Extracts red component from gfx_color32_t
+#define gfx_color32_to_r(c) (((c) & 0x00FF0000) >> 16)
+// Extracts green component from gfx_color32_t
+#define gfx_color32_to_g(c) (((c) & 0x0000FF00) >> 8)
+// Extracts blue component from gfx_color32_t
+#define gfx_color32_to_b(c) (((c) & 0x000000FF) >> 0)
+// Extracts alpha component from gfx_color32_t
+#define gfx_color32_to_a(c) (((c) & 0xFF000000) >> 24)
+// Sets alpha component of gfx_color32_t
+#define gfx_color32_replace_a(c, a) (((c) & 0x00FFFFFF) | ((a) << 24))
+
+// 4-bit linear interpolation between `fg` and `bg`
+#define a4_lerp(fg, bg, alpha) (((fg) * (alpha) + ((bg) * (15 - (alpha)))) / 15)
+// 8-bit linear interpolation between `fg` and `bg`
+#define a8_lerp(fg, bg, alpha) \
+ (((fg) * (alpha) + ((bg) * (255 - (alpha)))) / 255)
+
+// Constructs a 16-bit color from the given red (r),
+// green (g), and blue (b) values in the range 0..255
+static inline gfx_color16_t gfx_color16_rgb(uint8_t r, uint8_t g, uint8_t b) {
+ return ((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | ((b & 0xF8U) >> 3);
+}
+
+// Constructs a 32-bit color from the given red (r),
+// green (g), and blue (b) values in the range 0..255.
+// Alpha is set to 255.
+static inline gfx_color32_t gfx_color32_rgb(uint8_t r, uint8_t g, uint8_t b) {
+ return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
+}
+
+// Constructs a 32-bit color from the given red (r),
+// green (g), blue (b) and alhpa (a) values in the range 0..255.
+static inline gfx_color32_t gfx_color32_rgba(uint8_t r, uint8_t g, uint8_t b,
+ uint8_t a) {
+ return (a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
+}
+
+// Converts a 16-bit color to a 32-bit color; alpha is set to 255
+static inline gfx_color32_t gfx_color16_to_color32(gfx_color16_t color) {
+ uint32_t r = gfx_color16_to_r(color);
+ uint32_t g = gfx_color16_to_g(color);
+ uint32_t b = gfx_color16_to_b(color);
+
+ return gfx_color32_rgb(r, g, b);
+}
+
+// Converts 32-bit color to 16-bit color, alpha is ignored
+static inline gfx_color16_t gfx_color32_to_color16(gfx_color32_t color) {
+ uint16_t r = (color & 0x00F80000) >> 8;
+ uint16_t g = (color & 0x0000FC00) >> 5;
+ uint16_t b = (color & 0x000000F8) >> 3;
+
+ return r | g | b;
+}
+
+// Converts 16-bit color into luminance (ranging from 0 to 255)
+static inline uint8_t gfx_color16_lum(gfx_color16_t color) {
+ uint32_t r = gfx_color16_to_r(color);
+ uint32_t g = gfx_color16_to_g(color);
+ uint32_t b = gfx_color16_to_b(color);
+
+ return (r + g + b) / 3;
+}
+
+// Converts 32-bit color into luminance (ranging from 0 to 255)
+static inline uint8_t gfx_color32_lum(gfx_color32_t color) {
+ uint32_t r = gfx_color32_to_r(color);
+ uint32_t g = gfx_color32_to_g(color);
+ uint32_t b = gfx_color32_to_b(color);
+
+ return (r + g + b) / 3;
+}
+
+#ifdef GFX_COLOR_16BIT
+// Blends foreground and background colors with 4-bit alpha
+//
+// Returns a color in 16-bit format
+//
+// If `alpha` is 0, the function returns the background color
+// If `alpha` is 15, the function returns the foreground color
+static inline gfx_color16_t gfx_color16_blend_a4(gfx_color16_t fg,
+ gfx_color16_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = (fg & 0xF800) >> 11;
+ uint16_t bg_r = (bg & 0xF800) >> 11;
+ uint16_t r = a4_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = (fg & 0x07E0) >> 5;
+ uint16_t bg_g = (bg & 0x07E0) >> 5;
+ uint16_t g = a4_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = (fg & 0x001F) >> 0;
+ uint16_t bg_b = (bg & 0x001F) >> 0;
+ uint16_t b = a4_lerp(fg_b, bg_b, alpha);
+
+ return (r << 11) | (g << 5) | b;
+}
+
+// Blends foreground and background colors with 8-bit alpha
+//
+// Returns a color in 16-bit format
+//
+// If `alpha` is 0, the function returns the background color
+// If `alpha` is 15, the function returns the foreground color
+static inline gfx_color16_t gfx_color16_blend_a8(gfx_color16_t fg,
+ gfx_color16_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = (fg & 0xF800) >> 11;
+ uint16_t bg_r = (bg & 0xF800) >> 11;
+ uint16_t r = a8_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = (fg & 0x07E0) >> 5;
+ uint16_t bg_g = (bg & 0x07E0) >> 5;
+ uint16_t g = a8_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = (fg & 0x001F) >> 0;
+ uint16_t bg_b = (bg & 0x001F) >> 0;
+ uint16_t b = a8_lerp(fg_b, bg_b, alpha);
+
+ return (r << 11) | (g << 5) | b;
+}
+
+// Blends foreground and background colors with 4-bit alpha
+//
+// Returns a color in 32-bit format
+//
+// If alpha is 0, the function returns the background color
+// If alpha is 15, the function returns the foreground color
+static inline gfx_color32_t gfx_color32_blend_a4(gfx_color16_t fg,
+ gfx_color16_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = gfx_color16_to_r(fg);
+ uint16_t bg_r = gfx_color16_to_r(bg);
+ uint16_t r = a4_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = gfx_color16_to_g(fg);
+ uint16_t bg_g = gfx_color16_to_g(bg);
+ uint16_t g = a4_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = gfx_color16_to_b(fg);
+ uint16_t bg_b = gfx_color16_to_b(bg);
+ uint16_t b = a4_lerp(fg_b, bg_b, alpha);
+
+ return gfx_color32_rgb(r, g, b);
+}
+
+// Blends foreground and background colors with 8-bit alpha
+//
+// Returns a color in 32-bit format
+//
+// If `alpha` is 0, the function returns the background color
+// If `alpha` is 255, the function returns the foreground color
+static inline gfx_color32_t gfx_color32_blend_a8(gfx_color16_t fg,
+ gfx_color16_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = gfx_color16_to_r(fg);
+ uint16_t bg_r = gfx_color16_to_r(bg);
+ uint16_t r = a8_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = gfx_color16_to_g(fg);
+ uint16_t bg_g = gfx_color16_to_g(bg);
+ uint16_t g = a8_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = gfx_color16_to_b(fg);
+ uint16_t bg_b = gfx_color16_to_b(bg);
+ uint16_t b = a8_lerp(fg_b, bg_b, alpha);
+
+ return gfx_color32_rgb(r, g, b);
+}
+
+#elif defined GFX_COLOR_32BIT
+
+// Blends foreground and background colors with 4-bit alpha
+//
+// Returns a color in 16-bit format
+//
+// If `alpha` is 0, the function returns the background color
+// If `alpha` is 15, the function returns the foreground color
+static inline gfx_color16_t gfx_color16_blend_a4(gfx_color32_t fg,
+ gfx_color32_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = gfx_color32_to_r(fg);
+ uint16_t bg_r = gfx_color32_to_r(bg);
+ uint16_t r = a4_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = gfx_color32_to_g(fg);
+ uint16_t bg_g = gfx_color32_to_g(bg);
+ uint16_t g = a4_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = gfx_color32_to_b(fg);
+ uint16_t bg_b = gfx_color32_to_b(bg);
+ uint16_t b = a4_lerp(fg_b, bg_b, alpha);
+
+ return gfx_color16_rgb(r, g, b);
+}
+
+// Blends foreground and background colors with 8-bit alpha
+//
+// Returns a color in 16-bit format
+//
+// If `alpha` is 0, the function returns the background color
+// If `alpha` is 255, the function returns the foreground color
+static inline gfx_color16_t gfx_color16_blend_a8(gfx_color32_t fg,
+ gfx_color32_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = gfx_color32_to_r(fg);
+ uint16_t bg_r = gfx_color32_to_r(bg);
+ uint16_t r = a8_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = gfx_color32_to_g(fg);
+ uint16_t bg_g = gfx_color32_to_g(bg);
+ uint16_t g = a8_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = gfx_color32_to_b(fg);
+ uint16_t bg_b = gfx_color32_to_b(bg);
+ uint16_t b = g = a8_lerp(fg_b, bg_b, alpha);
+
+ return gfx_color16_rgb(r, g, b);
+}
+
+// Blends foreground and background colors with 4-bit alpha
+//
+// Returns a color in 32-bit format
+//
+// If `alpha` is 0, the function returns the background color
+// If `alpha` is 15, the function returns the foreground color
+static inline gfx_color32_t gfx_color32_blend_a4(gfx_color32_t fg,
+ gfx_color32_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = gfx_color32_to_r(fg);
+ uint16_t bg_r = gfx_color32_to_r(bg);
+ uint16_t r = a4_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = gfx_color32_to_g(fg);
+ uint16_t bg_g = gfx_color32_to_g(bg);
+ uint16_t g = a4_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = gfx_color32_to_b(fg);
+ uint16_t bg_b = gfx_color32_to_b(bg);
+ uint16_t b = a4_lerp(fg_b, bg_b, alpha);
+
+ return gfx_color32_rgb(r, g, b);
+}
+
+// Blends foreground and background colors with 8-bit alpha
+//
+// Returns a color in 32-bit format
+//
+// If `alpha` is 0, the function returns the background color
+// If `alpha` is 15, the function returns the foreground color
+static inline gfx_color32_t gfx_color32_blend_a8(gfx_color32_t fg,
+ gfx_color32_t bg,
+ uint8_t alpha) {
+ uint16_t fg_r = gfx_color32_to_r(fg);
+ uint16_t bg_r = gfx_color32_to_r(bg);
+ uint16_t r = a8_lerp(fg_r, bg_r, alpha);
+
+ uint16_t fg_g = gfx_color32_to_g(fg);
+ uint16_t bg_g = gfx_color32_to_g(bg);
+ uint16_t g = a8_lerp(fg_g, bg_g, alpha);
+
+ uint16_t fg_b = gfx_color32_to_b(fg);
+ uint16_t bg_b = gfx_color32_to_b(bg);
+ uint16_t b = a8_lerp(fg_b, bg_b, alpha);
+
+ return gfx_color32_rgb(r, g, b);
+}
+
+#else
+#error "GFX_COLOR_16BIT/32BIT not specified"
+#endif
+
+// Returns a gradient as an array of 16 consecutive 16-bit colors
+//
+// Each element in the array represents a color, with `retval[0]` being
+// the background (`bg`) color and `retval[15]` the foreground (`fg`) color
+const gfx_color16_t* gfx_color16_gradient_a4(gfx_color_t fg, gfx_color_t bg);
+
+// Returns a gradient as an array of 16 consecutive 32-bit colors
+//
+// Each element in the array represents a color, with `retval[0]` being
+// the background (`bg`) color and `retval[15]` the foreground (`fg`) color
+const gfx_color32_t* gfx_color32_gradient_a4(gfx_color_t fg, gfx_color_t bg);
+
+// Returns a color with alpha channel set
+//
+// The original color is not modified
+static inline gfx_color32_t gfx_color32_set_alpha(gfx_color32_t c,
+ uint8_t alpha) {
+ return (c & 0xFFFFFF) | (alpha << 24);
+}
+
+#endif // GFX_COLOR_H
diff --git a/core/embed/io/gfx/inc/io/gfx_draw.h b/core/embed/io/gfx/inc/io/gfx_draw.h
new file mode 100644
index 00000000..2815965d
--- /dev/null
+++ b/core/embed/io/gfx/inc/io/gfx_draw.h
@@ -0,0 +1,106 @@
+/*
+ * 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 <io/gfx_color.h>
+
+// 2D rectangle coordinates
+//
+// `x0`, `y0` - top-left coordinates
+// `x1`, `y1` - bottom-right coordinates point (not included)
+typedef struct {
+ int16_t x0;
+ int16_t y0;
+ int16_t x1;
+ int16_t y1;
+} gfx_rect_t;
+
+// Builds a rectangle (`gfx_rect_t`) from top-left coordinates and dimensions
+static inline gfx_rect_t gfx_rect_wh(int16_t x, int16_t y, int16_t w,
+ int16_t h) {
+ gfx_rect_t rect = {
+ .x0 = x,
+ .y0 = y,
+ .x1 = x + w,
+ .y1 = y + h,
+ };
+
+ return rect;
+}
+
+// Builds a rectangle (`gfx_rect_t`) from top-left and bottom-right coordinates
+static inline gfx_rect_t gfx_rect(int16_t x0, int16_t y0, int16_t x1,
+ int16_t y1) {
+ gfx_rect_t rect = {
+ .x0 = x0,
+ .y0 = y0,
+ .x1 = x1,
+ .y1 = y1,
+ };
+
+ return rect;
+}
+
+// 2D offset/ coordinates
+typedef struct {
+ int16_t x;
+ int16_t y;
+} gfx_offset_t;
+
+// Builds a `gfx_offset_t` structure
+static inline gfx_offset_t gfx_offset(int16_t x, int16_t y) {
+ gfx_offset_t offset = {
+ .x = x,
+ .y = y,
+ };
+
+ return offset;
+}
+
+// 2D size in pixels
+typedef struct {
+ int16_t x;
+ int16_t y;
+} gfx_size_t;
+
+// Builds a `gfx_size_t` structure
+static inline gfx_size_t gfx_size(int16_t x, int16_t y) {
+ gfx_size_t size = {
+ .x = x,
+ .y = y,
+ };
+
+ return size;
+}
+
+// Format of pixels in a bitmap
+typedef enum {
+ GFX_FORMAT_UNKNOWN, //
+ GFX_FORMAT_MONO1P, // 1-bpp per pixel (packed)
+ GFX_FORMAT_MONO4, // 4-bpp per pixel
+ GFX_FORMAT_RGB565, // 16-bpp per pixel
+ GFX_FORMAT_RGBA8888, // 32-bpp
+} gfx_format_t;
+
+// Clears the display with a black color.
+void gfx_clear(void);
+
+// Fills a rectangle with a specified color.
+void gfx_draw_bar(gfx_rect_t rect, gfx_color_t color);
diff --git a/core/embed/io/gfx/inc/io/jpegdec.h b/core/embed/io/gfx/inc/io/jpegdec.h
new file mode 100644
index 00000000..cf5a12f3
--- /dev/null
+++ b/core/embed/io/gfx/inc/io/jpegdec.h
@@ -0,0 +1,139 @@
+/*
+ * 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>
+
+// Maximum number of blocks (8x8) in a slice.
+// The more blocks we use, the decodeer is faster.
+// Minimum value is 4 to support 4:2:0 subsampling (MCU is 16x16).
+#define JPEGDEC_MAX_SLICE_BLOCKS 16
+
+// Size of Y/YCbCr data buffer
+// The worst case is 192 bytes per block (8x8 pixels) for 4:4:4 subsampling
+#define JPEGDEC_YCBCR_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8 * 3)
+
+// Maximum size of the RGBA8888 buffer for a slice.
+#define JPEGDEC_RGBA8888_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8 * 4)
+
+// Maximum size of the MONO8 buffer for a slice
+#define JPEGDEC_MONO8_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8)
+
+typedef struct jpegdec jpegdec_t;
+
+typedef enum {
+ // Decoder needs more data
+ // (jpegdec_process should be called with more data)
+ JPEGDEC_STATE_NEED_DATA,
+ // Image info is ready
+ // (jpegdec_get_info can be called to get the image info)
+ JPEGDEC_STATE_INFO_READY,
+ // Decoded slice is ready
+ // (jpegdec_get_slice_xxx can be called to get the slice data)
+ JPEGDEC_STATE_SLICE_READY,
+ // Decoding is finished
+ JPEGDEC_STATE_FINISHED,
+ // Error occurred, decoding is stopped
+ JPEGDEC_STATE_ERROR,
+} jpegdec_state_t;
+
+typedef enum {
+ JPEGDEC_IMAGE_GRAYSCALE, // Gray scale image
+ JPEGDEC_IMAGE_YCBCR420, // Color image with 4:2:0 subsampling
+ JPEGDEC_IMAGE_YCBCR422, // Color image with 4:2:2 subsampling
+ JPEGDEC_IMAGE_YCBCR444, // Color image with 4:4:4 subsampling
+} jpegdec_image_format_t;
+
+typedef struct {
+ // Pointer to the data
+ const uint8_t* data;
+ // Size of the data in bytes
+ size_t size;
+ // Current offset in the data
+ size_t offset;
+ // Set to true when no more data is available
+ bool last_chunk;
+} jpegdec_input_t;
+
+typedef struct {
+ // Image format
+ jpegdec_image_format_t format;
+ // Image width in pixels
+ int16_t width;
+ // Image height in pixels
+ int16_t height;
+} jpegdec_image_t;
+
+typedef struct {
+ // Slice x-coordinate
+ int16_t x;
+ // Slice y-coordinate
+ int16_t y;
+ // Slice width
+ int16_t width;
+ // Slice height
+ int16_t height;
+} jpegdec_slice_t;
+
+// Initialize and reset the decoder internal state
+bool jpegdec_open(void);
+
+// Release the decoder and free resources
+void jpegdec_close(void);
+
+// Process all or part of the input buffer and advances the `input->offset`
+//
+// `input->offset` must be aligned to 4 bytes.
+// `input->size` must be aligned to 4 bytes except for the last chunk.
+// `input->last_chunk` must be set to true when no more data is available.
+//
+// Returns the current state of the decoder:
+// - `JPEGDEC_STATE_NEED_DATA` - more data is needed
+// - `JPEGDEC_STATE_INFO_READY` - the image info is ready
+// - `JPEGDEC_STATE_SLICE_READY` - a decoded slice is ready
+// - `JPEGDEC_STATE_FINISHED` - the decoding is finished
+// - `JPEGDEC_STATE_ERROR` - an error occurred
+jpegdec_state_t jpegdec_process(jpegdec_input_t* input);
+
+// Get the decoded image info
+//
+// Can be called anytimer if the decoder went through the
+// `JPEGDEC_STATE_INFO_READY` state.
+//
+// Returns true if the info is available
+bool jpegdec_get_info(jpegdec_image_t* info);
+
+// Copy the last decoded slice to the buffer
+//
+// `rgba8888` must be a buffer of at least `JPEGDEC_RGBA8888_BUFFER_SIZE`
+// bytes and must be aligned to 4 bytes.
+//
+// Can be called immediately after `jpegdec_process` returns
+// `JPEGDEC_STATE_SLICE_READY`.
+bool jpegdec_get_slice_rgba8888(uint32_t* rgba8888, jpegdec_slice_t* slice);
+
+// Copy the last decoded slice to the buffer
+//
+// `mono8` must be a buffer of at least `JPEGDEC_MONO8_BUFFER_SIZE`
+// bytes and must be aligned to 4 bytes.
+//
+// Can be called immediately after `jpegdec_process` returns
+// `JPEGDEC_STATE_SLICE_READY`.
+bool jpegdec_get_slice_mono8(uint32_t* mono8, jpegdec_slice_t* slice);
diff --git a/core/embed/io/gfx/inc/io/terminal.h b/core/embed/io/gfx/inc/io/terminal.h
new file mode 100644
index 00000000..278a50ba
--- /dev/null
+++ b/core/embed/io/gfx/inc/io/terminal.h
@@ -0,0 +1,67 @@
+/*
+ * 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 <io/gfx_color.h>
+
+/**
+ * Sets foreground and background colors for terminal text.
+ *
+ * Note: The current implementation does not support per-character colors.
+ * It only supports setting global foreground and background colors, which
+ * may be used before printing any text to the terminal.
+ *
+ * @param fgcolor Foreground color.
+ * @param bgcolor Background color.
+ */
+void term_set_color(gfx_color_t fgcolor, gfx_color_t bgcolor);
+
+/**
+ * Prints a text of given length to the terminal.
+ *
+ * @param text Text to print.
+ * @param textlen Number of characters to print from the text.
+ */
+void term_nprint(const char *text, int textlen);
+
+/**
+ * Prints null-terminated text to the terminal.
+ *
+ * @param text Text to print.
+ */
+void term_print(const char *text);
+
+/**
+ * Prints a 32-bit integer in decimal format to the terminal.
+ *
+ * @param value Integer value to print.
+ */
+void term_print_int32(int32_t value);
+
+/**
+ * Prints printf-style formatted text to the terminal.
+ *
+ * The function internally uses `vsnprintf_` to format the text.
+ *
+ * @param fmt Format string (printf-style).
+ * @param ... Additional arguments for formatting.
+ */
+void term_printf(const char *fmt, ...)
+ __attribute__((__format__(__printf__, 1, 2)));
diff --git a/core/embed/io/gfx/jpegdec/stm32u5/jpegdec.c b/core/embed/io/gfx/jpegdec/stm32u5/jpegdec.c
new file mode 100644
index 00000000..8a37259d
--- /dev/null
+++ b/core/embed/io/gfx/jpegdec/stm32u5/jpegdec.c
@@ -0,0 +1,580 @@
+/*
+ * 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/>.
+ */
+
+#ifdef KERNEL_MODE
+
+#include <trezor_bsp.h>
+#include <trezor_rtl.h>
+
+#include <io/dma2d_bitblt.h>
+#include <io/jpegdec.h>
+#include <rtl/sizedefs.h>
+#include <sys/systick.h>
+
+// Fixes of STMicro bugs in cmsis-device-u5
+#undef JPEG_BASE_S
+#define JPEG_BASE_S (AHB1PERIPH_BASE_S + 0x0A000UL)
+#undef JPEG_CFR_CEOCF_Pos
+#define JPEG_CFR_CEOCF_Pos (5U)
+#undef JPEG_CFR_CHPDF_Pos
+#define JPEG_CFR_CHPDF_Pos (6U)
+
+// JPEG decoder processing timeout in microseconds.
+// The timeout must be selected to be long enough to process a single slice.
+// 100us @ 160MHZ CPU clock speed => 8000 CPU cycles
+// JPEG decoder issues 1pixel/1cycles => 125 8x8 blocks
+#define JPEGDEC_PROCESSING_TIMEOUT_US 100
+
+// JPEG decoder state
+struct jpegdec {
+ // Set if the decoder is in use
+ bool inuse;
+
+ // DMA channel for JPEG data output
+ DMA_HandleTypeDef hdma;
+
+ // Current state of the FSM
+ jpegdec_state_t state;
+ // Decoded image parameters
+ jpegdec_image_t image;
+
+ // Decoded image MCU width
+ int16_t mcu_width;
+ // Decoded image MCU height
+ int16_t mcu_height;
+ // Decoded image MCU size in bytes
+ size_t mcu_size;
+
+ // Decoded YCbCr data for the current slice
+ uint32_t ycbcr_buffer[JPEGDEC_YCBCR_BUFFER_SIZE / sizeof(uint32_t)];
+
+ // Current slice x-coordinate
+ int16_t slice_x;
+ // Current slice y-coordinate
+ int16_t slice_y;
+ // Current slice width
+ int16_t slice_width;
+ // Current slice height
+ int16_t slice_height;
+};
+
+// JPEG decoder instance
+jpegdec_t g_jpegdec = {
+ .inuse = false,
+};
+
+bool jpegdec_open(void) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (dec->inuse) {
+ return false;
+ }
+
+ memset(dec, 0, sizeof(jpegdec_t));
+ dec->inuse = true;
+
+ __HAL_RCC_JPEG_FORCE_RESET();
+ __HAL_RCC_JPEG_RELEASE_RESET();
+ __HAL_RCC_JPEG_CLK_ENABLE();
+
+ // Configure JPEG codec for decoding and header parsing
+ JPEG->CR |= JPEG_CR_JCEN;
+ JPEG->CONFR1 |= JPEG_CONFR1_DE;
+ JPEG->CONFR1 |= JPEG_CONFR1_HDR;
+ JPEG->CONFR0 |= JPEG_CONFR0_START;
+ JPEG->CR |= JPEG_CR_OFF | JPEG_CR_IFF;
+
+ // Configure DMA channel for JPEG data output
+ __HAL_RCC_GPDMA1_CLK_ENABLE();
+ dec->hdma.Instance = GPDMA1_Channel4;
+ dec->hdma.Init.Request = GPDMA1_REQUEST_JPEG_TX;
+ dec->hdma.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
+ dec->hdma.Init.Direction = DMA_PERIPH_TO_MEMORY;
+ dec->hdma.Init.SrcInc = DMA_SINC_FIXED;
+ dec->hdma.Init.DestInc = DMA_DINC_INCREMENTED;
+ dec->hdma.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
+ dec->hdma.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
+ dec->hdma.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
+ dec->hdma.Init.SrcBurstLength = 8;
+ dec->hdma.Init.DestBurstLength = 8;
+ dec->hdma.Init.TransferAllocatedPort =
+ DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0;
+ dec->hdma.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
+ dec->hdma.Init.Mode = DMA_NORMAL;
+
+ if (HAL_DMA_Init(&dec->hdma) != HAL_OK) {
+ dec->hdma.Instance = NULL;
+ goto cleanup;
+ }
+
+ if (HAL_DMA_ConfigChannelAttributes(
+ &dec->hdma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | DMA_CHANNEL_SRC_SEC |
+ DMA_CHANNEL_DEST_SEC) != HAL_OK) {
+ goto cleanup;
+ }
+
+ return true;
+
+cleanup:
+ jpegdec_close();
+ return false;
+}
+
+void jpegdec_close(void) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (dec->hdma.Instance != NULL) {
+ HAL_DMA_Abort(&dec->hdma);
+ HAL_DMA_DeInit(&dec->hdma);
+ }
+
+ __HAL_RCC_JPEG_CLK_DISABLE();
+ __HAL_RCC_JPEG_FORCE_RESET();
+ __HAL_RCC_JPEG_RELEASE_RESET();
+
+ memset(dec, 0, sizeof(jpegdec_t));
+}
+
+#define READ_REG_FIELD(reg, field) (((reg) & field##_Msk) >> field##_Pos)
+
+// Extracts image parameters from the JPEG codec registers
+// and set `dec->image` and `dec->mcu_xxx` fields
+static bool jpegdec_extract_header_info(jpegdec_t *dec) {
+ jpegdec_image_t image = {0};
+ size_t mcu_size = 64; // Grayscale, 8x8 blocks
+ int16_t mcu_width = 8;
+ int16_t mcu_height = 8;
+
+ image.height = READ_REG_FIELD(JPEG->CONFR1, JPEG_CONFR1_YSIZE);
+ image.width = READ_REG_FIELD(JPEG->CONFR3, JPEG_CONFR3_XSIZE);
+
+ if (image.height == 0 || image.width == 0) {
+ // Image size is zero, invalid header
+ return false;
+ }
+
+ if (image.height > 32767 || image.width > 32767) {
+ // Image is too large
+ return false;
+ }
+
+ // Number of quantization tables
+ int n_qt = 1 + READ_REG_FIELD(JPEG->CONFR1, JPEG_CONFR1_NF);
+
+ if (n_qt == 1) {
+ // 1 quantization table => Grayscale
+ image.format = JPEGDEC_IMAGE_GRAYSCALE;
+ } else if (n_qt == 3) {
+ // 3 quantization table => YCbCr
+ int y_blocks = 1 + READ_REG_FIELD(JPEG->CONFR4, JPEG_CONFR4_NB);
+ int cb_blocks = 1 + READ_REG_FIELD(JPEG->CONFR5, JPEG_CONFR5_NB);
+ int cr_blocks = 1 + READ_REG_FIELD(JPEG->CONFR6, JPEG_CONFR6_NB);
+
+ mcu_size = (y_blocks + cb_blocks + cr_blocks) * 64;
+ mcu_width = (y_blocks == 1) ? 8 : 16;
+ mcu_height = (y_blocks == 4) ? 16 : 8;
+
+ if (y_blocks == 2 && cb_blocks == 1 && cr_blocks == 1) {
+ // 4:2:2 subsampling
+ image.format = JPEGDEC_IMAGE_YCBCR422;
+ } else if (y_blocks == 4 && cb_blocks == 1 && cr_blocks == 1) {
+ // 4:2:0 subsampling
+ image.format = JPEGDEC_IMAGE_YCBCR420;
+ } else if (y_blocks == 1 && cb_blocks == 1 && cr_blocks == 1) {
+ // 4:4:4 subsampling
+ image.format = JPEGDEC_IMAGE_YCBCR444;
+ } else {
+ // Unsupported subsampling
+ return false;
+ }
+ } else {
+ // 2 or 4 quantization tables are not supported
+ return false;
+ }
+
+ dec->image = image;
+ dec->mcu_size = mcu_size;
+ dec->mcu_width = mcu_width;
+ dec->mcu_height = mcu_height;
+ return true;
+}
+
+// Starts DMA transfer of the decoded YCbCr data for the current slice
+static bool jpegdec_start_dma_transfer(jpegdec_t *dec) {
+ // Number ofs MCU that fit into the YCbCr buffer
+ int n_ycbcr = sizeof(dec->ycbcr_buffer) / dec->mcu_size;
+ // Number ofs MCUs that fit into the RGB buffer
+ int n_rgb =
+ JPEGDEC_MAX_SLICE_BLOCKS / ((dec->mcu_width * dec->mcu_height) / 64);
+ // Number of remaining MCUs in the current row
+ int n_row =
+ (dec->image.width - dec->slice_x + dec->mcu_width - 1) / dec->mcu_width;
+ // Number of MCUs to decode in the current slice
+ int mcu_count = MIN(MIN(n_ycbcr, n_rgb), n_row);
+
+ dec->slice_width = dec->mcu_width * mcu_count;
+ dec->slice_height = dec->mcu_height;
+
+ if (HAL_DMA_Start(&dec->hdma, (uint32_t)&JPEG->DOR,
+ (uint32_t)dec->ycbcr_buffer,
+ dec->mcu_size * mcu_count) != HAL_OK) {
+ return false;
+ }
+
+ JPEG->CR |= JPEG_CR_ODMAEN;
+ return true;
+}
+
+// Feeds the input FIFO with the data from the input buffer.
+// Returns `true` if at least one word was written to the FIFO.
+static inline bool jpegdec_feed_fifo(jpegdec_t *dec, jpegdec_input_t *inp) {
+ // Input FIFO needs data
+ uint32_t *ptr = (uint32_t *)&inp->data[inp->offset];
+ if (inp->offset + 16 <= inp->size) {
+ // Feed the FIFO with 16 bytes
+ JPEG->DIR = ptr[0];
+ JPEG->DIR = ptr[1];
+ JPEG->DIR = ptr[2];
+ JPEG->DIR = ptr[3];
+ inp->offset += 16;
+ return true;
+ } else if (inp->offset < inp->size) {
+ // Feed the FIFO with the remaining data
+ while (inp->offset + 4 < inp->size) {
+ JPEG->DIR = *ptr++;
+ inp->offset += 4;
+ }
+ if (inp->offset < inp->size) {
+ size_t bits = (inp->size - inp->offset) * 8;
+ JPEG->DIR = *ptr & (0xFFFFFFFF >> (32 - bits));
+ inp->offset = inp->size;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+// Advances the slice coordinates to the next slice.
+// Returns `true` if the decoding is complete.
+static inline bool jpegdec_advance_slice_coordinates(jpegdec_t *dec) {
+ dec->slice_x += dec->slice_width;
+ if (dec->slice_x >= dec->image.width) {
+ dec->slice_x = 0;
+ dec->slice_y += dec->slice_height;
+ }
+ return dec->slice_y >= dec->image.height;
+}
+
+jpegdec_state_t jpegdec_process(jpegdec_input_t *inp) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return JPEGDEC_STATE_ERROR;
+ }
+
+ // Check input buffer alignment
+ if (inp->offset < inp->size) {
+ if (!IS_ALIGNED(inp->offset, 4) ||
+ (!IS_ALIGNED(inp->size, 4) && !inp->last_chunk)) {
+ return JPEGDEC_STATE_ERROR;
+ }
+ }
+
+ switch (dec->state) {
+ case JPEGDEC_STATE_ERROR:
+ case JPEGDEC_STATE_FINISHED:
+ return dec->state;
+
+ case JPEGDEC_STATE_SLICE_READY:
+ if (jpegdec_advance_slice_coordinates(dec)) {
+ dec->state = JPEGDEC_STATE_FINISHED;
+ return dec->state;
+ }
+ // pass through
+ case JPEGDEC_STATE_INFO_READY:
+ if (!jpegdec_start_dma_transfer(dec)) {
+ dec->state = JPEGDEC_STATE_ERROR;
+ return dec->state;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ uint64_t expire_time = 0; // = 0 => not active
+ bool timed_out = false;
+ uint8_t poll_counter = 0;
+
+ for (;;) {
+ uint32_t sr = JPEG->SR;
+
+ if ((sr & JPEG_SR_IFTF) != 0) {
+ if (jpegdec_feed_fifo(dec, inp)) {
+ expire_time = 0;
+ continue; // Feed the FIFO as fast as possible
+ } else if (!inp->last_chunk) {
+ dec->state = JPEGDEC_STATE_NEED_DATA;
+ break;
+ }
+ }
+
+ if (__HAL_DMA_GET_FLAG(&dec->hdma, DMA_FLAG_TC)) {
+ // Clear status flags and prepare for the next transfer
+ HAL_DMA_PollForTransfer(&dec->hdma, HAL_DMA_FULL_TRANSFER, 0);
+ dec->state = JPEGDEC_STATE_SLICE_READY;
+ break;
+ }
+
+ if ((sr & JPEG_SR_HPDF) != 0) {
+ // Header parsing is complete
+ // Clear the HPDF flag
+ JPEG->CFR |= JPEG_CFR_CHPDF;
+ bool unexpected_header = dec->image.width > 0;
+ if (unexpected_header || !jpegdec_extract_header_info(dec)) {
+ dec->state = JPEGDEC_STATE_ERROR;
+ } else {
+ dec->state = JPEGDEC_STATE_INFO_READY;
+ }
+ break;
+ }
+
+ // Timeout processing (especially `systick_us()`) is quite expensive
+ // and therefore it is done only every 16 passes.
+ if (poll_counter-- == 0) {
+ poll_counter = 16;
+ if (expire_time == 0) {
+ // The timeout handles two situations:
+ // 1) Invalid input data that causes the JPEG codec not produce
+ // any output and the processing is stuck.
+ // 2) Unexpected JPEG codec stuck in the processing state.
+ expire_time = systick_us() + JPEGDEC_PROCESSING_TIMEOUT_US;
+ } else if (timed_out) {
+ dec->state = JPEGDEC_STATE_ERROR;
+ break;
+ } else {
+ // `timed_out` flag is checked in the next pass
+ timed_out = systick_us() > expire_time;
+ }
+ }
+ }
+
+ if (dec->state == JPEGDEC_STATE_ERROR ||
+ dec->state == JPEGDEC_STATE_FINISHED) {
+ JPEG->CR &= ~JPEG_CR_JCEN;
+ HAL_DMA_Abort(&dec->hdma);
+ }
+
+ return dec->state;
+}
+
+bool jpegdec_get_info(jpegdec_image_t *image) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return false;
+ }
+
+ if (dec->image.width == 0 || dec->image.height == 0) {
+ return false;
+ }
+
+ *image = dec->image;
+ return true;
+}
+
+bool jpegdec_get_slice_rgba8888(uint32_t *rgba8888, jpegdec_slice_t *slice) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return false;
+ }
+
+ if (dec->state != JPEGDEC_STATE_SLICE_READY) {
+ return false;
+ }
+
+ if (!IS_ALIGNED((uint32_t)rgba8888, 4)) {
+ return false;
+ }
+
+ slice->width = dec->slice_width;
+ slice->height = dec->slice_height;
+ slice->x = dec->slice_x;
+ slice->y = dec->slice_y;
+
+ gfx_bitblt_t bb = {
+ .height = dec->slice_height,
+ .width = dec->slice_width,
+ .dst_row = rgba8888,
+ .dst_stride = dec->slice_width * 4,
+ .dst_x = 0,
+ .dst_y = 0,
+ .src_row = dec->ycbcr_buffer,
+ .src_stride = 0,
+ .src_x = 0,
+ .src_y = 0,
+ .src_fg = 0,
+ .src_bg = 0,
+ .src_alpha = 255,
+ };
+
+ bool result = false;
+
+ switch (dec->image.format) {
+ case JPEGDEC_IMAGE_YCBCR420:
+ result = dma2d_rgba8888_copy_ycbcr420(&bb);
+ break;
+ case JPEGDEC_IMAGE_YCBCR422:
+ result = dma2d_rgba8888_copy_ycbcr422(&bb);
+ break;
+ case JPEGDEC_IMAGE_YCBCR444:
+ result = dma2d_rgba8888_copy_ycbcr444(&bb);
+ break;
+ case JPEGDEC_IMAGE_GRAYSCALE:
+ result = dma2d_rgba8888_copy_y(&bb);
+ break;
+ default:
+ result = false;
+ break;
+ }
+
+ // Wait until the DMA transfer is complete so that the caller can use
+ // data in the `rgba8888` buffer immediately.
+ dma2d_wait();
+
+ return result;
+}
+
+// Initialize DMA base copy for fast copying of 8x8 blocks
+//
+// 'dst_stride' is the number of bytes between the start of two consecutive
+// rows in the destination buffer.
+static void fast_copy_init(DMA_HandleTypeDef *hdma, size_t dst_stride) {
+ hdma->Instance = GPDMA1_Channel13;
+ hdma->Init.Request = GPDMA1_REQUEST_HASH_IN;
+ hdma->Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
+ hdma->Init.Direction = DMA_MEMORY_TO_MEMORY;
+ hdma->Init.SrcInc = DMA_SINC_INCREMENTED;
+ hdma->Init.DestInc = DMA_DINC_INCREMENTED;
+ hdma->Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
+ hdma->Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
+ hdma->Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
+ hdma->Init.SrcBurstLength = 2;
+ hdma->Init.DestBurstLength = 2;
+ hdma->Init.TransferAllocatedPort =
+ DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0;
+ hdma->Init.TransferEventMode = DMA_TCEM_REPEATED_BLOCK_TRANSFER;
+ hdma->Init.Mode = DMA_NORMAL;
+ HAL_DMA_Init(hdma);
+ HAL_DMA_ConfigChannelAttributes(hdma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC |
+ DMA_CHANNEL_SRC_SEC |
+ DMA_CHANNEL_DEST_SEC);
+
+ DMA_RepeatBlockConfTypeDef rep = {0};
+
+ rep.DestAddrOffset = dst_stride - 8;
+ rep.RepeatCount = 1;
+ HAL_DMAEx_ConfigRepeatBlock(hdma, &rep);
+}
+
+// Initiate a fast copy of an 8x8 block from 'src' to 'dst'
+//
+// `src` is expected to be a pointer to the start of an 8x8 block.
+// `dst` is expected to be a pointer to destination bitmap buffer
+static inline void fast_copy_block(DMA_HandleTypeDef *hdma, uint8_t *dst,
+ uint8_t *src) {
+ while ((hdma->Instance->CSR & DMA_FLAG_IDLE) == 0)
+ ;
+
+ hdma->Lock = 0;
+ hdma->State = HAL_DMA_STATE_READY;
+
+ HAL_DMA_Start(hdma, (uint32_t)src, (uint32_t)dst, 64);
+}
+
+// Deinitialize the DMA base copy
+static inline void fast_copy_deinit(DMA_HandleTypeDef *hdma) {
+ while ((hdma->Instance->CSR & DMA_FLAG_IDLE) == 0)
+ ;
+
+ hdma->Lock = 0;
+ hdma->State = HAL_DMA_STATE_READY;
+
+ HAL_DMA_DeInit(hdma);
+}
+
+bool jpegdec_get_slice_mono8(uint32_t *mono8, jpegdec_slice_t *slice) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return false;
+ }
+
+ if (dec->state != JPEGDEC_STATE_SLICE_READY) {
+ return false;
+ }
+
+ if (!IS_ALIGNED((uint32_t)mono8, 4)) {
+ return false;
+ }
+
+ slice->width = dec->slice_width;
+ slice->height = dec->slice_height;
+ slice->x = dec->slice_x;
+ slice->y = dec->slice_y;
+
+ bool result = false;
+
+ switch (dec->image.format) {
+ case JPEGDEC_IMAGE_YCBCR420:
+ // Not implemented
+ break;
+ case JPEGDEC_IMAGE_YCBCR422:
+ // Not implemented
+ break;
+ case JPEGDEC_IMAGE_YCBCR444:
+ // Not implemented
+ break;
+ case JPEGDEC_IMAGE_GRAYSCALE: {
+ static DMA_HandleTypeDef hdma = {0};
+ fast_copy_init(&hdma, dec->slice_width);
+ uint8_t *src = (uint8_t *)dec->ycbcr_buffer;
+ for (int y = 0; y < dec->slice_height; y += 8) {
+ for (int x = 0; x < dec->slice_width; x += 8) {
+ uint8_t *dst = (uint8_t *)mono8 + y * dec->slice_width + x;
+ fast_copy_block(&hdma, dst, src);
+ src += 64;
+ }
+ }
+
+ fast_copy_deinit(&hdma);
+ result = true;
+ } break;
+
+ default:
+ result = false;
+ break;
+ }
+
+ return result;
+}
+
+#endif // KERNEL_MODE
diff --git a/core/embed/io/gfx/jpegdec/unix/jpegdec.c b/core/embed/io/gfx/jpegdec/unix/jpegdec.c
new file mode 100644
index 00000000..806f7e4c
--- /dev/null
+++ b/core/embed/io/gfx/jpegdec/unix/jpegdec.c
@@ -0,0 +1,345 @@
+/*
+ * 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/>.
+ */
+
+#include <trezor_rtl.h>
+
+#include <io/gfx_color.h>
+#include <io/jpegdec.h>
+
+#include <jpeglib.h>
+
+#define MAX_SLICE_HEIGHT 16
+#define MAX_SLICE_WIDTH \
+ (JPEGDEC_RGBA8888_BUFFER_SIZE / (MAX_SLICE_HEIGHT * sizeof(uint32_t)))
+
+// Custom source manager structure
+typedef struct {
+ struct jpeg_source_mgr pub;
+ uint8_t buffer[4096];
+ jpegdec_input_t *input;
+
+} custom_source_mgr_t;
+
+// Our internal decoder state.
+struct jpegdec {
+ bool inuse;
+
+ // jpeglib context
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+
+ // Our custom source manager
+ custom_source_mgr_t source_mgr;
+
+ // Last decoder state
+ jpegdec_state_t state;
+ // Image info
+ jpegdec_image_t image;
+
+ // Up to MAX_SLICE_HEIGHT lines of decoded data in RGBA8888 format
+ JSAMPARRAY slice_buffer;
+
+ // Current slice coordinates
+ int16_t slice_x;
+ int16_t slice_y;
+};
+
+// JPEG decoder instance
+jpegdec_t g_jpegdec = {
+ .inuse = false,
+};
+
+//---------------------------------------------------------------------
+// Custom source manager functions
+//---------------------------------------------------------------------
+
+static void init_source(j_decompress_ptr cinfo) {
+ // No special initialization is needed.
+ UNUSED(cinfo);
+}
+
+static boolean fill_input_buffer(j_decompress_ptr cinfo) {
+ custom_source_mgr_t *src = (custom_source_mgr_t *)cinfo->src;
+ jpegdec_input_t *input = src->input;
+
+ if (input->offset < input->size) {
+ size_t nbytes = MIN(input->size - input->offset, sizeof(src->buffer));
+ memcpy(src->buffer, input->data + input->offset, nbytes);
+ input->offset += nbytes;
+ src->pub.next_input_byte = src->buffer;
+ src->pub.bytes_in_buffer = nbytes;
+ return TRUE;
+ }
+
+ // If no more data is available and this is the last chunk,
+ // supply a fake EOI marker.
+
+ if (input->last_chunk) {
+ src->buffer[0] = 0xFF;
+ src->buffer[1] = JPEG_EOI;
+ src->pub.next_input_byte = src->buffer;
+ src->pub.bytes_in_buffer = 2;
+ return TRUE;
+ }
+
+ // No data available, but not the last chunk: suspend input
+ return FALSE;
+}
+
+static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ custom_source_mgr_t *src = (custom_source_mgr_t *)cinfo->src;
+
+ if (num_bytes > 0) {
+ while (num_bytes > (long)src->pub.bytes_in_buffer) {
+ num_bytes -= (long)src->pub.bytes_in_buffer;
+ fill_input_buffer(cinfo);
+ }
+ src->pub.next_input_byte += num_bytes;
+ src->pub.bytes_in_buffer -= num_bytes;
+ }
+}
+
+static void term_source(j_decompress_ptr cinfo) {
+ // No cleanup necessary
+ UNUSED(cinfo);
+}
+
+//---------------------------------------------------------------------
+// JPEG decoder functions
+//---------------------------------------------------------------------
+
+bool jpegdec_open(void) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (dec->inuse) {
+ return false;
+ }
+
+ memset(dec, 0, sizeof(jpegdec_t));
+ dec->inuse = true;
+
+ // Set up the JPEG decompression object with default error handling
+ dec->cinfo.err = jpeg_std_error(&dec->jerr);
+ jpeg_create_decompress(&dec->cinfo);
+
+ // Init our custom source manager
+ custom_source_mgr_t *src = &dec->source_mgr;
+ src->pub.init_source = init_source;
+ src->pub.fill_input_buffer = fill_input_buffer;
+ src->pub.skip_input_data = skip_input_data;
+ src->pub.resync_to_restart = jpeg_resync_to_restart;
+ src->pub.term_source = term_source;
+ src->pub.bytes_in_buffer = 0;
+ src->pub.next_input_byte = NULL;
+ src->input = NULL;
+ dec->cinfo.src = (struct jpeg_source_mgr *)src;
+
+ dec->state = JPEGDEC_STATE_NEED_DATA;
+
+ return true;
+}
+
+jpegdec_state_t jpegdec_process(jpegdec_input_t *input) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return JPEGDEC_STATE_ERROR;
+ }
+
+ dec->source_mgr.input = input;
+
+ if (dec->state == JPEGDEC_STATE_SLICE_READY) {
+ dec->slice_x += MAX_SLICE_WIDTH;
+ if (dec->slice_x < dec->image.width) {
+ // There is more data ready in the slice buffer
+ return JPEGDEC_STATE_SLICE_READY;
+ }
+ dec->slice_x = 0;
+ dec->slice_y = MIN(dec->image.height, dec->slice_y + MAX_SLICE_HEIGHT);
+
+ if (dec->slice_y >= dec->image.height) {
+ // The image is fully decoded
+ dec->state = JPEGDEC_STATE_FINISHED;
+ return dec->state;
+ }
+ }
+
+ if (dec->state == JPEGDEC_STATE_FINISHED ||
+ dec->state == JPEGDEC_STATE_ERROR) {
+ // Do nothing if the decoder is finished or in error state
+ } else if (dec->image.width == 0 && dec->image.height == 0) {
+ // Decode jpeg headers and get image parameters
+ int ret = jpeg_consume_input(&dec->cinfo);
+
+ switch (ret) {
+ case JPEG_SUSPENDED:
+ dec->state = JPEGDEC_STATE_NEED_DATA;
+ break;
+
+ case JPEG_REACHED_SOS:
+ dec->image.width = (int16_t)dec->cinfo.image_width;
+ dec->image.height = (int16_t)dec->cinfo.image_height;
+
+ if (dec->cinfo.num_components == 1) {
+ dec->image.format = JPEGDEC_IMAGE_GRAYSCALE;
+ } else if (dec->cinfo.num_components == 3) {
+ int h = dec->cinfo.comp_info[0].h_samp_factor;
+ int v = dec->cinfo.comp_info[0].v_samp_factor;
+ if (h == 2 && v == 2)
+ dec->image.format = JPEGDEC_IMAGE_YCBCR420;
+ else if (h == 2 && v == 1)
+ dec->image.format = JPEGDEC_IMAGE_YCBCR422;
+ else
+ dec->image.format = JPEGDEC_IMAGE_YCBCR444;
+ } else {
+ dec->state = JPEGDEC_STATE_ERROR;
+ break;
+ }
+
+ dec->slice_x = 0;
+ dec->slice_y = 0;
+
+ // Set the output color space
+ // (need to be set before jpeg_start_decompress)
+ dec->cinfo.out_color_space = JCS_EXT_BGRA;
+
+ jpeg_start_decompress(&dec->cinfo);
+
+ // Allocate the output buffer
+ dec->slice_buffer = (*dec->cinfo.mem->alloc_sarray)(
+ (j_common_ptr)&dec->cinfo, JPOOL_IMAGE,
+ dec->cinfo.output_width * dec->cinfo.output_components,
+ MAX_SLICE_HEIGHT);
+
+ dec->state = JPEGDEC_STATE_INFO_READY;
+ break;
+
+ default:
+ dec->state = JPEGDEC_STATE_ERROR;
+ break;
+ }
+ } else {
+ // Image headers where decoded, now we can decode the image data
+ // Read scanlines until we have a full slice
+
+ for (;;) {
+ // Row in slice buffer
+ size_t row = dec->cinfo.output_scanline - dec->slice_y;
+
+ if (row >= MAX_SLICE_HEIGHT) {
+ dec->slice_y = dec->cinfo.output_scanline;
+ row = 0;
+ }
+
+ int lines_read =
+ jpeg_read_scanlines(&dec->cinfo, &dec->slice_buffer[row], 1);
+
+ if (lines_read == 0) {
+ dec->state = JPEGDEC_STATE_NEED_DATA;
+ break;
+ }
+
+ if (row == MAX_SLICE_HEIGHT - 1 ||
+ dec->cinfo.output_scanline >= dec->cinfo.output_height) {
+ dec->state = JPEGDEC_STATE_SLICE_READY;
+ break;
+ }
+ }
+ }
+
+ return dec->state;
+}
+
+bool jpegdec_get_info(jpegdec_image_t *image) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return false;
+ }
+
+ if (dec->image.width == 0 || dec->image.height == 0) {
+ return false;
+ }
+
+ *image = dec->image;
+ return true;
+}
+
+bool jpegdec_get_slice_rgba8888(uint32_t *rgba8888, jpegdec_slice_t *slice) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return false;
+ }
+
+ if (dec->state != JPEGDEC_STATE_SLICE_READY) {
+ return false;
+ }
+
+ slice->x = dec->slice_x;
+ slice->y = dec->slice_y;
+ slice->width = MIN(dec->image.width - dec->slice_x, MAX_SLICE_WIDTH);
+ slice->height = MIN(dec->image.height - dec->slice_y, MAX_SLICE_HEIGHT);
+
+ for (int y = 0; y < slice->height; y++) {
+ void *src = &((uint32_t *)dec->slice_buffer[y])[slice->x];
+ void *dst = rgba8888 + y * slice->width;
+ memcpy(dst, src, slice->width * sizeof(uint32_t));
+ }
+
+ return true;
+}
+
+bool jpegdec_get_slice_mono8(uint32_t *mono8, jpegdec_slice_t *slice) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (!dec->inuse) {
+ return false;
+ }
+
+ if (dec->state != JPEGDEC_STATE_SLICE_READY) {
+ return false;
+ }
+
+ slice->x = dec->slice_x;
+ slice->y = dec->slice_y;
+ slice->width = MIN(dec->image.width - dec->slice_x, MAX_SLICE_WIDTH);
+ slice->height = MIN(dec->image.height - dec->slice_y, MAX_SLICE_HEIGHT);
+
+ uint8_t *dst = (uint8_t *)mono8;
+
+ for (int y = 0; y < slice->height; y++) {
+ for (int x = 0; x < slice->width; x++) {
+ gfx_color32_t color = ((uint32_t *)dec->slice_buffer[y])[slice->x + x];
+ dst[y * slice->width + x] = gfx_color32_lum(color);
+ }
+ }
+
+ return true;
+}
+
+void jpegdec_close(void) {
+ jpegdec_t *dec = &g_jpegdec;
+
+ if (dec->inuse) {
+ jpeg_destroy_decompress(&dec->cinfo);
+
+ memset(dec, 0, sizeof(jpegdec_t));
+ }
+}
diff --git a/core/embed/io/gfx/terminal/font_bitmap.c b/core/embed/io/gfx/terminal/font_bitmap.c
new file mode 100644
index 00000000..ab017f66
--- /dev/null
+++ b/core/embed/io/gfx/terminal/font_bitmap.c
@@ -0,0 +1,101 @@
+#include "font_bitmap.h"
+
+// clang-format off
+
+const uint8_t * const Font_Bitmap = (const uint8_t * const)
+ "\x00\x00\x00\x00\x00"
+ "\x00\x00\x5f\x00\x00"
+ "\x00\x07\x00\x07\x00"
+ "\x14\x7f\x14\x7f\x14"
+ "\x24\x2a\x7f\x2a\x12"
+ "\x23\x13\x08\x64\x62"
+ "\x36\x49\x56\x20\x50"
+ "\x00\x08\x07\x03\x00"
+ "\x00\x1c\x22\x41\x00"
+ "\x00\x41\x22\x1c\x00"
+ "\x2a\x1c\x7f\x1c\x2a"
+ "\x08\x08\x3e\x08\x08"
+ "\x00\x80\x70\x30\x00"
+ "\x08\x08\x08\x08\x08"
+ "\x00\x00\x60\x60\x00"
+ "\x20\x10\x08\x04\x02"
+ "\x3e\x5Why this scored 15/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.