From 3b291669ca5e7a11f19a4983a3139b3ad7ac9dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Thu, 26 Feb 2026 02:02:25 +0100 Subject: [PATCH] Move bounce buffer impl into the `esp-hal-bounce-buffers` crate --- firmware/Cargo.lock | 11 + firmware/acid-firmware/Cargo.toml | 1 + firmware/acid-firmware/src/main.rs | 14 +- firmware/acid-firmware/src/ui/backend.rs | 8 +- firmware/acid-firmware/src/ui/dpi.rs | 889 +---------------------- 5 files changed, 22 insertions(+), 901 deletions(-) diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index a75b39d..72079af 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "esp-backtrace", "esp-bootloader-esp-idf", "esp-hal", + "esp-hal-bounce-buffers", "esp-metadata-generated 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "esp-println", "esp-radio", @@ -2143,6 +2144,15 @@ dependencies = [ "xtensa-lx-rt", ] +[[package]] +name = "esp-hal-bounce-buffers" +version = "0.1.0" +dependencies = [ + "document-features", + "esp-hal", + "ouroboros", +] + [[package]] name = "esp-hal-procmacros" version = "0.21.0" @@ -6875,6 +6885,7 @@ checksum = "a0f368519fc6c85fc1afdb769fb5a51123f6158013e143656e25a3485a0d401c" [[package]] name = "spectre-api-sys" version = "0.1.0" +source = "git+c:/Users/Limeth/workspace/rust/spectre-api-sys#9e844eb056c3dfee8286ac21ec40fa689a8b8aa2" dependencies = [ "bindgen", "cc", diff --git a/firmware/acid-firmware/Cargo.toml b/firmware/acid-firmware/Cargo.toml index 1fbda72..aed0cb7 100644 --- a/firmware/acid-firmware/Cargo.toml +++ b/firmware/acid-firmware/Cargo.toml @@ -87,6 +87,7 @@ esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } indoc = "2.0.7" ouroboros = "0.18.5" +esp-hal-bounce-buffers = { git = "https://forgejo.limeth.cz/limeth/esp-hal-bounce-buffers", features = ["esp32s3"] } # A fork of slint with patches for `allocator_api` support. # Don't forget to change `slint-build` in build dependencies, if this is changed. diff --git a/firmware/acid-firmware/src/main.rs b/firmware/acid-firmware/src/main.rs index 0c9e22d..6ab5ed4 100644 --- a/firmware/acid-firmware/src/main.rs +++ b/firmware/acid-firmware/src/main.rs @@ -37,9 +37,7 @@ use embassy_time::{Duration, Timer}; use esp_alloc::{HeapRegion, MemoryCapability}; use esp_bootloader_esp_idf::partitions::PartitionTable; use esp_hal::clock::CpuClock; -use esp_hal::dma::{ - BurstConfig, ExternalBurstConfig, InternalBurstConfig, -}; +use esp_hal::dma::{BurstConfig, ExternalBurstConfig, InternalBurstConfig}; use esp_hal::efuse::Efuse; #[cfg(not(feature = "alt-log"))] use esp_hal::gpio::NoPin; @@ -59,6 +57,7 @@ use esp_hal::system::Stack; use esp_hal::timer::timg::TimerGroup; use esp_hal::uart::{Uart, UartRx}; use esp_hal::{Blocking, interrupt}; +use esp_hal_bounce_buffers::{DmaBounce, Swapchain, allocate_dma_buffer_in}; use esp_rtos::embassy::{Executor, InterruptExecutor}; use esp_storage::FlashStorage; use i_slint_core::software_renderer::TargetPixel; @@ -85,7 +84,7 @@ use crate::matrix::IoeMatrix; use crate::peripherals::st7701s::St7701s; use crate::proxy::create_hid_report_interceptor; use crate::ui::backend::SlintBackend; -use crate::ui::dpi::{DmaBounce, Framebuffer, Swapchain, allocate_dma_buffer_in}; +use crate::ui::dpi::Framebuffer; use crate::vial::{ CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID, VIAL_VENDOR_ID, @@ -192,7 +191,7 @@ async fn test_bounce_buffers( { let write_guard = &mut swapchain_writer.write(); - let buffer_src = write_guard.cast::(); + let buffer_src = bytemuck::cast_slice_mut::(write_guard); let colors = (0..WIDTH_VISIBLE_PIXELS as u8 / 2) .rev() .map(|val| Rgb565Pixel::from_rgb(0xFF, val * 2, 0)) @@ -231,7 +230,7 @@ async fn test_bounce_buffers( burst_config, true, ); - buf.launch_interrupt_driven_task().await; + buf.launch_interrupt_driven_task(); error!("TEST BOUNCE BUFFERS SECTION DONE"); } @@ -771,8 +770,7 @@ async fn main_task(peripherals: MainPeripherals) { .bounce_buffers .take() .unwrap() - .launch_interrupt_driven_task() - .await; + .launch_interrupt_driven_task(); // TODO: Probably want to select! instead and re-try. join_all![ diff --git a/firmware/acid-firmware/src/ui/backend.rs b/firmware/acid-firmware/src/ui/backend.rs index 4b57ee5..b62f068 100644 --- a/firmware/acid-firmware/src/ui/backend.rs +++ b/firmware/acid-firmware/src/ui/backend.rs @@ -9,6 +9,7 @@ use alloc::{ }; use critical_section::Mutex; use esp_hal::time::Instant; +use esp_hal_bounce_buffers::SwapchainWriter; use log::{debug, info}; use slint::{ EventLoopError, PhysicalSize, SharedString, WindowSize, @@ -19,10 +20,7 @@ use slint::{ }; use xkbcommon::xkb::{self, Keysym}; -use crate::{ - proxy::{KEY_MESSAGE_CHANNEL, TryFromKeysym}, - ui::dpi::SwapchainWriter, -}; +use crate::proxy::{KEY_MESSAGE_CHANNEL, TryFromKeysym}; use super::window_adapter::SoftwareWindowAdapter; @@ -135,7 +133,7 @@ impl slint::platform::Platform for SlintBackend { // TODO: VSYNC support. let mut swapchain = self.swapchain.borrow_mut(); let mut write_guard = swapchain.write(); - let framebuffer = write_guard.cast::(); + let framebuffer = bytemuck::cast_slice_mut::(&mut write_guard); renderer.render(framebuffer, self.window_size[1] as usize); info!("UI rendered."); }); diff --git a/firmware/acid-firmware/src/ui/dpi.rs b/firmware/acid-firmware/src/ui/dpi.rs index ee97c1d..406bb11 100644 --- a/firmware/acid-firmware/src/ui/dpi.rs +++ b/firmware/acid-firmware/src/ui/dpi.rs @@ -25,6 +25,7 @@ use esp_hal::{ ram, spi::master::AnySpi, }; +use esp_hal_bounce_buffers::{DmaBounce, Swapchain, SwapchainWriter, allocate_dma_buffer_in}; use log::error; use ouroboros::self_referencing; @@ -61,894 +62,6 @@ pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) { } } -// const DMA_CHANNEL_INBOUND: usize = 0; -// const INTERRUPT_INBOUND: Interrupt = Interrupt::DMA_IN_CH0; -const DMA_CHANNEL_OUTBOUND: usize = 2; -const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2; - -#[self_referencing] -struct ReceivingTransfer { - mem2mem: SimpleMem2Mem<'static, Blocking>, - #[borrows(mut mem2mem)] - #[covariant] - transfer: Option>, -} - -pub struct Swapchain { - pub framebuffers: [&'static mut [u8]; 2], -} - -impl Swapchain { - pub fn into_reader_writer(self) -> (SwapchainReader, SwapchainWriter) { - assert_eq!( - self.framebuffers[0].len(), - self.framebuffers[1].len(), - "framebuffers in a swapchain must have an equal length" - ); - - let reader_index = Arc::new(AtomicBool::new(true)); - - ( - SwapchainReader { - framebuffers_rw: [ - self.framebuffers[0] as *const [u8], - self.framebuffers[1] as *const [u8], - ], - reader_index: reader_index.clone(), - }, - SwapchainWriter { - framebuffers_wr: [ - self.framebuffers[1] as *mut [u8], - self.framebuffers[0] as *mut [u8], - ], - reader_index, - }, - ) - } -} - -// TODO: Don't need to store the framebuffer length twice. Use `*const u8` instead, and store length separately. -pub struct SwapchainReader { - /// These are in the opposite order to `SwapchainWriter`'s framebuffers. - framebuffers_rw: [*const [u8]; 2], - reader_index: Arc, -} - -unsafe impl Send for SwapchainReader {} - -impl SwapchainReader { - fn len(&self) -> usize { - self.framebuffers_rw[0].len() - } - - fn load_read_index(&self) -> usize { - self.reader_index.load(atomic::Ordering::SeqCst) as usize - } - - fn get_latest_framebuffer(&self) -> &[u8] { - unsafe { &*self.framebuffers_rw[self.load_read_index()] } - } -} - -// TODO: Don't need to store the framebuffer length twice. Use `*mut u8` instead, and store length separately. -pub struct SwapchainWriter { - /// These are in the opposite order to `SwapchainReader`'s framebuffers. - framebuffers_wr: [*mut [u8]; 2], - reader_index: Arc, -} - -unsafe impl Send for SwapchainWriter {} - -impl SwapchainWriter { - fn len(&self) -> usize { - self.framebuffers_wr[0].len() - } - - pub fn write(&mut self) -> SwapchainWriteGuard<'_> { - let framebuffer_ptr = - self.framebuffers_wr[self.reader_index.load(atomic::Ordering::SeqCst) as usize]; - SwapchainWriteGuard { - framebuffer: unsafe { &mut *framebuffer_ptr }, - reader_index: &self.reader_index, - } - } -} - -pub struct SwapchainWriteGuard<'a> { - framebuffer: &'a mut [u8], - reader_index: &'a AtomicBool, -} - -impl<'a> SwapchainWriteGuard<'a> { - pub fn cast(&mut self) -> &mut [T] { - bytemuck::cast_slice_mut::<_, T>(self.framebuffer) - } -} - -impl Drop for SwapchainWriteGuard<'_> { - fn drop(&mut self) { - self.reader_index.fetch_xor(true, atomic::Ordering::SeqCst); - } -} - -impl<'a> Deref for SwapchainWriteGuard<'a> { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.framebuffer - } -} - -impl<'a> DerefMut for SwapchainWriteGuard<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.framebuffer - } -} - -pub struct DmaBounce { - // TODO: Make these generic. - // They currently cannot be generic, because they lacks a `reborrow` method. - channel: DMA_CH0<'static>, - // This can also be more generic, see `DmaEligible` in `Mem2Mem::new`. - peripheral_src: AnySpi<'static>, - // This can also be more generic, see `DmaEligible` in `Mem2Mem::new`. - peripheral_dst: Option>, - // TODO: Combine with peripheral_dst using an enum? - transfer_dst: Option>, - - // TODO: Consider having a separate burst config for the two transfers. - burst_config: BurstConfig, - cyclic: bool, - /// The size of each window. - window_size: usize, - /// The number of windows. - windows_len: usize, - swapchain_src: SwapchainReader, - // Two buffers of size `window_size`, - // one of which is being written to, while the other is being read from. - bounce_buffer_dst: &'static mut [u8], - bounce_buffer_src: &'static mut [u8], - // A descriptor list that spans a buffer of size `window_size`. - // The buffer pointers need to be updated before each transmission to point to the correct window in the source buffer `src_buffer`. - src_descs: &'static mut [DmaDescriptor], - // A descriptor list that spans a buffer of size `window_size`. - // The buffer pointers need to be updated before each transmission to point to the correct bounce buffer. - bounce_dst_descs: &'static mut [DmaDescriptor], - // A cyclic descriptor list that spans the buffers `bounce_buffer_dst` and `bounce_buffer_src`. - bounce_src_descs: &'static mut [DmaDescriptor], - descriptors_per_window: usize, - // The index of the next window about to be received into the destination bounce buffer. - window_index_next: usize, - frame_index_next: usize, - receiving_transfer: Option, -} - -impl DmaBounce { - /// * `allocator` - The allocator used to allocate the bounce buffers. - /// * `channel` - The DMA channel used to transfer data from the source buffer to the bounce buffers. - /// * `peripheral_src` - The peripheral to transfer data from the source buffer to the bounce buffers. - /// * `peripheral_dst` - The peripheral to transfer data to, from the bounce buffers. - /// * `buffer_src` - The source buffer, typically allocated in external memory. - /// * `row_front_porch_bytes` - The number of arbitrary-valued bytes to be sent in front of each row to the destination peripheral. - /// * `row_width_bytes` - The width of a row, in bytes. - /// * `window_size_rows` - The size of a single bounce buffer, in rows. - /// * `burst_config` - The burst config to use for memory transfers (both in and out). TODO: This could be split. - /// * `cyclic` - Experimental! Whether to use a cyclic descriptor list for transfer from the bounce buffers to the destination peripheral. - pub fn new( - allocator: impl Allocator + Copy + 'static, - channel: DMA_CH0<'static>, - peripheral_src: AnySpi<'static>, - peripheral_dst: Dpi<'static, Blocking>, - swapchain_src: SwapchainReader, - row_front_porch_bytes: usize, - row_width_bytes: usize, - window_size_rows: usize, - burst_config: BurstConfig, - cyclic: bool, - ) -> Self { - let window_size = row_width_bytes * window_size_rows; - - assert_eq!( - swapchain_src.len() % window_size, - 0, - "the size of a source buffer must be a multiple of the window size ({window_size} bytes), but it is {len} bytes large", - len = swapchain_src.len() - ); - - // Conservative alignment. Maxiumum of the cartesian product of [tx, rx] × [internal, external]. - let alignment = burst_config.min_compatible_alignment(); - - for &swapchain_ptr in &swapchain_src.framebuffers_rw { - assert_eq!( - unsafe { &*swapchain_ptr }.as_ptr() as usize % alignment, - 0, - "the source buffer must be sufficiently aligned to {alignment} bytes for the burst config", - ); - } - - assert_eq!( - row_width_bytes % alignment, - 0, - "the size of a row in bytes must be sufficiently aligned to {alignment} bytes for the burst config", - ); - assert_eq!( - row_front_porch_bytes % alignment, - 0, - "the size of a row's front porch in bytes must be sufficiently aligned to {alignment} bytes for the burst config", - ); - // We need to make the destination peripheral read the front porch data from somewhere, - // and that somewhere is currently the bounce buffer. - // Therefore the front porch must be in bounds. - assert!( - row_front_porch_bytes <= window_size, - "front porch too large" - ); - - let windows_len = swapchain_src.len() / window_size; - // TODO: Figure out a way to avoid `leak`ing memory. - // We probably want to store the `Box`es and then unsafely extend the lifetime at sites of usage. - let bounce_buffer_dst = - Box::leak(allocate_dma_buffer_in(window_size, burst_config, allocator)); - let bounce_buffer_src = - Box::leak(allocate_dma_buffer_in(window_size, burst_config, allocator)); - let src_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |desc| { - desc.reset_for_tx(desc.next.is_null()); - // Length for TX buffers must be set in software. - // In RX buffers, it is set by hardware. - desc.set_length(desc.size()); - }); - let bounce_dst_descs = - Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {}); - let (bounce_src_descs, descriptors_per_window) = Self::bounce_descriptors_for_buffer( - windows_len, - row_front_porch_bytes, - row_width_bytes, - window_size_rows, - unsafe { - ( - &mut *(bounce_buffer_dst as *mut _), - &mut *(bounce_buffer_src as *mut _), - ) - }, - burst_config, - cyclic, - ); - - Self { - channel, - peripheral_src, - peripheral_dst: Some(peripheral_dst), - transfer_dst: None, - burst_config, - cyclic, - window_size, - windows_len, - swapchain_src, - bounce_buffer_dst, - bounce_buffer_src, - src_descs, - bounce_dst_descs, - bounce_src_descs, - descriptors_per_window, - window_index_next: 0, - frame_index_next: 0, - receiving_transfer: None, - } - } - - fn linear_descriptors_for_buffer( - buffer_len: usize, - burst_config: BurstConfig, - mut setup_desc: impl FnMut(&mut DmaDescriptor), - ) -> &'static mut [DmaDescriptor] { - let max_chunk_size = burst_config.max_compatible_chunk_size(); - let descriptors_len = dma::descriptor_count(buffer_len, max_chunk_size, false); - // TODO: This leaks memory. Ensure it's only called during setup. - let descriptors = Box::leak(vec![DmaDescriptor::EMPTY; descriptors_len].into_boxed_slice()); - - // Link up the descriptors. - let mut next = core::ptr::null_mut(); - for desc in descriptors.iter_mut().rev() { - desc.next = next; - next = desc; - } - - // Prepare each descriptor's buffer size. - let mut descriptors_it = descriptors.iter_mut(); - let mut remaining_len = buffer_len; - - while remaining_len > 0 { - let chunk_size = core::cmp::min(max_chunk_size, remaining_len); - let desc = descriptors_it.next().unwrap(); - desc.set_size(chunk_size); - (setup_desc)(desc); - remaining_len -= chunk_size; - } - - descriptors - } - - fn prepare_descriptors_window( - bounce_buffer: &mut [u8], - descriptors_window: &mut [DmaDescriptor], - row_front_porch_bytes: usize, - row_width_bytes: usize, - window_size_rows: usize, - max_chunk_size: usize, - descriptors_per_row: usize, - descriptors_per_row_front_porch: usize, - ) { - for (row_index_in_window, descriptors_row) in descriptors_window - .chunks_mut(descriptors_per_row) - .enumerate() - { - // let row_index = row_index_in_window + window_index * window_size_rows; - let (descriptors_row_front_porch, descriptors_row_stored) = - descriptors_row.split_at_mut(descriptors_per_row_front_porch); - - // Prepare front porch descriptors. - { - let mut descriptors_it = descriptors_row_front_porch.iter_mut(); - let mut remaining_front_porch = row_front_porch_bytes; - - while remaining_front_porch > 0 { - let desc = descriptors_it.next().unwrap(); - let chunk_size = core::cmp::min(max_chunk_size, remaining_front_porch); - remaining_front_porch -= chunk_size; - // Just make it point at a bounce buffer. - // It is guaranteed to have enough bytes by `DmaBounce::new`. - desc.buffer = bounce_buffer.as_mut_ptr(); - desc.set_size(chunk_size); - desc.set_length(chunk_size); - desc.reset_for_tx(false); - } - - assert!( - descriptors_it.next().is_none(), - "front porch descriptors must be used up" - ); - assert_eq!( - descriptors_row_front_porch - .iter() - .map(|desc| desc.size()) - .sum::(), - row_front_porch_bytes - ); - } - - // Prepare window descriptors. - { - let mut remaining_bounce_buffer = - &mut bounce_buffer[row_index_in_window * row_width_bytes..][..row_width_bytes]; - - // if remaining_bounce_buffer.len() > row_width_bytes { - // remaining_bounce_buffer = &mut remaining_bounce_buffer[..row_width_bytes]; - // } - - for desc in &mut *descriptors_row_stored { - let chunk_size = core::cmp::min(max_chunk_size, remaining_bounce_buffer.len()); - desc.buffer = remaining_bounce_buffer.as_mut_ptr(); - remaining_bounce_buffer = &mut remaining_bounce_buffer[chunk_size..]; - desc.set_size(chunk_size); - desc.set_length(chunk_size); - desc.reset_for_tx(false); - } - - assert!( - remaining_bounce_buffer.is_empty(), - "bounce buffer must be used up" - ); - assert_eq!( - descriptors_row_stored - .iter() - .map(|desc| desc.size()) - .sum::(), - row_width_bytes - ); - } - } - - // Set EOF bit on the last descriptor of the window, to signal - // that the bounce buffer is done being read from. - if let Some(last_desc) = descriptors_window.last_mut() { - last_desc.reset_for_tx(true); - } - - assert_eq!( - descriptors_window - .iter() - .map(|desc| desc.size()) - .sum::(), - window_size_rows * (row_front_porch_bytes + row_width_bytes) - ); - } - - fn bounce_descriptors_for_buffer( - windows_len: usize, - row_front_porch_bytes: usize, - row_width_bytes: usize, - window_size_rows: usize, - bounce_buffers: (&'static mut [u8], &'static mut [u8]), - burst_config: BurstConfig, - cyclic: bool, - ) -> (&'static mut [DmaDescriptor], usize) { - assert_eq!( - bounce_buffers.0.len(), - bounce_buffers.1.len(), - "bounce buffers must be equal in size" - ); - // If an odd number of windows were needed, two descriptor lists would be needed, - assert_eq!(windows_len % 2, 0, "the number of windows must be even"); - - let buffer_len = bounce_buffers.0.len(); - - assert_eq!( - buffer_len, - row_width_bytes * window_size_rows, - "the provided bounce buffers have an invalid size" - ); - - // Implementation note: - // A cyclic descriptor could consist of just a set of descriptors per window, - // so two sets in total, because there are two bounce buffers. - // However, we can also access the pointer of the EOF descriptor within the - // EOF interrupt handler, which lets us compute which descriptor generated that - // interrupt. - // This is useful in the case when an interrupt is missed. Then the number of interrupts - // handled doesn't correspond to the number of windows sent to the destination peripheral. - // In that case, the number of windows sent can be computed from the address of the descriptor. - let max_chunk_size = burst_config.max_compatible_chunk_size(); - let descriptors_per_row_front_porch = - dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false); - let descriptors_per_row_stored = - dma::descriptor_count(row_width_bytes, max_chunk_size, false); - let descriptors_per_row = descriptors_per_row_stored + descriptors_per_row_front_porch; - let descriptors_per_window = window_size_rows * descriptors_per_row; - let descriptors_per_frame = descriptors_per_window * windows_len; - let descriptors_frame = - Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice()); - - // Link up the descriptors. - let mut next = if cyclic { - descriptors_frame.first_mut().unwrap() as *mut _ - } else { - core::ptr::null_mut() - }; - - for desc in descriptors_frame.iter_mut().rev() { - desc.next = next; - next = desc; - } - - // Prepare each descriptor's buffer size. - let bounce_buffers = [bounce_buffers.0, bounce_buffers.1]; - - for (window_index, descriptors_window) in descriptors_frame - .chunks_mut(descriptors_per_window) - .enumerate() - { - let bounce_buffer_index = window_index % 2; - let bounce_buffer = &mut *bounce_buffers[bounce_buffer_index]; - - Self::prepare_descriptors_window( - bounce_buffer, - descriptors_window, - row_front_porch_bytes, - row_width_bytes, - window_size_rows, - max_chunk_size, - descriptors_per_row, - descriptors_per_row_front_porch, - ); - } - - assert_eq!( - descriptors_frame - .iter() - .map(|desc| desc.size()) - .sum::(), - windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes) - ); - - (descriptors_frame, descriptors_per_window) - } - - /// Safety: - /// TX descriptors require read access to the buffer. - /// RX descriptors require write access to the buffer. - unsafe fn linear_descriptors_prepare( - descriptors: &mut [DmaDescriptor], - mut buffer: Option<&[u8]>, - mut setup_desc: impl FnMut(&mut DmaDescriptor), - ) { - for descriptor in descriptors.iter_mut() { - if let Some(inner_buffer) = buffer { - descriptor.buffer = inner_buffer.as_ptr() as *mut u8; - buffer = Some(&inner_buffer[descriptor.size()..]); - } - (setup_desc)(descriptor); - } - - if let Some(buffer) = buffer { - assert!( - buffer.is_empty(), - "a buffer of an incompatible length was assigned to a descriptor set" - ); - } - } - - fn enable_interrupts() { - // Enable interrupts for the peripheral, pt. 1. - interrupt::enable( - INTERRUPT_OUTBOUND, - dma_outbound_interrupt_handler.priority(), - ) - .unwrap(); - - // Bind the interrupt handler. - unsafe { - interrupt::bind_interrupt(INTERRUPT_OUTBOUND, dma_outbound_interrupt_handler.handler()); - } - - // Enable interrupts for the peripheral, pt. 2. - DMA::regs() - .ch(DMA_CHANNEL_OUTBOUND) - .out_int() - .ena() - .modify(|_, w| w.out_eof().bit(true)); - } - - /// Receive a window of bytes into the current dst bounce buffer. - /// Finally, swaps the bounce buffers. - /// - /// # Safety: - /// TODO - unsafe fn receive_window_start(&mut self) -> ReceivingTransfer { - // Descriptors are initialized by `DmaTxBuf::new`. - let buffer_src_window = &self.swapchain_src.get_latest_framebuffer() - [self.window_index_next * self.window_size..][..self.window_size]; - - unsafe { - Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_desc| { - // No need to call `DmaDescriptor::reset_for_tx`, because - // 1. we don't rely on the ownership flag; - // 2. the EOF flag is already set during the construction of this buffer. - }); - // TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`. - Self::linear_descriptors_prepare( - self.bounce_dst_descs, - Some(self.bounce_buffer_dst), - |desc| { - desc.reset_for_rx(); - }, - ); - } - - // Extend the lifetime to 'static because it is required by Mem2Mem. - // - // Safety: - // Pointees are done being used by the driver before this scope ends, - // this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish. - let bounce_dst_descs: &'static mut [DmaDescriptor] = - unsafe { &mut *(self.bounce_dst_descs as *mut _) }; - let src_descs: &'static mut [DmaDescriptor] = unsafe { &mut *(self.src_descs as *mut _) }; - - let mem2mem = unsafe { - Mem2Mem::new( - self.channel.clone_unchecked(), - self.peripheral_src.clone_unchecked(), - ) - } - .with_descriptors(bounce_dst_descs, src_descs, self.burst_config) - .unwrap(); - - ReceivingTransferBuilder { - mem2mem, - transfer_builder: |mem2mem| { - Some( - mem2mem - .start_transfer(self.bounce_buffer_dst, buffer_src_window) - .unwrap(), - ) - }, - } - .build() - } - - /// Receive a window of bytes into the current dst bounce buffer. - /// Finally, swaps the bounce buffers. - async fn receive_window(&mut self) { - // Descriptors are initialized by `DmaTxBuf::new`. - let buffer_src_window = &self.swapchain_src.get_latest_framebuffer() - [self.window_index_next * self.window_size..][..self.window_size]; - - unsafe { - Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_desc| { - // No need to call `DmaDescriptor::reset_for_tx`, because - // 1. we don't rely on the ownership flag; - // 2. the EOF flag is already set during the construction of this buffer. - }); - // TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`. - Self::linear_descriptors_prepare( - self.bounce_dst_descs, - Some(self.bounce_buffer_dst), - |desc| { - desc.reset_for_rx(); - }, - ); - } - - { - // Extend the lifetime to 'static because it is required by Mem2Mem. - // - // Safety: - // Pointees are done being used by the driver before this scope ends, - // this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish. - let bounce_dst_descs: &'static mut [DmaDescriptor] = - unsafe { &mut *(self.bounce_dst_descs as *mut _) }; - let src_descs: &'static mut [DmaDescriptor] = - unsafe { &mut *(self.src_descs as *mut _) }; - - let mut mem2mem = Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow()) - .into_async() - .with_descriptors(bounce_dst_descs, src_descs, self.burst_config) - .unwrap(); - let transfer = mem2mem - .start_transfer(self.bounce_buffer_dst, buffer_src_window) - .unwrap(); - - transfer.wait_async().await.unwrap(); - } - - self.increase_window_counter(1); - } - - fn increase_window_counter(&mut self, windows: isize) { - if windows.rem_euclid(2) == 1 { - core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src); - } - - let window_index_next = self.window_index_next as isize + windows; - self.frame_index_next = (self.frame_index_next as isize - + window_index_next / self.windows_len as isize) - as usize; - self.window_index_next = window_index_next.rem_euclid(self.windows_len as isize) as usize; - } - - pub async fn launch_interrupt_driven_task(mut self) { - Self::enable_interrupts(); - - // Receive the first 2 windows, so that the outbound transfer can read valid data. - self.receive_window().await; - - let dma_tx_buffer = self.get_dma_tx_buffer(); - let transfer = self - .peripheral_dst - .take() - .unwrap() - .send(self.cyclic /* Send perpetually */, dma_tx_buffer) - .unwrap_or_else(|(error, _, _)| { - panic!("failed to begin the transmission of the first frame: {error:?}"); - }); - self.transfer_dst = Some(transfer); - self.receiving_transfer = Some(unsafe { self.receive_window_start() }); - - unsafe { - *DMA_STATE.0.get() = Some(self); - } - } - - fn get_dma_tx_buffer(&mut self) -> DmaTxBounceBuf { - DmaTxBounceBuf { - preparation: dma::Preparation { - start: self.bounce_src_descs.first_mut().unwrap(), - direction: dma::TransferDirection::Out, - accesses_psram: false, - burst_transfer: self.burst_config, - // We don't care about ownership. - // Just yeet whatever the descriptors point to to the destination peripheral. - check_owner: Some(false), - auto_write_back: false, - }, - } - } -} - -pub struct DmaTxBounceBuf { - preparation: dma::Preparation, -} - -unsafe impl DmaTxBuffer for DmaTxBounceBuf { - type View = Self; - type Final = Self; - - fn prepare(&mut self) -> dma::Preparation { - dma::Preparation { - start: self.preparation.start, - direction: self.preparation.direction, - accesses_psram: self.preparation.accesses_psram, - burst_transfer: self.preparation.burst_transfer, - check_owner: self.preparation.check_owner, - auto_write_back: self.preparation.auto_write_back, - } - } - - fn into_view(self) -> Self::View { - self - } - - fn from_view(view: Self::View) -> Self::Final { - view - } -} - -static DMA_STATE: SyncUnsafeCell> = SyncUnsafeCell(UnsafeCell::new(None)); - -#[repr(transparent)] -pub struct SyncUnsafeCell(UnsafeCell); - -unsafe impl Sync for SyncUnsafeCell {} - -#[handler(priority = Priority::Priority3)] -#[ram] // Improves performance. -fn dma_outbound_interrupt_handler() { - let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).out_int(); - let bounce_buffer_sent = interrupt.st().read().out_eof().bit_is_set(); - - if !bounce_buffer_sent { - return; - } - - // Clear the bit by writing 1 to the clear bits. - interrupt.clr().write(|w| w.out_eof().bit(true)); - - // SAFETY: This value is only ever read in our interrupt handler, - // and interrupts are disabled, and we only use this in one thread. - let Some(dma_state) = unsafe { &mut *DMA_STATE.0.get() }.as_mut() else { - error!("no DMA state available when executing DMA interrupt handler"); - return; - }; - - // The descriptor of the buffer with an EOF flag that just finished being sent. - let descriptor_ptr = DMA::regs() - .ch(DMA_CHANNEL_OUTBOUND) - .out_eof_des_addr() - .read() - .out_eof_des_addr() - .bits() as *const DmaDescriptor; - // This is the index of the window that just finished being transmitted to the destination peripheral. - let window_sent_index = - unsafe { descriptor_ptr.offset_from_unsigned(dma_state.bounce_src_descs.as_ptr()) } - / dma_state.descriptors_per_window; - // The next window to be sent is `(window_sent_index + 1) % dma_state.windows_len`. - // That is not the window we want to buffer, because the transmissions would race. - // We instead want to buffer the next window: - let window_index_next = (window_sent_index + 2) % dma_state.windows_len; - - // Swap bounce buffers. - if (dma_state.windows_len + window_index_next - dma_state.window_index_next) % 2 == 1 { - core::mem::swap( - &mut dma_state.bounce_buffer_dst, - &mut dma_state.bounce_buffer_src, - ); - } - - dma_state.window_index_next = window_index_next; - - let mut receiving_transfer = dma_state - .receiving_transfer - .take() - .expect("no ongoing transfer to a bounce buffer present"); - let receiving_transfer = receiving_transfer - .with_mut(|x| x.transfer.take()) - .expect("no ongoing inner transfer to a bounce buffer present"); - - if receiving_transfer.is_done() { - drop(receiving_transfer); - } else { - // error!("the transfer to a bounce buffer has not finished yet, waiting"); - receiving_transfer.wait().unwrap(); - } - - // If there is any ongoing transfer, cancel it and start a new one. - dma_state.receiving_transfer = Some(unsafe { dma_state.receive_window_start() }); -} - -// #[handler(priority = Priority::Priority3)] -// #[ram] // Improves performance. -// fn dma_inbound_interrupt_handler() { -// warn!("Inbound"); - -// let interrupt = DMA::regs().ch(DMA_CHANNEL_INBOUND).in_int(); -// let bounce_buffer_processed = interrupt.st().read().in_done().bit_is_set(); -// if bounce_buffer_processed { -// // Clear the bit by writing 1 to the clear bits. -// interrupt.clr().write(|w| w.in_done().bit(true)); - -// assert!( -// !INBOUND_TRANSFER_FINISHED.signaled(), -// "inbound transfer already signalled" -// ); -// INBOUND_TRANSFER_FINISHED.signal(()); -// } -// } - -// pub async fn run_lcd( -// mut st7701s: St7701s<'static, Blocking>, -// framebuffer: &'static mut Framebuffer, -// ) { -// loop { -// // Timer::after(Duration::from_millis(100)).await; -// // yield_now().await; -// SIGNAL_LCD_SUBMIT.wait().await; - -// // TODO: Use bounce buffers: -// // https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32s3/api-reference/peripherals/lcd.html#bounce-buffer-with-single-psram-frame-buffer -// // This can be implemented as a `DmaTxBuffer`. -// let transfer = match st7701s.dpi.send(false, framebuffer.dma_buf.take().unwrap()) { -// Err((error, result_dpi, result_dma_buf)) => { -// error!( -// "An error occurred while initiating transfer of the framebuffer to the LCD display: {error:?}" -// ); -// st7701s.dpi = result_dpi; -// framebuffer.dma_buf = Some(result_dma_buf); -// continue; -// } -// Ok(transfer) => transfer, -// }; - -// // This could be used to allow other tasks to be executed on the first core, but that causes -// // the flash to be accessed, which interferes with the framebuffer transfer. -// // For that reason, it is disabled, and this task blocks the first core, until the transfer -// // is complete. -// #[cfg(not(feature = "limit-fps"))] -// while !transfer.is_done() { -// // Timer::after_millis(1).await; -// rmk::embassy_futures::yield_now().await; -// } - -// let result; -// let dma_buf; -// (result, st7701s.dpi, dma_buf) = transfer.wait(); -// framebuffer.dma_buf = Some(dma_buf); - -// SIGNAL_UI_RENDER.signal(()); - -// if let Err(error) = result { -// error!( -// "An error occurred while transferring framebuffer to the LCD display: {error:?}" -// ); -// } -// } -// } - -/// Allocates a buffer appropriately aligned for use with DMA. -pub fn allocate_dma_buffer_in( - len: usize, - burst_config: BurstConfig, - alloc: A, -) -> Box<[u8], A> { - // Conservative alignment. Maxiumum of the cartesian product of [tx, rx] × [internal, external]. - let alignment = burst_config.min_compatible_alignment(); - - assert_eq!( - len % alignment, - 0, - "the size of a DMA buffer must be a multiple of {alignment} bytes, but it is {len} bytes large" - ); - - // ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM - // have extra alignment requirements. The address and size of the buffer pointed to by each - // descriptor must be a multiple of the cache line (block) size. This is 32 bytes on ESP32-S3. - // That is ensured by the `assert_eq` preceding this block. - unsafe { - let raw = alloc - .allocate_zeroed(Layout::from_size_align(len, alignment).unwrap()) - .expect("failed to allocate a DMA buffer"); - Box::from_raw_in(raw.as_ptr(), alloc) - } -} - // TODO: Rename or get rid of. pub struct Framebuffer { pub width: u32,