1 //! ARM64 CPU feature detection support.
2 //!
3 //! Unfortunately ARM instructions to detect CPU features cannot be called from
4 //! unprivileged userspace code, so this implementation relies on OS-specific
5 //! APIs for feature detection.
6 
7 // Evaluate the given `$body` expression any of the supplied target features
8 // are not enabled. Otherwise returns true.
9 #[macro_export]
10 #[doc(hidden)]
11 macro_rules! __unless_target_features {
12     ($($tf:tt),+ => $body:expr ) => {
13         {
14             #[cfg(not(all($(target_feature=$tf,)*)))]
15             $body
16 
17             #[cfg(all($(target_feature=$tf,)*))]
18             true
19         }
20     };
21 }
22 
23 // Linux runtime detection of target CPU features using `getauxval`.
24 #[cfg(target_os = "linux")]
25 #[macro_export]
26 #[doc(hidden)]
27 macro_rules! __detect_target_features {
28     ($($tf:tt),+) => {{
29         let hwcaps = $crate::aarch64::getauxval_hwcap();
30         $($crate::check!(hwcaps, $tf) & )+ true
31     }};
32 }
33 
34 /// Linux helper function for calling `getauxval` to get `AT_HWCAP`.
35 #[cfg(target_os = "linux")]
getauxval_hwcap() -> u6436 pub fn getauxval_hwcap() -> u64 {
37     unsafe { libc::getauxval(libc::AT_HWCAP) }
38 }
39 
40 // MacOS runtime detection of target CPU features using `sysctlbyname`.
41 #[cfg(target_os = "macos")]
42 #[macro_export]
43 #[doc(hidden)]
44 macro_rules! __detect_target_features {
45     ($($tf:tt),+) => {{
46         $($crate::check!($tf) & )+ true
47     }};
48 }
49 
50 // Linux `expand_check_macro`
51 #[cfg(target_os = "linux")]
52 macro_rules! __expand_check_macro {
53     ($(($name:tt, $hwcap:ident)),* $(,)?) => {
54         #[macro_export]
55         #[doc(hidden)]
56         macro_rules! check {
57             $(
58                 ($hwcaps:expr, $name) => {
59                     (($hwcaps & $crate::aarch64::hwcaps::$hwcap) != 0)
60                 };
61             )*
62         }
63     };
64 }
65 
66 // Linux `expand_check_macro`
67 #[cfg(target_os = "linux")]
68 __expand_check_macro! {
69     ("aes",    AES),    // Enable AES support.
70     ("crypto", CRYPTO), // Enable cryptographic instructions.
71     ("sha2",   SHA2),   // Enable SHA1 and SHA256 support.
72     ("sha3",   SHA3),   // Enable SHA512 and SHA3 support.
73 }
74 
75 /// Linux hardware capabilities mapped to target features.
76 ///
77 /// Note that LLVM target features are coarser grained than what Linux supports
78 /// and imply more capabilities under each feature. This module attempts to
79 /// provide that mapping accordingly.
80 ///
81 /// See this issue for more info: <https://github.com/RustCrypto/utils/issues/395>
82 #[cfg(target_os = "linux")]
83 pub mod hwcaps {
84     use libc::c_ulong;
85 
86     pub const AES: c_ulong = libc::HWCAP_AES | libc::HWCAP_PMULL;
87     pub const CRYPTO: c_ulong = AES | SHA2;
88     pub const SHA2: c_ulong = libc::HWCAP_SHA2;
89     pub const SHA3: c_ulong = libc::HWCAP_SHA3 | libc::HWCAP_SHA512;
90 }
91 
92 // macOS `check!` macro.
93 //
94 // NOTE: several of these instructions (e.g. `aes`, `sha2`) can be assumed to
95 // be present on all Apple ARM64 hardware.
96 //
97 // Newer CPU instructions now have nodes within sysctl's `hw.optional`
98 // namespace, however the ones that do not can safely be assumed to be
99 // present on all Apple ARM64 devices, now and for the foreseeable future.
100 //
101 // See discussion on this issue for more information:
102 // <https://github.com/RustCrypto/utils/issues/378>
103 #[cfg(target_os = "macos")]
104 #[macro_export]
105 #[doc(hidden)]
106 macro_rules! check {
107     ("aes") => {
108         true
109     };
110     ("crypto") => {
111         true
112     };
113     ("sha2") => {
114         true
115     };
116     ("sha3") => {
117         unsafe {
118             // `sha3` target feature implies SHA-512 as well
119             $crate::aarch64::sysctlbyname(b"hw.optional.armv8_2_sha512\0")
120                 && $crate::aarch64::sysctlbyname(b"hw.optional.armv8_2_sha3\0")
121         }
122     };
123 }
124 
125 /// macOS helper function for calling `sysctlbyname`.
126 #[cfg(target_os = "macos")]
sysctlbyname(name: &[u8]) -> bool127 pub unsafe fn sysctlbyname(name: &[u8]) -> bool {
128     assert_eq!(
129         name.last().cloned(),
130         Some(0),
131         "name is not NUL terminated: {:?}",
132         name
133     );
134 
135     let mut value: u32 = 0;
136     let mut size = core::mem::size_of::<u32>();
137 
138     let rc = libc::sysctlbyname(
139         name.as_ptr() as *const i8,
140         &mut value as *mut _ as *mut libc::c_void,
141         &mut size,
142         core::ptr::null_mut(),
143         0,
144     );
145 
146     assert_eq!(size, 4, "unexpected sysctlbyname(3) result size");
147     assert_eq!(rc, 0, "sysctlbyname returned error code: {}", rc);
148     value != 0
149 }
150 
151 // iOS `check!` macro.
152 //
153 // Unfortunately iOS does not provide access to the `sysctl(3)` API which means
154 // we can only return static values for CPU features which  can be assumed to
155 // be present on all Apple ARM64 hardware.
156 //
157 // See discussion on this issue for more information:
158 // <https://github.com/RustCrypto/utils/issues/378>
159 #[cfg(target_os = "ios")]
160 #[macro_export]
161 #[doc(hidden)]
162 macro_rules! check {
163     ("aes") => {
164         true
165     };
166     ("crypto") => {
167         true
168     };
169     ("sha2") => {
170         true
171     };
172     ("sha3") => {
173         false
174     };
175 }
176 
177 // On other targets, runtime CPU feature detection is unavailable
178 #[cfg(not(any(target_os = "ios", target_os = "linux", target_os = "macos")))]
179 #[macro_export]
180 #[doc(hidden)]
181 macro_rules! __detect_target_features {
182     ($($tf:tt),+) => {
183         false
184     };
185 }
186