safe_mmio/
aarch64_mmio.rs

1// Copyright 2025 The safe-mmio Authors.
2// This project is dual-licensed under Apache 2.0 and MIT terms.
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5use crate::{SharedMmioPointer, UniqueMmioPointer};
6use core::ptr::NonNull;
7use zerocopy::{FromBytes, Immutable, IntoBytes};
8
9macro_rules! asm_mmio {
10    ($t:ty, $read_name:ident, $read_assembly:literal, $write_name:ident, $write_assembly:literal) => {
11        unsafe fn $read_name(ptr: *const $t) -> $t {
12            let value;
13            unsafe {
14                core::arch::asm!(
15                    $read_assembly,
16                    value = out(reg) value,
17                    ptr = in(reg) ptr,
18                );
19            }
20            value
21        }
22
23        unsafe fn $write_name(ptr: *mut $t, value: $t) {
24            unsafe {
25                core::arch::asm!(
26                    $write_assembly,
27                    value = in(reg) value,
28                    ptr = in(reg) ptr,
29                );
30            }
31        }
32    };
33}
34
35asm_mmio!(
36    u8,
37    read_u8,
38    "ldrb {value:w}, [{ptr}]",
39    write_u8,
40    "strb {value:w}, [{ptr}]"
41);
42asm_mmio!(
43    u16,
44    read_u16,
45    "ldrh {value:w}, [{ptr}]",
46    write_u16,
47    "strh {value:w}, [{ptr}]"
48);
49asm_mmio!(
50    u32,
51    read_u32,
52    "ldr {value:w}, [{ptr}]",
53    write_u32,
54    "str {value:w}, [{ptr}]"
55);
56asm_mmio!(
57    u64,
58    read_u64,
59    "ldr {value:x}, [{ptr}]",
60    write_u64,
61    "str {value:x}, [{ptr}]"
62);
63
64impl<T: FromBytes + IntoBytes> UniqueMmioPointer<'_, T> {
65    /// Performs an MMIO read and returns the value.
66    ///
67    /// If `T` is exactly 1, 2, 4 or 8 bytes long then this will be a single operation. Otherwise
68    /// it will be split into several, reading chunks as large as possible.
69    ///
70    /// Note that this takes `&mut self` rather than `&self` because an MMIO read may cause
71    /// side-effects that change the state of the device.
72    ///
73    /// # Safety
74    ///
75    /// This field must be safe to perform an MMIO read from.
76    pub unsafe fn read_unsafe(&mut self) -> T {
77        unsafe { mmio_read(self.regs) }
78    }
79}
80
81impl<T: Immutable + IntoBytes> UniqueMmioPointer<'_, T> {
82    /// Performs an MMIO write of the given value.
83    ///
84    /// If `T` is exactly 1, 2, 4 or 8 bytes long then this will be a single operation. Otherwise
85    /// it will be split into several, writing chunks as large as possible.
86    ///
87    /// # Safety
88    ///
89    /// This field must be safe to perform an MMIO write to.
90    pub unsafe fn write_unsafe(&self, value: T) {
91        match size_of::<T>() {
92            1 => unsafe { write_u8(self.regs.cast().as_ptr(), value.as_bytes()[0]) },
93            2 => unsafe { write_u16(self.regs.cast().as_ptr(), convert(value)) },
94            4 => unsafe { write_u32(self.regs.cast().as_ptr(), convert(value)) },
95            8 => unsafe { write_u64(self.regs.cast().as_ptr(), convert(value)) },
96            _ => unsafe { write_slice(self.regs.cast(), value.as_bytes()) },
97        }
98    }
99}
100
101impl<T: FromBytes + IntoBytes> SharedMmioPointer<'_, T> {
102    /// Performs an MMIO read and returns the value.
103    ///
104    /// If `T` is exactly 1, 2, 4 or 8 bytes long then this will be a single operation. Otherwise
105    /// it will be split into several, reading chunks as large as possible.
106    ///
107    /// # Safety
108    ///
109    /// This field must be safe to perform an MMIO read from, and doing so must not cause any
110    /// side-effects.
111    pub unsafe fn read_unsafe(&self) -> T {
112        unsafe { mmio_read(self.regs) }
113    }
114}
115
116/// Performs an MMIO read and returns the value.
117///
118/// # Safety
119///
120/// The pointer must be valid to perform an MMIO read from.
121unsafe fn mmio_read<T: FromBytes + IntoBytes>(ptr: NonNull<T>) -> T {
122    match size_of::<T>() {
123        1 => convert(unsafe { read_u8(ptr.cast().as_ptr()) }),
124        2 => convert(unsafe { read_u16(ptr.cast().as_ptr()) }),
125        4 => convert(unsafe { read_u32(ptr.cast().as_ptr()) }),
126        8 => convert(unsafe { read_u64(ptr.cast().as_ptr()) }),
127        _ => {
128            let mut value = T::new_zeroed();
129            unsafe { read_slice(ptr.cast(), value.as_mut_bytes()) };
130            value
131        }
132    }
133}
134
135fn convert<T: Immutable + IntoBytes, U: FromBytes>(value: T) -> U {
136    U::read_from_bytes(value.as_bytes()).unwrap()
137}
138
139unsafe fn write_slice(ptr: NonNull<u8>, slice: &[u8]) {
140    if let Some((first, rest)) = slice.split_at_checked(8) {
141        unsafe {
142            write_u64(ptr.cast().as_ptr(), u64::read_from_bytes(first).unwrap());
143            write_slice(ptr.add(8), rest);
144        }
145    } else if let Some((first, rest)) = slice.split_at_checked(4) {
146        unsafe {
147            write_u32(ptr.cast().as_ptr(), u32::read_from_bytes(first).unwrap());
148            write_slice(ptr.add(4), rest);
149        }
150    } else if let Some((first, rest)) = slice.split_at_checked(2) {
151        unsafe {
152            write_u16(ptr.cast().as_ptr(), u16::read_from_bytes(first).unwrap());
153            write_slice(ptr.add(2), rest);
154        }
155    } else if let [first, rest @ ..] = slice {
156        unsafe {
157            write_u8(ptr.as_ptr(), *first);
158            write_slice(ptr.add(1), rest);
159        }
160    }
161}
162
163unsafe fn read_slice(ptr: NonNull<u8>, slice: &mut [u8]) {
164    if let Some((first, rest)) = slice.split_at_mut_checked(8) {
165        unsafe {
166            read_u64(ptr.cast().as_ptr()).write_to(first).unwrap();
167            read_slice(ptr.add(8), rest);
168        }
169    } else if let Some((first, rest)) = slice.split_at_mut_checked(4) {
170        unsafe {
171            read_u32(ptr.cast().as_ptr()).write_to(first).unwrap();
172            read_slice(ptr.add(4), rest);
173        }
174    } else if let Some((first, rest)) = slice.split_at_mut_checked(2) {
175        unsafe {
176            read_u16(ptr.cast().as_ptr()).write_to(first).unwrap();
177            read_slice(ptr.add(2), rest);
178        }
179    } else if let [first, rest @ ..] = slice {
180        unsafe {
181            *first = read_u8(ptr.as_ptr());
182            read_slice(ptr.add(1), rest);
183        }
184    }
185}