1use super::ArchTopology;
7use super::InvalidTopology;
8use super::ProcessorTopology;
9use super::THREADS_PER_CORE;
10use super::TopologyBuilder;
11use super::VpIndex;
12use super::VpInfo;
13use super::VpTopologyInfo;
14use aarch64defs::MpidrEl1;
15
16#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
18#[derive(Debug, Copy, Clone)]
19#[non_exhaustive]
20pub struct Aarch64Topology {
21 platform: Aarch64PlatformConfig,
22}
23
24impl ArchTopology for Aarch64Topology {
25 type ArchVpInfo = Aarch64VpInfo;
26 type BuilderState = Aarch64TopologyBuilderState;
27
28 fn vp_topology(topology: &ProcessorTopology<Self>, info: &Self::ArchVpInfo) -> VpTopologyInfo {
29 topology.logical_topology(info.base.vp_index)
36 }
37}
38
39fn non_smt_mpidr(vp_index: u32) -> MpidrEl1 {
48 MpidrEl1::new()
49 .with_aff0((vp_index % AFF0_PER_GROUP) as u8)
50 .with_aff1((vp_index / AFF0_PER_GROUP) as u8)
51 .with_aff2((vp_index / (AFF0_PER_GROUP << 8)) as u8)
52 .with_aff3((vp_index / (AFF0_PER_GROUP << 16)) as u8)
53}
54
55fn smt_mpidr(vp_index: u32) -> MpidrEl1 {
64 let core = vp_index / THREADS_PER_CORE;
65 MpidrEl1::new()
66 .with_mt(true)
67 .with_aff0((vp_index % THREADS_PER_CORE) as u8)
68 .with_aff1(core as u8)
69 .with_aff2((core >> 8) as u8)
70 .with_aff3((core >> 16) as u8)
71}
72
73const AFF0_PER_GROUP: u32 = 16;
76
77pub struct Aarch64TopologyBuilderState {
79 platform: Aarch64PlatformConfig,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
85#[cfg_attr(feature = "inspect", inspect(external_tag))]
86pub enum GicVersion {
87 V2 {
90 #[cfg_attr(feature = "inspect", inspect(hex))]
92 cpu_interface_base: u64,
93 },
94 V3 {
96 #[cfg_attr(feature = "inspect", inspect(hex))]
98 redistributors_base: u64,
99 },
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
109pub struct Aarch64PlatformConfig {
110 #[cfg_attr(feature = "inspect", inspect(hex))]
112 pub gic_distributor_base: u64,
113 pub gic_version: GicVersion,
115 pub gic_msi: GicMsiController,
117 pub pmu_gsiv: Option<u32>,
119 pub virt_timer_ppi: u32,
121 pub gic_nr_irqs: u32,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
131pub struct GicV2mInfo {
132 #[cfg_attr(feature = "inspect", inspect(hex))]
134 pub frame_base: u64,
135 pub spi_base: u32,
137 pub spi_count: u32,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
144pub struct GicItsInfo {
145 #[cfg_attr(feature = "inspect", inspect(hex))]
147 pub its_base: u64,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
153#[cfg_attr(feature = "inspect", inspect(external_tag))]
154pub enum GicMsiController {
155 None,
157 V2m(GicV2mInfo),
159 Its(GicItsInfo),
161}
162
163#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
165#[derive(Debug, Copy, Clone)]
166pub struct Aarch64VpInfo {
167 #[cfg_attr(feature = "inspect", inspect(flatten))]
169 pub base: VpInfo,
170 #[cfg_attr(feature = "inspect", inspect(hex, with = "|&x| u64::from(x)"))]
172 pub mpidr: MpidrEl1,
173 #[cfg_attr(feature = "inspect", inspect(hex))]
175 pub gicr: Option<u64>,
176 #[cfg_attr(feature = "inspect", inspect(hex))]
178 pub pmu_gsiv: Option<u32>,
179}
180
181impl AsRef<VpInfo> for Aarch64VpInfo {
182 fn as_ref(&self) -> &VpInfo {
183 &self.base
184 }
185}
186
187impl AsMut<VpInfo> for Aarch64VpInfo {
188 fn as_mut(&mut self) -> &mut VpInfo {
189 &mut self.base
190 }
191}
192
193impl TopologyBuilder<Aarch64Topology> {
194 pub fn new_aarch64(platform: Aarch64PlatformConfig) -> Self {
196 Self {
197 vps_per_socket: 1,
198 smt_enabled: false,
199 arch: Aarch64TopologyBuilderState { platform },
200 }
201 }
202
203 pub fn build(
205 &self,
206 proc_count: u32,
207 ) -> Result<ProcessorTopology<Aarch64Topology>, InvalidTopology> {
208 if proc_count >= 256 {
209 return Err(InvalidTopology::TooManyVps {
210 requested: proc_count,
211 max: u8::MAX.into(),
212 });
213 }
214 if let GicVersion::V2 { .. } = self.arch.platform.gic_version {
215 if proc_count > 8 {
216 return Err(InvalidTopology::TooManyCpusForGicV2(proc_count));
217 }
218 }
219 if !(16..32).contains(&self.arch.platform.virt_timer_ppi) {
220 return Err(InvalidTopology::InvalidPpiIntid(
221 self.arch.platform.virt_timer_ppi,
222 ));
223 }
224 if let Some(gsiv) = self.arch.platform.pmu_gsiv {
225 if !(16..32).contains(&gsiv) {
226 return Err(InvalidTopology::InvalidPpiIntid(gsiv));
227 }
228 }
229 let nr = self.arch.platform.gic_nr_irqs;
230 if !(64..=992).contains(&nr) || !nr.is_multiple_of(32) {
231 return Err(InvalidTopology::InvalidGicNrIrqs(nr));
232 }
233 let smt_enabled = self.effective_smt();
234 let uni_proc = proc_count == 1;
235 let mpidrs = (0..proc_count).map(|vp_index| {
236 let mpidr = if smt_enabled {
237 smt_mpidr(vp_index)
238 } else {
239 non_smt_mpidr(vp_index)
240 };
241 mpidr.with_res1_31(true).with_u(uni_proc)
242 });
243 let gic_version = self.arch.platform.gic_version;
244 self.build_with_vp_info(mpidrs.enumerate().map(move |(id, mpidr)| {
245 let gicr = match gic_version {
248 GicVersion::V3 {
249 redistributors_base,
250 } => Some(redistributors_base + id as u64 * aarch64defs::GIC_REDISTRIBUTOR_SIZE),
251 GicVersion::V2 { .. } => None,
252 };
253 Aarch64VpInfo {
254 base: VpInfo {
255 vp_index: VpIndex::new(id as u32),
256 vnode: id as u32 / self.vps_per_socket,
257 },
258 mpidr,
259 gicr,
260 pmu_gsiv: self.arch.platform.pmu_gsiv,
261 }
262 }))
263 }
264
265 fn effective_smt(&self) -> bool {
271 self.smt_enabled && self.vps_per_socket > 1
272 }
273
274 pub fn build_with_vp_info(
282 &self,
283 vps: impl IntoIterator<Item = Aarch64VpInfo>,
284 ) -> Result<ProcessorTopology<Aarch64Topology>, InvalidTopology> {
285 let vps = Vec::from_iter(vps);
286 for (i, vp) in vps.iter().enumerate() {
287 if i != vp.base.vp_index.index() as usize {
288 return Err(InvalidTopology::InvalidVpIndices);
289 }
290 }
291
292 Ok(ProcessorTopology {
293 vps,
294 smt_enabled: self.effective_smt(),
295 vps_per_socket: self.vps_per_socket,
296 arch: Aarch64Topology {
297 platform: self.arch.platform,
298 },
299 })
300 }
301}
302
303impl ProcessorTopology<Aarch64Topology> {
304 pub fn gic_version(&self) -> GicVersion {
306 self.arch.platform.gic_version
307 }
308
309 pub fn gic_distributor_base(&self) -> u64 {
311 self.arch.platform.gic_distributor_base
312 }
313
314 pub fn pmu_gsiv(&self) -> Option<u32> {
316 self.arch.platform.pmu_gsiv
317 }
318
319 pub fn gic_msi(&self) -> GicMsiController {
321 self.arch.platform.gic_msi
322 }
323
324 pub fn virt_timer_ppi(&self) -> u32 {
326 self.arch.platform.virt_timer_ppi
327 }
328
329 pub fn gic_nr_irqs(&self) -> u32 {
331 self.arch.platform.gic_nr_irqs
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 fn platform() -> Aarch64PlatformConfig {
340 Aarch64PlatformConfig {
341 gic_distributor_base: 0xffff0000,
342 gic_version: GicVersion::V3 {
343 redistributors_base: 0xefff0000,
344 },
345 gic_msi: GicMsiController::None,
346 pmu_gsiv: None,
347 virt_timer_ppi: 20,
348 gic_nr_irqs: 992,
349 }
350 }
351
352 fn builder() -> TopologyBuilder<Aarch64Topology> {
353 TopologyBuilder::new_aarch64(platform())
354 }
355
356 fn describe(topology: &ProcessorTopology<Aarch64Topology>) -> Vec<(u64, u32, u32, u32)> {
358 topology
359 .vps_arch()
360 .map(|vp| {
361 let t = topology.vp_topology(vp.base.vp_index);
362 (vp.mpidr.into(), t.socket, t.core, t.thread)
363 })
364 .collect()
365 }
366
367 fn affinity(mpidr: u64) -> u64 {
369 mpidr & (u64::from(MpidrEl1::AFFINITY_MASK) | 1 << 24)
370 }
371
372 #[test]
375 fn single_vp_ignores_smt() {
376 let topology = builder().smt_enabled(true).build(1).unwrap();
377 assert!(!topology.smt_enabled());
378 let mpidr = topology.vp_arch(VpIndex::new(0)).mpidr;
379 assert!(mpidr.u());
380 assert!(!mpidr.mt());
381 assert_eq!(describe(&topology), [(u64::from(mpidr), 0, 0, 0)]);
382 }
383
384 #[test]
387 fn seventeen_vps_roll_into_aff1() {
388 let topology = builder().vps_per_socket(17).build(17).unwrap();
389 let vps = describe(&topology);
390 assert_eq!(affinity(vps[15].0), 0x0f);
391 assert_eq!(affinity(vps[16].0), 0x100);
392 for (i, (mpidr, socket, core, thread)) in vps.into_iter().enumerate() {
393 assert!(MpidrEl1::from(mpidr).aff0() < 16);
394 assert_eq!((socket, core, thread), (0, i as u32, 0));
395 }
396 }
397
398 #[test]
401 fn multiple_sockets_without_smt() {
402 let topology = builder().vps_per_socket(4).build(8).unwrap();
403 let vps = describe(&topology);
404 for (i, (mpidr, socket, core, thread)) in vps.iter().copied().enumerate() {
405 assert_eq!(affinity(mpidr), i as u64);
406 assert_eq!((socket, core, thread), (i as u32 / 4, i as u32 % 4, 0));
407 }
408 let vnodes: Vec<_> = topology.vps().map(|vp| vp.vnode).collect();
409 assert_eq!(vnodes, [0, 0, 0, 0, 1, 1, 1, 1]);
410 }
411
412 #[test]
415 fn mpidr_register_values() {
416 for (vp, expected) in [
417 (0, 0x8000_0000),
418 (1, 0x8000_0001),
419 (15, 0x8000_000f),
420 (16, 0x8000_0100),
421 (17, 0x8000_0101),
422 ] {
423 assert_eq!(
424 u64::from(non_smt_mpidr(vp).with_res1_31(true)),
425 expected,
426 "non-SMT VP {vp}"
427 );
428 }
429
430 for (vp, expected) in [
431 (0, 0x8100_0000),
432 (1, 0x8100_0001),
433 (2, 0x8100_0100),
434 (3, 0x8100_0101),
435 ] {
436 assert_eq!(
437 u64::from(smt_mpidr(vp).with_res1_31(true)),
438 expected,
439 "SMT VP {vp}"
440 );
441 }
442 }
443
444 #[test]
447 fn smt_sockets_do_not_change_affinity() {
448 let topology = builder()
449 .vps_per_socket(2)
450 .smt_enabled(true)
451 .build(4)
452 .unwrap();
453 assert!(topology.smt_enabled());
454 assert_eq!(
455 describe(&topology)
456 .into_iter()
457 .map(|(mpidr, socket, core, thread)| (affinity(mpidr), socket, core, thread))
458 .collect::<Vec<_>>(),
459 [
460 (0x0100_0000, 0, 0, 0),
461 (0x0100_0001, 0, 0, 1),
462 (0x0100_0100, 1, 0, 0),
463 (0x0100_0101, 1, 0, 1),
464 ]
465 );
466 }
467
468 #[test]
475 fn odd_socket_size_under_smt() {
476 let topology = builder()
477 .vps_per_socket(3)
478 .smt_enabled(true)
479 .build(3)
480 .unwrap();
481 assert_eq!(
482 describe(&topology)
483 .into_iter()
484 .map(|(_, socket, core, thread)| (socket, core, thread))
485 .collect::<Vec<_>>(),
486 [(0, 0, 0), (0, 0, 1), (0, 1, 0)]
487 );
488 }
489
490 #[test]
493 fn caller_supplied_mpidrs_are_preserved() {
494 let mpidrs = [0x81, 0x40, 0x0];
495 let topology = builder()
496 .vps_per_socket(3)
497 .build_with_vp_info(mpidrs.iter().enumerate().map(|(i, &mpidr)| Aarch64VpInfo {
498 base: VpInfo {
499 vp_index: VpIndex::new(i as u32),
500 vnode: 0,
501 },
502 mpidr: MpidrEl1::from(mpidr),
503 gicr: None,
504 pmu_gsiv: None,
505 }))
506 .unwrap();
507
508 assert_eq!(
509 topology
510 .vps_arch()
511 .map(|vp| u64::from(vp.mpidr))
512 .collect::<Vec<_>>(),
513 mpidrs
514 );
515 assert_eq!(
517 describe(&topology)
518 .into_iter()
519 .map(|(_, socket, core, thread)| (socket, core, thread))
520 .collect::<Vec<_>>(),
521 [(0, 0, 0), (0, 1, 0), (0, 2, 0)]
522 );
523 }
524
525 #[test]
526 fn gicv2_vp_limit() {
527 let gicv2 = || {
528 TopologyBuilder::new_aarch64(Aarch64PlatformConfig {
529 gic_version: GicVersion::V2 {
530 cpu_interface_base: 0xefff0000,
531 },
532 ..platform()
533 })
534 };
535 assert!(gicv2().vps_per_socket(8).build(8).is_ok());
536 assert!(matches!(
537 gicv2().vps_per_socket(9).build(9),
538 Err(InvalidTopology::TooManyCpusForGicV2(9))
539 ));
540 }
541}