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