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    /// MSI controller for PCIe interrupt delivery.
75    pub gic_msi: GicMsiController,
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 guest-visible v2m MSI frame.
92    #[cfg_attr(feature = "inspect", inspect(hex))]
93    pub frame_base: u64,
94    /// Physical base address of the v2m MSI doorbell registered with the
95    /// hypervisor for PCI passthrough (GITS translater base). May differ from
96    /// `frame_base`; consumed only by the MSHV root/arm64 backend.
97    #[cfg_attr(feature = "inspect", inspect(hex))]
98    pub doorbell_base: u64,
99    /// First GIC interrupt ID in the SPI range owned by this frame.
100    pub spi_base: u32,
101    /// Number of SPIs owned by this frame.
102    pub spi_count: u32,
103}
104
105/// GICv3 ITS (Interrupt Translation Service) parameters.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
108pub struct GicItsInfo {
109    /// Physical base address of the ITS MMIO region (must be 64 KiB aligned).
110    #[cfg_attr(feature = "inspect", inspect(hex))]
111    pub its_base: u64,
112}
113
114/// MSI controller configuration for PCIe interrupt delivery.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
117#[cfg_attr(feature = "inspect", inspect(external_tag))]
118pub enum GicMsiController {
119    /// No MSI controller configured.
120    None,
121    /// GICv2m — maps MSI writes to a fixed pool of SPIs.
122    V2m(GicV2mInfo),
123    /// GICv3 ITS — routes MSIs via LPIs using (DeviceID, EventID) lookup.
124    Its(GicItsInfo),
125}
126
127/// ARM64 specific VP info.
128#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
129#[derive(Debug, Copy, Clone)]
130pub struct Aarch64VpInfo {
131    /// The base info.
132    #[cfg_attr(feature = "inspect", inspect(flatten))]
133    pub base: VpInfo,
134    /// The MPIDR_EL1 value of the processor.
135    #[cfg_attr(feature = "inspect", inspect(hex, with = "|&x| u64::from(x)"))]
136    pub mpidr: MpidrEl1,
137    /// GIC Redistributor Address (GICv3 only; `None` for GICv2).
138    #[cfg_attr(feature = "inspect", inspect(hex))]
139    pub gicr: Option<u64>,
140    /// Performance Interrupt GSIV (PMU)
141    #[cfg_attr(feature = "inspect", inspect(hex))]
142    pub pmu_gsiv: Option<u32>,
143}
144
145impl AsRef<VpInfo> for Aarch64VpInfo {
146    fn as_ref(&self) -> &VpInfo {
147        &self.base
148    }
149}
150
151impl AsMut<VpInfo> for Aarch64VpInfo {
152    fn as_mut(&mut self) -> &mut VpInfo {
153        &mut self.base
154    }
155}
156
157impl TopologyBuilder<Aarch64Topology> {
158    /// Returns a builder for creating an aarch64 processor topology.
159    pub fn new_aarch64(platform: Aarch64PlatformConfig) -> Self {
160        Self {
161            vps_per_socket: 1,
162            smt_enabled: false,
163            arch: Aarch64TopologyBuilderState { platform },
164        }
165    }
166
167    /// Builds a processor topology with `proc_count` processors.
168    pub fn build(
169        &self,
170        proc_count: u32,
171    ) -> Result<ProcessorTopology<Aarch64Topology>, InvalidTopology> {
172        if proc_count >= 256 {
173            return Err(InvalidTopology::TooManyVps {
174                requested: proc_count,
175                max: u8::MAX.into(),
176            });
177        }
178        if let GicVersion::V2 { .. } = self.arch.platform.gic_version {
179            if proc_count > 8 {
180                return Err(InvalidTopology::TooManyCpusForGicV2(proc_count));
181            }
182        }
183        if !(16..32).contains(&self.arch.platform.virt_timer_ppi) {
184            return Err(InvalidTopology::InvalidPpiIntid(
185                self.arch.platform.virt_timer_ppi,
186            ));
187        }
188        if let Some(gsiv) = self.arch.platform.pmu_gsiv {
189            if !(16..32).contains(&gsiv) {
190                return Err(InvalidTopology::InvalidPpiIntid(gsiv));
191            }
192        }
193        let nr = self.arch.platform.gic_nr_irqs;
194        if !(64..=992).contains(&nr) || !nr.is_multiple_of(32) {
195            return Err(InvalidTopology::InvalidGicNrIrqs(nr));
196        }
197        let mpidrs = (0..proc_count).map(|vp_index| {
198            // TODO: construct mpidr appropriately for the specified
199            // topology.
200            let uni_proc = proc_count == 1;
201            let mut aff = (0..4).map(|i| (vp_index >> (8 * i)) as u8);
202            MpidrEl1::new()
203                .with_res1_31(true)
204                .with_u(uni_proc)
205                .with_aff0(aff.next().unwrap())
206                .with_aff1(aff.next().unwrap())
207                .with_aff2(aff.next().unwrap())
208                .with_aff3(aff.next().unwrap())
209        });
210        let gic_version = self.arch.platform.gic_version;
211        self.build_with_vp_info(mpidrs.enumerate().map(move |(id, mpidr)| {
212            // GICv3 assigns a per-VP redistributor region; GICv2 has no
213            // redistributors so the field is zero.
214            let gicr = match gic_version {
215                GicVersion::V3 {
216                    redistributors_base,
217                } => Some(redistributors_base + id as u64 * aarch64defs::GIC_REDISTRIBUTOR_SIZE),
218                GicVersion::V2 { .. } => None,
219            };
220            Aarch64VpInfo {
221                base: VpInfo {
222                    vp_index: VpIndex::new(id as u32),
223                    vnode: 0,
224                },
225                mpidr,
226                gicr,
227                pmu_gsiv: self.arch.platform.pmu_gsiv,
228            }
229        }))
230    }
231
232    /// Builds a processor topology with processors with the specified information.
233    pub fn build_with_vp_info(
234        &self,
235        vps: impl IntoIterator<Item = Aarch64VpInfo>,
236    ) -> Result<ProcessorTopology<Aarch64Topology>, InvalidTopology> {
237        let vps = Vec::from_iter(vps);
238        let mut smt_enabled = false;
239        for (i, vp) in vps.iter().enumerate() {
240            if i != vp.base.vp_index.index() as usize {
241                return Err(InvalidTopology::InvalidVpIndices);
242            }
243
244            if vp.mpidr.mt() {
245                smt_enabled = true;
246            }
247        }
248
249        Ok(ProcessorTopology {
250            vps,
251            smt_enabled,
252            vps_per_socket: self.vps_per_socket,
253            arch: Aarch64Topology {
254                platform: self.arch.platform,
255            },
256        })
257    }
258}
259
260impl ProcessorTopology<Aarch64Topology> {
261    /// Returns the GIC version and version-specific addresses.
262    pub fn gic_version(&self) -> GicVersion {
263        self.arch.platform.gic_version
264    }
265
266    /// Returns the GIC distributor base
267    pub fn gic_distributor_base(&self) -> u64 {
268        self.arch.platform.gic_distributor_base
269    }
270
271    /// Returns the PMU GSIV
272    pub fn pmu_gsiv(&self) -> Option<u32> {
273        self.arch.platform.pmu_gsiv
274    }
275
276    /// Returns the MSI controller configuration.
277    pub fn gic_msi(&self) -> GicMsiController {
278        self.arch.platform.gic_msi
279    }
280
281    /// Returns the virtual timer PPI (GIC INTID).
282    pub fn virt_timer_ppi(&self) -> u32 {
283        self.arch.platform.virt_timer_ppi
284    }
285
286    /// Returns the total number of GIC interrupts to configure.
287    pub fn gic_nr_irqs(&self) -> u32 {
288        self.arch.platform.gic_nr_irqs
289    }
290}