1 // Copyright 2016-2021 Brian Smith.
2 //
3 // Permission to use, copy, modify, and/or distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10 // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 
15 #![cfg_attr(
16     not(any(target_arch = "aarch64", target_arch = "arm")),
17     allow(dead_code)
18 )]
19 
20 #[cfg(all(
21     any(target_os = "android", target_os = "linux"),
22     any(target_arch = "aarch64", target_arch = "arm")
23 ))]
detect_features() -> u3224 fn detect_features() -> u32 {
25     use libc::c_ulong;
26 
27     // XXX: The `libc` crate doesn't provide `libc::getauxval` consistently
28     // across all Android/Linux targets, e.g. musl.
29     extern "C" {
30         fn getauxval(type_: c_ulong) -> c_ulong;
31     }
32 
33     const AT_HWCAP: c_ulong = 16;
34 
35     #[cfg(target_arch = "aarch64")]
36     const HWCAP_NEON: c_ulong = 1 << 1;
37 
38     #[cfg(target_arch = "arm")]
39     const HWCAP_NEON: c_ulong = 1 << 12;
40 
41     let caps = unsafe { getauxval(AT_HWCAP) };
42 
43     // We assume NEON is available on AARCH64 because it is a required
44     // feature.
45     #[cfg(target_arch = "aarch64")]
46     debug_assert!(caps & HWCAP_NEON == HWCAP_NEON);
47 
48     let mut features = 0;
49 
50     // OpenSSL and BoringSSL don't enable any other features if NEON isn't
51     // available.
52     if caps & HWCAP_NEON == HWCAP_NEON {
53         features = NEON.mask;
54 
55         #[cfg(target_arch = "aarch64")]
56         const OFFSET: c_ulong = 3;
57 
58         #[cfg(target_arch = "arm")]
59         const OFFSET: c_ulong = 0;
60 
61         #[cfg(target_arch = "arm")]
62         let caps = {
63             const AT_HWCAP2: c_ulong = 26;
64             unsafe { getauxval(AT_HWCAP2) }
65         };
66 
67         const HWCAP_AES: c_ulong = 1 << 0 + OFFSET;
68         const HWCAP_PMULL: c_ulong = 1 << 1 + OFFSET;
69         const HWCAP_SHA2: c_ulong = 1 << 3 + OFFSET;
70 
71         if caps & HWCAP_AES == HWCAP_AES {
72             features |= AES.mask;
73         }
74         if caps & HWCAP_PMULL == HWCAP_PMULL {
75             features |= PMULL.mask;
76         }
77         if caps & HWCAP_SHA2 == HWCAP_SHA2 {
78             features |= SHA256.mask;
79         }
80     }
81 
82     features
83 }
84 
85 #[cfg(all(target_os = "fuchsia", target_arch = "aarch64"))]
detect_features() -> u3286 fn detect_features() -> u32 {
87     type zx_status_t = i32;
88 
89     #[link(name = "zircon")]
90     extern "C" {
91         fn zx_system_get_features(kind: u32, features: *mut u32) -> zx_status_t;
92     }
93 
94     const ZX_OK: i32 = 0;
95     const ZX_FEATURE_KIND_CPU: u32 = 0;
96     const ZX_ARM64_FEATURE_ISA_ASIMD: u32 = 1 << 2;
97     const ZX_ARM64_FEATURE_ISA_AES: u32 = 1 << 3;
98     const ZX_ARM64_FEATURE_ISA_PMULL: u32 = 1 << 4;
99     const ZX_ARM64_FEATURE_ISA_SHA2: u32 = 1 << 6;
100 
101     let mut caps = 0;
102     let rc = unsafe { zx_system_get_features(ZX_FEATURE_KIND_CPU, &mut caps) };
103 
104     let mut features = 0;
105 
106     // OpenSSL and BoringSSL don't enable any other features if NEON isn't
107     // available.
108     if rc == ZX_OK && (caps & ZX_ARM64_FEATURE_ISA_ASIMD == ZX_ARM64_FEATURE_ISA_ASIMD) {
109         features = NEON.mask;
110 
111         if caps & ZX_ARM64_FEATURE_ISA_AES == ZX_ARM64_FEATURE_ISA_AES {
112             features |= AES.mask;
113         }
114         if caps & ZX_ARM64_FEATURE_ISA_PMULL == ZX_ARM64_FEATURE_ISA_PMULL {
115             features |= PMULL.mask;
116         }
117         if caps & ZX_ARM64_FEATURE_ISA_SHA2 == ZX_ARM64_FEATURE_ISA_SHA2 {
118             features |= 1 << 4;
119         }
120     }
121 
122     features
123 }
124 
125 #[cfg(all(target_os = "windows", target_arch = "aarch64"))]
detect_features() -> u32126 fn detect_features() -> u32 {
127     // We do not need to check for the presence of NEON, as Armv8-A always has it
128     const _ASSERT_NEON_DETECTED: () = assert!((ARMCAP_STATIC & NEON.mask) == NEON.mask);
129     let mut features = ARMCAP_STATIC;
130 
131     let result = unsafe {
132         windows_sys::Win32::System::Threading::IsProcessorFeaturePresent(
133             windows_sys::Win32::System::Threading::PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE,
134         )
135     };
136 
137     if result != 0 {
138         // These are all covered by one call in Windows
139         features |= AES.mask;
140         features |= PMULL.mask;
141         features |= SHA256.mask;
142     }
143 
144     features
145 }
146 
147 macro_rules! features {
148     {
149         $(
150             $target_feature_name:expr => $name:ident {
151                 mask: $mask:expr,
152             }
153         ),+
154         , // trailing comma is required.
155     } => {
156         $(
157             #[allow(dead_code)]
158             pub(crate) const $name: Feature = Feature {
159                 mask: $mask,
160             };
161         )+
162 
163         const ARMCAP_STATIC: u32 = 0
164             $(
165                 | (
166                     if cfg!(all(any(target_arch = "aarch64", target_arch = "arm"),
167                                 target_feature = $target_feature_name)) {
168                         $name.mask
169                     } else {
170                         0
171                     }
172                 )
173             )+;
174 
175         #[cfg(all(test, any(target_arch = "arm", target_arch = "aarch64")))]
176         const ALL_FEATURES: [Feature; 4] = [
177             $(
178                 $name
179             ),+
180         ];
181     }
182 }
183 
184 pub(crate) struct Feature {
185     mask: u32,
186 }
187 
188 impl Feature {
189     #[inline(always)]
available(&self, _: super::Features) -> bool190     pub fn available(&self, _: super::Features) -> bool {
191         if self.mask == self.mask & ARMCAP_STATIC {
192             return true;
193         }
194 
195         #[cfg(all(
196             any(
197                 target_os = "android",
198                 target_os = "fuchsia",
199                 target_os = "linux",
200                 target_os = "windows"
201             ),
202             any(target_arch = "arm", target_arch = "aarch64")
203         ))]
204         {
205             // SAFETY: See `OPENSSL_armcap_P`'s safety documentation.
206             if self.mask == self.mask & unsafe { OPENSSL_armcap_P } {
207                 return true;
208             }
209         }
210 
211         false
212     }
213 }
214 
215 // Assumes all target feature names are the same for ARM and AAarch64.
216 features! {
217     // Keep in sync with `ARMV7_NEON`.
218     "neon" => NEON {
219         mask: 1 << 0,
220     },
221 
222     // Keep in sync with `ARMV8_AES`.
223     "aes" => AES {
224         mask: 1 << 2,
225     },
226 
227     // Keep in sync with `ARMV8_SHA256`.
228     "sha2" => SHA256 {
229         mask: 1 << 4,
230     },
231 
232     // Keep in sync with `ARMV8_PMULL`.
233     //
234     // TODO(MSRV): There is no "pmull" feature listed from
235     // `rustc --print cfg --target=aarch64-apple-darwin`. Originally ARMv8 tied
236     // PMULL detection into AES detection, but later versions split it; see
237     // https://developer.arm.com/downloads/-/exploration-tools/feature-names-for-a-profile
238     // "Features introduced prior to 2020." Change this to use "pmull" when
239     // that is supported.
240     "aes" => PMULL {
241         mask: 1 << 5,
242     },
243 }
244 
245 // SAFETY:
246 // - This may only be called from within `cpu::features()` and only while it is initializing its
247 //   `INIT`.
248 // - See the safety invariants of `OPENSSL_armcap_P` below.
249 #[cfg(all(
250     any(target_arch = "aarch64", target_arch = "arm"),
251     any(
252         target_os = "android",
253         target_os = "fuchsia",
254         target_os = "linux",
255         target_os = "windows"
256     )
257 ))]
initialize_OPENSSL_armcap_P()258 pub unsafe fn initialize_OPENSSL_armcap_P() {
259     let detected = detect_features();
260     let filtered = (if cfg!(feature = "unstable-testing-arm-no-hw") {
261         AES.mask | SHA256.mask | PMULL.mask
262     } else {
263         0
264     }) | (if cfg!(feature = "unstable-testing-arm-no-neon") {
265         NEON.mask
266     } else {
267         0
268     });
269     let detected = detected & !filtered;
270     OPENSSL_armcap_P = ARMCAP_STATIC | detected;
271 }
272 
273 // Some non-Rust code still checks this even when it is statically known
274 // the given feature is available, so we have to ensure that this is
275 // initialized properly. Keep this in sync with the initialization in
276 // BoringSSL's crypto.c.
277 //
278 // TODO: This should have "hidden" visibility but we don't have a way of
279 // controlling that yet: https://github.com/rust-lang/rust/issues/73958.
280 //
281 // SAFETY:
282 // - Rust code only accesses this through `cpu::Features`, which acts as a witness that
283 //   `cpu::features()` was called to initialize this.
284 // - Some assembly language functions access `OPENSSL_armcap_P` directly. Callers of those functions
285 //   must obtain a `cpu::Features` before calling them.
286 // - An instance of `cpu::Features` is a witness that this was initialized.
287 // - The initialization of the `INIT` in `cpu::features()` initializes this, and that `OnceCell`
288 //   implements acquire/release semantics that allow all the otherwise-apparently-unsynchronized
289 //   access.
290 // - `OPENSSL_armcap_P` must always be a superset of `ARMCAP_STATIC`.
291 // TODO: Remove all the direct accesses of this from assembly language code, and then replace this
292 // with a `OnceCell<u32>` that will provide all the necessary safety guarantees.
293 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
294 prefixed_export! {
295     #[allow(non_upper_case_globals)]
296     static mut OPENSSL_armcap_P: u32 = ARMCAP_STATIC;
297 }
298 
299 // MSRV: Enforce 1.61.0 on some aarch64-* targets (aarch64-apple-*, in particular) prior to. Earlier
300 // versions of Rust before did not report the AAarch64 CPU features correctly for these targets.
301 // Cargo.toml specifies `rust-version` but versions before Rust 1.56 don't know about it.
302 //
303 // ```
304 // $ rustc +1.61.0 --print cfg --target=aarch64-apple-ios | grep -E "neon|aes|sha|pmull"
305 // target_feature="aes"
306 // target_feature="neon"
307 // target_feature="sha2"
308 // $ rustc +1.61.0 --print cfg --target=aarch64-apple-darwin | grep -E "neon|aes|sha|pmull"
309 // target_feature="aes"
310 // target_feature="neon"
311 // target_feature="sha2"
312 // target_feature="sha3"
313 // ```
314 #[allow(clippy::assertions_on_constants)]
315 const _AARCH64_HAS_NEON: () =
316     assert!(((ARMCAP_STATIC & NEON.mask) == NEON.mask) || !cfg!(target_arch = "aarch64"));
317 #[allow(clippy::assertions_on_constants)]
318 const _AARCH64_APPLE_FEATURES: u32 = NEON.mask | AES.mask | SHA256.mask | PMULL.mask;
319 #[allow(clippy::assertions_on_constants)]
320 const _AARCH64_APPLE_TARGETS_EXPECTED_FEATURES: () = assert!(
321     ((ARMCAP_STATIC & _AARCH64_APPLE_FEATURES) == _AARCH64_APPLE_FEATURES)
322         || !cfg!(all(target_arch = "aarch64", target_vendor = "apple"))
323 );
324 
325 #[cfg(all(test, any(target_arch = "arm", target_arch = "aarch64")))]
326 mod tests {
327     use super::*;
328 
329     #[test]
test_mask_abi()330     fn test_mask_abi() {
331         assert_eq!(NEON.mask, 1);
332         assert_eq!(AES.mask, 4);
333         assert_eq!(SHA256.mask, 16);
334         assert_eq!(PMULL.mask, 32);
335     }
336 
337     #[test]
test_armcap_static_is_subset_of_armcap_dynamic()338     fn test_armcap_static_is_subset_of_armcap_dynamic() {
339         // Ensure `OPENSSL_armcap_P` is initialized.
340         let cpu = crate::cpu::features();
341 
342         let armcap_dynamic = unsafe { OPENSSL_armcap_P };
343         assert_eq!(armcap_dynamic & ARMCAP_STATIC, ARMCAP_STATIC);
344 
345         ALL_FEATURES.iter().for_each(|feature| {
346             if (ARMCAP_STATIC & feature.mask) != 0 {
347                 assert!(feature.available(cpu));
348             }
349         })
350     }
351 }
352