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};