Skip to content

Commit 6d1fd78

Browse files
committed
hvf: implement nested virt (EL2) support
Apple introduced Nested Virt (EL2) support in macOS Sequoia, available on Apple Silicon devices based on M3 and later SoCs. This commit introduces the infrastructure to enable Nested Virt on libkrun. The biggest change is setting up the vCPU reset registers to values that are acceptable in EL2 according. This isn't easy since HVF doesn't document its expectations, but the current implementation allows the guest to boot in EL2 and run a nested guest. Instead of linking directly against the new functions, we're using libloader again to find the new symbols, to avoid breaking the binaries in Sonoma. Signed-off-by: Sergio Lopez <[email protected]>
1 parent 2b293e6 commit 6d1fd78

File tree

4 files changed

+147
-14
lines changed

4 files changed

+147
-14
lines changed

src/devices/src/legacy/vcpu.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ impl Vcpus for VcpuList {
146146
}
147147

148148
match reg {
149+
SYSREG_CNTHCTL_EL2 => Some(0),
149150
SYSREG_ICC_IAR1_EL1 => Some(
150151
self.vcpus[vcpuid as usize]
151152
.lock()
@@ -206,12 +207,14 @@ impl Vcpus for VcpuList {
206207

207208
true
208209
}
209-
SYSREG_ICC_EOIR1_EL1
210+
SYSREG_CNTHCTL_EL2
211+
| SYSREG_ICC_EOIR1_EL1
210212
| SYSREG_ICC_IGRPEN1_EL1
211213
| SYSREG_ICC_PMR_EL1
212214
| SYSREG_ICC_BPR1_EL1
213215
| SYSREG_ICC_CTLR_EL1
214216
| SYSREG_ICC_AP1R0_EL1
217+
| SYSREG_LORC_EL1
215218
| SYSREG_OSLAR_EL1
216219
| SYSREG_OSDLR_EL1 => true,
217220
_ => false,

src/hvf/src/lib.rs

Lines changed: 134 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::arch::asm;
1616

1717
use std::convert::TryInto;
1818
use std::fmt::{Display, Formatter};
19-
use std::sync::Arc;
19+
use std::sync::{Arc, LazyLock};
2020
use std::time::Duration;
2121

2222
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
@@ -37,11 +37,54 @@ const TMR_CTL_IMASK: u64 = 1 << 1;
3737
const TMR_CTL_ISTATUS: u64 = 1 << 2;
3838

3939
const PSR_MODE_EL1H: u64 = 0x0000_0005;
40+
const PSR_MODE_EL2H: u64 = 0x0000_0009;
4041
const PSR_F_BIT: u64 = 0x0000_0040;
4142
const PSR_I_BIT: u64 = 0x0000_0080;
4243
const PSR_A_BIT: u64 = 0x0000_0100;
4344
const PSR_D_BIT: u64 = 0x0000_0200;
44-
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1H | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
45+
const PSTATE_EL1_FAULT_BITS_64: u64 = PSR_MODE_EL1H | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
46+
const PSTATE_EL2_FAULT_BITS_64: u64 = PSR_MODE_EL2H | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
47+
48+
const HCR_TLOR: u64 = 1 << 35;
49+
const HCR_RW: u64 = 1 << 31;
50+
const HCR_TSW: u64 = 1 << 22;
51+
const HCR_TACR: u64 = 1 << 21;
52+
const HCR_TIDCP: u64 = 1 << 20;
53+
const HCR_TSC: u64 = 1 << 19;
54+
const HCR_TID3: u64 = 1 << 18;
55+
const HCR_TWE: u64 = 1 << 14;
56+
const HCR_TWI: u64 = 1 << 13;
57+
const HCR_BSU_IS: u64 = 1 << 10;
58+
const HCR_FB: u64 = 1 << 9;
59+
const HCR_AMO: u64 = 1 << 5;
60+
const HCR_IMO: u64 = 1 << 4;
61+
const HCR_FMO: u64 = 1 << 3;
62+
const HCR_PTW: u64 = 1 << 2;
63+
const HCR_SWIO: u64 = 1 << 1;
64+
const HCR_VM: u64 = 1 << 0;
65+
// Use the same bits as KVM uses in vcpu reset.
66+
const HCR_EL2_BITS: u64 = HCR_TSC
67+
| HCR_TSW
68+
| HCR_TWE
69+
| HCR_TWI
70+
| HCR_VM
71+
| HCR_BSU_IS
72+
| HCR_FB
73+
| HCR_TACR
74+
| HCR_AMO
75+
| HCR_SWIO
76+
| HCR_TIDCP
77+
| HCR_RW
78+
| HCR_TLOR
79+
| HCR_FMO
80+
| HCR_IMO
81+
| HCR_PTW
82+
| HCR_TID3;
83+
84+
const CNTHCTL_EL0VCTEN: u64 = 1 << 1;
85+
const CNTHCTL_EL0PCTEN: u64 = 1 << 0;
86+
// Trap accesses to both virtual and physical counter registers.
87+
const CNTHCTL_EL2_BITS: u64 = CNTHCTL_EL0VCTEN | CNTHCTL_EL0PCTEN;
4588

4689
const EC_WFX_TRAP: u64 = 0x1;
4790
const EC_AA64_HVC: u64 = 0x16;
@@ -53,9 +96,11 @@ const EC_AA64_BKPT: u64 = 0x3c;
5396

5497
#[derive(Debug)]
5598
pub enum Error {
99+
EnableEL2,
56100
FindSymbol(libloading::Error),
57101
MemoryMap,
58102
MemoryUnmap,
103+
NestedCheck,
59104
VcpuCreate,
60105
VcpuInitialRegisters,
61106
VcpuReadRegister,
@@ -74,9 +119,14 @@ impl Display for Error {
74119
use self::Error::*;
75120

76121
match self {
122+
EnableEL2 => write!(f, "Error enabling EL2 mode in HVF"),
77123
FindSymbol(ref err) => write!(f, "Couldn't find symbol in HVF library: {}", err),
78124
MemoryMap => write!(f, "Error registering memory region in HVF"),
79125
MemoryUnmap => write!(f, "Error unregistering memory region in HVF"),
126+
NestedCheck => write!(
127+
f,
128+
"Nested virtualization was requested but it's not support in this system"
129+
),
80130
VcpuCreate => write!(f, "Error creating HVF vCPU instance"),
81131
VcpuInitialRegisters => write!(f, "Error setting up initial HVF vCPU registers"),
82132
VcpuReadRegister => write!(f, "Error reading HVF vCPU register"),
@@ -156,11 +206,56 @@ pub fn vcpu_set_vtimer_mask(vcpuid: u64, masked: bool) -> Result<(), Error> {
156206
}
157207
}
158208

209+
pub struct HvfNestedBindings {
210+
hv_vm_config_get_el2_supported:
211+
libloading::Symbol<'static, unsafe extern "C" fn(*mut bool) -> hv_return_t>,
212+
hv_vm_config_set_el2_enabled:
213+
libloading::Symbol<'static, unsafe extern "C" fn(hv_vm_config_t, *mut bool) -> hv_return_t>,
214+
}
215+
159216
pub struct HvfVm {}
160217

218+
static HVF: LazyLock<libloading::Library> = LazyLock::new(|| unsafe {
219+
libloading::Library::new(
220+
"/System/Library/Frameworks/Hypervisor.framework/Versions/A/Hypervisor",
221+
)
222+
.unwrap()
223+
});
224+
161225
impl HvfVm {
162-
pub fn new() -> Result<Self, Error> {
163-
let ret = unsafe { hv_vm_create(std::ptr::null_mut()) };
226+
pub fn new(nested_enabled: bool) -> Result<Self, Error> {
227+
let config = unsafe { hv_vm_config_create() };
228+
229+
if nested_enabled {
230+
let bindings = unsafe {
231+
HvfNestedBindings {
232+
hv_vm_config_get_el2_supported: HVF
233+
.get(b"hv_vm_config_get_el2_supported")
234+
.map_err(Error::FindSymbol)?,
235+
hv_vm_config_set_el2_enabled: HVF
236+
.get(b"hv_vm_config_set_el2_enabled")
237+
.map_err(Error::FindSymbol)?,
238+
}
239+
};
240+
241+
let mut el2_supported: bool = false;
242+
let ret = unsafe { (bindings.hv_vm_config_get_el2_supported)(&mut el2_supported) };
243+
if ret != HV_SUCCESS {
244+
return Err(Error::NestedCheck);
245+
}
246+
247+
if !el2_supported {
248+
return Err(Error::NestedCheck);
249+
}
250+
251+
let mut el2_enabled = true;
252+
let ret = unsafe { (bindings.hv_vm_config_set_el2_enabled)(config, &mut el2_enabled) };
253+
if ret != HV_SUCCESS {
254+
return Err(Error::EnableEL2);
255+
}
256+
}
257+
258+
let ret = unsafe { hv_vm_create(config) };
164259

165260
if ret != HV_SUCCESS {
166261
Err(Error::VmCreate)
@@ -232,10 +327,11 @@ pub struct HvfVcpu<'a> {
232327
pending_mmio_read: Option<MmioRead>,
233328
pending_advance_pc: bool,
234329
vtimer_masked: bool,
330+
nested_enabled: bool,
235331
}
236332

237333
impl HvfVcpu<'_> {
238-
pub fn new(mpidr: u64) -> Result<Self, Error> {
334+
pub fn new(mpidr: u64, nested_enabled: bool) -> Result<Self, Error> {
239335
let mut vcpuid: hv_vcpu_t = 0;
240336
let vcpu_exit_ptr: *mut hv_vcpu_exit_t = std::ptr::null_mut();
241337

@@ -276,14 +372,43 @@ impl HvfVcpu<'_> {
276372
pending_mmio_read: None,
277373
pending_advance_pc: false,
278374
vtimer_masked: false,
375+
nested_enabled,
279376
})
280377
}
281378

282379
pub fn set_initial_state(&self, entry_addr: u64, fdt_addr: u64) -> Result<(), Error> {
283-
let ret =
284-
unsafe { hv_vcpu_set_reg(self.vcpuid, hv_reg_t_HV_REG_CPSR, PSTATE_FAULT_BITS_64) };
285-
if ret != HV_SUCCESS {
286-
return Err(Error::VcpuInitialRegisters);
380+
if self.nested_enabled {
381+
let ret = unsafe {
382+
hv_vcpu_set_reg(self.vcpuid, hv_reg_t_HV_REG_CPSR, PSTATE_EL2_FAULT_BITS_64)
383+
};
384+
if ret != HV_SUCCESS {
385+
return Err(Error::VcpuInitialRegisters);
386+
}
387+
388+
let ret = unsafe {
389+
hv_vcpu_set_sys_reg(self.vcpuid, hv_sys_reg_t_HV_SYS_REG_HCR_EL2, HCR_EL2_BITS)
390+
};
391+
if ret != HV_SUCCESS {
392+
return Err(Error::VcpuInitialRegisters);
393+
}
394+
395+
let ret = unsafe {
396+
hv_vcpu_set_sys_reg(
397+
self.vcpuid,
398+
hv_sys_reg_t_HV_SYS_REG_CNTHCTL_EL2,
399+
CNTHCTL_EL2_BITS,
400+
)
401+
};
402+
if ret != HV_SUCCESS {
403+
return Err(Error::VcpuInitialRegisters);
404+
}
405+
} else {
406+
let ret = unsafe {
407+
hv_vcpu_set_reg(self.vcpuid, hv_reg_t_HV_REG_CPSR, PSTATE_EL1_FAULT_BITS_64)
408+
};
409+
if ret != HV_SUCCESS {
410+
return Err(Error::VcpuInitialRegisters);
411+
}
287412
}
288413

289414
let ret = unsafe { hv_vcpu_set_reg(self.vcpuid, hv_reg_t_HV_REG_PC, entry_addr) };

src/vmm/src/builder.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,7 @@ pub(crate) fn setup_vm(
12901290
pub(crate) fn setup_vm(
12911291
guest_memory: &GuestMemoryMmap,
12921292
) -> std::result::Result<Vm, StartMicrovmError> {
1293-
let mut vm = Vm::new()
1293+
let mut vm = Vm::new(false)
12941294
.map_err(Error::Vm)
12951295
.map_err(StartMicrovmError::Internal)?;
12961296
vm.memory_init(guest_memory)
@@ -1491,6 +1491,7 @@ fn create_vcpus_aarch64(
14911491
boot_receiver,
14921492
exit_evt.try_clone().map_err(Error::EventFd)?,
14931493
vcpu_list.clone(),
1494+
false,
14941495
)
14951496
.map_err(Error::Vcpu)?;
14961497

src/vmm/src/macos/vstate.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ pub struct Vm {
9898

9999
impl Vm {
100100
/// Constructs a new `Vm` using the given `Kvm` instance.
101-
pub fn new() -> Result<Self> {
102-
let hvf_vm = HvfVm::new().map_err(Error::VmSetup)?;
101+
pub fn new(nested_enabled: bool) -> Result<Self> {
102+
let hvf_vm = HvfVm::new(nested_enabled).map_err(Error::VmSetup)?;
103103

104104
Ok(Vm { hvf_vm })
105105
}
@@ -200,6 +200,7 @@ pub struct Vcpu {
200200
response_sender: Sender<VcpuResponse>,
201201

202202
vcpu_list: Arc<VcpuList>,
203+
nested_enabled: bool,
203204
}
204205

205206
impl Vcpu {
@@ -273,6 +274,7 @@ impl Vcpu {
273274
boot_receiver: Option<Receiver<u64>>,
274275
exit_evt: EventFd,
275276
vcpu_list: Arc<VcpuList>,
277+
nested_enabled: bool,
276278
) -> Result<Self> {
277279
let (event_sender, event_receiver) = unbounded();
278280
let (response_sender, response_receiver) = unbounded();
@@ -291,6 +293,7 @@ impl Vcpu {
291293
response_receiver: Some(response_receiver),
292294
response_sender,
293295
vcpu_list,
296+
nested_enabled,
294297
})
295298
}
296299

@@ -437,7 +440,8 @@ impl Vcpu {
437440

438441
/// Main loop of the vCPU thread.
439442
pub fn run(&mut self, init_tls_sender: Sender<bool>) {
440-
let mut hvf_vcpu = HvfVcpu::new(self.mpidr).expect("Can't create HVF vCPU");
443+
let mut hvf_vcpu =
444+
HvfVcpu::new(self.mpidr, self.nested_enabled).expect("Can't create HVF vCPU");
441445
let hvf_vcpuid = hvf_vcpu.id();
442446

443447
init_tls_sender

0 commit comments

Comments
 (0)