Skip to content

Commit 6e12c9f

Browse files
committed
devices: implement support for in-kernel HVF GICv3
macOS 15 extended Hypervisor.framework with an in-kernel HVF GICv3 device. Using it reduces the cost of GIC operations and enables us to use nested virt. Add support for it and enable it automatically when the functions are found in HVF. Signed-off-by: Sergio Lopez <[email protected]>
1 parent ef44c31 commit 6e12c9f

File tree

9 files changed

+226
-10
lines changed

9 files changed

+226
-10
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/devices/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ bitflags = "1.2.0"
1919
crossbeam-channel = "0.5"
2020
env_logger = "0.9.0"
2121
libc = ">=0.2.39"
22+
libloading = "0.8"
2223
log = "0.4.0"
2324
nix = { version = "0.24.1", features = ["poll"] }
2425
pw = { package = "pipewire", version = "0.8.0", optional = true }

src/devices/src/legacy/hvfgicv3.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::io;
5+
use std::sync::LazyLock;
6+
7+
use crate::bus::BusDevice;
8+
use crate::legacy::gic::GICDevice;
9+
use crate::legacy::irqchip::IrqChipT;
10+
use crate::Error as DeviceError;
11+
12+
use hvf::bindings::{hv_gic_config_t, hv_ipa_t, hv_return_t, HV_SUCCESS};
13+
use hvf::Error;
14+
use utils::eventfd::EventFd;
15+
16+
// Device trees specific constants
17+
const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
18+
19+
pub struct HvfGicBindings {
20+
hv_gic_create:
21+
libloading::Symbol<'static, unsafe extern "C" fn(hv_gic_config_t) -> hv_return_t>,
22+
hv_gic_config_create: libloading::Symbol<'static, unsafe extern "C" fn() -> hv_gic_config_t>,
23+
hv_gic_config_set_distributor_base:
24+
libloading::Symbol<'static, unsafe extern "C" fn(hv_gic_config_t, hv_ipa_t) -> hv_return_t>,
25+
hv_gic_config_set_redistributor_base:
26+
libloading::Symbol<'static, unsafe extern "C" fn(hv_gic_config_t, hv_ipa_t) -> hv_return_t>,
27+
hv_gic_get_distributor_size:
28+
libloading::Symbol<'static, unsafe extern "C" fn(*mut usize) -> hv_return_t>,
29+
hv_gic_get_redistributor_size:
30+
libloading::Symbol<'static, unsafe extern "C" fn(*mut usize) -> hv_return_t>,
31+
hv_gic_set_spi: libloading::Symbol<'static, unsafe extern "C" fn(u32, bool) -> hv_return_t>,
32+
}
33+
34+
pub struct HvfGicV3 {
35+
bindings: HvfGicBindings,
36+
37+
/// GIC device properties, to be used for setting up the fdt entry
38+
properties: [u64; 4],
39+
40+
/// Number of CPUs handled by the device
41+
vcpu_count: u64,
42+
}
43+
44+
static HVF: LazyLock<libloading::Library> = LazyLock::new(|| unsafe {
45+
libloading::Library::new(
46+
"/System/Library/Frameworks/Hypervisor.framework/Versions/A/Hypervisor",
47+
)
48+
.unwrap()
49+
});
50+
51+
impl HvfGicV3 {
52+
pub fn new(vcpu_count: u64) -> Result<Self, Error> {
53+
let bindings = unsafe {
54+
HvfGicBindings {
55+
hv_gic_create: HVF.get(b"hv_gic_create").map_err(Error::FindSymbol)?,
56+
hv_gic_config_create: HVF
57+
.get(b"hv_gic_config_create")
58+
.map_err(Error::FindSymbol)?,
59+
hv_gic_config_set_distributor_base: HVF
60+
.get(b"hv_gic_config_set_distributor_base")
61+
.map_err(Error::FindSymbol)?,
62+
hv_gic_config_set_redistributor_base: HVF
63+
.get(b"hv_gic_config_set_redistributor_base")
64+
.map_err(Error::FindSymbol)?,
65+
hv_gic_get_distributor_size: HVF
66+
.get(b"hv_gic_get_distributor_size")
67+
.map_err(Error::FindSymbol)?,
68+
hv_gic_get_redistributor_size: HVF
69+
.get(b"hv_gic_get_redistributor_size")
70+
.map_err(Error::FindSymbol)?,
71+
hv_gic_set_spi: HVF.get(b"hv_gic_set_spi").map_err(Error::FindSymbol)?,
72+
}
73+
};
74+
75+
let mut dist_size: usize = 0;
76+
let ret = unsafe { (bindings.hv_gic_get_distributor_size)(&mut dist_size) };
77+
if ret != HV_SUCCESS {
78+
return Err(Error::VmCreate);
79+
}
80+
let dist_size = dist_size as u64;
81+
82+
let mut redist_size: usize = 0;
83+
let ret = unsafe { (bindings.hv_gic_get_redistributor_size)(&mut redist_size) };
84+
if ret != HV_SUCCESS {
85+
return Err(Error::VmCreate);
86+
}
87+
88+
let redists_size = redist_size as u64 * vcpu_count;
89+
let dist_addr = arch::MMIO_MEM_START - dist_size - redists_size;
90+
let redists_addr = arch::MMIO_MEM_START - redists_size;
91+
92+
let gic_config = unsafe { (bindings.hv_gic_config_create)() };
93+
let ret = unsafe { (bindings.hv_gic_config_set_distributor_base)(gic_config, dist_addr) };
94+
if ret != HV_SUCCESS {
95+
return Err(Error::VmCreate);
96+
}
97+
98+
let ret = unsafe {
99+
(bindings.hv_gic_config_set_redistributor_base)(
100+
gic_config,
101+
arch::MMIO_MEM_START - redists_size,
102+
)
103+
};
104+
if ret != HV_SUCCESS {
105+
return Err(Error::VmCreate);
106+
}
107+
108+
let ret = unsafe { (bindings.hv_gic_create)(gic_config) };
109+
if ret != HV_SUCCESS {
110+
return Err(Error::VmCreate);
111+
}
112+
113+
Ok(Self {
114+
bindings,
115+
properties: [dist_addr, dist_size, redists_addr, redists_size],
116+
vcpu_count,
117+
})
118+
}
119+
}
120+
121+
impl IrqChipT for HvfGicV3 {
122+
fn get_mmio_addr(&self) -> u64 {
123+
0
124+
}
125+
126+
fn get_mmio_size(&self) -> u64 {
127+
0
128+
}
129+
130+
fn set_irq(
131+
&self,
132+
irq_line: Option<u32>,
133+
_interrupt_evt: Option<&EventFd>,
134+
) -> Result<(), DeviceError> {
135+
if let Some(irq_line) = irq_line {
136+
let ret = unsafe { (self.bindings.hv_gic_set_spi)(irq_line, true) };
137+
if ret != HV_SUCCESS {
138+
Err(DeviceError::FailedSignalingUsedQueue(io::Error::new(
139+
io::ErrorKind::Other,
140+
"HVF returned error when setting SPI",
141+
)))
142+
} else {
143+
Ok(())
144+
}
145+
} else {
146+
Err(DeviceError::FailedSignalingUsedQueue(io::Error::new(
147+
io::ErrorKind::InvalidData,
148+
"IRQ not line configured",
149+
)))
150+
}
151+
}
152+
}
153+
154+
impl BusDevice for HvfGicV3 {
155+
fn read(&mut self, _vcpuid: u64, _offset: u64, _data: &mut [u8]) {
156+
unreachable!("MMIO operations are managed in-kernel");
157+
}
158+
159+
fn write(&mut self, _vcpuid: u64, _offset: u64, _data: &[u8]) {
160+
unreachable!("MMIO operations are managed in-kernel");
161+
}
162+
}
163+
164+
impl GICDevice for HvfGicV3 {
165+
fn device_properties(&self) -> Vec<u64> {
166+
self.properties.to_vec()
167+
}
168+
169+
fn vcpu_count(&self) -> u64 {
170+
self.vcpu_count
171+
}
172+
173+
fn fdt_compatibility(&self) -> String {
174+
"arm,gic-v3".to_string()
175+
}
176+
177+
fn fdt_maint_irq(&self) -> u32 {
178+
ARCH_GIC_V3_MAINT_IRQ
179+
}
180+
181+
fn version(&self) -> u32 {
182+
7
183+
}
184+
}

src/devices/src/legacy/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
pub mod gic;
99
#[cfg(target_os = "macos")]
1010
mod gicv3;
11+
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
12+
mod hvfgicv3;
1113
mod i8042;
1214
mod irqchip;
1315
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
@@ -33,6 +35,8 @@ use aarch64::serial;
3335
pub use self::gicv3::GicV3;
3436
#[cfg(target_arch = "aarch64")]
3537
pub use self::gpio::Gpio;
38+
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
39+
pub use self::hvfgicv3::HvfGicV3;
3640
pub use self::i8042::Error as I8042DeviceError;
3741
pub use self::i8042::I8042Device;
3842
pub use self::irqchip::{IrqChip, IrqChipDevice, IrqChipT};

src/hvf/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2021"
66

77
[dependencies]
88
crossbeam-channel = "0.5"
9+
libloading = "0.8"
910
log = "0.4.0"
1011
env_logger = "0.9.0"
1112

src/hvf/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#[allow(non_snake_case)]
88
#[allow(non_upper_case_globals)]
99
#[allow(deref_nullptr)]
10-
mod bindings;
10+
pub mod bindings;
1111

1212
use bindings::*;
1313

@@ -51,8 +51,9 @@ const EC_SYSTEMREGISTERTRAP: u64 = 0x18;
5151
const EC_DATAABORT: u64 = 0x24;
5252
const EC_AA64_BKPT: u64 = 0x3c;
5353

54-
#[derive(Clone, Debug)]
54+
#[derive(Debug)]
5555
pub enum Error {
56+
FindSymbol(libloading::Error),
5657
MemoryMap,
5758
MemoryUnmap,
5859
VcpuCreate,
@@ -73,6 +74,7 @@ impl Display for Error {
7374
use self::Error::*;
7475

7576
match self {
77+
FindSymbol(ref err) => write!(f, "Couldn't find symbol in HVF library: {}", err),
7678
MemoryMap => write!(f, "Error registering memory region in HVF"),
7779
MemoryUnmap => write!(f, "Error unregistering memory region in HVF"),
7880
VcpuCreate => write!(f, "Error creating HVF vCPU instance"),

src/vmm/src/builder.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#[cfg(target_os = "macos")]
77
use crossbeam_channel::{unbounded, Sender};
88
use kernel::cmdline::Cmdline;
9+
#[cfg(target_os = "macos")]
910
use std::collections::HashMap;
1011
use std::fmt::{Display, Formatter};
1112
use std::fs::File;
@@ -22,15 +23,15 @@ use crate::device_manager::legacy::PortIODeviceManager;
2223
use crate::device_manager::mmio::MMIODeviceManager;
2324
use crate::resources::VmResources;
2425
use crate::vmm_config::external_kernel::{ExternalKernel, KernelFormat};
25-
#[cfg(target_os = "macos")]
26-
use devices::legacy::GicV3;
2726
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
2827
use devices::legacy::KvmGicV3;
2928
#[cfg(target_arch = "x86_64")]
3029
use devices::legacy::KvmIoapic;
3130
use devices::legacy::Serial;
3231
#[cfg(target_os = "macos")]
3332
use devices::legacy::VcpuList;
33+
#[cfg(target_os = "macos")]
34+
use devices::legacy::{GicV3, HvfGicV3};
3435
use devices::legacy::{IrqChip, IrqChipDevice};
3536
#[cfg(feature = "net")]
3637
use devices::virtio::Net;
@@ -89,6 +90,9 @@ static EDK2_BINARY: &[u8] = include_bytes!("../../../edk2/KRUN_EFI.silent.fd");
8990
pub enum StartMicrovmError {
9091
/// Unable to attach block device to Vmm.
9192
AttachBlockDevice(io::Error),
93+
#[cfg(target_os = "macos")]
94+
/// Failed to create HVF in-kernel IrqChip.
95+
CreateHvfIrqChip(hvf::Error),
9296
#[cfg(target_os = "linux")]
9397
/// Failed to create KVM in-kernel IrqChip.
9498
CreateKvmIrqChip(kvm_ioctls::Error),
@@ -208,6 +212,10 @@ impl Display for StartMicrovmError {
208212
AttachBlockDevice(ref err) => {
209213
write!(f, "Unable to attach block device to Vmm. Error: {err}")
210214
}
215+
#[cfg(target_os = "macos")]
216+
CreateHvfIrqChip(ref err) => {
217+
write!(f, "Cannot create HVF in-kernel IrqChip: {err}")
218+
}
211219
#[cfg(target_os = "linux")]
212220
CreateKvmIrqChip(ref err) => {
213221
write!(f, "Cannot create KVM in-kernel IrqChip: {err}")
@@ -698,9 +706,15 @@ pub fn build_microvm(
698706

699707
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
700708
{
701-
intc = Arc::new(Mutex::new(IrqChipDevice::new(Box::new(GicV3::new(
702-
vcpu_list.clone(),
703-
)))));
709+
intc = {
710+
// If the system supports the in-kernel GIC, use it. Otherwise, fall back to the
711+
// userspace implementation.
712+
let gic = match HvfGicV3::new(vm_resources.vm_config().vcpu_count.unwrap() as u64) {
713+
Ok(hvfgic) => IrqChipDevice::new(Box::new(hvfgic)),
714+
Err(_) => IrqChipDevice::new(Box::new(GicV3::new(vcpu_list.clone()))),
715+
};
716+
Arc::new(Mutex::new(gic))
717+
};
704718

705719
vcpus = create_vcpus_aarch64(
706720
&vm,

src/vmm/src/device_manager/hvf/mmio.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,13 @@ impl MMIODeviceManager {
265265
(intc.get_mmio_addr(), intc.get_mmio_size())
266266
};
267267

268-
self.bus
269-
.insert(intc, mmio_addr, mmio_size)
270-
.map_err(Error::BusError)?;
268+
// The in-kernel GIC reports a size of 0 to tell us we don't need to map
269+
// anything in the guest.
270+
if mmio_size != 0 {
271+
self.bus
272+
.insert(intc, mmio_addr, mmio_size)
273+
.map_err(Error::BusError)?;
274+
}
271275

272276
Ok(())
273277
}

src/vmm/src/macos/vstate.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ impl Vm {
104104
Ok(Vm { hvf_vm })
105105
}
106106

107+
pub fn hvf_vm(&self) -> &HvfVm {
108+
&self.hvf_vm
109+
}
110+
107111
/// Initializes the guest memory.
108112
pub fn memory_init(&mut self, guest_mem: &GuestMemoryMmap) -> Result<()> {
109113
for region in guest_mem.iter() {

0 commit comments

Comments
 (0)