diff --git a/core/.changelog.d/2414.added b/core/.changelog.d/2414.added new file mode 100644 index 0000000000..33ba51897e --- /dev/null +++ b/core/.changelog.d/2414.added @@ -0,0 +1 @@ +Using hardware acceleration (dma2d) for rendering diff --git a/core/SConscript.boardloader b/core/SConscript.boardloader index e4b548bd46..d85201f784 100644 --- a/core/SConscript.boardloader +++ b/core/SConscript.boardloader @@ -50,6 +50,8 @@ CPPPATH_MOD += [ ] SOURCE_MOD += [ 'embed/extmod/modtrezorui/display.c', + 'embed/extmod/modtrezorui/colors.c', + f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c', 'embed/extmod/modtrezorui/fonts/fonts.c', 'embed/extmod/modtrezorui/fonts/font_bitmap.c', ] diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index d5471ddb9c..ca1367699a 100644 --- a/core/SConscript.bootloader +++ b/core/SConscript.bootloader @@ -4,6 +4,7 @@ import os import tools TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') +DMA2D = False if TREZOR_MODEL in ('1', ): # skip bootloader build @@ -65,7 +66,10 @@ CPPPATH_MOD += [ ] SOURCE_MOD += [ + 'embed/extmod/modtrezorui/buffers.c', + 'embed/extmod/modtrezorui/colors.c', 'embed/extmod/modtrezorui/display.c', + f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c', 'embed/extmod/modtrezorui/fonts/fonts.c', 'embed/extmod/modtrezorui/fonts/font_bitmap.c', 'vendor/micropython/lib/uzlib/adler32.c', @@ -135,6 +139,12 @@ if TREZOR_MODEL in ('R'): if TREZOR_MODEL in ('T',): SOURCE_TREZORHAL.append('embed/trezorhal/touch.c') +if DMA2D: + SOURCE_STMHAL.append('vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c') + SOURCE_TREZORHAL.append('embed/trezorhal/dma2d.c') + CPPDEFINES_MOD += [ + 'USE_DMA2D', + ] # fonts tools.add_font('NORMAL', FONT_NORMAL, CPPDEFINES_MOD, SOURCE_MOD) @@ -161,6 +171,12 @@ if TREZOR_MODEL in ('T', 'R'): CPU_ASFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16' CPU_CCFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 ' CPU_MODEL = 'STM32F427xx' + RUST_TARGET = 'thumbv7em-none-eabihf' +elif TREZOR_MODEL in ('1',): + CPU_ASFLAGS = '-mthumb -mcpu=cortex-m3 -mfloat-abi=soft' + CPU_CCFLAGS = '-mthumb -mtune=cortex-m3 -mcpu=cortex-m3 -mfloat-abi=soft ' + CPU_MODEL = 'STM32F405xx' + RUST_TARGET = 'thumbv7m-none-eabi' else: raise ValueError('Unknown Trezor model') @@ -177,6 +193,7 @@ env.Replace( CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB', LINKFLAGS='-T embed/bootloader/memory.ld -Wl,--gc-sections -Wl,-Map=build/bootloader/bootloader.map -Wl,--warn-common', CPPPATH=[ + 'embed/rust', 'embed/bootloader', 'embed/bootloader/nanopb', 'embed/bootloader/protob', diff --git a/core/SConscript.bootloader_ci b/core/SConscript.bootloader_ci index 5807c26b5c..fd7fb81715 100644 --- a/core/SConscript.bootloader_ci +++ b/core/SConscript.bootloader_ci @@ -65,6 +65,8 @@ CPPPATH_MOD += [ ] SOURCE_MOD += [ 'embed/extmod/modtrezorui/display.c', + 'embed/extmod/modtrezorui/colors.c', + f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c', 'embed/extmod/modtrezorui/fonts/fonts.c', 'embed/extmod/modtrezorui/fonts/font_bitmap.c', 'vendor/micropython/lib/uzlib/adler32.c', diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 3308220803..bb47726ec1 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -8,6 +8,7 @@ BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0') EVERYTHING = BITCOIN_ONLY != '1' TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') UI2 = ARGUMENTS.get('UI2', '0') == '1' or TREZOR_MODEL in ('1', 'R') +DMA2D = TREZOR_MODEL in ('T', ) FEATURE_FLAGS = { "RDI": True, @@ -177,7 +178,10 @@ CPPPATH_MOD += [ 'vendor/micropython/lib/uzlib', ] SOURCE_MOD += [ + 'embed/extmod/modtrezorui/buffers.c', + 'embed/extmod/modtrezorui/colors.c', 'embed/extmod/modtrezorui/display.c', + f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c', 'embed/extmod/modtrezorui/fonts/fonts.c', 'embed/extmod/modtrezorui/fonts/font_bitmap.c', 'embed/extmod/modtrezorui/modtrezorui.c', @@ -189,6 +193,7 @@ SOURCE_MOD += [ if UI2: CPPDEFINES_MOD += [ 'TREZOR_UI2', + 'USE_RUST_LOADER' ] # modtrezorutils @@ -396,6 +401,18 @@ elif TREZOR_MODEL in ('1',): 'embed/trezorhal/button.c', ] + +if DMA2D: + CPPDEFINES_MOD += [ + 'USE_DMA2D', + ] + SOURCE_TREZORHAL += [ + 'embed/trezorhal/dma2d.c', + ] + SOURCE_STMHAL += [ + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c', + ] + CPPDEFINES_MOD += ['USE_SVC_SHUTDOWN'] if FEATURE_FLAGS["RDI"]: @@ -574,7 +591,7 @@ if FROZEN: if EVERYTHING: SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/altcoin.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/webauthn.py')) - if TREZOR_MODEL in ('T') and UI2: + if TREZOR_MODEL in ('T',) and UI2: SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/constants/tt.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/__init__.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/reset.py')) @@ -699,10 +716,10 @@ if FROZEN: protobuf_blobs = env.Command( target=[ - f'rust/proto_enums.data', - f'rust/proto_msgs.data', - f'rust/proto_names.data', - f'rust/proto_wire.data', + 'rust/proto_enums.data', + 'rust/proto_msgs.data', + 'rust/proto_names.data', + 'rust/proto_wire.data', ], source=PROTO_SOURCES, action='$PB2PY --bitcoin-only=%s --blob-outdir ${TARGET.dir} $SOURCES --qstr-defs build/firmware/genhdr/qstrdefs.generated.h' % BITCOIN_ONLY, @@ -728,6 +745,8 @@ def cargo_build(): features.append('ui') if PYOPT == '0': features.append('ui_debug') + if DMA2D: + features.append('dma2d') cargo_opts = [ f'--target={RUST_TARGET}', diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest index dc0226ca27..669351ad11 100644 --- a/core/SConscript.prodtest +++ b/core/SConscript.prodtest @@ -40,6 +40,8 @@ CPPPATH_MOD += [ SOURCE_MOD += [ 'embed/extmod/modtrezorui/display.c', + 'embed/extmod/modtrezorui/colors.c', + f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c', 'embed/extmod/modtrezorui/fonts/fonts.c', 'embed/extmod/modtrezorui/fonts/font_bitmap.c', 'embed/extmod/modtrezorui/qr-code-generator/qrcodegen.c', diff --git a/core/SConscript.reflash b/core/SConscript.reflash index 774aebe99a..e72db989be 100644 --- a/core/SConscript.reflash +++ b/core/SConscript.reflash @@ -35,6 +35,8 @@ CPPPATH_MOD += [ ] SOURCE_MOD += [ 'embed/extmod/modtrezorui/display.c', + 'embed/extmod/modtrezorui/colors.c', + f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c', 'embed/extmod/modtrezorui/fonts/fonts.c', 'embed/extmod/modtrezorui/font_bitmap.c', 'vendor/micropython/lib/uzlib/adler32.c', diff --git a/core/SConscript.unix b/core/SConscript.unix index 8b101f18db..782dab4526 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -8,6 +8,7 @@ BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0') EVERYTHING = BITCOIN_ONLY != '1' TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') UI2 = ARGUMENTS.get('UI2', '0') == '1' or TREZOR_MODEL in ('1', 'R') +DMA2D = TREZOR_MODEL in ('T', ) FEATURE_FLAGS = { "SECP256K1_ZKP": True, # required for trezor.crypto.curve.bip340 (BIP340/Taproot) @@ -70,15 +71,15 @@ CPPDEFINES_MOD += [ ] SOURCE_MOD += [ 'embed/extmod/trezorobj.c', - 'embed/extmod/modtrezorcrypto/modtrezorcrypto.c', 'embed/extmod/modtrezorcrypto/crc.c', + 'embed/extmod/modtrezorcrypto/modtrezorcrypto.c', 'vendor/trezor-crypto/address.c', + 'vendor/trezor-crypto/aes/aes_modes.c', 'vendor/trezor-crypto/aes/aescrypt.c', 'vendor/trezor-crypto/aes/aeskey.c', - 'vendor/trezor-crypto/aes/aes_modes.c', 'vendor/trezor-crypto/aes/aestab.c', - 'vendor/trezor-crypto/base58.c', 'vendor/trezor-crypto/base32.c', + 'vendor/trezor-crypto/base58.c', 'vendor/trezor-crypto/bignum.c', 'vendor/trezor-crypto/bip32.c', 'vendor/trezor-crypto/bip39.c', @@ -86,22 +87,22 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/blake256.c', 'vendor/trezor-crypto/blake2b.c', 'vendor/trezor-crypto/blake2s.c', - 'vendor/trezor-crypto/curves.c', - 'vendor/trezor-crypto/ecdsa.c', 'vendor/trezor-crypto/chacha20poly1305/chacha20poly1305.c', 'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c', 'vendor/trezor-crypto/chacha20poly1305/poly1305-donna.c', 'vendor/trezor-crypto/chacha20poly1305/rfc7539.c', 'vendor/trezor-crypto/chacha_drbg.c', + 'vendor/trezor-crypto/curves.c', + 'vendor/trezor-crypto/ecdsa.c', 'vendor/trezor-crypto/ed25519-donna/curve25519-donna-32bit.c', 'vendor/trezor-crypto/ed25519-donna/curve25519-donna-helpers.c', 'vendor/trezor-crypto/ed25519-donna/curve25519-donna-scalarmult-base.c', - 'vendor/trezor-crypto/ed25519-donna/ed25519.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-donna-32bit-tables.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-donna-basepoint-table.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-donna-impl-base.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c', + 'vendor/trezor-crypto/ed25519-donna/ed25519.c', 'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c', 'vendor/trezor-crypto/groestl.c', 'vendor/trezor-crypto/hasher.c', @@ -112,8 +113,8 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/nist256p1.c', 'vendor/trezor-crypto/pbkdf2.c', 'vendor/trezor-crypto/rand.c', - 'vendor/trezor-crypto/ripemd160.c', 'vendor/trezor-crypto/rfc6979.c', + 'vendor/trezor-crypto/ripemd160.c', 'vendor/trezor-crypto/secp256k1.c', 'vendor/trezor-crypto/sha2.c', 'vendor/trezor-crypto/sha3.c', @@ -173,7 +174,10 @@ CPPPATH_MOD += [ 'vendor/micropython/lib/uzlib', ] SOURCE_MOD += [ + 'embed/extmod/modtrezorui/buffers.c', + 'embed/extmod/modtrezorui/colors.c', 'embed/extmod/modtrezorui/display.c', + 'embed/extmod/modtrezorui/display-unix.c', 'embed/extmod/modtrezorui/fonts/fonts.c', 'embed/extmod/modtrezorui/fonts/font_bitmap.c', 'embed/extmod/modtrezorui/modtrezorui.c', @@ -185,6 +189,7 @@ SOURCE_MOD += [ if UI2: CPPDEFINES_MOD += [ 'TREZOR_UI2', + 'USE_RUST_LOADER' ] if FROZEN: CPPDEFINES_MOD += ['TREZOR_EMULATOR_FROZEN'] @@ -358,6 +363,15 @@ if TREZOR_MODEL in ('T',): 'embed/unix/sdcard.c', ] +if DMA2D: + CPPDEFINES_MOD += [ + 'USE_DMA2D', + ] + SOURCE_UNIX += [ + 'embed/unix/dma2d.c', + ] + + # fonts tools.add_font('NORMAL', FONT_NORMAL, CPPDEFINES_MOD, SOURCE_MOD) tools.add_font('BOLD', FONT_BOLD, CPPDEFINES_MOD, SOURCE_MOD) @@ -683,6 +697,8 @@ def cargo_build(): features.append('ui') if PYOPT == '0': features.append('debug') + if DMA2D: + features.append('dma2d') return f'cd embed/rust; cargo build --profile {RUST_PROFILE} --target-dir=../../build/unix/rust --no-default-features --features "{" ".join(features)}"' diff --git a/core/embed/boardloader/.changelog.d/2414.added b/core/embed/boardloader/.changelog.d/2414.added new file mode 100644 index 0000000000..33ba51897e --- /dev/null +++ b/core/embed/boardloader/.changelog.d/2414.added @@ -0,0 +1 @@ +Using hardware acceleration (dma2d) for rendering diff --git a/core/embed/boardloader/main.c b/core/embed/boardloader/main.c index ec6f94217a..d11c1a1d97 100644 --- a/core/embed/boardloader/main.c +++ b/core/embed/boardloader/main.c @@ -192,6 +192,15 @@ static secbool copy_sdcard(void) { } #endif +// this function resets settings changed in boardloader, which might be +// incompatible with older bootloader versions, where this setting might be +// unknown +void set_bld_compatible_settings(void) { +#ifdef TREZOR_MODEL_T + display_set_big_endian(); +#endif +} + int main(void) { reset_flags_reset(); @@ -210,6 +219,7 @@ int main(void) { clear_otg_hs_memory(); display_init(); + display_clear(); #if defined TREZOR_MODEL_T sdcard_init(); @@ -233,6 +243,8 @@ int main(void) { ensure(check_image_contents(&hdr, IMAGE_HEADER_SIZE, sectors, 1), "invalid bootloader hash"); + set_bld_compatible_settings(); + jump_to(BOOTLOADER_START + IMAGE_HEADER_SIZE); return 0; diff --git a/core/embed/bootloader/.changelog.d/2414.added b/core/embed/bootloader/.changelog.d/2414.added new file mode 100644 index 0000000000..33ba51897e --- /dev/null +++ b/core/embed/bootloader/.changelog.d/2414.added @@ -0,0 +1 @@ +Using hardware acceleration (dma2d) for rendering diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index 1038252419..fac7e13d1c 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -105,7 +105,7 @@ void ui_screen_boot(const vendor_header *const vhdr, int image_top = show_string ? 30 : (DISPLAY_RESY - 120) / 2; // check whether vendor image is 120x120 - if (memcmp(vimg, "TOIf\x78\x00\x78\x00", 4) == 0) { + if (memcmp(vimg, "TOIF\x78\x00\x78\x00", 4) == 0) { uint32_t datalen = *(uint32_t *)(vimg + 8); display_image((DISPLAY_RESX - 120) / 2, image_top, 120, 120, vimg + 12, datalen); diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c index 1c91569593..d0494e8d17 100644 --- a/core/embed/bootloader/main.c +++ b/core/embed/bootloader/main.c @@ -30,6 +30,7 @@ #include "random_delays.h" #include "secbool.h" #ifdef TREZOR_MODEL_T +#include "dma2d.h" #include "touch.h" #endif #if defined TREZOR_MODEL_R @@ -249,7 +250,12 @@ int main(void) { random_delays_init(); // display_init_seq(); +#ifdef USE_DMA2D + dma2d_init(); +#endif + #if defined TREZOR_MODEL_T + display_set_little_endian(); touch_power_on(); touch_init(); #endif @@ -413,6 +419,8 @@ int main(void) { ui_fadeout(); } + ensure_compatible_settings(); + // mpu_config_firmware(); // jump_to_unprivileged(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE); diff --git a/core/embed/bootloader_ci/.changelog.d/2414.added b/core/embed/bootloader_ci/.changelog.d/2414.added new file mode 100644 index 0000000000..33ba51897e --- /dev/null +++ b/core/embed/bootloader_ci/.changelog.d/2414.added @@ -0,0 +1 @@ +Using hardware acceleration (dma2d) for rendering diff --git a/core/embed/extmod/modtrezorui/buffers.c b/core/embed/extmod/modtrezorui/buffers.c new file mode 100644 index 0000000000..b3d44d86b4 --- /dev/null +++ b/core/embed/extmod/modtrezorui/buffers.c @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +#include "buffers.h" +#include "common.h" +#include "fonts/fonts.h" +#include "memzero.h" + +#if USE_DMA2D + +#if defined BOOTLOADER +#define BUFFER_SECTION __attribute__((section(".buf"))) +#else +#define BUFFER_SECTION +#endif + +#define BUFFERS_16BPP 3 +#define BUFFERS_4BPP 3 +#define BUFFERS_TEXT 1 + +const int32_t text_buffer_height = FONT_MAX_HEIGHT; +const int32_t buffer_width = DISPLAY_RESX; + +BUFFER_SECTION line_buffer_16bpp_t line_buffers_16bpp[BUFFERS_16BPP]; +BUFFER_SECTION line_buffer_4bpp_t line_buffers_4bpp[BUFFERS_4BPP]; +BUFFER_SECTION buffer_text_t text_buffers[BUFFERS_TEXT]; + +line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear) { + if (idx >= BUFFERS_16BPP) { + return NULL; + } + if (clear) { + memzero(&line_buffers_16bpp[idx], sizeof(line_buffers_16bpp[idx])); + } + return &line_buffers_16bpp[idx]; +} + +line_buffer_4bpp_t* buffers_get_line_buffer_4bpp(uint16_t idx, bool clear) { + if (idx >= BUFFERS_4BPP) { + return NULL; + } + if (clear) { + memzero(&line_buffers_4bpp[idx], sizeof(line_buffers_4bpp[idx])); + } + return &line_buffers_4bpp[idx]; +} + +buffer_text_t* buffers_get_text_buffer(uint16_t idx, bool clear) { + if (idx >= BUFFERS_TEXT) { + return NULL; + } + if (clear) { + memzero(&text_buffers[idx], sizeof(text_buffers[idx])); + } + return &text_buffers[idx]; +} + +#endif diff --git a/core/embed/extmod/modtrezorui/buffers.h b/core/embed/extmod/modtrezorui/buffers.h new file mode 100644 index 0000000000..5acd07df12 --- /dev/null +++ b/core/embed/extmod/modtrezorui/buffers.h @@ -0,0 +1,59 @@ +/* + * 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 . + */ + +#ifndef _BUFFERS_H +#define _BUFFERS_H + +#include + +#include "common.h" +#include "display_defs.h" + +#define BUFFER_PIXELS DISPLAY_RESX + +#define TEXT_BUFFER_HEIGHT 24 + +#if TEXT_BUFFER_HEIGHT < FONT_MAX_HEIGHT +#error Text buffer height is too small, please adjust to match used fonts +#endif + +#define LINE_BUFFER_16BPP_SIZE BUFFER_PIXELS * 2 +#define LINE_BUFFER_4BPP_SIZE BUFFER_PIXELS / 2 +#define TEXT_BUFFER_SIZE (BUFFER_PIXELS * TEXT_BUFFER_HEIGHT) / 2 + +typedef __attribute__((aligned(4))) struct { + uint8_t buffer[LINE_BUFFER_16BPP_SIZE]; +} line_buffer_16bpp_t; + +typedef __attribute__((aligned(4))) struct { + uint8_t buffer[LINE_BUFFER_4BPP_SIZE]; +} line_buffer_4bpp_t; + +typedef __attribute__((aligned(4))) struct { + uint8_t buffer[TEXT_BUFFER_SIZE]; +} buffer_text_t; + +extern const int32_t text_buffer_height; +extern const int32_t buffer_width; + +line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear); +line_buffer_4bpp_t* buffers_get_line_buffer_4bpp(uint16_t idx, bool clear); +buffer_text_t* buffers_get_text_buffer(uint16_t idx, bool clear); + +#endif //_BUFFERS_H diff --git a/core/embed/extmod/modtrezorui/colors.c b/core/embed/extmod/modtrezorui/colors.c new file mode 100644 index 0000000000..abda0331a5 --- /dev/null +++ b/core/embed/extmod/modtrezorui/colors.c @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#include "colors.h" + +uint32_t rgb565_to_rgb888(uint16_t color) { + uint32_t res = 0; + res |= ((((((uint32_t)color & 0xF800) >> 11) * 527) + 23) >> 6) << 16; + res |= ((((((uint32_t)color & 0x07E0) >> 5) * 259) + 33) >> 6) << 8; + res |= ((((((uint32_t)color & 0x001F) >> 0) * 527) + 23) >> 6) << 0; + return res; +} + +uint32_t interpolate_rgb888_color(uint32_t color0, uint32_t color1, + uint8_t step) { + uint32_t cr, cg, cb; + cr = (((color0 & 0xFF0000) >> 16) * step + + ((color1 & 0xFF0000) >> 16) * (15 - step)) / + 15; + cg = (((color0 & 0xFF00) >> 8) * step + + ((color1 & 0xFF00) >> 8) * (15 - step)) / + 15; + cb = ((color0 & 0x00FF) * step + (color1 & 0x00FF) * (15 - step)) / 15; + return (cr << 16) | (cg << 8) | cb; +} diff --git a/core/embed/extmod/modtrezorui/colors.h b/core/embed/extmod/modtrezorui/colors.h new file mode 100644 index 0000000000..b1b6397829 --- /dev/null +++ b/core/embed/extmod/modtrezorui/colors.h @@ -0,0 +1,57 @@ +/* + * 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 . + */ + +#ifndef _COLORS_H +#define _COLORS_H + +#include "common.h" + +#ifdef TREZOR_MODEL_T +#define RGB16(R, G, B) ((R & 0xF8) << 8) | ((G & 0xFC) << 3) | ((B & 0xF8) >> 3) +#endif + +#define COLOR_WHITE 0xFFFF +#define COLOR_BLACK 0x0000 + +static inline uint16_t interpolate_color(uint16_t color0, uint16_t color1, + uint8_t step) { + uint8_t cr = 0, cg = 0, cb = 0; + cr = (((color0 & 0xF800) >> 11) * step + + ((color1 & 0xF800) >> 11) * (15 - step)) / + 15; + cg = (((color0 & 0x07E0) >> 5) * step + + ((color1 & 0x07E0) >> 5) * (15 - step)) / + 15; + cb = ((color0 & 0x001F) * step + (color1 & 0x001F) * (15 - step)) / 15; + return (cr << 11) | (cg << 5) | cb; +} + +static inline void set_color_table(uint16_t colortable[16], uint16_t fgcolor, + uint16_t bgcolor) { + for (int i = 0; i < 16; i++) { + colortable[i] = interpolate_color(fgcolor, bgcolor, i); + } +} + +uint32_t rgb565_to_rgb888(uint16_t color); + +uint32_t interpolate_rgb888_color(uint32_t color0, uint32_t color1, + uint8_t step); + +#endif //_COLORS_H diff --git a/core/embed/extmod/modtrezorui/display-stm32_1.h b/core/embed/extmod/modtrezorui/display-stm32_1.c similarity index 93% rename from core/embed/extmod/modtrezorui/display-stm32_1.h rename to core/embed/extmod/modtrezorui/display-stm32_1.c index 7b838c9ac6..4777a9e4f0 100644 --- a/core/embed/extmod/modtrezorui/display-stm32_1.h +++ b/core/embed/extmod/modtrezorui/display-stm32_1.c @@ -17,6 +17,10 @@ * along with this program. If not, see . */ +#include +#include +#include "display_defs.h" +#include "display_interface.h" #include STM32_HAL_H #define OLED_BUFSIZE (DISPLAY_RESX * DISPLAY_RESY / 8) @@ -52,6 +56,8 @@ #define OLED_RST_PORT GPIOB #define OLED_RST_PIN GPIO_PIN_1 // PB1 | Reset display +static int DISPLAY_BACKLIGHT = -1; +static int DISPLAY_ORIENTATION = -1; static uint8_t OLED_BUFFER[OLED_BUFSIZE]; static struct { @@ -91,7 +97,7 @@ void display_pixeldata(uint16_t c) { #define PIXELDATA(c) display_pixeldata(c) -static void display_reset_state() {} +void display_reset_state() {} void PIXELDATA_DIRTY() { pixeldata_dirty = true; } @@ -104,9 +110,22 @@ void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { PIXELWINDOW.pos.y = y0; } -static void display_set_orientation(int degrees) { display_refresh(); } +int display_orientation(int degrees) { + if (degrees != DISPLAY_ORIENTATION) { + if (degrees == 0 || degrees == 180) { + DISPLAY_ORIENTATION = degrees; + display_refresh(); + } + } + return DISPLAY_ORIENTATION; +} -static void display_set_backlight(int val) {} +int display_get_orientation(void) { return DISPLAY_ORIENTATION; } + +int display_backlight(int val) { + DISPLAY_BACKLIGHT = 255; + return DISPLAY_BACKLIGHT; +} SPI_HandleTypeDef spi_handle; @@ -207,7 +226,6 @@ void display_init(void) { spi_send(s, 25); HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); // SPI deselect - display_clear(); display_refresh(); } diff --git a/core/embed/extmod/modtrezorui/display-stm32_R.h b/core/embed/extmod/modtrezorui/display-stm32_R.c similarity index 90% rename from core/embed/extmod/modtrezorui/display-stm32_R.h rename to core/embed/extmod/modtrezorui/display-stm32_R.c index 33e7c798bd..1b09a13747 100644 --- a/core/embed/extmod/modtrezorui/display-stm32_R.h +++ b/core/embed/extmod/modtrezorui/display-stm32_R.c @@ -17,6 +17,10 @@ * along with this program. If not, see . */ +#include +#include "display_defs.h" +#include "display_interface.h" +#include "memzero.h" #include STM32_HAL_H // FSMC/FMC Bank 1 - NOR/PSRAM 1 @@ -32,6 +36,8 @@ // noop on TR as we don't need to push data to display #define PIXELDATA_DIRTY() +static int DISPLAY_BACKLIGHT = -1; +static int DISPLAY_ORIENTATION = -1; struct { uint8_t RAM[DISPLAY_RESY / 8][DISPLAY_RESX]; uint32_t row; @@ -95,8 +101,8 @@ void display_pixeldata(uint16_t c) { #define PIXELDATA(c) display_pixeldata(c) -static void display_reset_state(void) { - memset(DISPLAY_STATE.RAM, 0, sizeof(DISPLAY_STATE.RAM)); +void display_reset_state(void) { + memzero(DISPLAY_STATE.RAM, sizeof(DISPLAY_STATE.RAM)); DISPLAY_STATE.row = 0; DISPLAY_STATE.col = 0; DISPLAY_STATE.window_x0 = 0; @@ -138,25 +144,38 @@ void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { } } -static void display_set_orientation(int degrees) { - if (degrees == 0) { - // Set Segment Re-map: (A0H - A1H) - CMD(0xA1); - // Set COM Output Scan Direction - CMD(0xC8); - } - if (degrees == 180) { - // Set Segment Re-map: (A0H - A1H) - CMD(0xA0); - // Set COM Output Scan Direction - CMD(0xC0); +int display_orientation(int degrees) { + if (degrees != DISPLAY_ORIENTATION) { + if (degrees == 0 || degrees == 180) { + DISPLAY_ORIENTATION = degrees; + if (degrees == 0) { + // Set Segment Re-map: (A0H - A1H) + CMD(0xA1); + // Set COM Output Scan Direction + CMD(0xC8); + } + if (degrees == 180) { + // Set Segment Re-map: (A0H - A1H) + CMD(0xA0); + // Set COM Output Scan Direction + CMD(0xC0); + } + } } + + return DISPLAY_ORIENTATION; } -static void display_set_backlight(int val) { - // Set Contrast Control Register: (Double Bytes Command) - CMD(0x81); - CMD(val & 0xFF); +int display_get_orientation(void) { return DISPLAY_ORIENTATION; } + +int display_backlight(int val) { + if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) { + DISPLAY_BACKLIGHT = val; + // Set Contrast Control Register: (Double Bytes Command) + CMD(0x81); + CMD(val & 0xFF); + } + return DISPLAY_BACKLIGHT; } static void send_init_seq_SH1107(void) { @@ -246,7 +265,6 @@ void display_init_seq(void) { send_init_seq_SH1107(); - display_clear(); display_unsleep(); } diff --git a/core/embed/extmod/modtrezorui/display-stm32_T.h b/core/embed/extmod/modtrezorui/display-stm32_T.c similarity index 83% rename from core/embed/extmod/modtrezorui/display-stm32_T.h rename to core/embed/extmod/modtrezorui/display-stm32_T.c index 15c570f41c..d6b5152778 100644 --- a/core/embed/extmod/modtrezorui/display-stm32_T.h +++ b/core/embed/extmod/modtrezorui/display-stm32_T.c @@ -17,13 +17,16 @@ * along with this program. If not, see . */ +#include +#include "display_defs.h" +#include "display_interface.h" +#include "memzero.h" #include STM32_HAL_H // using const volatile instead of #define results in binaries that change // only in 1-byte when the flag changes. // using #define leads compiler to over-optimize the code leading to bigger // differencies in the resulting binaries. - const volatile uint8_t DISPLAY_ST7789V_INVERT_COLORS = 0; // FSMC/FMC Bank 1 - NOR/PSRAM 1 @@ -36,15 +39,6 @@ __IO uint8_t *const DISPLAY_DATA_ADDRESS = (__IO uint8_t *const)((uint32_t)DISPLAY_MEMORY_BASE | (1 << DISPLAY_MEMORY_PIN)); -#define CMD(X) (*DISPLAY_CMD_ADDRESS = (X)) -#define DATA(X) (*DISPLAY_DATA_ADDRESS = (X)) -#define PIXELDATA(X) \ - DATA((X) >> 8); \ - DATA((X)&0xFF) - -// noop on TT as we don't need to push data to display -#define PIXELDATA_DIRTY() - #define LED_PWM_TIM_PERIOD (10000) // section "9.1.3 RDDID (04h): Read Display ID" @@ -59,6 +53,9 @@ __IO uint8_t *const DISPLAY_DATA_ADDRESS = // of ILI9341V datasheet #define DISPLAY_ID_ILI9341V 0x009341U +static int DISPLAY_BACKLIGHT = -1; +static int DISPLAY_ORIENTATION = -1; + void display_pixeldata(uint16_t c) { PIXELDATA(c); } static uint32_t read_display_id(uint8_t command) { @@ -66,7 +63,7 @@ static uint32_t read_display_id(uint8_t command) { uint32_t id = 0; CMD(command); c = *DISPLAY_DATA_ADDRESS; // first returned value is a dummy value and - // should be discarded + // should be discarded c = *DISPLAY_DATA_ADDRESS; id |= (c << 16); c = *DISPLAY_DATA_ADDRESS; @@ -97,7 +94,7 @@ static uint32_t display_identify(void) { return id; } -static void display_reset_state() {} +void display_reset_state() {} static void __attribute__((unused)) display_sleep(void) { uint32_t id = display_identify(); @@ -106,7 +103,7 @@ static void __attribute__((unused)) display_sleep(void) { CMD(0x28); // DISPOFF: Display Off CMD(0x10); // SLPIN: Sleep in HAL_Delay(5); // need to wait 5 milliseconds after "sleep in" before - // sending any new commands + // sending any new commands } } @@ -116,8 +113,8 @@ static void display_unsleep(void) { (id == DISPLAY_ID_ST7789V)) { CMD(0x11); // SLPOUT: Sleep Out HAL_Delay(5); // need to wait 5 milliseconds after "sleep out" before - // sending any new commands - CMD(0x29); // DISPON: Display On + // sending any new commands + CMD(0x29); // DISPON: Display On } } @@ -145,52 +142,65 @@ void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { } } -static void display_set_orientation(int degrees) { - char BX = 0, BY = 0; - uint32_t id = display_identify(); - if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) || - (id == DISPLAY_ID_ST7789V)) { +int display_orientation(int degrees) { + if (degrees != DISPLAY_ORIENTATION) { + if (degrees == 0 || degrees == 90 || degrees == 180 || degrees == 270) { + DISPLAY_ORIENTATION = degrees; + + char BX = 0, BY = 0; + uint32_t id = display_identify(); + if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) || + (id == DISPLAY_ID_ST7789V)) { #define RGB (1 << 3) #define MV (1 << 5) #define MX (1 << 6) #define MY (1 << 7) - // MADCTL: Memory Data Access Control - reference: - // section 9.3 in the ILI9341 manual - // section 6.2.18 in the GC9307 manual - // section 8.12 in the ST7789V manual - uint8_t display_command_parameter = 0; - switch (degrees) { - case 0: - display_command_parameter = 0; - BY = (id == DISPLAY_ID_GC9307); - break; - case 90: - display_command_parameter = MV | MX; - BX = (id == DISPLAY_ID_GC9307); - break; - case 180: - display_command_parameter = MX | MY; - BY = (id != DISPLAY_ID_GC9307); - break; - case 270: - display_command_parameter = MV | MY; - BX = (id != DISPLAY_ID_GC9307); - break; + // MADCTL: Memory Data Access Control - reference: + // section 9.3 in the ILI9341 manual + // section 6.2.18 in the GC9307 manual + // section 8.12 in the ST7789V manual + uint8_t display_command_parameter = 0; + switch (degrees) { + case 0: + display_command_parameter = 0; + BY = (id == DISPLAY_ID_GC9307); + break; + case 90: + display_command_parameter = MV | MX; + BX = (id == DISPLAY_ID_GC9307); + break; + case 180: + display_command_parameter = MX | MY; + BY = (id != DISPLAY_ID_GC9307); + break; + case 270: + display_command_parameter = MV | MY; + BX = (id != DISPLAY_ID_GC9307); + break; + } + if (id == DISPLAY_ID_GC9307) { + display_command_parameter ^= RGB | MY; // XOR RGB and MY settings + } + CMD(0x36); + DATA(display_command_parameter); + // reset the column and page extents + display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); + } + BUFFER_OFFSET.x = BX ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0; + BUFFER_OFFSET.y = BY ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0; } - if (id == DISPLAY_ID_GC9307) { - display_command_parameter ^= RGB | MY; // XOR RGB and MY settings - } - CMD(0x36); - DATA(display_command_parameter); - // reset the column and page extents - display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); } - BUFFER_OFFSET.x = BX ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0; - BUFFER_OFFSET.y = BY ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0; + return DISPLAY_ORIENTATION; } -static void display_set_backlight(int val) { - TIM1->CCR1 = LED_PWM_TIM_PERIOD * val / 255; +int display_get_orientation(void) { return DISPLAY_ORIENTATION; } + +int display_backlight(int val) { + if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) { + DISPLAY_BACKLIGHT = val; + TIM1->CCR1 = LED_PWM_TIM_PERIOD * val / 255; + } + return DISPLAY_BACKLIGHT; } static void send_init_seq_GC9307(void) { @@ -472,7 +482,6 @@ void display_init_seq(void) { send_init_seq_ILI9341V(); } - display_clear(); display_unsleep(); } @@ -588,6 +597,8 @@ void display_init(void) { HAL_SRAM_Init(&external_display_data_sram, &normal_mode_timing, NULL); display_init_seq(); + + display_set_little_endian(); } void display_refresh(void) { @@ -602,6 +613,40 @@ void display_refresh(void) { } } +void display_set_little_endian(void) { + uint32_t id = display_identify(); + if (id == DISPLAY_ID_GC9307) { + // CANNOT SET ENDIAN FOR GC9307 + } else if (id == DISPLAY_ID_ST7789V) { + CMD(0xB0); + DATA(0x00); + DATA(0xF8); + } else if (id == DISPLAY_ID_ILI9341V) { + // Interface Control: XOR BGR as ST7789V does + CMD(0xF6); + DATA(0x09); + DATA(0x30); + DATA(0x20); + } +} + +void display_set_big_endian(void) { + uint32_t id = display_identify(); + if (id == DISPLAY_ID_GC9307) { + // CANNOT SET ENDIAN FOR GC9307 + } else if (id == DISPLAY_ID_ST7789V) { + CMD(0xB0); + DATA(0x00); + DATA(0xF0); + } else if (id == DISPLAY_ID_ILI9341V) { + // Interface Control: XOR BGR as ST7789V does + CMD(0xF6); + DATA(0x09); + DATA(0x30); + DATA(0x00); + } +} + const char *display_save(const char *prefix) { return NULL; } void display_clear_save(void) {} diff --git a/core/embed/extmod/modtrezorui/display-unix.h b/core/embed/extmod/modtrezorui/display-unix.c similarity index 87% rename from core/embed/extmod/modtrezorui/display-unix.h rename to core/embed/extmod/modtrezorui/display-unix.c index a8ccd29201..c3a336297d 100644 --- a/core/embed/extmod/modtrezorui/display-unix.h +++ b/core/embed/extmod/modtrezorui/display-unix.c @@ -16,11 +16,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#define _GNU_SOURCE #include #include +#include +#include +#include +#include #include #include +#include + +#include "common.h" +#include "display_defs.h" +#include "display_interface.h" #include "profile.h" #define EMULATOR_BORDER 16 @@ -57,9 +67,14 @@ static SDL_Texture *TEXTURE, *BACKGROUND; static SDL_Surface *PREV_SAVED; +static int DISPLAY_BACKLIGHT = -1; +static int DISPLAY_ORIENTATION = -1; int sdl_display_res_x = DISPLAY_RESX, sdl_display_res_y = DISPLAY_RESY; int sdl_touch_offset_x, sdl_touch_offset_y; +// this is just for compatibility with DMA2D using algorithms +uint8_t *const DISPLAY_DATA_ADDRESS = 0; + static struct { struct { uint16_t x, y; @@ -72,9 +87,6 @@ static struct { } pos; } PIXELWINDOW; -// noop on unix, display is refreshed every loop step -#define PIXELDATA_DIRTY() - void display_pixeldata(uint16_t c) { #if defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R // set to white if highest bits of all R, G, B values are set to 1 @@ -100,7 +112,7 @@ void display_pixeldata(uint16_t c) { #define PIXELDATA(c) display_pixeldata(c) -static void display_reset_state() {} +void display_reset_state() {} void display_init_seq(void) {} @@ -248,9 +260,34 @@ void display_refresh(void) { SDL_RenderPresent(RENDERER); } -static void display_set_orientation(int degrees) { display_refresh(); } +int display_orientation(int degrees) { + if (degrees != DISPLAY_ORIENTATION) { +#if defined TREZOR_MODEL_T + if (degrees == 0 || degrees == 90 || degrees == 180 || degrees == 270) { +#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R + if (degrees == 0 || degrees == 180) { +#else +#error Unknown Trezor model +#endif + DISPLAY_ORIENTATION = degrees; + display_refresh(); + } + } + return DISPLAY_ORIENTATION; +} -static void display_set_backlight(int val) { display_refresh(); } +int display_get_orientation(void) { return DISPLAY_ORIENTATION; } + +int display_backlight(int val) { +#if defined TREZOR_MODEL_1 + val = 255; +#endif + if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) { + DISPLAY_BACKLIGHT = val; + display_refresh(); + } + return DISPLAY_BACKLIGHT; +} const char *display_save(const char *prefix) { if (!RENDERER) { diff --git a/core/embed/extmod/modtrezorui/display.c b/core/embed/extmod/modtrezorui/display.c index 979303128a..b5599cb4c5 100644 --- a/core/embed/extmod/modtrezorui/display.c +++ b/core/embed/extmod/modtrezorui/display.c @@ -23,9 +23,18 @@ #include "uzlib.h" +#include "buffers.h" #include "common.h" #include "display.h" +#ifdef USE_DMA2D +#include "dma2d.h" +#endif + +#ifdef USE_RUST_LOADER +#include "rust_ui.h" +#endif + #include "fonts/fonts.h" #include @@ -33,47 +42,12 @@ #include "memzero.h" -static int DISPLAY_BACKLIGHT = -1; -static int DISPLAY_ORIENTATION = -1; +#include "display_interface.h" static struct { int x, y; } DISPLAY_OFFSET; -#ifdef TREZOR_EMULATOR -#include "display-unix.h" -#else -#if defined TREZOR_MODEL_T -#include "display-stm32_T.h" -#elif defined TREZOR_MODEL_1 -#include "display-stm32_1.h" -#elif defined TREZOR_MODEL_R -#include "display-stm32_R.h" -#else -#error Unknown Trezor model -#endif -#endif - // common display functions -static inline uint16_t interpolate_color(uint16_t color0, uint16_t color1, - uint8_t step) { - uint8_t cr = 0, cg = 0, cb = 0; - cr = (((color0 & 0xF800) >> 11) * step + - ((color1 & 0xF800) >> 11) * (15 - step)) / - 15; - cg = (((color0 & 0x07E0) >> 5) * step + - ((color1 & 0x07E0) >> 5) * (15 - step)) / - 15; - cb = ((color0 & 0x001F) * step + (color1 & 0x001F) * (15 - step)) / 15; - return (cr << 11) | (cg << 5) | cb; -} - -static inline void set_color_table(uint16_t colortable[16], uint16_t fgcolor, - uint16_t bgcolor) { - for (int i = 0; i < 16; i++) { - colortable[i] = interpolate_color(fgcolor, bgcolor, i); - } -} - static inline void clamp_coords(int x, int y, int w, int h, int *x0, int *y0, int *x1, int *y1) { *x0 = MAX(x, 0); @@ -83,7 +57,7 @@ static inline void clamp_coords(int x, int y, int w, int h, int *x0, int *y0, } void display_clear(void) { - const int saved_orientation = DISPLAY_ORIENTATION; + const int saved_orientation = display_get_orientation(); display_reset_state(); @@ -190,6 +164,70 @@ static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window, uzlib_uncompress_init(decomp, window, window ? UZLIB_WINDOW_SIZE : 0); } +void display_text_render_buffer(const char *text, int textlen, int font, + buffer_text_t *buffer, int text_offset) { + // determine text length if not provided + if (textlen < 0) { + textlen = strlen(text); + } + + int x = 0; + int max_height = font_max_height(font); + int baseline = font_baseline(font); + + // render glyphs + for (int c_idx = 0; c_idx < textlen; c_idx++) { + const uint8_t *g = font_get_glyph(font, (uint8_t)text[c_idx]); + if (!g) continue; + const uint8_t w = g[0]; // width + const uint8_t h = g[1]; // height + const uint8_t adv = g[2]; // advance + const uint8_t bearX = g[3]; // bearingX + const uint8_t bearY = g[4]; // bearingY + if (w && h) { + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + const int a = i + j * w; +#if TREZOR_FONT_BPP == 1 + const uint8_t c = ((g[5 + a / 8] >> (7 - (a % 8) * 1)) & 0x01) * 15; +#elif TREZOR_FONT_BPP == 2 + const uint8_t c = ((g[5 + a / 4] >> (6 - (a % 4) * 2)) & 0x03) * 5; +#elif TREZOR_FONT_BPP == 4 + const uint8_t c = (g[5 + a / 2] >> (4 - (a % 2) * 4)) & 0x0F; +#elif TREZOR_FONT_BPP == 8 +#error Rendering into buffer not supported when using TREZOR_FONT_BPP = 8 + // const uint8_t c = g[5 + a / 1] >> 4; +#else +#error Unsupported TREZOR_FONT_BPP value +#endif + + int x_pos = text_offset + i + x + bearX; + int y_pos = j + max_height - bearY - baseline; + + if (y_pos < 0) continue; + + if (x_pos >= BUFFER_PIXELS || x_pos < 0) { + continue; + } + + int buffer_pos = x_pos + y_pos * BUFFER_PIXELS; + + if (buffer_pos < (sizeof(buffer_text_t) * 2)) { + int b = buffer_pos / 2; + if (buffer_pos % 2) { + buffer->buffer[b] |= c << 4; + } else { + buffer->buffer[b] |= (c); + } + } + } + } + } + x += adv; + } +} + +#ifndef USE_DMA2D void display_image(int x, int y, int w, int h, const void *data, uint32_t datalen) { #if defined TREZOR_MODEL_T @@ -217,12 +255,48 @@ void display_image(int x, int y, int w, int h, const void *data, const int px = pos % w; const int py = pos / w; if (px >= x0 && px <= x1 && py >= y0 && py <= y1) { - PIXELDATA((decomp_out[0] << 8) | decomp_out[1]); + PIXELDATA((decomp_out[1] << 8) | decomp_out[0]); } decomp.dest = (uint8_t *)&decomp_out; } #endif } +#else +void display_image(int x, int y, int w, int h, const void *data, + uint32_t datalen) { + x += DISPLAY_OFFSET.x; + y += DISPLAY_OFFSET.y; + int x0 = 0, y0 = 0, x1 = 0, y1 = 0; + clamp_coords(x, y, w, h, &x0, &y0, &x1, &y1); + display_set_window(x0, y0, x1, y1); + x0 -= x; + x1 -= x; + y0 -= y; + y1 -= y; + + struct uzlib_uncomp decomp = {0}; + uint8_t decomp_window[UZLIB_WINDOW_SIZE] = {0}; + + line_buffer_16bpp_t *b1 = buffers_get_line_buffer_16bpp(0, false); + line_buffer_16bpp_t *b2 = buffers_get_line_buffer_16bpp(1, false); + + uzlib_prepare(&decomp, decomp_window, data, datalen, b1, w * 2); + + dma2d_setup_16bpp(); + + for (int32_t pos = 0; pos < h; pos++) { + int32_t pixels = w; + line_buffer_16bpp_t *next_buf = (pos % 2 == 1) ? b1 : b2; + decomp.dest = next_buf->buffer; + decomp.dest_limit = next_buf->buffer + w * 2; + int st = uzlib_uncompress(&decomp); + if (st < 0) break; // error + dma2d_wait_for_transfer(); + dma2d_start(next_buf->buffer, (uint8_t *)DISPLAY_DATA_ADDRESS, pixels); + } + dma2d_wait_for_transfer(); +} +#endif #define AVATAR_BORDER_SIZE 4 #define AVATAR_BORDER_LOW \ @@ -289,6 +363,7 @@ void display_avatar(int x, int y, const void *data, uint32_t datalen, #endif } +#ifndef USE_DMA2D void display_icon(int x, int y, int w, int h, const void *data, uint32_t datalen, uint16_t fgcolor, uint16_t bgcolor) { x += DISPLAY_OFFSET.x; @@ -318,25 +393,74 @@ void display_icon(int x, int y, int w, int h, const void *data, const int px = (pos * 2) % w; const int py = (pos * 2) / w; if (px >= x0 && px <= x1 && py >= y0 && py <= y1) { - PIXELDATA(colortable[decomp_out >> 4]); PIXELDATA(colortable[decomp_out & 0x0F]); + PIXELDATA(colortable[decomp_out >> 4]); } decomp.dest = (uint8_t *)&decomp_out; } PIXELDATA_DIRTY(); } +#else +void display_icon(int x, int y, int w, int h, const void *data, + uint32_t datalen, uint16_t fgcolor, uint16_t bgcolor) { + x += DISPLAY_OFFSET.x; + y += DISPLAY_OFFSET.y; + x &= ~1; // cannot draw at odd coordinate + w &= ~1; // cannot draw odd-wide icons + int x0 = 0, y0 = 0, x1 = 0, y1 = 0; + clamp_coords(x, y, w, h, &x0, &y0, &x1, &y1); + display_set_window(x0, y0, x1, y1); + x0 -= x; + x1 -= x; + y0 -= y; + y1 -= y; + + int width = x1 - x0 + 1; + + uint8_t b[DISPLAY_RESX / 2]; + line_buffer_4bpp_t *b1 = buffers_get_line_buffer_4bpp(0, false); + line_buffer_4bpp_t *b2 = buffers_get_line_buffer_4bpp(1, false); + + struct uzlib_uncomp decomp = {0}; + uint8_t decomp_window[UZLIB_WINDOW_SIZE] = {0}; + + uzlib_prepare(&decomp, decomp_window, data, datalen, b, w / 2); + + dma2d_setup_4bpp(fgcolor, bgcolor); + + int off_x = x < 0 ? -x : 0; + + for (uint32_t pos = 0; pos < h; pos++) { + line_buffer_4bpp_t *next_buf = (pos % 2 == 0) ? b1 : b2; + decomp.dest = b; + decomp.dest_limit = b + w / 2; + int st = uzlib_uncompress(&decomp); + if (st < 0) break; // error + if (pos >= y0 && pos <= y1) { + memcpy(next_buf->buffer, &b[off_x / 2], width / 2); + dma2d_wait_for_transfer(); + dma2d_start(next_buf->buffer, (uint8_t *)DISPLAY_DATA_ADDRESS, width); + } + } + dma2d_wait_for_transfer(); +} +#endif // see docs/misc/toif.md for definition of the TOIF format bool display_toif_info(const uint8_t *data, uint32_t len, uint16_t *out_w, - uint16_t *out_h, bool *out_grayscale) { + uint16_t *out_h, toif_format_t *out_format) { if (len < 12 || memcmp(data, "TOI", 3) != 0) { return false; } - bool grayscale = false; + toif_format_t format = false; if (data[3] == 'f') { - grayscale = false; + format = TOIF_FULL_COLOR_BE; } else if (data[3] == 'g') { - grayscale = true; + format = TOIF_GRAYSCALE_OH; + } else if (data[3] == 'F') { + format = TOIF_FULL_COLOR_LE; + } else if (data[3] == 'G') { + format = TOIF_GRAYSCALE_EH; } else { return false; } @@ -349,14 +473,15 @@ bool display_toif_info(const uint8_t *data, uint32_t len, uint16_t *out_w, return false; } - if (out_w != NULL && out_h != NULL && out_grayscale != NULL) { + if (out_w != NULL && out_h != NULL && out_format != NULL) { *out_w = w; *out_h = h; - *out_grayscale = grayscale; + *out_format = format; } return true; } +#ifndef USE_RUST_LOADER #if defined TREZOR_MODEL_T #include "loader_T.h" #elif defined TREZOR_MODEL_R @@ -381,7 +506,7 @@ void display_loader(uint16_t progress, bool indeterminate, int yoffset, DISPLAY_RESX / 2 + img_loader_size - 1, DISPLAY_RESY / 2 + img_loader_size - 1 + yoffset); uint8_t icondata[(LOADER_ICON_SIZE * LOADER_ICON_SIZE) / 2] = {0}; - if (icon && memcmp(icon, "TOIg", 4) == 0 && + if (icon && memcmp(icon, "TOIG", 4) == 0 && LOADER_ICON_SIZE == *(uint16_t *)(icon + 4) && LOADER_ICON_SIZE == *(uint16_t *)(icon + 6) && iconlen == 12 + *(uint32_t *)(icon + 8)) { @@ -423,9 +548,9 @@ void display_loader(uint16_t progress, bool indeterminate, int yoffset, (y - (img_loader_size - (LOADER_ICON_SIZE / 2))) * LOADER_ICON_SIZE; uint8_t c = 0; if (i % 2) { - c = icon[i / 2] & 0x0F; - } else { c = (icon[i / 2] & 0xF0) >> 4; + } else { + c = icon[i / 2] & 0x0F; } PIXELDATA(iconcolortable[c]); } else { @@ -453,6 +578,17 @@ void display_loader(uint16_t progress, bool indeterminate, int yoffset, PIXELDATA_DIRTY(); #endif } +#else + +void display_loader(uint16_t progress, bool indeterminate, int yoffset, + uint16_t fgcolor, uint16_t bgcolor, const uint8_t *icon, + uint32_t iconlen, uint16_t iconfgcolor) { +#if defined TREZOR_MODEL_T || defined TREZOR_MODEL_R + loader_uncompress_r(yoffset, fgcolor, bgcolor, iconfgcolor, progress, + indeterminate, icon, iconlen); +#endif +} +#endif #ifndef TREZOR_PRINT_DISABLE @@ -560,7 +696,6 @@ void display_printf(const char *fmt, ...) { #endif // TREZOR_PRINT_DISABLE - static void display_text_render(int x, int y, const char *text, int textlen, int font, uint16_t fgcolor, uint16_t bgcolor) { // determine text length if not provided @@ -738,33 +873,6 @@ void display_offset(int set_xy[2], int *get_x, int *get_y) { *get_y = DISPLAY_OFFSET.y; } -int display_orientation(int degrees) { - if (degrees != DISPLAY_ORIENTATION) { -#if defined TREZOR_MODEL_T - if (degrees == 0 || degrees == 90 || degrees == 180 || degrees == 270) { -#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R - if (degrees == 0 || degrees == 180) { -#else -#error Unknown Trezor model -#endif - DISPLAY_ORIENTATION = degrees; - display_set_orientation(degrees); - } - } - return DISPLAY_ORIENTATION; -} - -int display_backlight(int val) { -#if defined TREZOR_MODEL_1 - val = 255; -#endif - if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) { - DISPLAY_BACKLIGHT = val; - display_set_backlight(val); - } - return DISPLAY_BACKLIGHT; -} - void display_fade(int start, int end, int delay) { for (int i = 0; i < 100; i++) { display_backlight(start + i * (end - start) / 100); diff --git a/core/embed/extmod/modtrezorui/display.h b/core/embed/extmod/modtrezorui/display.h index bfcf4ad17a..cc2a1f55cb 100644 --- a/core/embed/extmod/modtrezorui/display.h +++ b/core/embed/extmod/modtrezorui/display.h @@ -24,35 +24,11 @@ #include #include +#include "buffers.h" +#include "colors.h" +#include "display_defs.h" +#include "display_interface.h" #include "fonts/fonts.h" -#if defined TREZOR_MODEL_T - -// ILI9341V, GC9307 and ST7789V drivers support 240px x 320px display resolution -#define MAX_DISPLAY_RESX 240 -#define MAX_DISPLAY_RESY 320 -#define DISPLAY_RESX 240 -#define DISPLAY_RESY 240 -#define TREZOR_FONT_BPP 4 - -#elif defined TREZOR_MODEL_1 - -#define MAX_DISPLAY_RESX 128 -#define MAX_DISPLAY_RESY 64 -#define DISPLAY_RESX 128 -#define DISPLAY_RESY 64 -#define TREZOR_FONT_BPP 1 - -#elif defined TREZOR_MODEL_R - -#define MAX_DISPLAY_RESX 128 -#define MAX_DISPLAY_RESY 128 -#define DISPLAY_RESX 128 -#define DISPLAY_RESY 128 -#define TREZOR_FONT_BPP 1 - -#else -#error Unknown Trezor model -#endif #define AVATAR_IMAGE_SIZE 144 #if defined TREZOR_MODEL_T || defined TREZOR_MODEL_1 @@ -63,12 +39,12 @@ #error Unknown Trezor model #endif -#ifdef TREZOR_MODEL_T -#define RGB16(R, G, B) ((R & 0xF8) << 8) | ((G & 0xFC) << 3) | ((B & 0xF8) >> 3) -#endif - -#define COLOR_WHITE 0xFFFF -#define COLOR_BLACK 0x0000 +typedef enum { + TOIF_FULL_COLOR_BE = 0, // big endian + TOIF_GRAYSCALE_OH = 1, // odd hi + TOIF_FULL_COLOR_LE = 2, // little endian + TOIF_GRAYSCALE_EH = 3, // even hi +} toif_format_t; // provided by port @@ -87,7 +63,7 @@ void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b, uint8_t r); bool display_toif_info(const uint8_t *buf, uint32_t len, uint16_t *out_w, - uint16_t *out_h, bool *out_grayscale); + uint16_t *out_h, toif_format_t *out_format); void display_image(int x, int y, int w, int h, const void *data, uint32_t datalen); void display_avatar(int x, int y, const void *data, uint32_t datalen, @@ -114,26 +90,19 @@ void display_text_right(int x, int y, const char *text, int textlen, int font, int display_text_width(const char *text, int textlen, int font); int display_text_split(const char *text, int textlen, int font, int requested_width); +void display_text_render_buffer(const char *text, int textlen, int font, + buffer_text_t *buffer, int text_offset); void display_qrcode(int x, int y, const char *data, uint8_t scale); void display_offset(int set_xy[2], int *get_x, int *get_y); -int display_orientation(int degrees); -int display_backlight(int val); void display_fade(int start, int end, int delay); // helper for locating a substring in buffer with utf-8 string void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off, int char_len, const char **out_start, int *out_len); -// pixeldata accessors -void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); -void display_pixeldata(uint16_t c); +// pixeldata accessor void display_pixeldata_dirty(); -#if !(defined EMULATOR) && (defined TREZOR_MODEL_T) -extern volatile uint8_t *const DISPLAY_CMD_ADDRESS; -extern volatile uint8_t *const DISPLAY_DATA_ADDRESS; -#endif - #endif diff --git a/core/embed/extmod/modtrezorui/display_defs.h b/core/embed/extmod/modtrezorui/display_defs.h new file mode 100644 index 0000000000..dadb339bc3 --- /dev/null +++ b/core/embed/extmod/modtrezorui/display_defs.h @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +#ifndef _DISPLAY_DEFS_H +#define _DISPLAY_DEFS_H + +#if defined TREZOR_MODEL_T + +// ILI9341V, GC9307 and ST7789V drivers support 240px x 320px display resolution +#define MAX_DISPLAY_RESX 240 +#define MAX_DISPLAY_RESY 320 +#define DISPLAY_RESX 240 +#define DISPLAY_RESY 240 +#define TREZOR_FONT_BPP 4 + +#elif defined TREZOR_MODEL_1 + +#define MAX_DISPLAY_RESX 128 +#define MAX_DISPLAY_RESY 64 +#define DISPLAY_RESX 128 +#define DISPLAY_RESY 64 +#define TREZOR_FONT_BPP 1 + +#elif defined TREZOR_MODEL_R + +#define MAX_DISPLAY_RESX 128 +#define MAX_DISPLAY_RESY 128 +#define DISPLAY_RESX 128 +#define DISPLAY_RESY 128 +#define TREZOR_FONT_BPP 1 + +#else +#error Unknown Trezor model +#endif + +#endif //_DISPLAY_DEFS_H diff --git a/core/embed/extmod/modtrezorui/display_interface.h b/core/embed/extmod/modtrezorui/display_interface.h new file mode 100644 index 0000000000..bd0a804df4 --- /dev/null +++ b/core/embed/extmod/modtrezorui/display_interface.h @@ -0,0 +1,75 @@ +/* + * 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 . + */ + +#ifndef _DISPLAY_INTERFACE_H +#define _DISPLAY_INTERFACE_H + +#include +#include "common.h" + +#if !defined TREZOR_EMULATOR +#include STM32_HAL_H +#endif + +#if (defined TREZOR_MODEL_T) && !(defined TREZOR_EMULATOR) + +extern __IO uint8_t *const DISPLAY_CMD_ADDRESS; +extern __IO uint8_t *const DISPLAY_DATA_ADDRESS; + +#define CMD(X) (*DISPLAY_CMD_ADDRESS = (X)) +#define DATA(X) (*DISPLAY_DATA_ADDRESS = (X)) +#define PIXELDATA(X) \ + DATA((X)&0xFF); \ + DATA((X) >> 8) + +#else +#define PIXELDATA(c) display_pixeldata(c) +#endif + +#ifdef TREZOR_EMULATOR +extern uint8_t *const DISPLAY_DATA_ADDRESS; +#endif + +void display_pixeldata(uint16_t c); + +#if (defined TREZOR_MODEL_1) && !(defined TREZOR_EMULATOR) +void PIXELDATA_DIRTY(); +#else +// noop +#define PIXELDATA_DIRTY() +#endif + +void display_reset_state(); + +void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); +int display_orientation(int degrees); +int display_get_orientation(void); +int display_backlight(int val); + +void display_init(void); +void display_refresh(void); +const char *display_save(const char *prefix); +void display_clear_save(void); + +#ifdef TREZOR_MODEL_T +void display_set_little_endian(void); +void display_set_big_endian(void); +#endif + +#endif //_DISPLAY_INTERFACE_H diff --git a/core/embed/extmod/modtrezorui/modtrezorui-display.h b/core/embed/extmod/modtrezorui/modtrezorui-display.h index 0fb03c65ea..9625b59d89 100644 --- a/core/embed/extmod/modtrezorui/modtrezorui-display.h +++ b/core/embed/extmod/modtrezorui/modtrezorui-display.h @@ -29,6 +29,10 @@ /// FONT_MONO: int # id of monospace font /// FONT_NORMAL: int # id of normal-width font /// FONT_BOLD: int # id of bold-width font +/// TOIF_FULL_COLOR_BE: int # full color big endian TOI image +/// TOIF_FULL_COLOR_LE: int # full color little endian TOI image +/// TOIF_GRAYSCALE_EH: int # grayscale even high TOI image +/// TOIF_FULL_COLOR_BE: int # grayscale odd high TOI image /// typedef struct _mp_obj_Display_t { mp_obj_base_t base; @@ -117,11 +121,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_bar_radius_obj, 8, 8, mod_trezorui_Display_bar_radius); -/// def toif_info(self, image: bytes) -> tuple[int, int, bool]: +/// def toif_info(self, image: bytes) -> tuple[int, int, int]: /// """ /// Returns tuple containing TOIF image dimensions: width, height, and -/// whether it is grayscale. -/// Raises an exception for corrupted images. +/// format Raises an exception for corrupted images. /// """ STATIC mp_obj_t mod_trezorui_Display_toif_info(mp_obj_t self, mp_obj_t image) { mp_buffer_info_t buffer = {0}; @@ -129,8 +132,8 @@ STATIC mp_obj_t mod_trezorui_Display_toif_info(mp_obj_t self, mp_obj_t image) { uint16_t w = 0; uint16_t h = 0; - bool grayscale = false; - bool valid = display_toif_info(buffer.buf, buffer.len, &w, &h, &grayscale); + toif_format_t format = TOIF_FULL_COLOR_BE; + bool valid = display_toif_info(buffer.buf, buffer.len, &w, &h, &format); if (!valid) { mp_raise_ValueError("Invalid image format"); @@ -138,7 +141,7 @@ STATIC mp_obj_t mod_trezorui_Display_toif_info(mp_obj_t self, mp_obj_t image) { mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); tuple->items[0] = MP_OBJ_NEW_SMALL_INT(w); tuple->items[1] = MP_OBJ_NEW_SMALL_INT(h); - tuple->items[2] = mp_obj_new_bool(grayscale); + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(format); return MP_OBJ_FROM_PTR(tuple); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_toif_info_obj, @@ -160,9 +163,9 @@ STATIC mp_obj_t mod_trezorui_Display_image(size_t n_args, uint16_t w = 0; uint16_t h = 0; - bool grayscale = false; - bool valid = display_toif_info(data, image.len, &w, &h, &grayscale); - if (!valid || grayscale) { + toif_format_t format = TOIF_FULL_COLOR_BE; + bool valid = display_toif_info(data, image.len, &w, &h, &format); + if (!valid || format != TOIF_FULL_COLOR_LE) { mp_raise_ValueError("Invalid image format"); } display_image(x, y, w, h, data + 12, image.len - 12); @@ -190,9 +193,9 @@ STATIC mp_obj_t mod_trezorui_Display_avatar(size_t n_args, uint16_t w = 0; uint16_t h = 0; - bool grayscale = false; - bool valid = display_toif_info(data, image.len, &w, &h, &grayscale); - if (!valid || grayscale) { + toif_format_t format = TOIF_FULL_COLOR_BE; + bool valid = display_toif_info(data, image.len, &w, &h, &format); + if (!valid || format != TOIF_FULL_COLOR_BE) { mp_raise_ValueError("Invalid image format"); } if (w != AVATAR_IMAGE_SIZE || h != AVATAR_IMAGE_SIZE) { @@ -223,10 +226,10 @@ STATIC mp_obj_t mod_trezorui_Display_icon(size_t n_args, const mp_obj_t *args) { uint16_t w = 0; uint16_t h = 0; - bool grayscale = false; - bool valid = display_toif_info(data, icon.len, &w, &h, &grayscale); - if (!valid || !grayscale) { - mp_raise_ValueError("Invalid image format"); + toif_format_t format = TOIF_FULL_COLOR_BE; + bool valid = display_toif_info(data, icon.len, &w, &h, &format); + if (!valid || format != TOIF_GRAYSCALE_EH) { + mp_raise_ValueError("Invalid icon format"); } mp_int_t fgcolor = mp_obj_get_int(args[4]); mp_int_t bgcolor = mp_obj_get_int(args[5]); @@ -265,7 +268,7 @@ STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args, mp_buffer_info_t icon = {0}; mp_get_buffer_raise(args[6], &icon, MP_BUFFER_READ); const uint8_t *data = icon.buf; - if (icon.len < 8 || memcmp(data, "TOIg", 4) != 0) { + if (icon.len < 8 || memcmp(data, "TOIG", 4) != 0) { mp_raise_ValueError("Invalid image format"); } mp_int_t w = *(uint16_t *)(data + 4); @@ -642,6 +645,10 @@ STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_FONT_NORMAL), MP_ROM_INT(FONT_NORMAL)}, {MP_ROM_QSTR(MP_QSTR_FONT_BOLD), MP_ROM_INT(FONT_BOLD)}, {MP_ROM_QSTR(MP_QSTR_FONT_MONO), MP_ROM_INT(FONT_MONO)}, + {MP_ROM_QSTR(MP_QSTR_TOIF_FULL_COLOR_BE), MP_ROM_INT(TOIF_FULL_COLOR_BE)}, + {MP_ROM_QSTR(MP_QSTR_TOIF_FULL_COLOR_LE), MP_ROM_INT(TOIF_FULL_COLOR_LE)}, + {MP_ROM_QSTR(MP_QSTR_TOIF_GRAYSCALE_EH), MP_ROM_INT(TOIF_GRAYSCALE_EH)}, + {MP_ROM_QSTR(MP_QSTR_TOIF_GRAYSCALE_OH), MP_ROM_INT(TOIF_GRAYSCALE_OH)}, }; STATIC MP_DEFINE_CONST_DICT(mod_trezorui_Display_locals_dict, mod_trezorui_Display_locals_dict_table); diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index 1a63c7a41d..2977b62068 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -47,6 +47,9 @@ #ifdef TREZOR_MODEL_R #include "rgb_led.h" #endif +#ifdef TREZOR_MODEL_T +#include "dma2d.h" +#endif #if defined TREZOR_MODEL_R || defined TREZOR_MODEL_1 #include "button.h" #endif @@ -96,6 +99,10 @@ int main(void) { // Init peripherals pendsv_init(); +#ifdef USE_DMA2D + dma2d_init(); +#endif + #if !PRODUCTION // enable BUS fault and USAGE fault handlers SCB->SHCSR |= (SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk); @@ -103,6 +110,7 @@ int main(void) { #if defined TREZOR_MODEL_1 display_init(); + display_clear(); button_init(); #endif @@ -114,7 +122,7 @@ int main(void) { #if defined TREZOR_MODEL_T touch_init(); - // display_init_seq(); + display_set_little_endian(); sdcard_init(); display_clear(); #endif @@ -230,6 +238,7 @@ void SVC_C_Handler(uint32_t *stack) { ; break; case SVC_REBOOT_TO_BOOTLOADER: + ensure_compatible_settings(); mpu_config_bootloader(); __asm__ volatile("msr control, %0" ::"r"(0x0)); __asm__ volatile("isb"); diff --git a/core/embed/rust/Cargo.lock b/core/embed/rust/Cargo.lock index abcb1132b2..27965aa7e3 100644 --- a/core/embed/rust/Cargo.lock +++ b/core/embed/rust/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bindgen" version = "0.60.1" @@ -166,6 +172,26 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -238,6 +264,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "syn" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "trezor_lib" version = "0.1.0" @@ -248,6 +285,8 @@ dependencies = [ "cty", "glob", "heapless", + "num-derive", + "num-traits", ] [[package]] diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index 1f6cdd4b57..2bf223023d 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -14,6 +14,7 @@ model_tr = ["buttons"] micropython = [] protobuf = ["micropython"] ui = [] +dma2d = [] ui_debug = [] buttons = [] touch = [] @@ -39,6 +40,7 @@ debug = 2 [profile.test] split-debuginfo = "off" +debug = 2 # Runtime dependencies @@ -49,6 +51,13 @@ version = "0.2.2" version = "0.7.3" default_features = false +[dependencies.num-traits] +version = "0.2.15" +default_features = false + +[dependencies.num-derive] +version = "0.3.3" + [dependencies.cstr_core] version = "0.2.4" default_features = false diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index fe003f1699..c8993cec49 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -105,6 +105,7 @@ fn prepare_bindings() -> bindgen::Builder { "-I../unix", "-I../../build/unix", "-I../../vendor/micropython/ports/unix", + "-DTREZOR_EMULATOR", ]); } @@ -258,6 +259,7 @@ fn generate_trezorhal_bindings() { .allowlist_function("display_refresh") .allowlist_function("display_backlight") .allowlist_function("display_text") + .allowlist_function("display_text_render_buffer") .allowlist_function("display_text_width") .allowlist_function("display_bar") .allowlist_function("display_bar_radius") @@ -270,8 +272,11 @@ fn generate_trezorhal_bindings() { .allowlist_function("display_set_window") .allowlist_var("DISPLAY_CMD_ADDRESS") .allowlist_var("DISPLAY_DATA_ADDRESS") + .allowlist_type("toif_format_t") // fonts .allowlist_function("font_height") + .allowlist_function("font_max_height") + .allowlist_function("font_baseline") .allowlist_function("font_get_glyph") // uzlib .allowlist_function("uzlib_uncompress_init") @@ -289,8 +294,18 @@ fn generate_trezorhal_bindings() { .allowlist_function("rgb_led_set_color") // time .allowlist_function("hal_delay") - .allowlist_function("hal_ticks_ms"); - + .allowlist_function("hal_ticks_ms") + // dma2d + .allowlist_function("dma2d_setup_4bpp_over_4bpp") + .allowlist_function("dma2d_setup_4bpp_over_16bpp") + .allowlist_function("dma2d_start_blend") + .allowlist_function("dma2d_wait_for_transfer") + //buffers + .allowlist_function("buffers_get_line_buffer_16bpp") + .allowlist_function("buffers_get_line_buffer_4bpp") + .allowlist_function("buffers_get_text_buffer") + .allowlist_var("text_buffer_height") + .allowlist_var("buffer_width"); // Write the bindings to a file in the OUR_DIR. bindings .generate() diff --git a/core/embed/rust/rust_ui.h b/core/embed/rust/rust_ui.h new file mode 100644 index 0000000000..2bda41a2ae --- /dev/null +++ b/core/embed/rust/rust_ui.h @@ -0,0 +1,6 @@ +#include "common.h" + +void loader_uncompress_r(int32_t y_offset, uint16_t fg_color, uint16_t bg_color, + uint16_t icon_color, int32_t progress, + int32_t indeterminate, const uint8_t* icon_data, + uint32_t icon_data_size); diff --git a/core/embed/rust/src/lib.rs b/core/embed/rust/src/lib.rs index 2f63df12b4..396e09fa41 100644 --- a/core/embed/rust/src/lib.rs +++ b/core/embed/rust/src/lib.rs @@ -4,6 +4,9 @@ #![deny(unsafe_op_in_unsafe_fn)] #![allow(dead_code)] +#[macro_use] +extern crate num_derive; + mod error; // use trezorhal for its macros early #[macro_use] diff --git a/core/embed/rust/src/trezorhal/buffers.rs b/core/embed/rust/src/trezorhal/buffers.rs new file mode 100644 index 0000000000..41879dac76 --- /dev/null +++ b/core/embed/rust/src/trezorhal/buffers.rs @@ -0,0 +1,45 @@ +use super::ffi; + +pub use ffi::{ + buffer_text_t as BufferText, line_buffer_16bpp_t as LineBuffer16Bpp, + line_buffer_4bpp_t as LineBuffer4Bpp, +}; + +/// Returns a buffer for one line of 16bpp data +/// +/// # Safety +/// +/// This function is unsafe because the caller has to guarantee +/// that he doesn't use buffer on same index multiple times +pub unsafe fn get_buffer_16bpp(idx: u16, clear: bool) -> &'static mut LineBuffer16Bpp { + unsafe { + let ptr = ffi::buffers_get_line_buffer_16bpp(idx, clear); + unwrap!(ptr.as_mut()) + } +} + +/// Returns a buffer for one line of 4bpp data +/// +/// # Safety +/// +/// This function is unsafe because the caller has to guarantee +/// that he doesn't use buffer on same index multiple times +pub unsafe fn get_buffer_4bpp(idx: u16, clear: bool) -> &'static mut LineBuffer4Bpp { + unsafe { + let ptr = ffi::buffers_get_line_buffer_4bpp(idx, clear); + unwrap!(ptr.as_mut()) + } +} + +/// Returns a buffer for one line of text +/// +/// # Safety +/// +/// This function is unsafe because the caller has to guarantee +/// that he doesn't use buffer on same index multiple times +pub unsafe fn get_text_buffer(idx: u16, clear: bool) -> &'static mut BufferText { + unsafe { + let ptr = ffi::buffers_get_text_buffer(idx, clear); + unwrap!(ptr.as_mut()) + } +} diff --git a/core/embed/rust/src/trezorhal/display.rs b/core/embed/rust/src/trezorhal/display.rs index 78b810d778..d23b603b1c 100644 --- a/core/embed/rust/src/trezorhal/display.rs +++ b/core/embed/rust/src/trezorhal/display.rs @@ -1,11 +1,22 @@ use super::ffi; use core::ptr; use cty::c_int; +use num_traits::FromPrimitive; + +use crate::trezorhal::buffers::BufferText; + +#[derive(PartialEq, Debug, Eq, FromPrimitive)] +pub enum ToifFormat { + FullColorBE = ffi::toif_format_t_TOIF_FULL_COLOR_BE as _, + GrayScaleOH = ffi::toif_format_t_TOIF_GRAYSCALE_OH as _, + FullColorLE = ffi::toif_format_t_TOIF_FULL_COLOR_LE as _, + GrayScaleEH = ffi::toif_format_t_TOIF_GRAYSCALE_EH as _, +} pub struct ToifInfo { pub width: u16, pub height: u16, - pub grayscale: bool, + pub format: ToifFormat, } pub fn backlight(val: i32) -> i32 { @@ -26,6 +37,18 @@ pub fn text(baseline_x: i16, baseline_y: i16, text: &str, font: i32, fgcolor: u1 } } +pub fn text_into_buffer(text: &str, font: i32, buffer: &mut BufferText, x_offset: i16) { + unsafe { + ffi::display_text_render_buffer( + text.as_ptr() as _, + text.len() as _, + font, + buffer as _, + x_offset.into(), + ) + } +} + pub fn text_width(text: &str, font: i32) -> i16 { unsafe { ffi::display_text_width(text.as_ptr() as _, text.len() as _, font) @@ -45,11 +68,15 @@ pub fn get_char_glyph(ch: u8, font: i32) -> *const u8 { } pub fn text_height(font: i32) -> i16 { - unsafe { - ffi::font_height(font) - .try_into() - .unwrap_or(i16::MAX) - } + unsafe { ffi::font_height(font).try_into().unwrap_or(i16::MAX) } +} + +pub fn text_max_height(font: i32) -> i16 { + unsafe { ffi::font_max_height(font).try_into().unwrap_or(i16::MAX) } +} + +pub fn text_baseline(font: i32) -> i16 { + unsafe { ffi::font_baseline(font).try_into().unwrap_or(i16::MAX) } } pub fn bar(x: i16, y: i16, w: i16, h: i16, fgcolor: u16) { @@ -101,20 +128,22 @@ pub fn image(x: i16, y: i16, w: i16, h: i16, data: &[u8]) { pub fn toif_info(data: &[u8]) -> Result { let mut width: cty::uint16_t = 0; let mut height: cty::uint16_t = 0; - let mut grayscale: bool = false; + let mut format: ffi::toif_format_t = ffi::toif_format_t_TOIF_FULL_COLOR_BE; if unsafe { ffi::display_toif_info( data.as_ptr() as _, data.len() as _, &mut width, &mut height, - &mut grayscale, + &mut format, ) } { + let format = ToifFormat::from_usize(format as usize).unwrap_or(ToifFormat::FullColorBE); + Ok(ToifInfo { width, height, - grayscale, + format, }) } else { Err(()) @@ -148,8 +177,8 @@ pub fn loader( #[cfg(all(feature = "model_tt", target_arch = "arm"))] pub fn pixeldata(c: u16) { unsafe { - ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8); ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8); + ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8); } } diff --git a/core/embed/rust/src/trezorhal/dma2d.rs b/core/embed/rust/src/trezorhal/dma2d.rs new file mode 100644 index 0000000000..ed56e3eacc --- /dev/null +++ b/core/embed/rust/src/trezorhal/dma2d.rs @@ -0,0 +1,26 @@ +use super::ffi; + +pub fn dma2d_setup_4bpp_over_4bpp(fg_color: u16, bg_color: u16, overlay_color: u16) { + unsafe { ffi::dma2d_setup_4bpp_over_4bpp(fg_color, bg_color, overlay_color) } +} + +pub fn dma2d_setup_4bpp_over_16bpp(overlay_color: u16) { + unsafe { ffi::dma2d_setup_4bpp_over_16bpp(overlay_color) } +} + +pub fn dma2d_start_blend(overlay_buffer: &[u8], bg_buffer: &[u8], pixels: i16) { + unsafe { + ffi::dma2d_start_blend( + overlay_buffer.as_ptr() as _, + bg_buffer.as_ptr() as _, + ffi::DISPLAY_DATA_ADDRESS as _, + pixels as _, + ); + } +} + +pub fn dma2d_wait_for_transfer() { + unsafe { + ffi::dma2d_wait_for_transfer(); + } +} diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs index ee23c39970..560dfb7186 100644 --- a/core/embed/rust/src/trezorhal/mod.rs +++ b/core/embed/rust/src/trezorhal/mod.rs @@ -4,6 +4,8 @@ pub mod bip39; pub mod common; #[cfg(feature = "ui")] pub mod display; +#[cfg(feature = "dma2d")] +pub mod dma2d; mod ffi; pub mod qr; pub mod random; @@ -12,7 +14,9 @@ pub mod rgb_led; pub mod slip39; pub mod uzlib; +pub mod buffers; #[cfg(not(feature = "micropython"))] pub mod time; + #[cfg(feature = "micropython")] pub use crate::micropython::time; diff --git a/core/embed/rust/src/ui/constant.rs b/core/embed/rust/src/ui/constant.rs index d329c74bb3..2216fffe64 100644 --- a/core/embed/rust/src/ui/constant.rs +++ b/core/embed/rust/src/ui/constant.rs @@ -1,13 +1,13 @@ //! Reexporting the `constant` module according to the //! current feature (Trezor model) -#[cfg(feature = "model_t1")] -pub use super::model_t1::constant::*; -#[cfg(all(feature = "model_tr", not(feature = "model_t1")))] -pub use super::model_tr::constant::*; #[cfg(all( - feature = "model_tt", + feature = "model_t1", not(feature = "model_tr"), - not(feature = "model_t1") + not(feature = "model_tt") ))] +pub use super::model_t1::constant::*; +#[cfg(all(feature = "model_tr", not(feature = "model_tt")))] +pub use super::model_tr::constant::*; +#[cfg(feature = "model_tt")] pub use super::model_tt::constant::*; diff --git a/core/embed/rust/src/ui/display/loader.rs b/core/embed/rust/src/ui/display/loader.rs new file mode 100644 index 0000000000..104527d763 --- /dev/null +++ b/core/embed/rust/src/ui/display/loader.rs @@ -0,0 +1,396 @@ +use crate::{ + trezorhal::uzlib::UzlibContext, + ui::{ + constant, display, + display::{Color, ToifFormat}, + geometry::{Offset, Point, Rect}, + }, +}; +use core::slice::from_raw_parts; + +#[cfg(feature = "dma2d")] +use crate::trezorhal::{ + buffers::{get_buffer_16bpp, get_buffer_4bpp}, + dma2d::{dma2d_setup_4bpp_over_4bpp, dma2d_start_blend, dma2d_wait_for_transfer}, +}; + +use crate::ui::{ + constant::{screen, LOADER_OUTER}, + display::toif_info_ensure, +}; + +pub const LOADER_MIN: u16 = 0; +pub const LOADER_MAX: u16 = 1000; + +const LOADER_SIZE: i32 = (LOADER_OUTER * 2.0) as i32; + +const OUTER: f32 = constant::LOADER_OUTER; +const INNER: f32 = constant::LOADER_INNER; +const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE; + +const IN_INNER_ANTI: i32 = ((INNER - 0.5) * (INNER - 0.5)) as i32; +const INNER_MIN: i32 = ((INNER + 0.5) * (INNER + 0.5)) as i32; +const INNER_MAX: i32 = ((INNER + 1.5) * (INNER + 1.5)) as i32; +const INNER_OUTER_ANTI: i32 = ((INNER + 2.5) * (INNER + 2.5)) as i32; +const OUTER_OUT_ANTI: i32 = ((OUTER - 1.5) * (OUTER - 1.5)) as i32; +const OUTER_MAX: i32 = ((OUTER - 0.5) * (OUTER - 0.5)) as i32; + +pub fn loader_uncompress( + y_offset: i16, + fg_color: Color, + bg_color: Color, + progress: u16, + indeterminate: bool, + icon: Option<(&[u8], Color)>, +) { + const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE; + + if let Some((data, color)) = icon { + let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH); + if toif_size.x <= ICON_MAX_SIZE && toif_size.y <= ICON_MAX_SIZE { + let mut icon_data = [0_u8; ((ICON_MAX_SIZE * ICON_MAX_SIZE) / 2) as usize]; + let mut ctx = UzlibContext::new(toif_data, None); + unwrap!(ctx.uncompress(&mut icon_data), "Decompression failed"); + let i = Some((icon_data.as_ref(), color, toif_size)); + loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, i); + } else { + loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None); + } + } else { + loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None); + } +} + +#[no_mangle] +pub extern "C" fn loader_uncompress_r( + y_offset: cty::int32_t, + fg_color: cty::uint16_t, + bg_color: cty::uint16_t, + icon_color: cty::uint16_t, + progress: cty::int32_t, + indeterminate: cty::int32_t, + icon_data: cty::uintptr_t, + icon_data_size: cty::uint32_t, +) { + let fg = Color::from_u16(fg_color); + let bg = Color::from_u16(bg_color); + let ic_color = Color::from_u16(icon_color); + + let i = if icon_data != 0 { + let data_slice = unsafe { from_raw_parts(icon_data as _, icon_data_size as _) }; + Some((data_slice, ic_color)) + } else { + None + }; + + loader_uncompress(y_offset as _, fg, bg, progress as _, indeterminate != 0, i); +} + +#[inline(always)] +fn get_loader_vectors(indeterminate: bool, progress: u16) -> (Point, Point) { + let (start_progress, end_progress) = if indeterminate { + const LOADER_INDETERMINATE_WIDTH: u16 = 100; + ( + progress - LOADER_INDETERMINATE_WIDTH, + progress + LOADER_INDETERMINATE_WIDTH, + ) + } else { + (0, progress) + }; + + let start = ((360 * start_progress as i32) / 1000) % 360; + let end = ((360 * end_progress as i32) / 1000) % 360; + + let start_vector; + let end_vector; + + if indeterminate { + start_vector = display::get_vector(start as _); + end_vector = display::get_vector(end as _); + } else if progress >= 1000 { + start_vector = Point::zero(); + end_vector = Point::zero(); + } else if progress > 500 { + start_vector = display::get_vector(end as _); + end_vector = display::get_vector(start as _); + } else { + start_vector = display::get_vector(start as _); + end_vector = display::get_vector(end as _); + } + + (start_vector, end_vector) +} + +#[inline(always)] +fn loader_get_pixel_color_idx( + show_all: bool, + inverted: bool, + end_vector: Point, + n_start: Point, + x_c: i16, + y_c: i16, + center: Point, +) -> u8 { + let y_p = -(y_c - center.y); + let x_p = x_c - center.x; + + let vx = Point::new(x_p, y_p); + let n_vx = Point::new(-y_p, x_p); + + let d = y_p as i32 * y_p as i32 + x_p as i32 * x_p as i32; + + let included = if inverted { + !display::is_clockwise_or_equal(n_start, vx) + || !display::is_clockwise_or_equal_inc(n_vx, end_vector) + } else { + display::is_clockwise_or_equal(n_start, vx) + && display::is_clockwise_or_equal_inc(n_vx, end_vector) + }; + + // The antialiasing calculation below uses simplified distance difference + // calculation. Optimally, SQRT should be used, but assuming + // diameter large enough and antialiasing over distance + // r_outer-r_inner = 1, the difference between simplified: + // (d^2-r_inner^2)/(r_outer^2-r_inner^2) and precise: (sqrt(d^2) + // - r_inner)/(r_outer-r_inner) is negligible + if show_all || included { + //active part + if d <= IN_INNER_ANTI { + 0 + } else if d <= INNER_MIN { + ((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8 + } else if d <= OUTER_OUT_ANTI { + 15 + } else if d <= OUTER_MAX { + (15 - ((15 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI))) as u8 + } else { + 0 + } + } else { + //inactive part + if d <= IN_INNER_ANTI { + 0 + } else if d <= INNER_MIN { + ((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8 + } else if d <= INNER_MAX { + 15 + } else if d <= INNER_OUTER_ANTI { + (15 - ((10 * (d - INNER_MAX)) / (INNER_OUTER_ANTI - INNER_MAX))) as u8 + } else if d <= OUTER_OUT_ANTI { + 5 + } else if d <= OUTER_MAX { + 5 - ((5 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI)) as u8 + } else { + 0 + } + } +} + +#[cfg(not(feature = "dma2d"))] +pub fn loader_rust( + y_offset: i16, + fg_color: Color, + bg_color: Color, + progress: u16, + indeterminate: bool, + icon: Option<(&[u8], Color, Offset)>, +) { + let center = screen().center() + Offset::new(0, y_offset); + let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2)); + let clamped = r.clamp(constant::screen()); + display::set_window(clamped); + + let center = r.center(); + + let colortable = display::get_color_table(fg_color, bg_color); + let mut icon_colortable = colortable; + + let mut use_icon = false; + let mut icon_area = Rect::zero(); + let mut icon_area_clamped = Rect::zero(); + let mut icon_width = 0; + let mut icon_data = [].as_ref(); + + if let Some((data, color, size)) = icon { + if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE { + icon_width = size.x; + icon_area = Rect::from_center_and_size(center, size); + icon_area_clamped = icon_area.clamp(constant::screen()); + icon_data = data; + use_icon = true; + icon_colortable = display::get_color_table(color, bg_color); + } + } + + let show_all = !indeterminate && progress >= 1000; + let inverted = !indeterminate && progress > 500; + let (start_vector, end_vector) = get_loader_vectors(indeterminate, progress); + + let n_start = Point::new(-start_vector.y, start_vector.x); + + for y_c in r.y0..r.y1 { + for x_c in r.x0..r.x1 { + let p = Point::new(x_c, y_c); + let mut icon_pixel = false; + + let mut underlying_color = bg_color; + + if use_icon && icon_area_clamped.contains(p) { + let x = x_c - center.x; + let y = y_c - center.y; + if (x as i32 * x as i32 + y as i32 * y as i32) <= IN_INNER_ANTI { + let x_i = x_c - icon_area.x0; + let y_i = y_c - icon_area.y0; + + let data = icon_data[(((x_i & 0xFE) + (y_i * icon_width)) / 2) as usize]; + if (x_i & 0x01) == 0 { + underlying_color = icon_colortable[(data & 0xF) as usize]; + } else { + underlying_color = icon_colortable[(data >> 4) as usize]; + } + icon_pixel = true; + } + } + + if clamped.contains(p) && !icon_pixel { + let pix_c_idx = loader_get_pixel_color_idx( + show_all, inverted, end_vector, n_start, x_c, y_c, center, + ); + underlying_color = colortable[pix_c_idx as usize]; + } + + display::pixeldata(underlying_color); + } + } + + display::pixeldata_dirty(); +} + +#[cfg(feature = "dma2d")] +pub fn loader_rust( + y_offset: i16, + fg_color: Color, + bg_color: Color, + progress: u16, + indeterminate: bool, + icon: Option<(&[u8], Color, Offset)>, +) { + let center = screen().center() + Offset::new(0, y_offset); + let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2)); + let clamped = r.clamp(constant::screen()); + display::set_window(clamped); + + let center = r.center(); + + let mut use_icon = false; + let mut icon_area = Rect::zero(); + let mut icon_area_clamped = Rect::zero(); + let mut icon_width = 0; + let mut icon_offset = 0; + let mut icon_color = Color::from_u16(0); + let mut icon_data = [].as_ref(); + + if let Some((data, color, size)) = icon { + if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE { + icon_width = size.x; + icon_area = Rect::from_center_and_size(center, size); + icon_area_clamped = icon_area.clamp(constant::screen()); + icon_offset = (icon_area_clamped.x0 - r.x0) / 2; + icon_color = color; + icon_data = data; + use_icon = true; + } + } + + let show_all = !indeterminate && progress >= 1000; + let inverted = !indeterminate && progress > 500; + let (start_vector, end_vector) = get_loader_vectors(indeterminate, progress); + + let n_start = Point::new(-start_vector.y, start_vector.x); + + let b1 = unsafe { get_buffer_16bpp(0, false) }; + let b2 = unsafe { get_buffer_16bpp(1, false) }; + let ib1 = unsafe { get_buffer_4bpp(0, true) }; + let ib2 = unsafe { get_buffer_4bpp(1, true) }; + let empty_line = unsafe { get_buffer_4bpp(2, true) }; + + dma2d_setup_4bpp_over_4bpp(fg_color.into(), bg_color.into(), icon_color.into()); + + for y_c in r.y0..r.y1 { + let mut icon_buffer = &mut *empty_line; + let icon_buffer_used; + let loader_buffer; + + if y_c % 2 == 0 { + icon_buffer_used = &mut *ib1; + loader_buffer = &mut *b1; + } else { + icon_buffer_used = &mut *ib2; + loader_buffer = &mut *b2; + } + + if use_icon && y_c >= icon_area_clamped.y0 && y_c < icon_area_clamped.y1 { + let y_i = y_c - icon_area.y0; + + // Optimally, we should cut corners of the icon if it happens to be large enough + // to invade loader area. but this would require calculation of circle chord + // length (since we need to limit data copied to the buffer), + // which requires expensive SQRT. Therefore, when using this method of loader + // drawing, special care needs to be taken to ensure that the icons + // have transparent corners. + + icon_buffer_used.buffer[icon_offset as usize..(icon_offset + icon_width / 2) as usize] + .copy_from_slice( + &icon_data[(y_i * (icon_width / 2)) as usize + ..((y_i + 1) * (icon_width / 2)) as usize], + ); + icon_buffer = icon_buffer_used; + } + + let mut pix_c_idx_prev: u8 = 0; + + for x_c in r.x0..r.x1 { + let p = Point::new(x_c, y_c); + + let pix_c_idx = if clamped.contains(p) { + loader_get_pixel_color_idx( + show_all, inverted, end_vector, n_start, x_c, y_c, center, + ) + } else { + 0 + }; + + let x = x_c - r.x0; + if x % 2 == 0 { + pix_c_idx_prev = pix_c_idx; + } else { + loader_buffer.buffer[(x >> 1) as usize] = pix_c_idx_prev | pix_c_idx << 4; + } + } + + dma2d_wait_for_transfer(); + dma2d_start_blend(&icon_buffer.buffer, &loader_buffer.buffer, clamped.width()); + } + + dma2d_wait_for_transfer(); +} + +pub fn loader( + progress: u16, + y_offset: i16, + fg_color: Color, + bg_color: Color, + icon: Option<(&[u8], Color)>, +) { + loader_uncompress(y_offset, fg_color, bg_color, progress, false, icon); +} + +pub fn loader_indeterminate( + progress: u16, + y_offset: i16, + fg_color: Color, + bg_color: Color, + icon: Option<(&[u8], Color)>, +) { + loader_uncompress(y_offset, fg_color, bg_color, progress, true, icon); +} diff --git a/core/embed/rust/src/ui/display.rs b/core/embed/rust/src/ui/display/mod.rs similarity index 62% rename from core/embed/rust/src/ui/display.rs rename to core/embed/rust/src/ui/display/mod.rs index f8cf364017..02c56cccf0 100644 --- a/core/embed/rust/src/ui/display.rs +++ b/core/embed/rust/src/ui/display/mod.rs @@ -1,18 +1,34 @@ +#[cfg(any(feature = "model_tt", feature = "model_tr"))] +pub mod loader; + use super::{ constant, geometry::{Offset, Point, Rect}, }; +#[cfg(feature = "dma2d")] +use crate::trezorhal::{ + buffers::{get_buffer_16bpp, get_buffer_4bpp, get_text_buffer}, + dma2d::{ + dma2d_setup_4bpp_over_16bpp, dma2d_setup_4bpp_over_4bpp, dma2d_start_blend, + dma2d_wait_for_transfer, + }, +}; use crate::{ error::Error, time::Duration, trezorhal::{ - display, qr, time, + display, + display::ToifFormat, + qr, time, uzlib::{UzlibContext, UZLIB_WINDOW_SIZE}, }, ui::lerp::Lerp, }; use core::slice; +#[cfg(any(feature = "model_tt", feature = "model_tr"))] +pub use loader::{loader, loader_indeterminate, LOADER_MAX, LOADER_MIN}; + pub fn backlight() -> i32 { display::backlight(-1) } @@ -66,7 +82,7 @@ pub fn rect_fill_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u8) /// NOTE: Cannot start at odd x-coordinate. In this case icon is shifted 1px /// left. pub fn icon_top_left(top_left: Point, data: &[u8], fg_color: Color, bg_color: Color) { - let (toif_size, toif_data) = toif_info_ensure(data, true); + let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH); display::icon( top_left.x, top_left.y, @@ -79,7 +95,7 @@ pub fn icon_top_left(top_left: Point, data: &[u8], fg_color: Color, bg_color: Co } pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { - let (toif_size, toif_data) = toif_info_ensure(data, true); + let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH); let r = Rect::from_center_and_size(center, toif_size); display::icon( r.x0, @@ -93,7 +109,7 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { } pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { - let (toif_size, toif_data) = toif_info_ensure(data, true); + let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH); let r = Rect::from_center_and_size(center, toif_size); let area = r.translate(get_offset()); @@ -115,9 +131,9 @@ pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { if clamped.contains(p) { if x % 2 == 0 { unwrap!(ctx.uncompress(&mut dest), "Decompression failed"); - pixeldata(colortable[(dest[0] >> 4) as usize]); - } else { pixeldata(colortable[(dest[0] & 0xF) as usize]); + } else { + pixeldata(colortable[(dest[0] >> 4) as usize]); } } else if x % 2 == 0 { //continue unzipping but dont write to display @@ -130,20 +146,20 @@ pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { } pub fn image(center: Point, data: &[u8]) { - let (toif_size, toif_data) = toif_info_ensure(data, false); + let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::FullColorLE); let r = Rect::from_center_and_size(center, toif_size); display::image(r.x0, r.y0, r.width(), r.height(), toif_data); } -pub fn toif_info(data: &[u8]) -> Option<(Offset, bool)> { +pub fn toif_info(data: &[u8]) -> Option<(Offset, ToifFormat)> { if let Ok(info) = display::toif_info(data) { Some(( Offset::new( unwrap!(info.width.try_into()), unwrap!(info.height.try_into()), ), - info.grayscale, + info.format, )) } else { None @@ -152,9 +168,9 @@ pub fn toif_info(data: &[u8]) -> Option<(Offset, bool)> { /// Aborts if the TOIF file does not have the correct grayscale flag, do not use /// with user-supplied inputs. -fn toif_info_ensure(data: &[u8], grayscale: bool) -> (Offset, &[u8]) { +fn toif_info_ensure(data: &[u8], format: ToifFormat) -> (Offset, &[u8]) { let info = unwrap!(display::toif_info(data), "Invalid TOIF data"); - assert_eq!(grayscale, info.grayscale); + assert_eq!(info.format, format); let size = Offset::new( unwrap!(info.width.try_into()), unwrap!(info.height.try_into()), @@ -266,7 +282,7 @@ fn get_vector(angle: i16) -> Point { /// ( if v1=(x1,y1), then the counter-clockwise normal is n_v1=(-y1,x1) #[inline(always)] fn is_clockwise_or_equal(n_v1: Point, v2: Point) -> bool { - let psize = v2.x * n_v1.x + v2.y * n_v1.y; + let psize = v2.x as i32 * n_v1.x as i32 + v2.y as i32 * n_v1.y as i32; psize < 0 } @@ -275,7 +291,7 @@ fn is_clockwise_or_equal(n_v1: Point, v2: Point) -> bool { /// ( if v1=(x1,y1), then the counter-clockwise normal is n_v1=(-y1,x1) #[inline(always)] fn is_clockwise_or_equal_inc(n_v1: Point, v2: Point) -> bool { - let psize = v2.x * n_v1.x + v2.y * n_v1.y; + let psize = v2.x as i32 * n_v1.x as i32 + v2.y as i32 * n_v1.y as i32; psize <= 0 } @@ -308,7 +324,7 @@ pub fn rect_rounded2_partial( let mut icon_width = 0; if let Some((icon_bytes, icon_color)) = icon { - let (toif_size, toif_data) = toif_info_ensure(icon_bytes, true); + let (toif_size, toif_data) = toif_info_ensure(icon_bytes, ToifFormat::GrayScaleEH); if toif_size.x <= MAX_ICON_SIZE && toif_size.y <= MAX_ICON_SIZE { icon_area = Rect::from_center_and_size(center, toif_size); @@ -357,9 +373,9 @@ pub fn rect_rounded2_partial( let data = icon_data[(((x_i & 0xFE) + (y_i * icon_width)) / 2) as usize]; if (x_i & 0x01) == 0 { - pixeldata(icon_colortable[(data >> 4) as usize]); - } else { pixeldata(icon_colortable[(data & 0xF) as usize]); + } else { + pixeldata(icon_colortable[(data > 4) as usize]); } icon_pixel = true; } @@ -393,6 +409,345 @@ pub fn rect_rounded2_partial( pixeldata_dirty(); } +/// Shifts position of pixel data in `src_buffer` horizontally by `offset_x` +/// pixels and places the result into `dest_buffer`. Or in another words, +/// `src_buffer[n]` is copied into `dest_buffer[n+offset_x]`, if it fits the +/// `dest_buffer`. +/// +/// Buffers hold one line of pixels on the screen, the copying is limited to +/// respect the size of screen. +/// +/// `buffer_bpp` determines size of pixel data +/// `data_width` sets the width of valid data in the `src_buffer` +fn position_buffer( + dest_buffer: &mut [u8], + src_buffer: &[u8], + buffer_bpp: usize, + offset_x: i16, + data_width: i16, +) { + let start: usize = (offset_x).clamp(0, constant::WIDTH) as usize; + let end: usize = (offset_x + data_width).clamp(0, constant::WIDTH) as usize; + let width = end - start; + // if the offset is negative, need to skip beginning of uncompressed data + let x_sh = if offset_x < 0 { + (-offset_x).clamp(0, constant::WIDTH - width as i16) as usize + } else { + 0 + }; + dest_buffer[((start * buffer_bpp) / 8)..((start + width) * buffer_bpp) / 8].copy_from_slice( + &src_buffer[((x_sh * buffer_bpp) / 8) as usize..((x_sh as usize + width) * buffer_bpp) / 8], + ); +} + +/// Performs decompression of one line of pixels, +/// vertically positions the line against the display area (current position of +/// which is described by `display_area_y`) by skipping relevant number of lines +/// and finally horizontally positions the line against the display area +/// by calling `position_buffer`. +/// +/// Number of already decompressed lines is stored in `decompressed_lines` to +/// keep track of how many need to be skipped. +/// +/// Signals to the caller whether some data should be drawn on this line. +fn process_buffer( + display_area_y: i16, + img_area: Rect, + offset: Offset, + ctx: &mut UzlibContext, + buffer: &mut [u8], + decompressed_lines: &mut i16, + buffer_bpp: usize, +) -> bool { + let mut not_empty = false; + let uncomp_buffer = + &mut [0u8; (constant::WIDTH * 2) as usize][..((constant::WIDTH as usize) * buffer_bpp) / 8]; + + if display_area_y >= img_area.y0 && display_area_y < img_area.y1 { + let img_line_idx = display_area_y - img_area.y0; + + while *decompressed_lines < img_line_idx { + //compensate uncompressed unused lines + unwrap!( + ctx.uncompress( + &mut uncomp_buffer[0..((img_area.width() * buffer_bpp as i16) / 8) as usize] + ), + "Decompression failed" + ); + + (*decompressed_lines) += 1; + } + // decompress whole line + unwrap!( + ctx.uncompress( + &mut uncomp_buffer[0..((img_area.width() * buffer_bpp as i16) / 8) as usize] + ), + "Decompression failed" + ); + + (*decompressed_lines) += 1; + + position_buffer( + buffer, + uncomp_buffer, + buffer_bpp, + offset.x, + img_area.width(), + ); + + not_empty = true; + } + + not_empty +} + +/// Renders text over image background +/// If `bg_area` is given, it is filled with its color in places where there are +/// neither text or image Positioning also depends on whether `bg_area` is +/// provided: +/// - if it is, text and image are positioned relative to the `bg_area` top left +/// corner, using respective offsets. Nothing is drawn outside the `bg_area`. +/// - if it is not, text is positioned relative to the images top left corner +/// using `offset_text` and image is positioned on the screen using +/// `offset_img`. Nothing is drawn outside the image. +/// `offset_text` is interpreted as baseline, so using (0,0) will position most +/// of the text outside the drawing area in either case. +/// +/// The drawing area is coerced to even width, which is due to dma2d limitation +/// when using 4bpp +#[cfg(feature = "dma2d")] +pub fn text_over_image( + bg_area: Option<(Rect, Color)>, + image_data: &[u8], + text: &str, + font: Font, + offset_img: Offset, + offset_text: Offset, + text_color: Color, +) { + let text_buffer = unsafe { get_text_buffer(0, true) }; + let img1 = unsafe { get_buffer_16bpp(0, true) }; + let img2 = unsafe { get_buffer_16bpp(1, true) }; + let empty_img = unsafe { get_buffer_16bpp(2, true) }; + let t1 = unsafe { get_buffer_4bpp(0, true) }; + let t2 = unsafe { get_buffer_4bpp(1, true) }; + let empty_t = unsafe { get_buffer_4bpp(2, true) }; + + let (toif_size, toif_data) = toif_info_ensure(image_data, ToifFormat::FullColorLE); + + let r_img; + let area; + let offset_img_final; + if let Some((a, color)) = bg_area { + let hi = color.hi_byte(); + let lo = color.lo_byte(); + //prefill image/bg buffers with the bg color + for i in 0..(constant::WIDTH) as usize { + img1.buffer[2 * i] = lo; + img1.buffer[2 * i + 1] = hi; + } + img2.buffer.copy_from_slice(&img1.buffer); + empty_img.buffer.copy_from_slice(&img1.buffer); + + area = a; + r_img = Rect::from_top_left_and_size(a.top_left() + offset_img, toif_size); + offset_img_final = offset_img; + } else { + area = Rect::from_top_left_and_size(offset_img.into(), toif_size); + r_img = area; + offset_img_final = Offset::zero(); + } + let clamped = area.clamp(constant::screen()).ensure_even_width(); + + let text_width = display::text_width(text, font.into()); + let font_max_height = display::text_max_height(font.into()); + let font_baseline = display::text_baseline(font.into()); + let text_width_clamped = text_width.clamp(0, clamped.width()); + + let text_top = area.y0 + offset_text.y - font_max_height + font_baseline; + let text_bottom = area.y0 + offset_text.y + font_baseline; + let text_left = area.x0 + offset_text.x; + let text_right = area.x0 + offset_text.x + text_width_clamped; + + let text_area = Rect::new( + Point::new(text_left, text_top), + Point::new(text_right, text_bottom), + ); + + display::text_into_buffer(text, font.into(), text_buffer, 0); + + set_window(clamped); + + let mut window = [0; UZLIB_WINDOW_SIZE]; + let mut ctx = UzlibContext::new(toif_data, Some(&mut window)); + + dma2d_setup_4bpp_over_16bpp(text_color.into()); + + let mut i = 0; + + for y in clamped.y0..clamped.y1 { + let mut img_buffer = &mut *empty_img; + let mut t_buffer = &mut *empty_t; + let img_buffer_used; + let t_buffer_used; + + if y % 2 == 0 { + t_buffer_used = &mut *t1; + img_buffer_used = &mut *img1; + } else { + t_buffer_used = &mut *t2; + img_buffer_used = &mut *img2; + } + + let using_img = process_buffer( + y, + r_img, + offset_img_final, + &mut ctx, + &mut img_buffer_used.buffer, + &mut i, + 16, + ); + + if y >= text_area.y0 && y < text_area.y1 { + let y_pos = y - text_area.y0; + position_buffer( + &mut t_buffer_used.buffer, + &text_buffer.buffer[(y_pos * constant::WIDTH / 2) as usize + ..((y_pos + 1) * constant::WIDTH / 2) as usize], + 4, + offset_text.x, + text_width, + ); + t_buffer = t_buffer_used; + } + + if using_img { + img_buffer = img_buffer_used; + } + + dma2d_wait_for_transfer(); + dma2d_start_blend(&t_buffer.buffer, &img_buffer.buffer, clamped.width()); + } + + dma2d_wait_for_transfer(); +} + +/// Renders text over image background +/// If `bg_area` is given, it is filled with its color in places where there is +/// neither icon. Positioning also depends on whether `bg_area` is provided: +/// - if it is, icons are positioned relative to the `bg_area` top left corner, +/// using respective offsets. Nothing is drawn outside the `bg_area`. +/// - if it is not, `fg` icon is positioned relative to the `bg` icons top left +/// corner using its offset and `fg` icon is positioned on the screen using +/// its offset. Nothing is drawn outside the `bg` icon. +/// +/// The drawing area is coerced to even width, which is due to dma2d limitation +/// when using 4bpp +#[cfg(feature = "dma2d")] +pub fn icon_over_icon( + bg_area: Option, + bg: (&[u8], Offset, Color), + fg: (&[u8], Offset, Color), + bg_color: Color, +) { + let bg1 = unsafe { get_buffer_16bpp(0, true) }; + let bg2 = unsafe { get_buffer_16bpp(1, true) }; + let empty1 = unsafe { get_buffer_16bpp(2, true) }; + let fg1 = unsafe { get_buffer_4bpp(0, true) }; + let fg2 = unsafe { get_buffer_4bpp(1, true) }; + let empty2 = unsafe { get_buffer_4bpp(2, true) }; + + let (data_bg, offset_bg, color_icon_bg) = bg; + let (data_fg, offset_fg, color_icon_fg) = fg; + + let (toif_bg_size, toif_bg_data) = toif_info_ensure(data_bg, ToifFormat::GrayScaleEH); + assert!(toif_bg_size.x <= constant::WIDTH); + assert_eq!(toif_bg_size.x % 2, 0); + + let (toif_fg_size, toif_fg_data) = toif_info_ensure(data_fg, ToifFormat::GrayScaleEH); + assert!(toif_bg_size.x <= constant::WIDTH); + assert_eq!(toif_bg_size.x % 2, 0); + + let area; + let r_bg; + let final_offset_bg; + if let Some(a) = bg_area { + area = a; + r_bg = Rect::from_top_left_and_size(a.top_left() + offset_bg, toif_bg_size); + final_offset_bg = offset_bg; + } else { + r_bg = Rect::from_top_left_and_size(Point::new(offset_bg.x, offset_bg.y), toif_bg_size); + area = r_bg; + final_offset_bg = Offset::zero(); + } + + let r_fg = Rect::from_top_left_and_size(area.top_left() + offset_fg, toif_fg_size); + + let clamped = area.clamp(constant::screen()).ensure_even_width(); + + set_window(clamped); + + let mut window_bg = [0; UZLIB_WINDOW_SIZE]; + let mut ctx_bg = UzlibContext::new(toif_bg_data, Some(&mut window_bg)); + + let mut window_fg = [0; UZLIB_WINDOW_SIZE]; + let mut ctx_fg = UzlibContext::new(toif_fg_data, Some(&mut window_fg)); + + dma2d_setup_4bpp_over_4bpp(color_icon_bg.into(), bg_color.into(), color_icon_fg.into()); + + let mut fg_i = 0; + let mut bg_i = 0; + + for y in clamped.y0..clamped.y1 { + let mut fg_buffer = &mut *empty2; + let mut bg_buffer = &mut *empty1; + let fg_buffer_used; + let bg_buffer_used; + + if y % 2 == 0 { + fg_buffer_used = &mut *fg1; + bg_buffer_used = &mut *bg1; + } else { + fg_buffer_used = &mut *fg2; + bg_buffer_used = &mut *bg2; + } + + const BUFFER_BPP: usize = 4; + + let using_fg = process_buffer( + y, + r_fg, + offset_fg, + &mut ctx_fg, + &mut fg_buffer_used.buffer, + &mut fg_i, + 4, + ); + let using_bg = process_buffer( + y, + r_bg, + final_offset_bg, + &mut ctx_bg, + &mut bg_buffer_used.buffer, + &mut bg_i, + 4, + ); + + if using_fg { + fg_buffer = fg_buffer_used; + } + if using_bg { + bg_buffer = bg_buffer_used; + } + + dma2d_wait_for_transfer(); + dma2d_start_blend(&fg_buffer.buffer, &bg_buffer.buffer, clamped.width()); + } + + dma2d_wait_for_transfer(); +} + /// Gets a color of a pixel on `p` coordinates of rounded rectangle with corner /// radius 2 fn rect_rounded2_get_pixel( @@ -478,45 +833,6 @@ pub fn dotted_line(start: Point, width: i16, color: Color) { } } -pub const LOADER_MIN: u16 = 0; -pub const LOADER_MAX: u16 = 1000; - -pub fn loader( - progress: u16, - y_offset: i16, - fg_color: Color, - bg_color: Color, - icon: Option<(&[u8], Color)>, -) { - display::loader( - progress, - false, - y_offset, - fg_color.into(), - bg_color.into(), - icon.map(|i| i.0), - icon.map(|i| i.1.into()).unwrap_or(0), - ); -} - -pub fn loader_indeterminate( - progress: u16, - y_offset: i16, - fg_color: Color, - bg_color: Color, - icon: Option<(&[u8], Color)>, -) { - display::loader( - progress, - true, - y_offset, - fg_color.into(), - bg_color.into(), - icon.map(|i| i.0), - icon.map(|i| i.1.into()).unwrap_or(0), - ); -} - pub fn qrcode(center: Point, data: &str, max_size: u32, case_sensitive: bool) -> Result<(), Error> { qr::render_qrcode(center.x, center.y, data, max_size, case_sensitive) } @@ -758,6 +1074,12 @@ impl Color { Self(r | g | b) } + pub const fn luminance(self) -> u32 { + ((self.r() as u32 * 299) / 1000) + + (self.g() as u32 * 587) / 1000 + + (self.b() as u32 * 114) / 1000 + } + pub const fn r(self) -> u8 { (self.0 >> 8) as u8 & 0xF8 } @@ -774,6 +1096,14 @@ impl Color { self.0 } + pub fn hi_byte(self) -> u8 { + (self.to_u16() >> 8) as u8 + } + + pub fn lo_byte(self) -> u8 { + (self.to_u16() & 0xFF) as u8 + } + pub fn negate(self) -> Self { Self(!self.0) } diff --git a/core/embed/rust/src/ui/geometry.rs b/core/embed/rust/src/ui/geometry.rs index a8e40a80fb..09703cb76a 100644 --- a/core/embed/rust/src/ui/geometry.rs +++ b/core/embed/rust/src/ui/geometry.rs @@ -128,6 +128,12 @@ impl Sub for Offset { } } +impl From for Offset { + fn from(val: Point) -> Self { + Offset::new(val.x, val.y) + } +} + /// A point in 2D space defined by the the `x` and `y` coordinate. Relative /// coordinates, vectors, and offsets are represented by the `Offset` type. #[derive(Copy, Clone, PartialEq, Eq)] @@ -188,6 +194,12 @@ impl Lerp for Point { } } +impl From for Point { + fn from(val: Offset) -> Self { + Point::new(val.x, val.y) + } +} + /// A rectangle in 2D space defined by the top-left point `x0`,`y0` and the /// bottom-right point `x1`,`y1`. #[derive(Copy, Clone, PartialEq, Eq)] @@ -367,6 +379,14 @@ impl Rect { } } + pub const fn ensure_even_width(self) -> Self { + if self.width() % 2 == 0 { + self + } else { + self.with_size(Offset::new(self.size().x - 1, self.size().y)) + } + } + pub const fn translate(&self, offset: Offset) -> Self { Self { x0: self.x0 + offset.x, diff --git a/core/embed/rust/src/ui/model_tr/constant.rs b/core/embed/rust/src/ui/model_tr/constant.rs index 2e96ffccc1..dc5f341e5c 100644 --- a/core/embed/rust/src/ui/model_tr/constant.rs +++ b/core/embed/rust/src/ui/model_tr/constant.rs @@ -5,6 +5,10 @@ pub const HEIGHT: i16 = 128; pub const LINE_SPACE: i16 = 1; pub const FONT_BPP: i16 = 1; +pub const LOADER_OUTER: f32 = 20_f32; +pub const LOADER_INNER: f32 = 14_f32; +pub const LOADER_ICON_MAX_SIZE: i16 = 24; + pub const fn size() -> Offset { Offset::new(WIDTH, HEIGHT) } diff --git a/core/embed/rust/src/ui/model_tt/constant.rs b/core/embed/rust/src/ui/model_tt/constant.rs index cbc66328b6..ceed7fe464 100644 --- a/core/embed/rust/src/ui/model_tt/constant.rs +++ b/core/embed/rust/src/ui/model_tt/constant.rs @@ -5,6 +5,10 @@ pub const HEIGHT: i16 = 240; pub const LINE_SPACE: i16 = 4; pub const FONT_BPP: i16 = 4; +pub const LOADER_OUTER: f32 = 60_f32; +pub const LOADER_INNER: f32 = 42_f32; +pub const LOADER_ICON_MAX_SIZE: i16 = 64; + pub const fn size() -> Offset { Offset::new(WIDTH, HEIGHT) } diff --git a/core/embed/rust/trezorhal.h b/core/embed/rust/trezorhal.h index 6a9a5f68e8..dd18552dee 100644 --- a/core/embed/rust/trezorhal.h +++ b/core/embed/rust/trezorhal.h @@ -1,5 +1,8 @@ +#include "buffers.h" #include "common.h" #include "display.h" +#include "display_interface.h" +#include "dma2d.h" #include "fonts/fonts.h" #include "rgb_led.h" #include "secbool.h" diff --git a/core/embed/trezorhal/common.c b/core/embed/trezorhal/common.c index f0396a7be3..0bacc5f1a1 100644 --- a/core/embed/trezorhal/common.c +++ b/core/embed/trezorhal/common.c @@ -184,3 +184,12 @@ void collect_hw_entropy(void) { FLASH_OTP_BLOCK_SIZE), NULL); } + +// this function resets settings changed in one layer (bootloader/firmware), +// which might be incompatible with the other layers older versions, +// where this setting might be unknown +void ensure_compatible_settings(void) { +#ifdef TREZOR_MODEL_T + display_set_big_endian(); +#endif +} diff --git a/core/embed/trezorhal/common.h b/core/embed/trezorhal/common.h index 1bee151f98..770e7ec97c 100644 --- a/core/embed/trezorhal/common.h +++ b/core/embed/trezorhal/common.h @@ -82,5 +82,6 @@ void memset_reg(volatile void *start, volatile void *stop, uint32_t val); void jump_to(uint32_t address); void jump_to_unprivileged(uint32_t address); void jump_to_with_flag(uint32_t address, uint32_t register_flag); +void ensure_compatible_settings(void); #endif diff --git a/core/embed/trezorhal/dma2d.c b/core/embed/trezorhal/dma2d.c new file mode 100644 index 0000000000..ea8e2e7b37 --- /dev/null +++ b/core/embed/trezorhal/dma2d.c @@ -0,0 +1,148 @@ +/* + * 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 . + */ + +#include "dma2d.h" +#include "colors.h" +#include STM32_HAL_H + +typedef enum { + DMA2D_LAYER_FG = 1, + DMA2D_LAYER_BG = 0, +} dma2d_layer_t; + +static DMA2D_HandleTypeDef dma2d_handle = {0}; + +void dma2d_init(void) { + __HAL_RCC_DMA2D_CLK_ENABLE(); + + dma2d_handle.Instance = (DMA2D_TypeDef*)DMA2D_BASE; + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565; + dma2d_handle.Init.OutputOffset = 0; +} + +static void dma2d_init_clut(uint16_t fg, uint16_t bg, dma2d_layer_t layer) { + volatile uint32_t* table = NULL; + if (layer == DMA2D_LAYER_BG) { + table = dma2d_handle.Instance->BGCLUT; + } else { + table = dma2d_handle.Instance->FGCLUT; + } + + uint32_t fg32 = rgb565_to_rgb888(fg); + uint32_t bg32 = rgb565_to_rgb888(bg); + + for (uint8_t i = 0; i < 16; i++) { + table[i] = interpolate_rgb888_color(fg32, bg32, i); + } + + DMA2D_CLUTCfgTypeDef clut; + clut.CLUTColorMode = DMA2D_CCM_ARGB8888; + clut.Size = 0xf; + clut.pCLUT = 0; // loading directly + + HAL_DMA2D_ConfigCLUT(&dma2d_handle, clut, layer); +} + +void dma2d_setup_const(void) { + dma2d_handle.Init.Mode = DMA2D_R2M; + HAL_DMA2D_Init(&dma2d_handle); +} + +void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color) { + dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4; + dma2d_handle.LayerCfg[1].InputOffset = 0; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = 0; + + dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_FG); + + HAL_DMA2D_Init(&dma2d_handle); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); +} + +void dma2d_setup_16bpp(void) { + dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; + dma2d_handle.LayerCfg[1].InputOffset = 0; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = 0; + + HAL_DMA2D_Init(&dma2d_handle); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); +} + +void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color) { + dma2d_handle.Init.Mode = DMA2D_M2M_BLEND; + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4; + dma2d_handle.LayerCfg[1].InputOffset = 0; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = + 0xFF000000 | rgb565_to_rgb888(overlay_color); + + dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565; + dma2d_handle.LayerCfg[0].InputOffset = 0; + dma2d_handle.LayerCfg[0].AlphaMode = 0; + dma2d_handle.LayerCfg[0].InputAlpha = 0; + + HAL_DMA2D_Init(&dma2d_handle); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 0); +} + +void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color, + uint16_t overlay_color) { + dma2d_handle.Init.Mode = DMA2D_M2M_BLEND; + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4; + dma2d_handle.LayerCfg[1].InputOffset = 0; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = rgb565_to_rgb888(overlay_color); + + dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_L4; + dma2d_handle.LayerCfg[0].InputOffset = 0; + dma2d_handle.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA; + dma2d_handle.LayerCfg[0].InputAlpha = 0xFF; + + dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_BG); + + HAL_DMA2D_Init(&dma2d_handle); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 0); +} + +void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels) { + HAL_DMA2D_Start(&dma2d_handle, (uint32_t)in_addr, (uint32_t)out_addr, pixels, + 1); +} + +void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels) { + HAL_DMA2D_Start(&dma2d_handle, rgb565_to_rgb888(color), (uint32_t)out_addr, + pixels, 1); +} + +void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr, + uint8_t* out_addr, int32_t pixels) { + HAL_DMA2D_BlendingStart(&dma2d_handle, (uint32_t)overlay_addr, + (uint32_t)bg_addr, (uint32_t)out_addr, pixels, 1); +} + +void dma2d_wait_for_transfer(void) { + while (HAL_DMA2D_PollForTransfer(&dma2d_handle, 10) != HAL_OK) + ; +} diff --git a/core/embed/trezorhal/dma2d.h b/core/embed/trezorhal/dma2d.h new file mode 100644 index 0000000000..790cbc2d62 --- /dev/null +++ b/core/embed/trezorhal/dma2d.h @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#ifndef TREZORHAL_DMA2D_H +#define TREZORHAL_DMA2D_H + +#include "common.h" + +void dma2d_init(void); + +void dma2d_setup_const(void); +void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color); +void dma2d_setup_16bpp(void); +void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color, + uint16_t overlay_color); +void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color); + +void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels); +void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels); +void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr, + uint8_t* out_addr, int32_t pixels); + +void dma2d_wait_for_transfer(void); + +#endif // TREZORHAL_DMA2D_H diff --git a/core/embed/trezorhal/stm32f4xx_hal_conf.h b/core/embed/trezorhal/stm32f4xx_hal_conf.h index 2882f8a16b..0258c1a326 100644 --- a/core/embed/trezorhal/stm32f4xx_hal_conf.h +++ b/core/embed/trezorhal/stm32f4xx_hal_conf.h @@ -69,7 +69,7 @@ /* #define HAL_DAC_MODULE_ENABLED */ /* #define HAL_DCMI_MODULE_ENABLED */ #define HAL_DMA_MODULE_ENABLED -/* #define HAL_DMA2D_MODULE_ENABLED */ +#define HAL_DMA2D_MODULE_ENABLED /* #define HAL_ETH_MODULE_ENABLED */ #define HAL_FLASH_MODULE_ENABLED /* #define HAL_NAND_MODULE_ENABLED */ diff --git a/core/embed/unix/dma2d.c b/core/embed/unix/dma2d.c new file mode 100644 index 0000000000..32af3438d6 --- /dev/null +++ b/core/embed/unix/dma2d.c @@ -0,0 +1,145 @@ +/* + * 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 . + */ + +#include "dma2d.h" +#include "colors.h" +#include "display_interface.h" + +typedef enum { + DMA2D_LAYER_FG = 1, + DMA2D_LAYER_BG = 0, +} dma2d_layer_t; + +typedef enum { + DMA2D_MODE_CONST = 0, + DMA2D_MODE_4BPP, + DMA2D_MODE_16BPP, + DMA2D_MODE_4BPP_OVER_4BPP, + DMA2D_MODE_4BPP_OVER_16BPP, +} dma2d_mode_t; + +static uint16_t clut_bg[16]; +static uint16_t clut_fg[16]; +static uint16_t dma2d_color; +static dma2d_mode_t mode = 0; + +void dma2d_init(void) { + // do nothing +} + +void dma2d_init_clut(uint16_t fg, uint16_t bg, dma2d_layer_t layer) { + uint16_t* table; + if (layer == DMA2D_LAYER_BG) { + table = clut_bg; + } else { + table = clut_fg; + } + + set_color_table(table, fg, bg); +} + +void dma2d_setup_const(void) { mode = DMA2D_MODE_CONST; } + +void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color) { + dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_FG); + mode = DMA2D_MODE_4BPP; +} + +void dma2d_setup_16bpp(void) { mode = DMA2D_MODE_16BPP; } + +void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color) { + mode = DMA2D_MODE_4BPP_OVER_16BPP; + dma2d_color = overlay_color; +} + +void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color, + uint16_t overlay_color) { + mode = DMA2D_MODE_4BPP_OVER_4BPP; + + dma2d_color = overlay_color; + dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_BG); +} + +void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels) { + (void)out_addr; + for (int i = 0; i < pixels; i++) { + if (mode == DMA2D_MODE_4BPP) { + uint8_t c = ((uint8_t*)in_addr)[i / 2]; + uint8_t even_pix = c >> 4; + uint8_t odd_pix = c & 0xF; + PIXELDATA(clut_fg[odd_pix]); + PIXELDATA(clut_fg[even_pix]); + i++; // wrote two pixels + } + if (mode == DMA2D_MODE_16BPP) { + uint16_t c = ((uint16_t*)in_addr)[i]; + PIXELDATA(c); + } + } +} + +void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels) { + (void)out_addr; + for (int i = 0; i < pixels; i++) { + PIXELDATA(color); + } +} + +void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr, + uint8_t* out_addr, int32_t pixels) { + (void)out_addr; + for (int i = 0; i < pixels; i++) { + if (mode == DMA2D_MODE_4BPP_OVER_4BPP) { + uint8_t c = overlay_addr[i / 2]; + uint8_t b = bg_addr[i / 2]; + + uint8_t odd_overlay_pix = c & 0xF; + uint8_t odd_bg_pix = b & 0xF; + uint16_t c_odd_bg = clut_bg[odd_bg_pix]; + uint16_t final_odd_color = + interpolate_color(dma2d_color, c_odd_bg, odd_overlay_pix); + PIXELDATA(final_odd_color); + + uint8_t even_overlay_pix = c >> 4; + uint8_t even_bg_pix = b >> 4; + uint16_t c_even_bg = clut_bg[even_bg_pix]; + uint16_t final_even_color = + interpolate_color(dma2d_color, c_even_bg, even_overlay_pix); + PIXELDATA(final_even_color); + + i++; // wrote two pixels + } + if (mode == DMA2D_MODE_4BPP_OVER_16BPP) { + uint16_t c = ((uint16_t*)bg_addr)[i]; + uint8_t o = overlay_addr[i / 2]; + uint8_t o_pix; + if (i % 2 == 0) { + o_pix = o & 0xF; + } else { + o_pix = o >> 4; + } + uint16_t final_odd_color = interpolate_color(dma2d_color, c, o_pix); + PIXELDATA(final_odd_color); + } + } +} + +void dma2d_wait_for_transfer(void) { + // done in place when emulating, so no need for wait here +} diff --git a/core/embed/unix/dma2d.h b/core/embed/unix/dma2d.h new file mode 100644 index 0000000000..263ec54eb9 --- /dev/null +++ b/core/embed/unix/dma2d.h @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +#ifndef _DMA2D_H +#define _DMA2D_H + +// Mockup file so that rust doesn't complain when building bindings + +#include "common.h" + +void dma2d_init(void); + +void dma2d_setup_const(void); +void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color); +void dma2d_setup_16bpp(void); +void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color, + uint16_t overlay_color); +void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color); + +void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels); +void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels); +void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr, + uint8_t* out_addr, int32_t pixels); + +void dma2d_wait_for_transfer(void); + +#endif //_DMA2D_H diff --git a/core/mocks/generated/trezorui.pyi b/core/mocks/generated/trezorui.pyi index 2c992d7797..233f1f9159 100644 --- a/core/mocks/generated/trezorui.pyi +++ b/core/mocks/generated/trezorui.pyi @@ -11,6 +11,10 @@ class Display: FONT_MONO: int # id of monospace font FONT_NORMAL: int # id of normal-width font FONT_BOLD: int # id of bold-width font + TOIF_FULL_COLOR_BE: int # full color big endian TOI image + TOIF_FULL_COLOR_LE: int # full color little endian TOI image + TOIF_GRAYSCALE_EH: int # grayscale even high TOI image + TOIF_FULL_COLOR_BE: int # grayscale odd high TOI image def __init__(self) -> None: """ @@ -49,11 +53,10 @@ class Display: are drawn with radius radius. """ - def toif_info(self, image: bytes) -> tuple[int, int, bool]: + def toif_info(self, image: bytes) -> tuple[int, int, int]: """ Returns tuple containing TOIF image dimensions: width, height, and - whether it is grayscale. - Raises an exception for corrupted images. + format Raises an exception for corrupted images. """ def image(self, x: int, y: int, image: bytes) -> None: diff --git a/core/src/apps/management/apply_settings.py b/core/src/apps/management/apply_settings.py index 438d70fa10..fdf8bfd34d 100644 --- a/core/src/apps/management/apply_settings.py +++ b/core/src/apps/management/apply_settings.py @@ -24,12 +24,12 @@ def validate_homescreen(homescreen: bytes) -> None: ) try: - w, h, grayscale = ui.display.toif_info(homescreen) + w, h, toif_format = ui.display.toif_info(homescreen) except ValueError: raise wire.DataError("Invalid homescreen") if w != 144 or h != 144: raise wire.DataError("Homescreen must be 144x144 pixel large") - if grayscale: + if toif_format != ui.display.TOIF_FULL_COLOR_BE: raise wire.DataError("Homescreen must be full-color TOIF image") diff --git a/core/tools/snippets/change_icon_format.py b/core/tools/snippets/change_icon_format.py new file mode 100644 index 0000000000..2c65a658fe --- /dev/null +++ b/core/tools/snippets/change_icon_format.py @@ -0,0 +1,161 @@ +import os + +from trezorlib import toif + + +def process_line(infile, outfile): + line = infile.readline() + data = [x.strip().lower() for x in line.split(',')] + for c in data: + if len(c) == 4: + outfile.write(bytes((int(c, 16),))) + + +def header_to_toif(path) -> bool: + with open(path, "r") as infile, open('tmp.toif', "wb") as outfile: + infile.readline() + name_line = infile.readline() + name = name_line.split(" ")[3].split("[")[0] + infile.readline() + magic_line = infile.readline().split(',')[3] + + outfile.write(bytes((0x54,))) + outfile.write(bytes((0x4f,))) + outfile.write(bytes((0x49,))) + if "g" in magic_line: + outfile.write(bytes((ord('g'),))) + elif "G" in magic_line: + outfile.write(bytes((ord('G'),))) + elif "f" in magic_line: + outfile.write(bytes((ord('f'),))) + elif "F" in magic_line: + outfile.write(bytes((ord('F'),))) + else: + print(magic_line) + raise Exception("Unknown format") + + infile.readline() + process_line(infile, outfile) + infile.readline() + process_line(infile, outfile) + infile.readline() + process_line(infile, outfile) + infile.readline() + return name + + +def toif_to_header(path, name): + with open('tmp_c.toif', "rb") as infile, open(path, "w") as outfile: + b = infile.read(4) + outfile.write("// clang-format off\n") + outfile.write(f'static const uint8_t {name}[] = {{\n',) + outfile.write(" // magic\n",) + if b[3] == ord('f'): + outfile.write(" 'T', 'O', 'I', 'f',\n",) + elif b[3] == ord('F'): + outfile.write(" 'T', 'O', 'I', 'F',\n",) + elif b[3] == ord('g'): + outfile.write(" 'T', 'O', 'I', 'g',\n",) + elif b[3] == ord('G'): + outfile.write(" 'T', 'O', 'I', 'G',\n",) + else: + raise Exception("Unknown format") + + + outfile.write(" // width (16-bit), height (16-bit)\n",) + outfile.write(" ") + for i in range(4): + hex_data = infile.read(1).hex() + outfile.write(f'0x{hex_data},') + if i != 3: + outfile.write(' ') + outfile.write("\n") + + outfile.write(" // compressed data length (32-bit)\n",) + outfile.write(" ") + for i in range(4): + hex_data = infile.read(1).hex() + outfile.write(f'0x{hex_data},') + if i != 3: + outfile.write(' ') + outfile.write("\n") + + outfile.write(" // compressed data\n",) + outfile.write(" ") + hex_data = infile.read(1).hex() + first = True + while hex_data: + if not first: + outfile.write(' ') + first = False + outfile.write(f'0x{hex_data},') + hex_data = infile.read(1).hex() + outfile.write("\n};\n") + + byte = infile.read(1) + + +def reformat_c_icon(path): + name = header_to_toif(path) + with open("tmp.toif", "rb") as f: + toi = toif.from_bytes(f.read()) + im = toi.to_image() + with open("tmp_c.toif", "wb") as f: + toi = toif.from_image(im) + f.write(toi.to_bytes()) + toif_to_header(path, name) + + os.remove("tmp.toif") + os.remove("tmp_c.toif") + + +def reformat_c_icons(p): + files = os.listdir(p) + for file in files: + if file.startswith("icon_") and file.endswith(".h"): + reformat_c_icon(os.path.join(p, file)) + + +def reformat_toif_icon(p): + with open(p, "rb") as f: + toi = toif.from_bytes(f.read()) + im = toi.to_image() + with open(p, "wb") as f: + toi = toif.from_image(im) + f.write(toi.to_bytes()) + + +def reformat_toif_icons(p): + files = os.listdir(p) + for file in files: + if file.endswith(".toif"): + reformat_toif_icon(os.path.join(p, file)) + + +def change_icon_format(): + # bootloader icons + reformat_c_icons("../../embed/bootloader") + + # bootloader_ci icons + reformat_c_icons("../../embed/bootloader_ci") + + # rust icons + reformat_toif_icons("../../embed/rust/src/ui/model_tr/res") + reformat_toif_icons("../../embed/rust/src/ui/model_tt/res") + + # python icons + reformat_toif_icons("../../src/trezor/res") + reformat_toif_icons("../../src/trezor/res/header_icons") + + # vendor header icons + reformat_toif_icon("../../embed/vendorheader/vendor_satoshilabs.toif") + reformat_toif_icon("../../embed/vendorheader/vendor_unsafe.toif") + + # additional python icons + # reformat_toif_icon("../src/apps/homescreen/res/bg.toif") - unchanged - using as avatar + reformat_toif_icon("../../src/apps/management/res/small-arrow.toif") + reformat_toif_icon("../../src/apps/webauthn/res/icon_webauthn.toif") + + +if __name__ == "__main__": + change_icon_format() diff --git a/docs/misc/toif.md b/docs/misc/toif.md index 468f1db5da..fbe0d9894a 100644 --- a/docs/misc/toif.md +++ b/docs/misc/toif.md @@ -15,10 +15,12 @@ All multibyte integer values are little endian! ## Format -TOI currently supports 2 variants: +TOI currently supports 4 variants: -* `f`: full-color -* `g`: gray-scale +* `f`: full-color big endian +* `F`: full-color little endian +* `g`: gray-scale odd high +* `G`: gray-scale even high ### Full-color @@ -30,15 +32,25 @@ final 5 bits are blue: |----|----|----|----|----|----|---|---|---|---|---|---|---|---|---|---| | R | R | R | R | R | G | G | G | G | G | G | B | B | B | B | B | +The data is stored according to endianness. + ### Gray-scale Each pixel is encoded using a 4-bit value. Each byte contains color of two pixels: +#### Odd high: + | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |---|---|---|---|---|---|---|---| | Po | Po | Po | Po | Pe | Pe | Pe | Pe | +#### Even high: + +| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|-----|-----|-----|-----|-----|-----|-----|-----| +| Pe | Pe | Pe | Pe | Po | Po | Po | Po | + Where Po is odd pixel and Pe is even pixel. ## Compression diff --git a/python/.changelog.d/2414.added b/python/.changelog.d/2414.added new file mode 100644 index 0000000000..68cb1d57e5 --- /dev/null +++ b/python/.changelog.d/2414.added @@ -0,0 +1 @@ +Added new TOI formats - little endian full-color and even-high grayscale diff --git a/python/src/trezorlib/firmware.py b/python/src/trezorlib/firmware.py index 54408082cd..eadad475c4 100644 --- a/python/src/trezorlib/firmware.py +++ b/python/src/trezorlib/firmware.py @@ -99,8 +99,10 @@ class Unsigned(FirmwareIntegrityError): class ToifMode(Enum): - full_color = b"f" - grayscale = b"g" + full_color = b"f" # big endian + grayscale = b"g" # odd hi + full_color_le = b"F" # little endian + grayscale_eh = b"G" # even hi class HeaderType(Enum): diff --git a/python/src/trezorlib/toif.py b/python/src/trezorlib/toif.py index 699d6e298f..db976c6a70 100644 --- a/python/src/trezorlib/toif.py +++ b/python/src/trezorlib/toif.py @@ -45,50 +45,68 @@ def _decompress(data: bytes) -> bytes: return zlib.decompress(data, wbits=-10) -def _from_pil_rgb(pixels: Sequence[RGBPixel]) -> bytes: +def _from_pil_rgb(pixels: Sequence[RGBPixel], little_endian: bool) -> bytes: data = bytearray() for r, g, b in pixels: c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3) - data += struct.pack(">H", c) + if little_endian: + data += struct.pack("H", c) return bytes(data) -def _to_rgb(data: bytes) -> bytes: +def _to_rgb(data: bytes, little_endian: bool) -> bytes: res = bytearray() for i in range(0, len(data), 2): - (c,) = struct.unpack(">H", data[i : i + 2]) + if little_endian: + (c,) = struct.unpack("H", data[i : i + 2]) r = (c & 0xF800) >> 8 - g = (c & 0x07C0) >> 3 + g = (c & 0x07E0) >> 3 b = (c & 0x001F) << 3 res += bytes((r, g, b)) return bytes(res) -def _from_pil_grayscale(pixels: Sequence[int]) -> bytes: +def _from_pil_grayscale(pixels: Sequence[int], right_hi: bool) -> bytes: data = bytearray() for i in range(0, len(pixels), 2): left, right = pixels[i], pixels[i + 1] - c = (left & 0xF0) | ((right & 0xF0) >> 4) + if right_hi: + c = (right & 0xF0) | ((left & 0xF0) >> 4) + else: + c = (left & 0xF0) | ((right & 0xF0) >> 4) data += struct.pack(">B", c) return bytes(data) -def _from_pil_grayscale_alpha(pixels: Sequence[Tuple[int, int]]) -> bytes: +def _from_pil_grayscale_alpha( + pixels: Sequence[Tuple[int, int]], right_hi: bool +) -> bytes: data = bytearray() for i in range(0, len(pixels), 2): left_w_alpha, right_w_alpha = pixels[i], pixels[i + 1] left = int((left_w_alpha[0] * left_w_alpha[1]) / 255) right = int((right_w_alpha[0] * right_w_alpha[1]) / 255) - c = (left & 0xF0) | ((right & 0xF0) >> 4) + if right_hi: + c = (right & 0xF0) | ((left & 0xF0) >> 4) + else: + c = (left & 0xF0) | ((right & 0xF0) >> 4) data += struct.pack(">B", c) return bytes(data) -def _to_grayscale(data: bytes) -> bytes: +def _to_grayscale(data: bytes, right_hi: bool) -> bytes: res = bytearray() for pixel in data: - left = pixel & 0xF0 - right = (pixel & 0x0F) << 4 + if right_hi: + right = pixel & 0xF0 + left = (pixel & 0x0F) << 4 + else: + left = pixel & 0xF0 + right = (pixel & 0x0F) << 4 res += bytes((left, right)) return bytes(res) @@ -102,7 +120,10 @@ class Toif: def __post_init__(self) -> None: # checking the data size width, height = self.size - if self.mode is firmware.ToifMode.grayscale: + if ( + self.mode is firmware.ToifMode.grayscale + or self.mode is firmware.ToifMode.grayscale_eh + ): expected_size = width * height // 2 else: expected_size = width * height * 2 @@ -123,10 +144,16 @@ class Toif: pil_mode: Literal["L", "RGB"] if self.mode is firmware.ToifMode.grayscale: pil_mode = "L" - raw_data = _to_grayscale(uncompressed) - else: + raw_data = _to_grayscale(uncompressed, right_hi=False) + elif self.mode is firmware.ToifMode.grayscale_eh: + pil_mode = "L" + raw_data = _to_grayscale(uncompressed, right_hi=True) + elif self.mode is firmware.ToifMode.full_color: pil_mode = "RGB" - raw_data = _to_rgb(uncompressed) + raw_data = _to_rgb(uncompressed, little_endian=False) + else: # self.mode is firmware.ToifMode.full_color_le: + pil_mode = "RGB" + raw_data = _to_rgb(uncompressed, little_endian=True) return Image.frombuffer(pil_mode, self.size, raw_data, "raw", pil_mode, 0, 1) @@ -152,7 +179,9 @@ def load(filename: str) -> Toif: def from_image( - image: "Image.Image", background: Tuple[int, int, int, int] = (0, 0, 0, 255) + image: "Image.Image", + background: Tuple[int, int, int, int] = (0, 0, 0, 255), + legacy_format: bool = False, ) -> Toif: if not PIL_AVAILABLE: raise RuntimeError( @@ -168,18 +197,31 @@ def from_image( image = image.convert("L") if image.mode == "L": - toif_mode = firmware.ToifMode.grayscale if image.size[0] % 2 != 0: raise ValueError("Only even-width grayscale images are supported") - toif_data = _from_pil_grayscale(image.getdata()) + if not legacy_format: + toif_mode = firmware.ToifMode.grayscale_eh + toif_data = _from_pil_grayscale(image.getdata(), right_hi=True) + else: + toif_mode = firmware.ToifMode.grayscale + toif_data = _from_pil_grayscale(image.getdata(), right_hi=False) elif image.mode == "LA": toif_mode = firmware.ToifMode.grayscale if image.size[0] % 2 != 0: raise ValueError("Only even-width grayscale images are supported") - toif_data = _from_pil_grayscale_alpha(image.getdata()) + if not legacy_format: + toif_mode = firmware.ToifMode.grayscale_eh + toif_data = _from_pil_grayscale_alpha(image.getdata(), right_hi=True) + else: + toif_mode = firmware.ToifMode.grayscale + toif_data = _from_pil_grayscale_alpha(image.getdata(), right_hi=False) elif image.mode == "RGB": - toif_mode = firmware.ToifMode.full_color - toif_data = _from_pil_rgb(image.getdata()) + if not legacy_format: + toif_mode = firmware.ToifMode.full_color_le + toif_data = _from_pil_rgb(image.getdata(), little_endian=True) + else: + toif_mode = firmware.ToifMode.full_color + toif_data = _from_pil_rgb(image.getdata(), little_endian=False) else: raise ValueError(f"Unsupported image mode: {image.mode}")