-
Notifications
You must be signed in to change notification settings - Fork 287
/
Copy pathriscv.rs
315 lines (283 loc) · 13.4 KB
/
riscv.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
//! Run-time feature detection for RISC-V on Linux.
//!
//! On RISC-V, detection using auxv only supports single-letter extensions.
//! So, we use riscv_hwprobe that supports multi-letter extensions if available.
//! <https://www.kernel.org/doc/html/latest/arch/riscv/hwprobe.html>
use core::ptr;
use super::super::riscv::imply_features;
use super::auxvec;
use crate::detect::{Feature, bit, cache};
// See <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/prctl.h?h=v6.14>
// for runtime status query constants.
const PR_RISCV_V_GET_CONTROL: libc::c_int = 70;
const PR_RISCV_V_VSTATE_CTRL_ON: libc::c_int = 2;
const PR_RISCV_V_VSTATE_CTRL_CUR_MASK: libc::c_int = 3;
// See <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/include/uapi/asm/hwprobe.h?h=v6.14>
// for riscv_hwprobe struct and hardware probing constants.
#[repr(C)]
struct riscv_hwprobe {
key: i64,
value: u64,
}
#[allow(non_upper_case_globals)]
const __NR_riscv_hwprobe: libc::c_long = 258;
const RISCV_HWPROBE_KEY_BASE_BEHAVIOR: i64 = 3;
const RISCV_HWPROBE_BASE_BEHAVIOR_IMA: u64 = 1 << 0;
const RISCV_HWPROBE_KEY_IMA_EXT_0: i64 = 4;
const RISCV_HWPROBE_IMA_FD: u64 = 1 << 0;
const RISCV_HWPROBE_IMA_C: u64 = 1 << 1;
const RISCV_HWPROBE_IMA_V: u64 = 1 << 2;
const RISCV_HWPROBE_EXT_ZBA: u64 = 1 << 3;
const RISCV_HWPROBE_EXT_ZBB: u64 = 1 << 4;
const RISCV_HWPROBE_EXT_ZBS: u64 = 1 << 5;
const RISCV_HWPROBE_EXT_ZICBOZ: u64 = 1 << 6;
const RISCV_HWPROBE_EXT_ZBC: u64 = 1 << 7;
const RISCV_HWPROBE_EXT_ZBKB: u64 = 1 << 8;
const RISCV_HWPROBE_EXT_ZBKC: u64 = 1 << 9;
const RISCV_HWPROBE_EXT_ZBKX: u64 = 1 << 10;
const RISCV_HWPROBE_EXT_ZKND: u64 = 1 << 11;
const RISCV_HWPROBE_EXT_ZKNE: u64 = 1 << 12;
const RISCV_HWPROBE_EXT_ZKNH: u64 = 1 << 13;
const RISCV_HWPROBE_EXT_ZKSED: u64 = 1 << 14;
const RISCV_HWPROBE_EXT_ZKSH: u64 = 1 << 15;
const RISCV_HWPROBE_EXT_ZKT: u64 = 1 << 16;
const RISCV_HWPROBE_EXT_ZVBB: u64 = 1 << 17;
const RISCV_HWPROBE_EXT_ZVBC: u64 = 1 << 18;
const RISCV_HWPROBE_EXT_ZVKB: u64 = 1 << 19;
const RISCV_HWPROBE_EXT_ZVKG: u64 = 1 << 20;
const RISCV_HWPROBE_EXT_ZVKNED: u64 = 1 << 21;
const RISCV_HWPROBE_EXT_ZVKNHA: u64 = 1 << 22;
const RISCV_HWPROBE_EXT_ZVKNHB: u64 = 1 << 23;
const RISCV_HWPROBE_EXT_ZVKSED: u64 = 1 << 24;
const RISCV_HWPROBE_EXT_ZVKSH: u64 = 1 << 25;
const RISCV_HWPROBE_EXT_ZVKT: u64 = 1 << 26;
const RISCV_HWPROBE_EXT_ZFH: u64 = 1 << 27;
const RISCV_HWPROBE_EXT_ZFHMIN: u64 = 1 << 28;
const RISCV_HWPROBE_EXT_ZIHINTNTL: u64 = 1 << 29;
const RISCV_HWPROBE_EXT_ZVFH: u64 = 1 << 30;
const RISCV_HWPROBE_EXT_ZVFHMIN: u64 = 1 << 31;
const RISCV_HWPROBE_EXT_ZFA: u64 = 1 << 32;
const RISCV_HWPROBE_EXT_ZTSO: u64 = 1 << 33;
const RISCV_HWPROBE_EXT_ZACAS: u64 = 1 << 34;
const RISCV_HWPROBE_EXT_ZICOND: u64 = 1 << 35;
const RISCV_HWPROBE_EXT_ZIHINTPAUSE: u64 = 1 << 36;
const RISCV_HWPROBE_EXT_ZVE32X: u64 = 1 << 37;
const RISCV_HWPROBE_EXT_ZVE32F: u64 = 1 << 38;
const RISCV_HWPROBE_EXT_ZVE64X: u64 = 1 << 39;
const RISCV_HWPROBE_EXT_ZVE64F: u64 = 1 << 40;
const RISCV_HWPROBE_EXT_ZVE64D: u64 = 1 << 41;
const RISCV_HWPROBE_EXT_ZIMOP: u64 = 1 << 42;
const RISCV_HWPROBE_EXT_ZCA: u64 = 1 << 43;
const RISCV_HWPROBE_EXT_ZCB: u64 = 1 << 44;
const RISCV_HWPROBE_EXT_ZCD: u64 = 1 << 45;
const RISCV_HWPROBE_EXT_ZCF: u64 = 1 << 46;
const RISCV_HWPROBE_EXT_ZCMOP: u64 = 1 << 47;
const RISCV_HWPROBE_EXT_ZAWRS: u64 = 1 << 48;
// Excluded because it only reports the existence of `prctl`-based pointer masking control.
// const RISCV_HWPROBE_EXT_SUPM: u64 = 1 << 49;
const RISCV_HWPROBE_KEY_CPUPERF_0: i64 = 5;
const RISCV_HWPROBE_MISALIGNED_FAST: u64 = 3;
const RISCV_HWPROBE_MISALIGNED_MASK: u64 = 7;
const RISCV_HWPROBE_KEY_MISALIGNED_SCALAR_PERF: i64 = 9;
const RISCV_HWPROBE_MISALIGNED_SCALAR_FAST: u64 = 3;
const RISCV_HWPROBE_KEY_MISALIGNED_VECTOR_PERF: i64 = 10;
const RISCV_HWPROBE_MISALIGNED_VECTOR_FAST: u64 = 3;
// syscall returns an unsupported error if riscv_hwprobe is not supported,
// so we can safely use this function on older versions of Linux.
fn _riscv_hwprobe(out: &mut [riscv_hwprobe]) -> bool {
unsafe fn __riscv_hwprobe(
pairs: *mut riscv_hwprobe,
pair_count: libc::size_t,
cpu_set_size: libc::size_t,
cpus: *mut libc::c_ulong,
flags: libc::c_uint,
) -> libc::c_long {
unsafe {
libc::syscall(
__NR_riscv_hwprobe,
pairs,
pair_count,
cpu_set_size,
cpus,
flags,
)
}
}
let len = out.len();
unsafe { __riscv_hwprobe(out.as_mut_ptr(), len, 0, ptr::null_mut(), 0) == 0 }
}
/// Read list of supported features from (1) the auxiliary vector
/// and (2) the results of `riscv_hwprobe` and `prctl` system calls.
pub(crate) fn detect_features() -> cache::Initializer {
let mut value = cache::Initializer::default();
let mut enable_feature = |feature, enable| {
if enable {
value.set(feature as u32);
}
};
// Use auxiliary vector to enable single-letter ISA extensions.
// The values are part of the platform-specific [asm/hwcap.h][hwcap]
//
// [hwcap]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/include/uapi/asm/hwcap.h?h=v6.14
let auxv = auxvec::auxv().expect("read auxvec"); // should not fail on RISC-V platform
let mut has_i = bit::test(auxv.hwcap, (b'i' - b'a').into());
#[allow(clippy::eq_op)]
enable_feature(Feature::a, bit::test(auxv.hwcap, (b'a' - b'a').into()));
enable_feature(Feature::c, bit::test(auxv.hwcap, (b'c' - b'a').into()));
enable_feature(Feature::d, bit::test(auxv.hwcap, (b'd' - b'a').into()));
enable_feature(Feature::f, bit::test(auxv.hwcap, (b'f' - b'a').into()));
enable_feature(Feature::m, bit::test(auxv.hwcap, (b'm' - b'a').into()));
let has_v = bit::test(auxv.hwcap, (b'v' - b'a').into());
let mut is_v_set = false;
// Use riscv_hwprobe syscall to query more extensions and
// performance-related capabilities.
'hwprobe: {
let mut out = [
riscv_hwprobe {
key: RISCV_HWPROBE_KEY_BASE_BEHAVIOR,
value: 0,
},
riscv_hwprobe {
key: RISCV_HWPROBE_KEY_IMA_EXT_0,
value: 0,
},
riscv_hwprobe {
key: RISCV_HWPROBE_KEY_MISALIGNED_SCALAR_PERF,
value: 0,
},
riscv_hwprobe {
key: RISCV_HWPROBE_KEY_MISALIGNED_VECTOR_PERF,
value: 0,
},
riscv_hwprobe {
key: RISCV_HWPROBE_KEY_CPUPERF_0,
value: 0,
},
];
if !_riscv_hwprobe(&mut out) {
break 'hwprobe;
}
// Query scalar/vector misaligned behavior.
if out[2].key != -1 {
enable_feature(
Feature::unaligned_scalar_mem,
out[2].value == RISCV_HWPROBE_MISALIGNED_SCALAR_FAST,
);
} else if out[4].key != -1 {
// Deprecated method for fallback
enable_feature(
Feature::unaligned_scalar_mem,
out[4].value & RISCV_HWPROBE_MISALIGNED_MASK == RISCV_HWPROBE_MISALIGNED_FAST,
);
}
if out[3].key != -1 {
enable_feature(
Feature::unaligned_vector_mem,
out[3].value == RISCV_HWPROBE_MISALIGNED_VECTOR_FAST,
);
}
// Query whether "I" base and extensions "M" and "A" (as in the ISA
// manual version 2.2) are enabled. "I" base at that time corresponds
// to "I", "Zicsr", "Zicntr" and "Zifencei" (as in the ISA manual version
// 20240411) and we chose to imply "Zicsr" and "Zifencei" (not "Zicntr")
// because there will be a separate RISCV_HWPROBE_EXT_ZICNTR constant to
// determine existence of the "Zicntr" extension in Linux 6.15 (as of rc1).
// "fence.i" ("Zifencei") is conditionally valid on the Linux userland
// (when CMODX is enabled).
// This is a requirement of `RISCV_HWPROBE_KEY_IMA_EXT_0`-based tests.
let has_ima = (out[0].key != -1) && (out[0].value & RISCV_HWPROBE_BASE_BEHAVIOR_IMA != 0);
if !has_ima {
break 'hwprobe;
}
has_i |= has_ima;
enable_feature(Feature::zicsr, has_ima);
enable_feature(Feature::zifencei, has_ima);
enable_feature(Feature::m, has_ima);
enable_feature(Feature::a, has_ima);
// Enable features based on `RISCV_HWPROBE_KEY_IMA_EXT_0`.
if out[1].key == -1 {
break 'hwprobe;
}
let ima_ext_0 = out[1].value;
let test = |mask| (ima_ext_0 & mask) != 0;
enable_feature(Feature::d, test(RISCV_HWPROBE_IMA_FD)); // F is implied.
enable_feature(Feature::c, test(RISCV_HWPROBE_IMA_C));
enable_feature(Feature::zihintntl, test(RISCV_HWPROBE_EXT_ZIHINTNTL));
enable_feature(Feature::zihintpause, test(RISCV_HWPROBE_EXT_ZIHINTPAUSE));
enable_feature(Feature::zimop, test(RISCV_HWPROBE_EXT_ZIMOP));
enable_feature(Feature::zicboz, test(RISCV_HWPROBE_EXT_ZICBOZ));
enable_feature(Feature::zicond, test(RISCV_HWPROBE_EXT_ZICOND));
enable_feature(Feature::zawrs, test(RISCV_HWPROBE_EXT_ZAWRS));
enable_feature(Feature::zacas, test(RISCV_HWPROBE_EXT_ZACAS));
enable_feature(Feature::ztso, test(RISCV_HWPROBE_EXT_ZTSO));
enable_feature(Feature::zba, test(RISCV_HWPROBE_EXT_ZBA));
enable_feature(Feature::zbb, test(RISCV_HWPROBE_EXT_ZBB));
enable_feature(Feature::zbs, test(RISCV_HWPROBE_EXT_ZBS));
enable_feature(Feature::zbc, test(RISCV_HWPROBE_EXT_ZBC));
enable_feature(Feature::zbkb, test(RISCV_HWPROBE_EXT_ZBKB));
enable_feature(Feature::zbkc, test(RISCV_HWPROBE_EXT_ZBKC));
enable_feature(Feature::zbkx, test(RISCV_HWPROBE_EXT_ZBKX));
enable_feature(Feature::zknd, test(RISCV_HWPROBE_EXT_ZKND));
enable_feature(Feature::zkne, test(RISCV_HWPROBE_EXT_ZKNE));
enable_feature(Feature::zknh, test(RISCV_HWPROBE_EXT_ZKNH));
enable_feature(Feature::zksed, test(RISCV_HWPROBE_EXT_ZKSED));
enable_feature(Feature::zksh, test(RISCV_HWPROBE_EXT_ZKSH));
enable_feature(Feature::zkt, test(RISCV_HWPROBE_EXT_ZKT));
enable_feature(Feature::zcmop, test(RISCV_HWPROBE_EXT_ZCMOP));
enable_feature(Feature::zca, test(RISCV_HWPROBE_EXT_ZCA));
enable_feature(Feature::zcf, test(RISCV_HWPROBE_EXT_ZCF));
enable_feature(Feature::zcd, test(RISCV_HWPROBE_EXT_ZCD));
enable_feature(Feature::zcb, test(RISCV_HWPROBE_EXT_ZCB));
enable_feature(Feature::zfh, test(RISCV_HWPROBE_EXT_ZFH));
enable_feature(Feature::zfhmin, test(RISCV_HWPROBE_EXT_ZFHMIN));
enable_feature(Feature::zfa, test(RISCV_HWPROBE_EXT_ZFA));
// Use prctl (if any) to determine whether the vector extension
// is enabled on the current thread (assuming the entire process
// share the same status). If prctl fails (e.g. QEMU userland emulator
// as of version 9.2.3), use auxiliary vector to retrieve the default
// vector status on the process startup.
let has_vectors = {
let v_status = unsafe { libc::prctl(PR_RISCV_V_GET_CONTROL) };
if v_status >= 0 {
(v_status & PR_RISCV_V_VSTATE_CTRL_CUR_MASK) == PR_RISCV_V_VSTATE_CTRL_ON
} else {
has_v
}
};
if has_vectors {
enable_feature(Feature::v, test(RISCV_HWPROBE_IMA_V));
enable_feature(Feature::zve32x, test(RISCV_HWPROBE_EXT_ZVE32X));
enable_feature(Feature::zve32f, test(RISCV_HWPROBE_EXT_ZVE32F));
enable_feature(Feature::zve64x, test(RISCV_HWPROBE_EXT_ZVE64X));
enable_feature(Feature::zve64f, test(RISCV_HWPROBE_EXT_ZVE64F));
enable_feature(Feature::zve64d, test(RISCV_HWPROBE_EXT_ZVE64D));
enable_feature(Feature::zvbb, test(RISCV_HWPROBE_EXT_ZVBB));
enable_feature(Feature::zvbc, test(RISCV_HWPROBE_EXT_ZVBC));
enable_feature(Feature::zvkb, test(RISCV_HWPROBE_EXT_ZVKB));
enable_feature(Feature::zvkg, test(RISCV_HWPROBE_EXT_ZVKG));
enable_feature(Feature::zvkned, test(RISCV_HWPROBE_EXT_ZVKNED));
enable_feature(Feature::zvknha, test(RISCV_HWPROBE_EXT_ZVKNHA));
enable_feature(Feature::zvknhb, test(RISCV_HWPROBE_EXT_ZVKNHB));
enable_feature(Feature::zvksed, test(RISCV_HWPROBE_EXT_ZVKSED));
enable_feature(Feature::zvksh, test(RISCV_HWPROBE_EXT_ZVKSH));
enable_feature(Feature::zvkt, test(RISCV_HWPROBE_EXT_ZVKT));
enable_feature(Feature::zvfh, test(RISCV_HWPROBE_EXT_ZVFH));
enable_feature(Feature::zvfhmin, test(RISCV_HWPROBE_EXT_ZVFHMIN));
}
is_v_set = true;
};
// Set V purely depending on the auxiliary vector
// only if no fine-grained vector extension detection is available.
if !is_v_set {
enable_feature(Feature::v, has_v);
}
// Handle base ISA.
// If future RV128I is supported, implement with `enable_feature` here.
// Note that we should use `target_arch` instead of `target_pointer_width`
// to avoid misdetection caused by experimental ABIs such as RV64ILP32.
#[cfg(target_arch = "riscv64")]
enable_feature(Feature::rv64i, has_i);
#[cfg(target_arch = "riscv32")]
enable_feature(Feature::rv32i, has_i);
imply_features(value)
}