Skip to main content

oxigdal_embedded/target/
arm.rs

1//! ARM-specific optimizations and support
2//!
3//! Provides implementations for ARM Cortex-M and Cortex-A processors
4
5use super::{TargetArch, TargetCapabilities};
6use core::sync::atomic::{Ordering, fence};
7
8/// ARM target implementation
9pub struct ArmTarget;
10
11impl TargetArch for ArmTarget {
12    fn name(&self) -> &'static str {
13        "ARM"
14    }
15
16    fn pointer_size(&self) -> usize {
17        core::mem::size_of::<usize>()
18    }
19
20    fn native_alignment(&self) -> usize {
21        4 // ARM typically prefers 4-byte alignment
22    }
23
24    fn supports_unaligned_access(&self) -> bool {
25        // Cortex-M3/M4/M7 support unaligned access
26        // Cortex-M0/M0+ do not
27        cfg!(any(
28            target_feature = "v7",
29            target_feature = "v8",
30            target_arch = "aarch64"
31        ))
32    }
33
34    fn memory_barrier(&self) {
35        memory_barrier();
36    }
37
38    fn cycle_count(&self) -> Option<u64> {
39        cycle_count()
40    }
41}
42
43/// Get ARM target capabilities
44pub fn get_capabilities() -> TargetCapabilities {
45    TargetCapabilities {
46        has_fpu: cfg!(target_feature = "vfp2")
47            || cfg!(target_feature = "vfp3")
48            || cfg!(target_feature = "vfp4"),
49        has_simd: cfg!(target_feature = "neon"),
50        has_aes: cfg!(target_feature = "aes"),
51        has_crc: cfg!(target_feature = "crc"),
52        cache_line_size: 64, // Common ARM cache line size
53        num_cores: 1,        // Embedded systems typically have 1 core
54    }
55}
56
57/// ARM memory barrier
58#[inline]
59pub fn memory_barrier() {
60    fence(Ordering::SeqCst);
61
62    #[cfg(target_arch = "arm")]
63    {
64        // Data Memory Barrier
65        unsafe {
66            core::arch::asm!("dmb", options(nostack, nomem));
67        }
68    }
69
70    #[cfg(target_arch = "aarch64")]
71    {
72        // Data Memory Barrier
73        unsafe {
74            core::arch::asm!("dmb sy", options(nostack, nomem));
75        }
76    }
77}
78
79/// Get cycle count from ARM performance counter
80///
81/// The performance-counter registers (`PMCCNTR`/`PMCCNTR_EL0`) are only
82/// accessible from privileged modes, so the register reads are emitted solely
83/// for bare-metal targets (`target_os = "none"`). On a hosted OS (including an
84/// `aarch64` development host) reading them from user space would trap with an
85/// illegal-instruction fault, so this returns `None` there instead.
86#[inline]
87pub fn cycle_count() -> Option<u64> {
88    #[cfg(all(target_arch = "arm", target_os = "none"))]
89    {
90        // Read PMCCNTR (Performance Monitors Cycle Count Register)
91        // Note: This requires appropriate permissions and setup
92        let count: u32;
93        unsafe {
94            core::arch::asm!(
95                "mrc p15, 0, {}, c9, c13, 0",
96                out(reg) count,
97                options(nostack, nomem, preserves_flags)
98            );
99        }
100        Some(count as u64)
101    }
102
103    #[cfg(all(target_arch = "aarch64", target_os = "none"))]
104    {
105        // Read PMCCNTR_EL0 (Performance Monitors Cycle Count Register)
106        let count: u64;
107        unsafe {
108            core::arch::asm!(
109                "mrs {}, pmccntr_el0",
110                out(reg) count,
111                options(nostack, nomem, preserves_flags)
112            );
113        }
114        Some(count)
115    }
116
117    #[cfg(not(all(any(target_arch = "arm", target_arch = "aarch64"), target_os = "none")))]
118    {
119        None
120    }
121}
122
123/// ARM cache operations
124pub mod cache {
125    use crate::error::Result;
126
127    /// Clean data cache by address range
128    ///
129    /// # Safety
130    ///
131    /// The address range must be valid
132    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
133    pub unsafe fn clean_dcache(addr: usize, size: usize) -> Result<()> {
134        let cache_line_size = 64; // Common ARM cache line size
135        let start = addr & !(cache_line_size - 1);
136        let end = (addr + size + cache_line_size - 1) & !(cache_line_size - 1);
137
138        let mut current = start;
139        while current < end {
140            #[cfg(target_arch = "arm")]
141            {
142                // SAFETY: Inline assembly for cache cleaning
143                unsafe {
144                    core::arch::asm!(
145                        "mcr p15, 0, {}, c7, c10, 1",
146                        in(reg) current,
147                        options(nostack, preserves_flags)
148                    );
149                }
150            }
151
152            #[cfg(target_arch = "aarch64")]
153            {
154                // SAFETY: Inline assembly for cache cleaning
155                unsafe {
156                    core::arch::asm!(
157                        "dc cvac, {}",
158                        in(reg) current,
159                        options(nostack, preserves_flags)
160                    );
161                }
162            }
163
164            current = current.wrapping_add(cache_line_size);
165        }
166
167        super::memory_barrier();
168        Ok(())
169    }
170
171    /// Invalidate data cache by address range
172    ///
173    /// # Safety
174    ///
175    /// The address range must be valid
176    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
177    pub unsafe fn invalidate_dcache(addr: usize, size: usize) -> Result<()> {
178        let cache_line_size = 64;
179        let start = addr & !(cache_line_size - 1);
180        let end = (addr + size + cache_line_size - 1) & !(cache_line_size - 1);
181
182        let mut current = start;
183        while current < end {
184            #[cfg(target_arch = "arm")]
185            {
186                // SAFETY: Inline assembly for cache invalidation
187                unsafe {
188                    core::arch::asm!(
189                        "mcr p15, 0, {}, c7, c6, 1",
190                        in(reg) current,
191                        options(nostack, preserves_flags)
192                    );
193                }
194            }
195
196            #[cfg(target_arch = "aarch64")]
197            {
198                // SAFETY: Inline assembly for cache invalidation
199                unsafe {
200                    core::arch::asm!(
201                        "dc ivac, {}",
202                        in(reg) current,
203                        options(nostack, preserves_flags)
204                    );
205                }
206            }
207
208            current = current.wrapping_add(cache_line_size);
209        }
210
211        super::memory_barrier();
212        Ok(())
213    }
214
215    /// Clean data cache for the specified address range (no-op on non-ARM)
216    #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
217    pub unsafe fn clean_dcache(_addr: usize, _size: usize) -> Result<()> {
218        Ok(())
219    }
220
221    /// Invalidate data cache for the specified address range (no-op on non-ARM)
222    #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
223    pub unsafe fn invalidate_dcache(_addr: usize, _size: usize) -> Result<()> {
224        Ok(())
225    }
226}
227
228/// ARM power management
229///
230/// These primitives use ISA-level instructions (WFI/WFE) that are portable
231/// across ARM cores. CPU frequency scaling and peripheral clock gating are
232/// SoC-specific (they live in vendor clock/power controllers, not the ARM ISA)
233/// and are therefore not provided here.
234pub mod power {
235    use crate::error::Result;
236
237    /// Wait for interrupt (WFI) — halt the CPU until an interrupt arrives
238    ///
239    /// On Cortex-M/Cortex-A this suspends execution and drops the core into a
240    /// low-power state until a wake event (interrupt) occurs. This is a real,
241    /// ISA-level power reduction.
242    ///
243    /// The instruction is only emitted for bare-metal targets
244    /// (`target_os = "none"`); on a hosted OS (including an `aarch64` dev host)
245    /// executing `WFI` from user space can trap, so this is a no-op there.
246    #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), target_os = "none"))]
247    pub fn wait_for_interrupt() -> Result<()> {
248        // SAFETY: WFI has no memory or stack effects; it only affects the core
249        // power state and returns on the next interrupt.
250        unsafe {
251            core::arch::asm!("wfi", options(nostack, nomem));
252        }
253        Ok(())
254    }
255
256    /// Wait for interrupt (no-op on non-bare-metal / non-ARM targets)
257    #[cfg(not(all(any(target_arch = "arm", target_arch = "aarch64"), target_os = "none")))]
258    pub fn wait_for_interrupt() -> Result<()> {
259        Ok(())
260    }
261
262    /// Wait for event (WFE) — halt the CPU until an event or interrupt arrives
263    ///
264    /// Emitted only for bare-metal targets (`target_os = "none"`); a no-op on a
265    /// hosted OS where `WFE` from user space can trap.
266    #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), target_os = "none"))]
267    pub fn wait_for_event() -> Result<()> {
268        // SAFETY: WFE has no memory or stack effects; it only affects the core
269        // power state and returns on the next event/interrupt.
270        unsafe {
271            core::arch::asm!("wfe", options(nostack, nomem));
272        }
273        Ok(())
274    }
275
276    /// Wait for event (no-op on non-bare-metal / non-ARM targets)
277    #[cfg(not(all(any(target_arch = "arm", target_arch = "aarch64"), target_os = "none")))]
278    pub fn wait_for_event() -> Result<()> {
279        Ok(())
280    }
281}
282
283/// ARM SIMD operations (NEON)
284#[cfg(target_feature = "neon")]
285pub mod simd {
286    #[cfg(target_arch = "arm")]
287    use core::arch::arm::*;
288
289    /// Copy memory using NEON instructions
290    ///
291    /// # Safety
292    ///
293    /// src and dst must be valid and properly aligned
294    #[cfg(target_arch = "arm")]
295    pub unsafe fn memcpy_neon(dst: *mut u8, src: *const u8, len: usize) {
296        let mut offset = 0;
297        let chunks = len / 16;
298
299        for _ in 0..chunks {
300            // SAFETY: Caller guarantees dst and src are valid and aligned
301            unsafe {
302                let data = vld1q_u8(src.add(offset));
303                vst1q_u8(dst.add(offset), data);
304            }
305            offset += 16;
306        }
307
308        // Handle remaining bytes
309        for i in offset..len {
310            // SAFETY: Caller guarantees dst and src are valid
311            unsafe {
312                *dst.add(i) = *src.add(i);
313            }
314        }
315    }
316
317    /// Copy memory using NEON instructions (AArch64)
318    ///
319    /// # Safety
320    ///
321    /// src and dst must be valid and properly aligned
322    #[cfg(target_arch = "aarch64")]
323    pub unsafe fn memcpy_neon(dst: *mut u8, src: *const u8, len: usize) {
324        use core::arch::aarch64::*;
325
326        let mut offset = 0;
327        let chunks = len / 16;
328
329        for _ in 0..chunks {
330            // SAFETY: Caller guarantees dst and src are valid and aligned
331            unsafe {
332                let data = vld1q_u8(src.add(offset));
333                vst1q_u8(dst.add(offset), data);
334            }
335            offset += 16;
336        }
337
338        // Handle remaining bytes
339        for i in offset..len {
340            // SAFETY: Caller guarantees dst and src are valid
341            unsafe {
342                *dst.add(i) = *src.add(i);
343            }
344        }
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_arm_target() {
354        let target = ArmTarget;
355        assert_eq!(target.name(), "ARM");
356        assert!(target.pointer_size() > 0);
357        assert!(target.native_alignment() > 0);
358    }
359
360    #[test]
361    fn test_capabilities() {
362        let caps = get_capabilities();
363        assert!(caps.cache_line_size > 0);
364    }
365}