Skip to main content

squib_arch/
layout.rs

1//! aarch64 memory layout — pinned constants for squib's microvm.
2//!
3//! The fixed layout is sized for the 32-vCPU worst case to defeat the GICR-vs-virtio-MMIO
4//! overlap that an earlier draft introduced (D22). The const-evaluated overlap-check at
5//! the bottom of this file ensures any future regression breaks the build, not the boot.
6//!
7//! See [13-arch-and-boot.md § 2](../../../specs/13-arch-and-boot.md#2-memory-layout-concrete)
8//! and [99-key-decisions.md § D22](../../../specs/99-key-decisions.md#d22).
9
10use squib_core::MAX_SUPPORTED_VCPUS;
11
12/// MSI region base. HVF's GIC interception only kicks in once
13/// distributor + redistributor + MSI are all configured (skipping MSI
14/// causes HVF to silently treat GIC accesses as unmapped, which
15/// surfaces as data aborts to the host). We park MSI well below the
16/// GICD at a 16 MiB-aligned address. The MSI region itself is sized
17/// by `hv_gic_get_msi_region_size` (typically 64 KiB on macOS 15+);
18/// reserve 16 MiB of headroom in case Apple grows it.
19pub const MSI_REGION_BASE: u64 = 0x0500_0000;
20
21/// GIC distributor base address.
22pub const GICD_BASE: u64 = 0x0800_0000;
23
24/// Maximum GICD region we reserve. The live size is queried from
25/// `hv_gic_get_distributor_size`; this is the placement-only cap.
26pub const GICD_RESERVED_SIZE: u64 = 0x0001_0000;
27
28/// GIC redistributor window base.
29///
30/// The live region is `[GICR_BASE, GICR_BASE + vcpu_count *
31/// hv_gic_get_redistributor_size)`. The reserved window is sized for the worst case
32/// (`MAX_SUPPORTED_VCPUS` × 128 KiB = 4 MiB) plus headroom up to `PL011_BASE`.
33pub const GICR_BASE: u64 = 0x080A_0000;
34
35/// End of the GICR reservation window. The live GICR live region must not exceed this.
36pub const GICR_RESERVED_END: u64 = 0x0E0A_0000;
37
38/// PL011 UART base. FDT SPI cell 1 → INTID 33, level-high.
39pub const PL011_BASE: u64 = 0x0E0A_0000;
40
41/// PL011 UART region size.
42pub const PL011_SIZE: u64 = 0x1000;
43
44/// virtio-MMIO region base.
45///
46/// 32 × 4 KiB slots. FDT SPI cells 16..47 → raw INTIDs 48..79 (edge-rising).
47pub const VIRTIO_MMIO_BASE: u64 = 0x0F00_0000;
48
49/// Total virtio-MMIO region size: 32 slots × 4 KiB.
50pub const VIRTIO_MMIO_SIZE: u64 = 32 * 0x1000;
51
52/// First DRAM byte. Matches Firecracker's `DRAM_MEM_START`.
53pub const DRAM_BASE: u64 = 0x8000_0000;
54
55/// Maximum DRAM extent — `0x00FF_8000_0000` matches upstream `DRAM_MEM_MAX_SIZE` of 1022 GiB
56/// above `DRAM_BASE`.
57pub const DRAM_MAX_END: u64 = 0x00FF_8000_0000 + DRAM_BASE;
58
59/// Kernel image is loaded at `DRAM_BASE + KERNEL_LOAD_OFFSET`. Matches Firecracker's reserved
60/// 2 MiB for system metadata at the head of DRAM.
61pub const KERNEL_LOAD_OFFSET: u64 = 0x0020_0000;
62
63/// Initrd start offset: `DRAM_BASE + 256 MiB`, or `kernel_end + 16 MiB` rounded up to the
64/// 2 MiB grain — whichever is larger. The constant here is the lower bound; the builder
65/// computes the actual offset against the kernel size.
66pub const INITRD_FALLBACK_OFFSET: u64 = 0x1000_0000;
67
68/// FDT region size — at most 2 MiB per `arm64/booting.rst`.
69pub const FDT_MAX_SIZE: u64 = 0x0020_0000;
70
71/// Worst-case GICR live size (`MAX_SUPPORTED_VCPUS × 128 KiB`).
72///
73/// The actual live size on the host is `vcpu_count × hv_gic_get_redistributor_size`,
74/// which Apple may grow in a future macOS release. This constant is the floor that the
75/// reserved layout window must accommodate.
76pub const GICR_REDISTRIBUTOR_SIZE_PER_VCPU: u64 = 0x0002_0000;
77const _GICR_LIVE_MAX: u64 = (MAX_SUPPORTED_VCPUS as u64) * GICR_REDISTRIBUTOR_SIZE_PER_VCPU;
78
79// === Compile-time overlap check (I-AB-6 in 13 § 10) ===
80//
81// Any future revert that re-introduces the GICR/virtio overlap from the original
82// draft breaks the build, not the boot. Three independent assertions:
83//   1. The live GICR for 32 vCPUs fits inside the reserved [GICR_BASE, GICR_RESERVED_END).
84//   2. PL011 sits exactly at GICR_RESERVED_END (no slack we'd silently lose).
85//   3. virtio-MMIO sits above PL011 + PL011_SIZE.
86const _: () = {
87    assert!(
88        GICR_BASE + _GICR_LIVE_MAX <= GICR_RESERVED_END,
89        "GICR live region for 32 vCPUs overflows the reserved GICR window (D22)"
90    );
91    assert!(
92        PL011_BASE == GICR_RESERVED_END,
93        "PL011 must abut the GICR reservation (D22)"
94    );
95    assert!(
96        VIRTIO_MMIO_BASE >= PL011_BASE + PL011_SIZE,
97        "virtio-MMIO base must sit above PL011 (D22 — overlap regression)"
98    );
99    assert!(
100        VIRTIO_MMIO_BASE + VIRTIO_MMIO_SIZE <= DRAM_BASE,
101        "virtio-MMIO region overlaps DRAM (D22 — overlap regression)"
102    );
103};
104
105/// Page geometry constants — three sizes that must never be conflated.
106///
107/// On Apple Silicon the host page is 16 KiB, the HVF stage-2 granule is 16 KiB, and the
108/// dirty-tracking page defaults to 2 MiB with adaptive step-down to 16 KiB for hot regions.
109/// See [99-key-decisions.md § D21](../../../specs/99-key-decisions.md#d21).
110#[derive(Debug, Clone, Copy)]
111pub struct PageGeometry {
112    /// Apple Silicon host page (16 KiB) — `vm_page_size` returns this.
113    pub host_page: u64,
114    /// HVF stage-2 translation granule (16 KiB on Apple Silicon).
115    pub hvf_stage2_granule: u64,
116    /// Default tracking-page granularity for dirty-page bitmaps (2 MiB).
117    pub tracking_page_default: u64,
118    /// Step-down tracking page used by the adaptive heuristic for hot regions (16 KiB).
119    pub tracking_page_hot: u64,
120}
121
122impl PageGeometry {
123    /// The Apple-Silicon page geometry. macOS 15+ on M-series.
124    pub const APPLE_SILICON: Self = Self {
125        host_page: 16 * 1024,
126        hvf_stage2_granule: 16 * 1024,
127        tracking_page_default: 2 * 1024 * 1024,
128        tracking_page_hot: 16 * 1024,
129    };
130}
131
132/// Frozen memory layout for a microvm.
133#[derive(Debug, Clone, Copy)]
134pub struct MemoryLayout {
135    /// GIC distributor base.
136    pub gicd_base: u64,
137    /// GIC redistributor window base.
138    pub gicr_base: u64,
139    /// PL011 UART base.
140    pub pl011_base: u64,
141    /// virtio-MMIO region base (slot 0).
142    pub virtio_mmio_base: u64,
143    /// virtio-MMIO slot stride (4 KiB).
144    pub virtio_mmio_stride: u64,
145    /// Number of virtio-MMIO slots reserved.
146    pub virtio_mmio_slots: u32,
147    /// DRAM start.
148    pub dram_base: u64,
149    /// Page geometry.
150    pub page_geometry: PageGeometry,
151}
152
153/// Canonical squib memory layout. There is exactly one — D22 is "fixed".
154pub const MEMORY_LAYOUT: MemoryLayout = MemoryLayout {
155    gicd_base: GICD_BASE,
156    gicr_base: GICR_BASE,
157    pl011_base: PL011_BASE,
158    virtio_mmio_base: VIRTIO_MMIO_BASE,
159    virtio_mmio_stride: 0x1000,
160    virtio_mmio_slots: 32,
161    dram_base: DRAM_BASE,
162    page_geometry: PageGeometry::APPLE_SILICON,
163};
164
165/// Result of [`overlap_check`].
166#[derive(Debug, Clone, Copy, Eq, PartialEq)]
167pub enum LayoutOverlap {
168    /// The live GICR region fits within the reserved window for the given vCPU count.
169    Ok,
170    /// The live GICR region for `vcpu_count` would overlap the PL011/virtio-MMIO band.
171    /// `live_gicr_end` is `GICR_BASE + vcpu_count × redistributor_size_per_vcpu`.
172    GicrOverlapsMmio {
173        /// vCPU count that triggered the overlap.
174        vcpu_count: u32,
175        /// Computed live GICR end address.
176        live_gicr_end: u64,
177        /// PL011 base (the boundary the live GICR must not cross).
178        boundary: u64,
179    },
180}
181
182/// Runtime layout overlap check used by `squib-vmm::builder` before `hv_gic_create`.
183///
184/// Pairs with the const overlap-check above. The const-check is an absolute floor
185/// (32 vCPUs × the constant 128 KiB redistributor stride); the runtime check uses the
186/// host-reported `redistributor_size_per_vcpu`, since Apple may bump the size in a future
187/// macOS release.
188///
189/// # Errors
190/// Surfaces a structured [`LayoutOverlap::GicrOverlapsMmio`] result if the layout cannot
191/// fit `vcpu_count` redistributors in the reserved GICR window. This must be surfaced as
192/// `Error::Config` to the API layer; the VMM must not call `hv_gic_create` after a
193/// non-OK result.
194#[must_use]
195pub fn overlap_check(vcpu_count: u32, redistributor_size_per_vcpu: u64) -> LayoutOverlap {
196    let live_end =
197        GICR_BASE.saturating_add(u64::from(vcpu_count).saturating_mul(redistributor_size_per_vcpu));
198    if live_end > PL011_BASE {
199        LayoutOverlap::GicrOverlapsMmio {
200            vcpu_count,
201            live_gicr_end: live_end,
202            boundary: PL011_BASE,
203        }
204    } else {
205        LayoutOverlap::Ok
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn d22_reserved_window_has_correct_size() {
215        // 96 MiB — the headroom the spec calls out.
216        assert_eq!(GICR_RESERVED_END - GICR_BASE, 96 * 1024 * 1024);
217    }
218
219    #[test]
220    fn worst_case_gicr_fits_under_pl011() {
221        // 32 vCPUs × 128 KiB = 4 MiB, well under the 96 MiB reservation.
222        const { assert!(_GICR_LIVE_MAX < GICR_RESERVED_END - GICR_BASE) };
223    }
224
225    #[test]
226    fn pl011_abuts_gicr_window() {
227        assert_eq!(PL011_BASE, GICR_RESERVED_END);
228    }
229
230    #[test]
231    fn virtio_mmio_does_not_overlap_pl011() {
232        const { assert!(VIRTIO_MMIO_BASE >= PL011_BASE + PL011_SIZE) };
233    }
234
235    #[test]
236    fn virtio_mmio_does_not_overlap_dram() {
237        const { assert!(VIRTIO_MMIO_BASE + VIRTIO_MMIO_SIZE <= DRAM_BASE) };
238    }
239
240    #[test]
241    fn page_geometry_apple_silicon_matches_d21() {
242        let p = PageGeometry::APPLE_SILICON;
243        assert_eq!(p.host_page, 16 * 1024);
244        assert_eq!(p.hvf_stage2_granule, 16 * 1024);
245        assert_eq!(p.tracking_page_default, 2 * 1024 * 1024);
246        assert_eq!(p.tracking_page_hot, 16 * 1024);
247    }
248
249    #[test]
250    fn overlap_check_passes_at_max_vcpus() {
251        assert_eq!(
252            overlap_check(MAX_SUPPORTED_VCPUS, 0x0002_0000),
253            LayoutOverlap::Ok
254        );
255    }
256
257    #[test]
258    fn overlap_check_fails_if_apple_grows_redistributor_excessively() {
259        // Hypothetical: Apple bumps redistributor stride to 4 MiB. 32 × 4 MiB = 128 MiB
260        // > 96 MiB reservation, so the runtime check must reject before hv_gic_create.
261        let result = overlap_check(MAX_SUPPORTED_VCPUS, 4 * 1024 * 1024);
262        assert!(matches!(result, LayoutOverlap::GicrOverlapsMmio { .. }));
263    }
264
265    #[test]
266    fn overlap_check_zero_vcpus_is_trivially_ok() {
267        assert_eq!(overlap_check(0, 0x0002_0000), LayoutOverlap::Ok);
268    }
269
270    #[test]
271    fn overlap_check_does_not_overflow_on_huge_redistributor_size() {
272        // Saturating arithmetic must not panic on adversarial inputs.
273        let result = overlap_check(MAX_SUPPORTED_VCPUS, u64::MAX);
274        assert!(matches!(result, LayoutOverlap::GicrOverlapsMmio { .. }));
275    }
276}