• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 use crate::{SharedMmioPointer, UniqueMmioPointer};
6 use core::ptr::NonNull;
7 use zerocopy::{FromBytes, Immutable, IntoBytes};
8 
9 macro_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 
35 asm_mmio!(
36     u8,
37     read_u8,
38     "ldrb {value:w}, [{ptr}]",
39     write_u8,
40     "strb {value:w}, [{ptr}]"
41 );
42 asm_mmio!(
43     u16,
44     read_u16,
45     "ldrh {value:w}, [{ptr}]",
46     write_u16,
47     "strh {value:w}, [{ptr}]"
48 );
49 asm_mmio!(
50     u32,
51     read_u32,
52     "ldr {value:w}, [{ptr}]",
53     write_u32,
54     "str {value:w}, [{ptr}]"
55 );
56 asm_mmio!(
57     u64,
58     read_u64,
59     "ldr {value:x}, [{ptr}]",
60     write_u64,
61     "str {value:x}, [{ptr}]"
62 );
63 
64 impl<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.
read_unsafe(&mut self) -> T76     pub unsafe fn read_unsafe(&mut self) -> T {
77         unsafe { mmio_read(self.regs) }
78     }
79 }
80 
81 impl<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.
write_unsafe(&self, value: T)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 
101 impl<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.
read_unsafe(&self) -> T111     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.
mmio_read<T: FromBytes + IntoBytes>(ptr: NonNull<T>) -> T121 unsafe 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 
convert<T: Immutable + IntoBytes, U: FromBytes>(value: T) -> U135 fn convert<T: Immutable + IntoBytes, U: FromBytes>(value: T) -> U {
136     U::read_from_bytes(value.as_bytes()).unwrap()
137 }
138 
write_slice(ptr: NonNull<u8>, slice: &[u8])139 unsafe 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 
read_slice(ptr: NonNull<u8>, slice: &mut [u8])163 unsafe 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 }
186