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