1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-15 23:05:45 +00:00

feat(core): allow JPEG image to be decoded as Mono8 bitmap

[no changelog]
This commit is contained in:
cepetr 2025-04-04 11:53:25 +02:00 committed by cepetr
parent 622fa05e48
commit db057ff6ae
9 changed files with 238 additions and 4 deletions

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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::<u32>().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::<u8>().1 };
let bitmap = unwrap!(Bitmap::new(
BitmapFormat::MONO8,
None,
r.size(),
None,
buff_u8
));
let view = BitmapView::new(&bitmap);
output(r, view)
}
}

View File

@ -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

View File

@ -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,

View File

@ -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
// =============================================================================

View File

@ -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
// ---------------------------------------------------------------------

View File

@ -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
// ---------------------------------------------------------------------