Skip to content

Commit 653eb3a

Browse files
committed
arch: define 64-bit capable MMIO memory regions
PCIe distinguishes MMIO regions between 32bit and 64bit, caring for devices that can't deal with 64-bit addresses. This commit defines the appropriate regions for both x86 and aarch64 architectures, extends the resource allocator to handle allocations for both of these regions and adjusts the logic that calculates the memory regions for the architecture. Signed-off-by: Babis Chalios <[email protected]>
1 parent f61d2a3 commit 653eb3a

File tree

13 files changed

+408
-225
lines changed

13 files changed

+408
-225
lines changed

src/vmm/src/arch/aarch64/layout.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,46 @@ pub const IRQ_BASE: u32 = 32;
8282

8383
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
8484
pub const MAPPED_IO_START: u64 = 1 << 30; // 1 GB
85+
86+
/// The start of the memory area reserved for MMIO 32-bit accesses.
87+
pub const MMIO32_MEM_START: u64 = MAPPED_IO_START;
88+
/// The size of the memory area reserved for MMIO 32-bit accesses (1GiB).
89+
pub const MMIO32_MEM_SIZE: u64 = DRAM_MEM_START - MMIO32_MEM_START;
90+
91+
// The rest of the MMIO address space (256 MiB) we dedicate to PCIe for memory-mapped access to
92+
// configuration.
93+
/// Size of MMIO region for PCIe configuration accesses.
94+
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
95+
/// Start of MMIO region for PCIe configuration accesses.
96+
pub const PCI_MMCONFIG_START: u64 = DRAM_MEM_START - PCI_MMCONFIG_SIZE;
97+
/// MMIO space per PCIe segment
98+
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
99+
100+
// We reserve 768 MiB for devices at the beginning of the MMIO region. This includes space both for
101+
// pure MMIO and PCIe devices.
102+
103+
/// Memory region start for boot device.
104+
pub const BOOT_DEVICE_MEM_START: u64 = MMIO32_MEM_START;
105+
/// Memory region start for RTC device.
106+
pub const RTC_MEM_START: u64 = BOOT_DEVICE_MEM_START + 0x1000;
107+
/// Memory region start for Serial device.
108+
pub const SERIAL_MEM_START: u64 = RTC_MEM_START + 0x1000;
109+
110+
/// Beginning of memory region for device MMIO 32-bit accesses
111+
pub const MEM_32BIT_DEVICES_START: u64 = SERIAL_MEM_START + 0x1000;
112+
/// Size of memory region for device MMIO 32-bit accesses
113+
pub const MEM_32BIT_DEVICES_SIZE: u64 = PCI_MMCONFIG_START - MEM_32BIT_DEVICES_START;
114+
115+
// 64-bits region for MMIO accesses
116+
/// The start of the memory area reserved for MMIO 64-bit accesses.
117+
pub const MMIO64_MEM_START: u64 = 256 << 30;
118+
/// The size of the memory area reserved for MMIO 64-bit accesses.
119+
pub const MMIO64_MEM_SIZE: u64 = 256 << 30;
120+
121+
// At the moment, all of this region goes to devices
122+
/// Beginning of memory region for device MMIO 64-bit accesses
123+
pub const MEM_64BIT_DEVICES_START: u64 = MMIO64_MEM_START;
124+
/// Size of memory region for device MMIO 32-bit accesses
125+
pub const MEM_64BIT_DEVICES_SIZE: u64 = MMIO64_MEM_SIZE;
126+
/// First address past the 64-bit MMIO gap
127+
pub const FIRST_ADDR_PAST_64BITS_MMIO: u64 = MMIO64_MEM_START + MMIO64_MEM_SIZE;

src/vmm/src/arch/aarch64/mod.rs

Lines changed: 86 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ use linux_loader::loader::pe::PE as Loader;
2424
use linux_loader::loader::{Cmdline, KernelLoader};
2525
use vm_memory::GuestMemoryError;
2626

27-
use crate::arch::{BootProtocol, EntryPoint};
27+
use crate::arch::{BootProtocol, EntryPoint, arch_memory_regions_with_gap};
2828
use crate::cpu_config::aarch64::{CpuConfiguration, CpuConfigurationError};
2929
use crate::cpu_config::templates::CustomCpuTemplate;
3030
use crate::initrd::InitrdConfig;
31-
use crate::utils::{align_up, usize_to_u64};
31+
use crate::utils::{align_up, u64_to_usize, usize_to_u64};
3232
use crate::vmm_config::machine_config::MachineConfig;
3333
use crate::vstate::memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
3434
use crate::vstate::vcpu::KvmVcpuError;
@@ -51,42 +51,34 @@ pub enum ConfigurationError {
5151
VcpuConfigure(#[from] KvmVcpuError),
5252
}
5353

54-
/// The start of the memory area reserved for MMIO devices.
55-
pub const MMIO_MEM_START: u64 = layout::MAPPED_IO_START;
56-
/// The size of the memory area reserved for MMIO devices.
57-
pub const MMIO_MEM_SIZE: u64 = layout::DRAM_MEM_START - layout::MAPPED_IO_START; //>> 1GB
58-
5954
/// Returns a Vec of the valid memory addresses for aarch64.
6055
/// See [`layout`](layout) module for a drawing of the specific memory model for this platform.
61-
///
62-
/// The `offset` parameter specified the offset from [`layout::DRAM_MEM_START`].
63-
pub fn arch_memory_regions(offset: usize, size: usize) -> Vec<(GuestAddress, usize)> {
56+
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize)> {
6457
assert!(size > 0, "Attempt to allocate guest memory of length 0");
65-
assert!(
66-
offset.checked_add(size).is_some(),
67-
"Attempt to allocate guest memory such that the address space would wrap around"
68-
);
69-
assert!(
70-
offset < layout::DRAM_MEM_MAX_SIZE,
71-
"offset outside allowed DRAM range"
72-
);
7358

74-
let dram_size = min(size, layout::DRAM_MEM_MAX_SIZE - offset);
59+
let dram_size = min(size, layout::DRAM_MEM_MAX_SIZE);
7560

7661
if dram_size != size {
7762
logger::warn!(
78-
"Requested offset/memory size {}/{} exceeds architectural maximum (1022GiB). Size has \
79-
been truncated to {}",
80-
offset,
63+
"Requested memory size {} exceeds architectural maximum (1022GiB). Size has been \
64+
truncated to {}",
8165
size,
8266
dram_size
8367
);
8468
}
8569

86-
vec![(
87-
GuestAddress(layout::DRAM_MEM_START + offset as u64),
70+
let mut regions = vec![];
71+
if let Some((offset, remaining)) = arch_memory_regions_with_gap(
72+
&mut regions,
73+
u64_to_usize(layout::DRAM_MEM_START),
8874
dram_size,
89-
)]
75+
u64_to_usize(layout::MMIO64_MEM_START),
76+
u64_to_usize(layout::MMIO64_MEM_SIZE),
77+
) {
78+
regions.push((GuestAddress(offset as u64), remaining));
79+
}
80+
81+
regions
9082
}
9183

9284
/// Configures the system for booting Linux.
@@ -211,73 +203,109 @@ pub fn load_kernel(
211203

212204
#[cfg(kani)]
213205
mod verification {
214-
use vm_memory::GuestAddress;
215-
216-
use crate::arch::aarch64::layout;
206+
use crate::arch::aarch64::layout::{
207+
DRAM_MEM_MAX_SIZE, DRAM_MEM_START, FIRST_ADDR_PAST_64BITS_MMIO, MMIO64_MEM_START,
208+
};
217209
use crate::arch::arch_memory_regions;
218210

219211
#[kani::proof]
220212
#[kani::unwind(3)]
221213
fn verify_arch_memory_regions() {
222-
let offset: u64 = kani::any::<u64>();
223-
let len: u64 = kani::any::<u64>();
224-
214+
let len: usize = kani::any::<usize>();
225215
kani::assume(len > 0);
226-
kani::assume(offset.checked_add(len).is_some());
227-
kani::assume(offset < layout::DRAM_MEM_MAX_SIZE as u64);
228216

229-
let regions = arch_memory_regions(offset as usize, len as usize);
217+
let regions = arch_memory_regions(len);
230218

231-
// No MMIO gap on ARM
232-
assert_eq!(regions.len(), 1);
219+
for region in &regions {
220+
println!(
221+
"region: [{:x}:{:x})",
222+
region.0.0,
223+
region.0.0 + region.1 as u64
224+
);
225+
}
233226

234-
let (GuestAddress(start), actual_len) = regions[0];
235-
let actual_len = actual_len as u64;
227+
// On Arm we have one MMIO gap that might fall within addressable ranges,
228+
// so we can get either 1 or 2 regions.
229+
assert!(regions.len() >= 1);
230+
assert!(regions.len() <= 2);
236231

237-
assert_eq!(start, layout::DRAM_MEM_START + offset);
238-
assert!(actual_len <= layout::DRAM_MEM_MAX_SIZE as u64);
232+
// The total length of all regions cannot exceed DRAM_MEM_MAX_SIZE
233+
let actual_len = regions.iter().map(|&(_, len)| len).sum::<usize>();
234+
assert!(actual_len <= DRAM_MEM_MAX_SIZE);
235+
// The total length is smaller or equal to the length we asked
239236
assert!(actual_len <= len);
237+
// If it's smaller, it's because we asked more than the the maximum possible.
238+
if (actual_len) < len {
239+
assert!(len > DRAM_MEM_MAX_SIZE);
240+
}
240241

241-
if actual_len < len {
242-
assert_eq!(
243-
start + actual_len,
244-
layout::DRAM_MEM_START + layout::DRAM_MEM_MAX_SIZE as u64
245-
);
246-
assert!(offset + len >= layout::DRAM_MEM_MAX_SIZE as u64);
242+
// No region overlaps the 64-bit MMIO gap
243+
assert!(
244+
regions
245+
.iter()
246+
.all(|&(start, len)| start.0 >= FIRST_ADDR_PAST_64BITS_MMIO
247+
|| start.0 + len as u64 <= MMIO64_MEM_START)
248+
);
249+
250+
// All regions start after our DRAM_MEM_START
251+
assert!(regions.iter().all(|&(start, _)| start.0 >= DRAM_MEM_START));
252+
253+
// All regions have non-zero length
254+
assert!(regions.iter().all(|&(_, len)| len > 0));
255+
256+
// If there's two regions, they perfectly snuggle up the 64bit MMIO gap
257+
if regions.len() == 2 {
258+
kani::cover!();
259+
260+
// The very first address should be DRAM_MEM_START
261+
assert_eq!(regions[0].0.0, DRAM_MEM_START);
262+
// The first region ends at the beginning of the 64 bits gap.
263+
assert_eq!(regions[0].0.0 + regions[0].1 as u64, MMIO64_MEM_START);
264+
// The second region starts exactly after the 64 bits gap.
265+
assert_eq!(regions[1].0.0, FIRST_ADDR_PAST_64BITS_MMIO);
247266
}
248267
}
249268
}
250269

251270
#[cfg(test)]
252271
mod tests {
253272
use super::*;
273+
use crate::arch::aarch64::layout::{
274+
DRAM_MEM_MAX_SIZE, DRAM_MEM_START, FDT_MAX_SIZE, FIRST_ADDR_PAST_64BITS_MMIO,
275+
MMIO64_MEM_START,
276+
};
254277
use crate::test_utils::arch_mem;
255278

256279
#[test]
257280
fn test_regions_lt_1024gb() {
258-
let regions = arch_memory_regions(0, 1usize << 29);
281+
let regions = arch_memory_regions(1usize << 29);
259282
assert_eq!(1, regions.len());
260-
assert_eq!(GuestAddress(super::layout::DRAM_MEM_START), regions[0].0);
283+
assert_eq!(GuestAddress(DRAM_MEM_START), regions[0].0);
261284
assert_eq!(1usize << 29, regions[0].1);
262285
}
263286

264287
#[test]
265288
fn test_regions_gt_1024gb() {
266-
let regions = arch_memory_regions(0, 1usize << 41);
267-
assert_eq!(1, regions.len());
268-
assert_eq!(GuestAddress(super::layout::DRAM_MEM_START), regions[0].0);
269-
assert_eq!(super::layout::DRAM_MEM_MAX_SIZE, regions[0].1);
289+
let regions = arch_memory_regions(1usize << 41);
290+
assert_eq!(2, regions.len());
291+
assert_eq!(GuestAddress(DRAM_MEM_START), regions[0].0);
292+
assert_eq!(MMIO64_MEM_START - DRAM_MEM_START, regions[0].1 as u64);
293+
assert_eq!(GuestAddress(FIRST_ADDR_PAST_64BITS_MMIO), regions[1].0);
294+
assert_eq!(
295+
DRAM_MEM_MAX_SIZE as u64 - MMIO64_MEM_START + DRAM_MEM_START,
296+
regions[1].1 as u64
297+
);
270298
}
271299

272300
#[test]
273301
fn test_get_fdt_addr() {
274-
let mem = arch_mem(layout::FDT_MAX_SIZE - 0x1000);
275-
assert_eq!(get_fdt_addr(&mem), layout::DRAM_MEM_START);
302+
let mem = arch_mem(FDT_MAX_SIZE - 0x1000);
303+
assert_eq!(get_fdt_addr(&mem), DRAM_MEM_START);
276304

277-
let mem = arch_mem(layout::FDT_MAX_SIZE);
278-
assert_eq!(get_fdt_addr(&mem), layout::DRAM_MEM_START);
305+
let mem = arch_mem(FDT_MAX_SIZE);
306+
assert_eq!(get_fdt_addr(&mem), DRAM_MEM_START);
279307

280-
let mem = arch_mem(layout::FDT_MAX_SIZE + 0x1000);
281-
assert_eq!(get_fdt_addr(&mem), 0x1000 + layout::DRAM_MEM_START);
308+
let mem = arch_mem(FDT_MAX_SIZE + 0x1000);
309+
assert_eq!(get_fdt_addr(&mem), 0x1000 + DRAM_MEM_START);
282310
}
283311
}

src/vmm/src/arch/mod.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@ pub use aarch64::vcpu::*;
2020
pub use aarch64::vm::{ArchVm, ArchVmError, VmState};
2121
#[cfg(target_arch = "aarch64")]
2222
pub use aarch64::{
23-
ConfigurationError, MMIO_MEM_SIZE, MMIO_MEM_START, arch_memory_regions,
24-
configure_system_for_boot, get_kernel_start, initrd_load_addr, layout::CMDLINE_MAX_SIZE,
25-
layout::IRQ_BASE, layout::IRQ_MAX, layout::SYSTEM_MEM_SIZE, layout::SYSTEM_MEM_START,
26-
load_kernel,
23+
ConfigurationError, arch_memory_regions, configure_system_for_boot, get_kernel_start,
24+
initrd_load_addr, layout::BOOT_DEVICE_MEM_START, layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE,
25+
layout::IRQ_MAX, layout::MEM_32BIT_DEVICES_SIZE, layout::MEM_32BIT_DEVICES_START,
26+
layout::MEM_64BIT_DEVICES_SIZE, layout::MEM_64BIT_DEVICES_START, layout::MMIO32_MEM_SIZE,
27+
layout::MMIO32_MEM_START, layout::PCI_MMCONFIG_SIZE, layout::PCI_MMCONFIG_START,
28+
layout::PCI_MMIO_CONFIG_SIZE_PER_SEGMENT, layout::RTC_MEM_START, layout::SERIAL_MEM_START,
29+
layout::SYSTEM_MEM_SIZE, layout::SYSTEM_MEM_START, load_kernel,
2730
};
2831

2932
/// Module for x86_64 related functionality.
@@ -39,9 +42,11 @@ pub use x86_64::vm::{ArchVm, ArchVmError, VmState};
3942

4043
#[cfg(target_arch = "x86_64")]
4144
pub use crate::arch::x86_64::{
42-
ConfigurationError, MMIO_MEM_SIZE, MMIO_MEM_START, arch_memory_regions,
43-
configure_system_for_boot, get_kernel_start, initrd_load_addr, layout::APIC_ADDR,
44-
layout::CMDLINE_MAX_SIZE, layout::IOAPIC_ADDR, layout::IRQ_BASE, layout::IRQ_MAX,
45+
ConfigurationError, arch_memory_regions, configure_system_for_boot, get_kernel_start,
46+
initrd_load_addr, layout::APIC_ADDR, layout::BOOT_DEVICE_MEM_START, layout::CMDLINE_MAX_SIZE,
47+
layout::IOAPIC_ADDR, layout::IRQ_BASE, layout::IRQ_MAX, layout::MEM_32BIT_DEVICES_SIZE,
48+
layout::MEM_32BIT_DEVICES_START, layout::MEM_64BIT_DEVICES_SIZE,
49+
layout::MEM_64BIT_DEVICES_START, layout::MMIO32_MEM_SIZE, layout::MMIO32_MEM_START,
4550
layout::SYSTEM_MEM_SIZE, layout::SYSTEM_MEM_START, load_kernel,
4651
};
4752

@@ -114,3 +119,32 @@ pub struct EntryPoint {
114119
/// Specifies which boot protocol to use
115120
pub protocol: BootProtocol,
116121
}
122+
123+
/// Adds in [`regions`] the valid memory regions suitable for RAM taking into account a gap in the
124+
/// available address space and returns the remaining region (if any) past this gap
125+
fn arch_memory_regions_with_gap(
126+
regions: &mut Vec<(GuestAddress, usize)>,
127+
offset: usize,
128+
size: usize,
129+
gap_start: usize,
130+
gap_size: usize,
131+
) -> Option<(usize, usize)> {
132+
// 0-sized gaps don't really make sense. We should never receive such a gap.
133+
assert!(gap_size > 0);
134+
135+
let first_addr_past_gap = gap_start + gap_size;
136+
match (size + offset).checked_sub(gap_start) {
137+
// case0: region fits all before gap
138+
None | Some(0) => {
139+
regions.push((GuestAddress(offset as u64), size));
140+
None
141+
}
142+
// case1: region starts before the gap and goes past it
143+
Some(remaining) if offset < gap_start => {
144+
regions.push((GuestAddress(offset as u64), gap_start - offset));
145+
Some((first_addr_past_gap, remaining))
146+
}
147+
// case2: region starts past the gap
148+
Some(_) => Some((first_addr_past_gap.max(offset), size)),
149+
}
150+
}

src/vmm/src/arch/x86_64/layout.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
//! Magic addresses externally used to lay out x86_64 VMs.
99
10+
use crate::utils::mib_to_bytes;
11+
1012
/// Initial stack for the boot CPU.
1113
pub const BOOT_STACK_POINTER: u64 = 0x8ff0;
1214

@@ -77,3 +79,45 @@ pub const SYSTEM_MEM_START: u64 = 0x9fc00;
7779
/// 257KiB is more than we need, however we reserve this space for potential future use of
7880
/// ACPI features (new tables and/or devices).
7981
pub const SYSTEM_MEM_SIZE: u64 = RSDP_ADDR - SYSTEM_MEM_START;
82+
83+
/// First address that cannot be addressed using 32 bit anymore.
84+
pub const FIRST_ADDR_PAST_32BITS: u64 = 1 << 32;
85+
86+
/// The size of the memory area reserved for MMIO 32-bit accesses.
87+
pub const MMIO32_MEM_SIZE: u64 = mib_to_bytes(1024) as u64;
88+
/// The start of the memory area reserved for MMIO 32-bit accesses.
89+
pub const MMIO32_MEM_START: u64 = FIRST_ADDR_PAST_32BITS - MMIO32_MEM_SIZE;
90+
91+
// We dedicate the last 256 MiB of the 32-bit MMIO address space PCIe for memory-mapped access to
92+
// configuration.
93+
/// Size of MMIO region for PCIe configuration accesses.
94+
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
95+
/// Start of MMIO region for PCIe configuration accesses.
96+
pub const PCI_MMCONFIG_START: u64 = FIRST_ADDR_PAST_32BITS - PCI_MMCONFIG_SIZE;
97+
/// MMIO space per PCIe segment
98+
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
99+
100+
// We reserve 768 MiB for devices at the beginning of the MMIO region. This includes space both for
101+
// pure MMIO and PCIe devices.
102+
103+
/// Memory region start for boot device.
104+
pub const BOOT_DEVICE_MEM_START: u64 = MMIO32_MEM_START;
105+
106+
/// Beginning of memory region for device MMIO 32-bit accesses
107+
pub const MEM_32BIT_DEVICES_START: u64 = BOOT_DEVICE_MEM_START + 0x1000;
108+
/// Size of memory region for device MMIO 32-bit accesses
109+
pub const MEM_32BIT_DEVICES_SIZE: u64 = PCI_MMCONFIG_START - MEM_32BIT_DEVICES_START;
110+
111+
// 64-bits region for MMIO accesses
112+
/// The start of the memory area reserved for MMIO 64-bit accesses.
113+
pub const MMIO64_MEM_START: u64 = 256 << 30;
114+
/// The size of the memory area reserved for MMIO 64-bit accesses.
115+
pub const MMIO64_MEM_SIZE: u64 = 256 << 30;
116+
117+
// At the moment, all of this region goes to devices
118+
/// Beginning of memory region for device MMIO 64-bit accesses
119+
pub const MEM_64BIT_DEVICES_START: u64 = MMIO64_MEM_START;
120+
/// Size of memory region for device MMIO 32-bit accesses
121+
pub const MEM_64BIT_DEVICES_SIZE: u64 = MMIO64_MEM_SIZE;
122+
/// First address past the 64-bit MMIO gap
123+
pub const FIRST_ADDR_PAST_64BITS_MMIO: u64 = MMIO64_MEM_START + MMIO64_MEM_SIZE;

0 commit comments

Comments
 (0)