starnix_uapi/union.rs
1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::user_address::ArchSpecific;
6
7/// Container that contains a union type that differs depending on the 32 vs 64 bit architecture.
8#[derive(Copy, Clone)]
9pub enum ArchSpecificUnionContainer<T64, T32> {
10 Arch64(T64),
11 #[allow(dead_code)]
12 Arch32(T32),
13}
14
15impl<T64, T32> ArchSpecific for ArchSpecificUnionContainer<T64, T32> {
16 fn is_arch32(&self) -> bool {
17 !matches!(self, Self::Arch64(_))
18 }
19}
20
21/// Initializes the given fields of a struct or union and returns the bytes of the
22/// resulting object as a byte array.
23///
24/// `struct_with_union_into_bytes` is invoked like so:
25///
26/// ```rust,ignore
27/// union Foo {
28/// a: u8,
29/// b: u16,
30/// }
31///
32/// struct Bar {
33/// a: Foo,
34/// b: u8,
35/// c: u16,
36/// }
37///
38/// struct_with_union_into_bytes!(Bar { a.b: 1, b: 2, c: 3 })
39/// ```
40///
41/// Each named field is initialized with a value whose type must implement
42/// `zerocopy::IntoBytes`. Any fields which are not explicitly initialized will be left as
43/// all zeroes.
44#[macro_export]
45macro_rules! struct_with_union_into_bytes {
46 ($ty:ty { $($($field:ident).*: $value:expr,)* }) => {{
47 use std::mem::MaybeUninit;
48
49 const BYTES: usize = std::mem::size_of::<$ty>();
50
51 struct AlignedBytes {
52 bytes: [u8; BYTES],
53 _align: MaybeUninit<$ty>,
54 }
55
56 let mut bytes = AlignedBytes { bytes: [0; BYTES], _align: MaybeUninit::uninit() };
57
58 $({
59 // Evaluate `$value` once to make sure it has the same type
60 // when passed to `type_check_as_bytes` as when assigned to
61 // the field.
62 let value = $value;
63 if false {
64 fn type_check_as_bytes<T: zerocopy::IntoBytes>(_: T) {
65 unreachable!()
66 }
67 type_check_as_bytes(value);
68 } else {
69 // SAFETY: We only treat these zeroed bytes as a `$ty` for the purposes of
70 // overwriting the given field. Thus, it's OK if a sequence of zeroes is
71 // not a valid instance of `$ty` or if the sub-sequence of zeroes is not a
72 // valid instance of the type of the field being overwritten. Note that we
73 // use `std::ptr::write`, not normal field assignment, as the latter would
74 // treat the current field value (all zeroes) as an initialized instance of
75 // the field's type (in order to drop it), which would be unsound.
76 //
77 // Since we know from the preceding `if` branch that the type of `value` is
78 // `IntoBytes`, we know that no uninitialized bytes will be written to the
79 // field. That, combined with the fact that the entire `bytes.bytes` is
80 // initialized to zero, ensures that all bytes of `bytes.bytes` are
81 // initialized, so we can safely return `bytes.bytes` as a byte array.
82 unsafe {
83 std::ptr::write(&mut (&mut *(&mut bytes.bytes as *mut [u8; BYTES] as *mut $ty)).$($field).*, value);
84 }
85 }
86 })*
87
88 bytes.bytes
89 }};
90}
91
92/// Initializes the given fields of a struct or union and returns the union as a
93/// `ArchSpecificUnionContainer`.
94///
95/// `arch_struct_with_union` is invoked like `struct_with_union_into_bytes`, but the first
96/// parameter of the macro must be an expression returning an `ArchSpecific` used to decide which
97/// macro to instantiate.
98#[macro_export]
99macro_rules! arch_struct_with_union {
100 ($arch:expr, $ty:ident { $($token:tt)* }) => {{
101 if $arch.is_arch32() {
102 let v32: starnix_uapi::arch32::$ty = zerocopy::transmute!(struct_with_union_into_bytes! {
103 starnix_uapi::arch32::$ty {
104 $($token)*
105 }
106 });
107 $crate::union::ArchSpecificUnionContainer::<starnix_uapi::$ty, starnix_uapi::arch32::$ty>::Arch32(v32)
108 } else {
109 let v64: starnix_uapi::$ty = zerocopy::transmute!(struct_with_union_into_bytes! {
110 starnix_uapi::$ty {
111 $($token)*
112 }
113 });
114 $crate::union::ArchSpecificUnionContainer::<starnix_uapi::$ty, starnix_uapi::arch32::$ty>::Arch64(v64)
115 }
116 }};
117}
118
119/// Build the wrapper type around a UABI specific union.
120///
121/// `arch_union_wrapper` is invoked like so:
122///
123/// ```rust,ignore
124/// arch_union_wrapper! {
125/// Wrapper(wrappee_name);
126/// }
127/// ```
128///
129/// where `Wrapper` is the name of the wrapper type being generated, and `wrappee_name` is the
130/// identifier of the union type of wrap.
131/// This will allow to read and write `Wrapper` object using the arch specific read/write methods
132/// of the memory manager passing a `WrapperPtr` reference.
133#[macro_export]
134macro_rules! arch_union_wrapper {
135 {} => {};
136 {
137 $vis:vis $wrapper:ident( $ty:ident );
138 $($token:tt)*
139 } => {
140 paste::paste! {
141 $vis type [<$wrapper Inner>] =
142 $crate::union::ArchSpecificUnionContainer::<
143 starnix_uapi::$ty,
144 starnix_uapi::arch32::$ty>;
145
146 $vis struct $wrapper([<$wrapper Inner>]);
147
148 $vis type [<$wrapper Ptr>] =
149 starnix_uapi::user_address::MappingMultiArchUserRef<
150 $wrapper,
151 [u8; std::mem::size_of::<starnix_uapi::$ty>()],
152 [u8; std::mem::size_of::<starnix_uapi::arch32::$ty>()]>;
153
154 impl $wrapper {
155 fn inner(&self) -> &[<$wrapper Inner>] {
156 &self.0
157 }
158 }
159
160 impl From<[u8; std::mem::size_of::<starnix_uapi::$ty>()]> for $wrapper {
161 fn from(bytes: [u8; std::mem::size_of::<starnix_uapi::$ty>()]) -> Self {
162 Self([<$wrapper Inner>]::Arch64(zerocopy::transmute!(bytes)))
163 }
164 }
165
166 #[cfg(target_arch = "aarch64")]
167 impl From<[u8; std::mem::size_of::<starnix_uapi::arch32::$ty>()]> for $wrapper {
168 fn from(bytes: [u8; std::mem::size_of::<starnix_uapi::arch32::$ty>()]) -> Self {
169 Self([<$wrapper Inner>]::Arch32(zerocopy::transmute!(bytes)))
170 }
171 }
172
173 impl TryFrom<$wrapper> for [u8; std::mem::size_of::<starnix_uapi::$ty>()] {
174 type Error = ();
175 fn try_from(v: $wrapper) -> Result<Self, ()> {
176 if let [<$wrapper Inner>]::Arch64(v) = v.0 {
177 // SAFETY: All union used by this wrapper are generated from bytes, so
178 // there is no uninitialized data.
179 Ok(unsafe { std::mem::transmute(v) })
180 } else {
181 Err(())
182 }
183 }
184 }
185
186 #[cfg(target_arch = "aarch64")]
187 impl TryFrom<$wrapper> for [u8; std::mem::size_of::<starnix_uapi::arch32::$ty>()] {
188 type Error = ();
189 fn try_from(v: $wrapper) -> Result<Self, ()> {
190 if let [<$wrapper Inner>]::Arch32(v) = v.0 {
191 // SAFETY: All union used by this wrapper are generated from bytes, so
192 // there is no uninitialized data.
193 Ok(unsafe { std::mem::transmute(v) })
194 } else {
195 Err(())
196 }
197 }
198 }
199
200 impl $crate::user_address::ArchSpecific for $wrapper {
201 fn is_arch32(&self) -> bool {
202 self.0.is_arch32()
203 }
204 }
205
206 impl std::fmt::Debug for $wrapper {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
208 let mut s = if self.is_arch32() {
209 f.debug_tuple(std::stringify!([<$wrapper 32>]))
210 } else {
211 f.debug_tuple(std::stringify!([<$wrapper 64>]))
212 };
213 // SAFETY: All union used by this wrapper are generated from bytes, so
214 // there is no uninitialized data.
215 match self.inner () {
216 [<$wrapper Inner>]::Arch64(v) => {
217 let bytes: &[u8; std::mem::size_of::<starnix_uapi::$ty>()] =
218 unsafe { std::mem::transmute(v) };
219 s.field(bytes);
220 }
221 [<$wrapper Inner>]::Arch32(v) => {
222 let bytes: &[u8; std::mem::size_of::<starnix_uapi::arch32::$ty>()] =
223 unsafe { std::mem::transmute(v) };
224 s.field(bytes);
225 }
226 }
227 s.finish()
228 }
229 }
230 }
231
232 $crate::arch_union_wrapper! { $($token)* }
233 };
234}
235
236pub use {arch_struct_with_union, arch_union_wrapper, struct_with_union_into_bytes};