From db057ff6ae8b971b397f166cea4863fb5faa47d3 Mon Sep 17 00:00:00 2001 From: cepetr Date: Fri, 4 Apr 2025 11:53:25 +0200 Subject: [PATCH] feat(core): allow JPEG image to be decoded as Mono8 bitmap [no changelog] --- core/embed/gfx/inc/gfx/jpegdec.h | 14 ++- core/embed/gfx/jpegdec/stm32u5/jpegdec.c | 114 ++++++++++++++++++ core/embed/rust/build.rs | 4 +- core/embed/rust/src/trezorhal/jpegdec.rs | 80 +++++++++++- .../sys/syscall/stm32/syscall_dispatch.c | 6 + .../embed/sys/syscall/stm32/syscall_numbers.h | 1 + core/embed/sys/syscall/stm32/syscall_stubs.c | 5 + .../sys/syscall/stm32/syscall_verifiers.c | 16 +++ .../sys/syscall/stm32/syscall_verifiers.h | 2 + 9 files changed, 238 insertions(+), 4 deletions(-) diff --git a/core/embed/gfx/inc/gfx/jpegdec.h b/core/embed/gfx/inc/gfx/jpegdec.h index 73d14e318f..cf5a12f3f7 100644 --- a/core/embed/gfx/inc/gfx/jpegdec.h +++ b/core/embed/gfx/inc/gfx/jpegdec.h @@ -33,6 +33,9 @@ // Maximum size of the RGBA8888 buffer for a slice. #define JPEGDEC_RGBA8888_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8 * 4) +// Maximum size of the MONO8 buffer for a slice +#define JPEGDEC_MONO8_BUFFER_SIZE (JPEGDEC_MAX_SLICE_BLOCKS * 8 * 8) + typedef struct jpegdec jpegdec_t; typedef enum { @@ -43,7 +46,7 @@ typedef enum { // (jpegdec_get_info can be called to get the image info) JPEGDEC_STATE_INFO_READY, // Decoded slice is ready - // (jpegdec_get_slice_rgba8888 can be called to get the slice data) + // (jpegdec_get_slice_xxx can be called to get the slice data) JPEGDEC_STATE_SLICE_READY, // Decoding is finished JPEGDEC_STATE_FINISHED, @@ -125,3 +128,12 @@ bool jpegdec_get_info(jpegdec_image_t* info); // Can be called immediately after `jpegdec_process` returns // `JPEGDEC_STATE_SLICE_READY`. bool jpegdec_get_slice_rgba8888(uint32_t* rgba8888, jpegdec_slice_t* slice); + +// Copy the last decoded slice to the buffer +// +// `mono8` must be a buffer of at least `JPEGDEC_MONO8_BUFFER_SIZE` +// bytes and must be aligned to 4 bytes. +// +// Can be called immediately after `jpegdec_process` returns +// `JPEGDEC_STATE_SLICE_READY`. +bool jpegdec_get_slice_mono8(uint32_t* mono8, jpegdec_slice_t* slice); diff --git a/core/embed/gfx/jpegdec/stm32u5/jpegdec.c b/core/embed/gfx/jpegdec/stm32u5/jpegdec.c index ab9dcaf1b1..0bc2675cdc 100644 --- a/core/embed/gfx/jpegdec/stm32u5/jpegdec.c +++ b/core/embed/gfx/jpegdec/stm32u5/jpegdec.c @@ -463,4 +463,118 @@ bool jpegdec_get_slice_rgba8888(uint32_t *rgba8888, jpegdec_slice_t *slice) { return result; } +// Initialize DMA base copy for fast copying of 8x8 blocks +// +// 'dst_stride' is the number of bytes between the start of two consecutive +// rows in the destination buffer. +static void fast_copy_init(DMA_HandleTypeDef *hdma, size_t dst_stride) { + hdma->Instance = GPDMA1_Channel13; + hdma->Init.Request = GPDMA1_REQUEST_HASH_IN; + hdma->Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST; + hdma->Init.Direction = DMA_MEMORY_TO_MEMORY; + hdma->Init.SrcInc = DMA_SINC_INCREMENTED; + hdma->Init.DestInc = DMA_DINC_INCREMENTED; + hdma->Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD; + hdma->Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD; + hdma->Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT; + hdma->Init.SrcBurstLength = 2; + hdma->Init.DestBurstLength = 2; + hdma->Init.TransferAllocatedPort = + DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0; + hdma->Init.TransferEventMode = DMA_TCEM_REPEATED_BLOCK_TRANSFER; + hdma->Init.Mode = DMA_NORMAL; + HAL_DMA_Init(hdma); + HAL_DMA_ConfigChannelAttributes(hdma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | + DMA_CHANNEL_SRC_SEC | + DMA_CHANNEL_DEST_SEC); + + DMA_RepeatBlockConfTypeDef rep = {0}; + + rep.DestAddrOffset = dst_stride - 8; + rep.RepeatCount = 1; + HAL_DMAEx_ConfigRepeatBlock(hdma, &rep); +} + +// Initiate a fast copy of an 8x8 block from 'src' to 'dst' +// +// `src` is expected to be a pointer to the start of an 8x8 block. +// `dst` is expected to be a pointer to destination bitmap buffer +static inline void fast_copy_block(DMA_HandleTypeDef *hdma, uint8_t *dst, + uint8_t *src) { + while ((hdma->Instance->CSR & DMA_FLAG_IDLE) == 0) + ; + + hdma->Lock = 0; + hdma->State = HAL_DMA_STATE_READY; + + HAL_DMA_Start(hdma, (uint32_t)src, (uint32_t)dst, 64); +} + +// Deinitialize the DMA base copy +static inline void fast_copy_deinit(DMA_HandleTypeDef *hdma) { + while ((hdma->Instance->CSR & DMA_FLAG_IDLE) == 0) + ; + + hdma->Lock = 0; + hdma->State = HAL_DMA_STATE_READY; + + HAL_DMA_DeInit(hdma); +} + +bool jpegdec_get_slice_mono8(uint32_t *mono8, jpegdec_slice_t *slice) { + jpegdec_t *dec = &g_jpegdec; + + if (!dec->inuse) { + return false; + } + + if (dec->state != JPEGDEC_STATE_SLICE_READY) { + return false; + } + + if (!IS_ALIGNED((uint32_t)mono8, 4)) { + return false; + } + + slice->width = dec->slice_width; + slice->height = dec->slice_height; + slice->x = dec->slice_x; + slice->y = dec->slice_y; + + bool result = false; + + switch (dec->image.format) { + case JPEGDEC_IMAGE_YCBCR420: + // Not implemented + break; + case JPEGDEC_IMAGE_YCBCR422: + // Not implemented + break; + case JPEGDEC_IMAGE_YCBCR444: + // Not implemented + break; + case JPEGDEC_IMAGE_GRAYSCALE: { + static DMA_HandleTypeDef hdma = {0}; + fast_copy_init(&hdma, dec->slice_width); + uint8_t *src = (uint8_t *)dec->ycbcr_buffer; + for (int y = 0; y < dec->slice_height; y += 8) { + for (int x = 0; x < dec->slice_width; x += 8) { + uint8_t *dst = (uint8_t *)mono8 + y * dec->slice_width + x; + fast_copy_block(&hdma, dst, src); + src += 64; + } + } + + fast_copy_deinit(&hdma); + result = true; + } break; + + default: + result = false; + break; + } + + return result; +} + #endif // KERNEL_MODE diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index 8f19ed602c..6505b19140 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -416,6 +416,7 @@ fn generate_trezorhal_bindings() { .allowlist_function("haptic_play_custom") // jpegdec .allowlist_var("JPEGDEC_RGBA8888_BUFFER_SIZE") + .allowlist_var("JPEGDEC_MONO8_BUFFER_SIZE") .allowlist_type("jpegdec_state_t") .allowlist_type("jpegdec_image_t") .allowlist_type("jpegdec_image_format_t") @@ -424,7 +425,8 @@ fn generate_trezorhal_bindings() { .allowlist_function("jpegdec_close") .allowlist_function("jpegdec_process") .allowlist_function("jpegdec_get_info") - .allowlist_function("jpegdec_get_slice_rgba8888"); + .allowlist_function("jpegdec_get_slice_rgba8888") + .allowlist_function("jpegdec_get_slice_mono8"); // Write the bindings to a file in the OUR_DIR. bindings diff --git a/core/embed/rust/src/trezorhal/jpegdec.rs b/core/embed/rust/src/trezorhal/jpegdec.rs index ac5369fca0..53ab9aa2dc 100644 --- a/core/embed/rust/src/trezorhal/jpegdec.rs +++ b/core/embed/rust/src/trezorhal/jpegdec.rs @@ -9,6 +9,7 @@ use crate::io::BinaryData; use num_traits::FromPrimitive; pub const RGBA8888_BUFFER_SIZE: usize = ffi::JPEGDEC_RGBA8888_BUFFER_SIZE as _; +pub const MONO8_BUFFER_SIZE: usize = ffi::JPEGDEC_MONO8_BUFFER_SIZE as _; #[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)] enum JpegDecState { @@ -129,6 +130,33 @@ impl<'a> JpegDecoder<'a> { Ok(()) } + /// Decodes the JPEG image and calls the output function for each slice. + /// The function decodes only the Y component of the image, so + /// the output is always grayscale (BitmapFormat::MONO8). + /// Requires a temporary buffer of size `RGBA8888_BUFFER_SIZE`. + /// The output function should return `true` to continue decoding or `false` + /// to stop. Returns `Ok(())` if the decoding was successful or + /// `Err(())` if an error occurred. + pub fn decode_mono8( + &mut self, + buff: &mut [u8], + output: &mut dyn FnMut(Rect, BitmapView) -> bool, + ) -> Result<(), ()> { + loop { + match self.read_input() { + JpegDecState::SliceReady => { + if !self.write_output_mono8(buff, output) { + break; + }; + } + JpegDecState::Finished => break, + JpegDecState::NeedData => {} + _ => return Err(()), + }; + } + Ok(()) + } + fn read_input(&mut self) -> JpegDecState { if self.buff_pos == self.buff_len { self.buff_len = self.jpeg.read(self.jpeg_pos, &mut self.buff); @@ -173,8 +201,8 @@ impl<'a> JpegDecoder<'a> { }; // SAFETY: - // - `rgba_u32` is a valid pointer to a mutable buffer of u32 of length at - // least `RGBA8888_BUFFER_SIZE` + // - `rgba_u32` is a valid pointer to a mutable buffer of length at least + // `RGBA8888_BUFFER_SIZE` aligned to u32 // - `slice` is a valid pointer to a mutable `jpegdec_slice_t` // - `jpegdec_get_slice_rgba8888` doesn't retain the pointers to the data for // later use @@ -201,4 +229,52 @@ impl<'a> JpegDecoder<'a> { output(r, view) } + + fn write_output_mono8( + &self, + buff: &mut [u8], + output: &mut dyn FnMut(Rect, BitmapView) -> bool, + ) -> bool { + // SAFETY: + // - after aligning the buffer to u32, the we check the + // length of the buffer to be at least `MONO8_BUFFER_SIZE` + let buff_u32 = unsafe { buff.align_to_mut::().1 }; + assert!(buff_u32.len() * 4 >= MONO8_BUFFER_SIZE); + + let mut slice = ffi::jpegdec_slice_t { + x: 0, + y: 0, + width: 0, + height: 0, + }; + + // SAFETY: + // - `buff` is a valid pointer to a mutable buffer of length at least + // `MONO8_BUFFER_SIZE` bytes aligned to u32 + // - `slice` is a valid pointer to a mutable `jpegdec_slice_t` + // - `jpegdec_get_slice_yonly` doesn't retain the pointers to the data for + // later use + unsafe { ffi::jpegdec_get_slice_mono8(buff_u32.as_mut_ptr(), &mut slice) }; + + let r = Rect::from_top_left_and_size( + Point::new(slice.x, slice.y), + Offset::new(slice.width, slice.height), + ); + + // SAFETY: + // - reinterpreting &[u32] to &[u8] is safe + let buff_u8 = unsafe { buff.align_to::().1 }; + + let bitmap = unwrap!(Bitmap::new( + BitmapFormat::MONO8, + None, + r.size(), + None, + buff_u8 + )); + + let view = BitmapView::new(&bitmap); + + output(r, view) + } } diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c index 36f3d38617..26492f4aa0 100644 --- a/core/embed/sys/syscall/stm32/syscall_dispatch.c +++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c @@ -779,6 +779,12 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args, (void *)args[0], (jpegdec_slice_t *)args[1]); break; } + + case SYSCALL_JPEGDEC_GET_SLICE_MONO8: { + args[0] = jpegdec_get_slice_mono8__verified((void *)args[0], + (jpegdec_slice_t *)args[1]); + break; + } #endif // USE_HW_JPEG_DECODER #ifdef USE_DMA2D diff --git a/core/embed/sys/syscall/stm32/syscall_numbers.h b/core/embed/sys/syscall/stm32/syscall_numbers.h index c7d736ea9b..0a20eeb2e8 100644 --- a/core/embed/sys/syscall/stm32/syscall_numbers.h +++ b/core/embed/sys/syscall/stm32/syscall_numbers.h @@ -157,6 +157,7 @@ typedef enum { SYSCALL_JPEGDEC_PROCESS, SYSCALL_JPEGDEC_GET_INFO, SYSCALL_JPEGDEC_GET_SLICE_RGBA8888, + SYSCALL_JPEGDEC_GET_SLICE_MONO8, SYSCALL_DMA2D_WAIT, SYSCALL_DMA2D_RGB565_FILL, diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c index 68984fab06..95d2920a21 100644 --- a/core/embed/sys/syscall/stm32/syscall_stubs.c +++ b/core/embed/sys/syscall/stm32/syscall_stubs.c @@ -733,6 +733,11 @@ bool jpegdec_get_slice_rgba8888(uint32_t *rgba8888, jpegdec_slice_t *slice) { SYSCALL_JPEGDEC_GET_SLICE_RGBA8888); } +bool jpegdec_get_slice_mono8(uint32_t *mono8, jpegdec_slice_t *slice) { + return (bool)syscall_invoke2((uint32_t)mono8, (uint32_t)slice, + SYSCALL_JPEGDEC_GET_SLICE_MONO8); +} + #endif // USE_HW_JPEG_DECODER // ============================================================================= diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c index a688718074..784f36fe3c 100644 --- a/core/embed/sys/syscall/stm32/syscall_verifiers.c +++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c @@ -878,6 +878,22 @@ access_violation: return false; } +bool jpegdec_get_slice_mono8__verified(void *mono8, jpegdec_slice_t *slice) { + if (!probe_write_access(mono8, JPEGDEC_RGBA8888_BUFFER_SIZE)) { + goto access_violation; + } + + if (!probe_write_access(slice, sizeof(*slice))) { + goto access_violation; + } + + return jpegdec_get_slice_mono8(mono8, slice); + +access_violation: + apptask_access_violation(); + return false; +} + #endif // USE_HW_JPEG_DECODER // --------------------------------------------------------------------- diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.h b/core/embed/sys/syscall/stm32/syscall_verifiers.h index 840ca9db7d..b61ecf7058 100644 --- a/core/embed/sys/syscall/stm32/syscall_verifiers.h +++ b/core/embed/sys/syscall/stm32/syscall_verifiers.h @@ -230,6 +230,8 @@ bool jpegdec_get_info__verified(jpegdec_image_t *image); bool jpegdec_get_slice_rgba8888__verified(void *rgba8888, jpegdec_slice_t *slice); +bool jpegdec_get_slice_mono8__verified(void *mono8, jpegdec_slice_t *slice); + #endif // USE_HW_JPEG_DECODER // ---------------------------------------------------------------------