Skip to main content

vm_topology/processor/
aarch64.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! ARM64-specific topology definitions.
5
6use super::ArchTopology;
7use super::InvalidTopology;
8use super::ProcessorTopology;
9use super::TopologyBuilder;
10use super::VpIndex;
11use super::VpInfo;
12use super::VpTopologyInfo;
13use aarch64defs::MpidrEl1;
14
15/// ARM64-specific topology information.
16#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
17#[derive(Debug, Copy, Clone)]
18#[non_exhaustive]
19pub struct Aarch64Topology {
20    platform: Aarch64PlatformConfig,
21}
22
23impl ArchTopology for Aarch64Topology {
24    type ArchVpInfo = Aarch64VpInfo;
25    type BuilderState = Aarch64TopologyBuilderState;
26
27    fn vp_topology(_topology: &ProcessorTopology<Self>, info: &Self::ArchVpInfo) -> VpTopologyInfo {
28        VpTopologyInfo {
29            socket: info.mpidr.aff2().into(),
30            core: info.mpidr.aff1().into(),
31            thread: info.mpidr.aff0().into(),
32        }
33    }
34}
35
36/// Aarch64-specific [`TopologyBuilder`] state.
37pub struct Aarch64TopologyBuilderState {
38    platform: Aarch64PlatformConfig,
39}
40
41/// GIC version and version-specific addressing for the virtual machine.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
44#[cfg_attr(feature = "inspect", inspect(external_tag))]
45pub enum GicVersion {
46    /// GICv2 — uses a shared CPU interface region instead of per-VP redistributors.
47    /// Required for platforms like Raspberry Pi 5 (GIC-400).
48    V2 {
49        /// Physical base address of the GIC CPU interface.
50        #[cfg_attr(feature = "inspect", inspect(hex))]
51        cpu_interface_base: u64,
52    },
53    /// GICv3 — uses per-VP redistributors. Default for most server/desktop platforms.
54    V3 {
55        /// Physical base address of the GIC redistributor region.
56        #[cfg_attr(feature = "inspect", inspect(hex))]
57        redistributors_base: u64,
58    },
59}
60
61/// ARM64 platform interrupt and GIC configuration.
62///
63/// Groups GIC base addresses, MSI frame info, and platform interrupt
64/// assignments (PMU, virtual timer) into a single struct so that the
65/// topology builder takes one value instead of several positional `u32`s.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
68pub struct Aarch64PlatformConfig {
69    /// GIC distributor base address.
70    #[cfg_attr(feature = "inspect", inspect(hex))]
71    pub gic_distributor_base: u64,
72    /// GIC version and version-specific addresses.
73    pub gic_version: GicVersion,
74    /// GIC v2m MSI frame, if MSIs via v2m are supported.
75    pub gic_v2m: Option<GicV2mInfo>,
76    /// Performance Monitor Unit GSIV (GIC INTID). `None` if not available.
77    pub pmu_gsiv: Option<u32>,
78    /// Virtual timer PPI (GIC INTID, e.g. 20 for PPI 4).
79    pub virt_timer_ppi: u32,
80    /// Total number of GIC interrupts (SGIs + PPIs + SPIs).
81    ///
82    /// KVM requires: `64 <= gic_nr_irqs <= 1023` and a multiple of 32.
83    /// The maximum valid value is 992 (31 × 32).
84    pub gic_nr_irqs: u32,
85}
86
87/// GIC v2m MSI frame parameters.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
90pub struct GicV2mInfo {
91    /// Physical base address of the v2m MSI frame.
92    #[cfg_attr(feature = "inspect", inspect(hex))]
93    pub frame_base: u64,
94    /// First GIC interrupt ID in the SPI range owned by this frame.
95    pub spi_base: u32,
96    /// Number of SPIs owned by this frame.
97    pub spi_count: u32,
98}
99
100/// ARM64 specific VP info.
101#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
102#[derive(Debug, Copy, Clone)]
103pub struct Aarch64VpInfo {
104    /// The base info.
105    #[cfg_attr(feature = "inspect", inspect(flatten))]
106    pub base: VpInfo,
107    /// The MPIDR_EL1 value of the processor.
108    #[cfg_attr(feature = "inspect", inspect(hex, with = "|&x| u64::from(x)"))]
109    pub mpidr: MpidrEl1,
110    /// GIC Redistributor Address (GICv3 only; `None` for GICv2).
111    #[cfg_attr(feature = "inspect", inspect(hex))]
112    pub gicr: Option<u64>,
113    /// Performance Interrupt GSIV (PMU)
114    #[cfg_attr(feature = "inspect", inspect(hex))]
115    pub pmu_gsiv: Option<u32>,
116}
117
118impl AsRef<VpInfo> for Aarch64VpInfo {
119    fn as_ref(&self) -> &VpInfo {
120        &self.base
121    }
122}
123
124impl TopologyBuilder<Aarch64Topology> {
125    /// Returns a builder for creating an aarch64 processor topology.
126    pub fn new_aarch64(platform: Aarch64PlatformConfig) -> Self {
127        Self {
128            vps_per_socket: 1,
129            smt_enabled: false,
130            arch: Aarch64TopologyBuilderState { platform },
131        }
132    }
133
134    /// Builds a processor topology with `proc_count` processors.
135    pub fn build(
136        &self,
137        proc_count: u32,
138    ) -> Result<ProcessorTopology<Aarch64Topology>, InvalidTopology> {
139        if proc_count >= 256 {
140            return Err(InvalidTopology::TooManyVps {
141                requested: proc_count,
142                max: u8::MAX.into(),
143            });
144        }
145        if let GicVersion::V2 { .. } = self.arch.platform.gic_version {
146            if proc_count > 8 {
147                return Err(InvalidTopology::TooManyCpusForGicV2(proc_count));
148            }
149        }
150        if !(16..32).contains(&self.arch.platform.virt_timer_ppi) {
151            return Err(InvalidTopology::InvalidPpiIntid(
152                self.arch.platform.virt_timer_ppi,
153            ));
154        }
155        if let Some(gsiv) = self.arch.platform.pmu_gsiv {
156            if !(16..32).contains(&gsiv) {
157                return Err(InvalidTopology::InvalidPpiIntid(gsiv));
158            }
159        }
160        let nr = self.arch.platform.gic_nr_irqs;
161        if !(64..=992).contains(&nr) || !nr.is_multiple_of(32) {
162            return Err(InvalidTopology::InvalidGicNrIrqs(nr));
163        }
164        let mpidrs = (0..proc_count).map(|vp_index| {
165            // TODO: construct mpidr appropriately for the specified
166            // topology.
167            let uni_proc = proc_count == 1;
168            let mut aff = (0..4).map(|i| (vp_index >> (8 * i)) as u8);
169            MpidrEl1::new()
170                .with_res1_31(true)
171                .with_u(uni_proc)
172                .with_aff0(aff.next().unwrap())
173                .with_aff1(aff.next().unwrap())
174                .with_aff2(aff.next().unwrap())
175                .with_aff3(aff.next().unwrap())
176        });
177        let gic_version = self.arch.platform.gic_version;
178        self.build_with_vp_info(mpidrs.enumerate().map(move |(id, mpidr)| {
179            // GICv3 assigns a per-VP redistributor region; GICv2 has no
180            // redistributors so the field is zero.
181            let gicr = match gic_version {
182                GicVersion::V3 {
183                    redistributors_base,
184                } => Some(redistributors_base + id as u64 * aarch64defs::GIC_REDISTRIBUTOR_SIZE),
185                GicVersion::V2 { .. } => None,
186            };
187            Aarch64VpInfo {
188                base: VpInfo {
189                    vp_index: VpIndex::new(id as u32),
190                    vnode: 0,
191                },
192                mpidr,
193                gicr,
194                pmu_gsiv: self.arch.platform.pmu_gsiv,
195            }
196        }))
197    }
198
199    /// Builds a processor topology with processors with the specified information.
200    pub fn build_with_vp_info(
201        &self,
202        vps: impl IntoIterator<Item = Aarch64VpInfo>,
203    ) -> Result<ProcessorTopology<Aarch64Topology>, InvalidTopology> {
204        let vps = Vec::from_iter(vps);
205        let mut smt_enabled = false;
206        for (i, vp) in vps.iter().enumerate() {
207            if i != vp.base.vp_index.index() as usize {
208                return Err(InvalidTopology::InvalidVpIndices);
209            }
210
211            if vp.mpidr.mt() {
212                smt_enabled = true;
213            }
214        }
215
216        Ok(ProcessorTopology {
217            vps,
218            smt_enabled,
219            vps_per_socket: self.vps_per_socket,
220            arch: Aarch64Topology {
221                platform: self.arch.platform,
222            },
223        })
224    }
225}
226
227impl ProcessorTopology<Aarch64Topology> {
228    /// Returns the GIC version and version-specific addresses.
229    pub fn gic_version(&self) -> GicVersion {
230        self.arch.platform.gic_version
231    }
232
233    /// Returns the GIC distributor base
234    pub fn gic_distributor_base(&self) -> u64 {
235        self.arch.platform.gic_distributor_base
236    }
237
238    /// Returns the PMU GSIV
239    pub fn pmu_gsiv(&self) -> Option<u32> {
240        self.arch.platform.pmu_gsiv
241    }
242
243    /// Returns the GIC v2m MSI frame info, if present.
244    pub fn gic_v2m(&self) -> Option<GicV2mInfo> {
245        self.arch.platform.gic_v2m
246    }
247
248    /// Returns the virtual timer PPI (GIC INTID).
249    pub fn virt_timer_ppi(&self) -> u32 {
250        self.arch.platform.virt_timer_ppi
251    }
252
253    /// Returns the total number of GIC interrupts to configure.
254    pub fn gic_nr_irqs(&self) -> u32 {
255        self.arch.platform.gic_nr_irqs
256    }
257}