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}