Skip to main content

rlvgl_platform/hwcore/
addr.rs

1//! Address-domain newtypes.
2//!
3//! Three distinct address species flow through the 747I platform layer and
4//! must not collapse into a single numeric type:
5//!
6//! - [`MmioAddr<T>`] — a memory-mapped register-block base. Carries
7//!   provenance via `NonNull<T>`; constructed through an `unsafe` entry
8//!   point so callers assert the region is a live, unaliased MMIO block
9//!   of `size_of::<T>()` bytes.
10//! - [`PhysAddr`] — a CPU-visible RAM address (SDRAM, SRAM). Used for
11//!   framebuffer placement and bank-collision math. Conversion to a
12//!   usable pointer is `unsafe`.
13//! - [`DmaAddr`] — an address as seen by a DMA master. On the STM32H747
14//!   this equals the physical address numerically, but the separate type
15//!   forces an alignment check at the boundary (`DmaAddr::from_phys`).
16//!
17//! The SDRAM bank-collision helper on `PhysAddr` replaces the inline magic
18//! math previously at `examples/stm32h747i-disco/src/main.rs:2890-2891`
19//! (`(back_addr - 0xD000_0000) / SDRAM_BANK_STRIDE`).
20
21use core::marker::PhantomData;
22use core::ptr::NonNull;
23
24/// Base address of a memory-mapped register block.
25///
26/// The `T` parameter is typically a `#[repr(C)]` struct laying out the
27/// peripheral registers in order.
28///
29/// Construction is `unsafe` because the caller asserts that the address
30/// names a live MMIO region of the appropriate size and that no other
31/// `MmioAddr<T>` referring to the same region exists in the program.
32#[derive(Debug)]
33#[repr(transparent)]
34pub struct MmioAddr<T> {
35    ptr: NonNull<T>,
36    _phantom: PhantomData<T>,
37}
38
39impl<T> MmioAddr<T> {
40    /// Construct an [`MmioAddr<T>`] from a raw address.
41    ///
42    /// # Safety
43    ///
44    /// The caller must ensure that `addr`:
45    ///
46    /// - names a memory-mapped region of at least `size_of::<T>()` bytes
47    ///   that is mapped for the lifetime of the returned value,
48    /// - is not aliased by any other `MmioAddr<T>` or `&mut T` in the
49    ///   program,
50    /// - is appropriately aligned for `T`.
51    ///
52    /// `addr` of zero panics (via `NonNull::new_unchecked`-adjacent check)
53    /// to catch obvious mistakes; this does not otherwise validate the
54    /// pointer.
55    pub const unsafe fn new(addr: usize) -> Self {
56        debug_assert!(addr != 0, "MmioAddr::new called with zero address");
57        // SAFETY: caller contract; the debug_assert above catches null.
58        let ptr = unsafe { NonNull::new_unchecked(addr as *mut T) };
59        Self {
60            ptr,
61            _phantom: PhantomData,
62        }
63    }
64
65    /// Raw pointer to the register block.
66    ///
67    /// The returned pointer carries MMIO provenance; dereferencing it
68    /// requires `unsafe` and is only valid per the construction contract
69    /// of this `MmioAddr<T>`.
70    #[inline]
71    pub fn as_ptr(&self) -> *mut T {
72        self.ptr.as_ptr()
73    }
74
75    /// Numeric address of the register block.
76    ///
77    /// Escape hatch for writing the base address into another register
78    /// (e.g. `LTDC_L1CFBAR`). Prefer typed APIs over this when available.
79    #[inline]
80    pub fn raw(&self) -> usize {
81        self.ptr.as_ptr() as usize
82    }
83}
84
85// `MmioAddr<T>` is `Send`/`Sync` by contract: the construction SAFETY note
86// requires the caller to prove uniqueness, so the type itself imposes no
87// interior-mutability hazard beyond what individual register fields do.
88unsafe impl<T> Send for MmioAddr<T> {}
89unsafe impl<T> Sync for MmioAddr<T> {}
90
91// ─── PhysAddr ────────────────────────────────────────────────────────────
92
93/// CPU-visible physical address of RAM (SDRAM, SRAM, etc.).
94///
95/// Distinct from [`DmaAddr`] and [`MmioAddr<T>`] so that a value intended
96/// as a framebuffer origin cannot be silently assigned to a DMA source or
97/// a register-block base.
98#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
99#[repr(transparent)]
100pub struct PhysAddr(u32);
101
102/// STM32H747I-DISCO SDRAM (Bank 2) base address.
103///
104/// Re-asserts the `feedback_sdram_bank2.md` invariant: the discovery board
105/// wires SDNE1/SDCKE1 (Bank 2), so SDRAM lives at `0xD000_0000`, not the
106/// Bank 1 address `0xC000_0000`.
107pub const SDRAM_BANK2_BASE: u32 = 0xD000_0000;
108
109/// Stride between independent SDRAM banks used for front/back framebuffer
110/// placement to avoid refresh-cycle collisions. Value matches the
111/// `SDRAM_BANK_STRIDE` previously inlined in the example binary.
112pub const SDRAM_BANK_STRIDE: u32 = 0x0080_0000;
113
114/// Number of independent banks in the SDRAM array (IS42S32800J-6BLI has 4).
115pub const SDRAM_BANK_COUNT: u8 = 4;
116
117impl PhysAddr {
118    /// Construct a [`PhysAddr`] from a raw 32-bit address.
119    #[inline]
120    pub const fn new(addr: u32) -> Self {
121        Self(addr)
122    }
123
124    /// Raw 32-bit address.
125    #[inline]
126    pub const fn raw(self) -> u32 {
127        self.0
128    }
129
130    /// Offset this address by `delta` bytes, returning a new [`PhysAddr`].
131    ///
132    /// Saturating on overflow. No aliasing, MMU, or mapping check is
133    /// performed — callers stay responsible for validity.
134    #[inline]
135    pub const fn offset(self, delta: u32) -> Self {
136        Self(self.0.saturating_add(delta))
137    }
138
139    /// SDRAM bank index (0..[`SDRAM_BANK_COUNT`]) for Bank-2-based SDRAM,
140    /// or `None` if this address is not inside the SDRAM Bank 2 window.
141    ///
142    /// Replaces the inline `(addr - 0xD000_0000) / SDRAM_BANK_STRIDE`
143    /// expression that framebuffer collision detection previously used.
144    #[inline]
145    pub const fn sdram_bank(self) -> Option<u8> {
146        let base = SDRAM_BANK2_BASE;
147        let stride = SDRAM_BANK_STRIDE;
148        let count = SDRAM_BANK_COUNT as u32;
149        if self.0 < base {
150            return None;
151        }
152        let offset = self.0 - base;
153        let bank = offset / stride;
154        if bank >= count {
155            return None;
156        }
157        Some(bank as u8)
158    }
159
160    /// Reconstruct a mutable byte slice spanning `len` bytes starting at
161    /// this address.
162    ///
163    /// # Safety
164    ///
165    /// The caller must ensure that:
166    ///
167    /// - the region `[self, self+len)` is mapped and writable for `'a`,
168    /// - no other `&_` or `&mut _` to any overlapping bytes exists for
169    ///   `'a`,
170    /// - the region is not concurrently written by a DMA master (see
171    ///   [`DmaAddr`] and the `InFlight<T>` handoff pattern).
172    #[inline]
173    pub unsafe fn as_mut_slice<'a>(self, len: usize) -> &'a mut [u8] {
174        // SAFETY: delegated to caller per the contract above.
175        unsafe { core::slice::from_raw_parts_mut(self.0 as *mut u8, len) }
176    }
177}
178
179// ─── DmaAddr ─────────────────────────────────────────────────────────────
180
181/// Alignment / validity error returned by [`DmaAddr::from_phys`].
182#[derive(Copy, Clone, Eq, PartialEq, Debug)]
183pub enum AddrError {
184    /// The physical address is not aligned to the required boundary.
185    Misaligned {
186        /// The address that failed the check.
187        addr: u32,
188        /// The alignment the caller requested.
189        required: usize,
190    },
191    /// The requested alignment is not a power of two.
192    InvalidAlignment(usize),
193}
194
195/// Address as seen by a DMA master (DMA2D, BDMA, MDMA).
196///
197/// Numerically identical to the CPU physical address on the STM32H747 (no
198/// IOMMU, no bus translation), but kept as a distinct type so that the
199/// transition from "CPU owns this" to "DMA owns this" is an explicit,
200/// alignment-checked conversion rather than an implicit cast.
201#[derive(Copy, Clone, Eq, PartialEq, Debug)]
202#[repr(transparent)]
203pub struct DmaAddr(u32);
204
205impl DmaAddr {
206    /// Convert a [`PhysAddr`] into a [`DmaAddr`] after verifying `align`.
207    ///
208    /// `align` is typically the DMA pixel or burst size — 2 for RGB565, 4
209    /// for ARGB8888 / DMA2D OMAR.
210    #[inline]
211    pub fn from_phys(p: PhysAddr, align: usize) -> Result<Self, AddrError> {
212        if align == 0 || !align.is_power_of_two() {
213            return Err(AddrError::InvalidAlignment(align));
214        }
215        let addr = p.raw();
216        if (addr as usize) & (align - 1) != 0 {
217            return Err(AddrError::Misaligned {
218                addr,
219                required: align,
220            });
221        }
222        Ok(Self(addr))
223    }
224
225    /// Raw 32-bit address for writing into a DMA register field.
226    #[inline]
227    pub const fn raw(self) -> u32 {
228        self.0
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn sdram_bank_zero_is_bank_zero() {
238        assert_eq!(PhysAddr::new(SDRAM_BANK2_BASE).sdram_bank(), Some(0));
239    }
240
241    #[test]
242    fn sdram_bank_one_is_bank_one() {
243        let a = SDRAM_BANK2_BASE + SDRAM_BANK_STRIDE;
244        assert_eq!(PhysAddr::new(a).sdram_bank(), Some(1));
245    }
246
247    #[test]
248    fn sdram_bank_offset_within_bank_still_counts() {
249        let a = SDRAM_BANK2_BASE + SDRAM_BANK_STRIDE + 0x1234;
250        assert_eq!(PhysAddr::new(a).sdram_bank(), Some(1));
251    }
252
253    #[test]
254    fn address_below_sdram_has_no_bank() {
255        assert_eq!(PhysAddr::new(0x2000_0000).sdram_bank(), None);
256    }
257
258    #[test]
259    fn address_above_sdram_array_has_no_bank() {
260        let a = SDRAM_BANK2_BASE + SDRAM_BANK_STRIDE * (SDRAM_BANK_COUNT as u32);
261        assert_eq!(PhysAddr::new(a).sdram_bank(), None);
262    }
263
264    #[test]
265    fn dma_addr_rejects_misaligned() {
266        let p = PhysAddr::new(SDRAM_BANK2_BASE + 2);
267        assert!(matches!(
268            DmaAddr::from_phys(p, 4),
269            Err(AddrError::Misaligned { required: 4, .. })
270        ));
271    }
272
273    #[test]
274    fn dma_addr_accepts_aligned() {
275        let p = PhysAddr::new(SDRAM_BANK2_BASE);
276        let d = DmaAddr::from_phys(p, 4).unwrap();
277        assert_eq!(d.raw(), SDRAM_BANK2_BASE);
278    }
279
280    #[test]
281    fn dma_addr_rejects_non_power_of_two_alignment() {
282        let p = PhysAddr::new(SDRAM_BANK2_BASE);
283        assert!(matches!(
284            DmaAddr::from_phys(p, 3),
285            Err(AddrError::InvalidAlignment(3))
286        ));
287    }
288
289    #[test]
290    fn phys_addr_offset_is_saturating() {
291        let p = PhysAddr::new(u32::MAX - 3);
292        assert_eq!(p.offset(10).raw(), u32::MAX);
293    }
294
295    #[test]
296    fn mmio_addr_round_trips_address() {
297        // SAFETY: test-only; the address is not dereferenced.
298        let m: MmioAddr<u32> = unsafe { MmioAddr::new(0x4002_1C00) };
299        assert_eq!(m.raw(), 0x4002_1C00);
300        assert_eq!(m.as_ptr() as usize, 0x4002_1C00);
301    }
302}