Skip to content

Commit

Permalink
Fixes KVM_CREATE_VM fails on Apple M1 (#998)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Sep 26, 2024
1 parent 80c2b3a commit 98825ea
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 89 deletions.
48 changes: 41 additions & 7 deletions src/core/src/vmm/hv/linux/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
use std::ffi::{c_int, c_void};
use std::ffi::{c_int, c_ulong, c_void};

extern "C" {
pub fn kvm_check_version(kvm: c_int, compat: *mut bool) -> c_int;
pub fn kvm_check_extension(fd: c_int, id: c_int) -> bool;
pub fn kvm_max_vcpus(kvm: c_int, max: *mut usize) -> c_int;
pub fn kvm_create_vm(kvm: c_int, fd: *mut c_int) -> c_int;
pub fn kvm_get_vcpu_mmap_size(kvm: c_int) -> c_int;
pub const KVM_GET_API_VERSION: c_ulong = _IO(KVMIO, 0x00);
pub const KVM_CREATE_VM: c_ulong = _IO(KVMIO, 0x01);
pub const KVM_CHECK_EXTENSION: c_ulong = _IO(KVMIO, 0x03);
pub const KVM_GET_VCPU_MMAP_SIZE: c_ulong = _IO(KVMIO, 0x04);

pub const KVM_API_VERSION: c_int = 12;

pub const KVM_CAP_MAX_VCPUS: c_int = 66;
pub const KVM_CAP_ONE_REG: c_int = 70;
pub const KVM_CAP_ARM_VM_IPA_SIZE: c_int = 165;

const KVMIO: c_ulong = 0xAE;
const _IOC_NONE: c_ulong = 0;
const _IOC_NRSHIFT: c_ulong = 0;
const _IOC_NRBITS: c_ulong = 8;
const _IOC_TYPEBITS: c_ulong = 8;
const _IOC_SIZEBITS: c_ulong = 14;
const _IOC_TYPESHIFT: c_ulong = _IOC_NRSHIFT + _IOC_NRBITS;
const _IOC_SIZESHIFT: c_ulong = _IOC_TYPESHIFT + _IOC_TYPEBITS;
const _IOC_DIRSHIFT: c_ulong = _IOC_SIZESHIFT + _IOC_SIZEBITS;

#[cfg(target_arch = "aarch64")]
#[allow(non_snake_case)]
pub fn KVM_VM_TYPE_ARM_IPA_SIZE(v: c_int) -> c_int {
v & 0xff
}

#[allow(non_snake_case)]
const fn _IO(ty: c_ulong, nr: c_ulong) -> c_ulong {
_IOC(_IOC_NONE, ty, nr, 0)
}

#[allow(non_snake_case)]
const fn _IOC(dir: c_ulong, ty: c_ulong, nr: c_ulong, size: c_ulong) -> c_ulong {
((dir) << _IOC_DIRSHIFT)
| ((ty) << _IOC_TYPESHIFT)
| ((nr) << _IOC_NRSHIFT)
| ((size) << _IOC_SIZESHIFT)
}

extern "C" {
pub fn kvm_set_user_memory_region(
vm: c_int,
slot: u32,
Expand Down
100 changes: 64 additions & 36 deletions src/core/src/vmm/hv/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use self::cpu::KvmCpu;
use self::ffi::{
kvm_check_extension, kvm_check_version, kvm_create_vcpu, kvm_create_vm, kvm_get_vcpu_mmap_size,
kvm_max_vcpus, kvm_set_user_memory_region,
kvm_create_vcpu, kvm_set_user_memory_region, KVM_API_VERSION, KVM_CAP_MAX_VCPUS,
KVM_CAP_ONE_REG, KVM_CHECK_EXTENSION, KVM_CREATE_VM, KVM_GET_API_VERSION,
KVM_GET_VCPU_MMAP_SIZE,
};
use super::{CpuFeats, Hypervisor};
use crate::vmm::ram::Ram;
use crate::vmm::VmmError;
use libc::{mmap, open, MAP_FAILED, MAP_PRIVATE, O_RDWR, PROT_READ, PROT_WRITE};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use libc::{ioctl, mmap, open, MAP_FAILED, MAP_PRIVATE, O_RDWR, PROT_READ, PROT_WRITE};
use std::io::Error;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
use std::ptr::null_mut;
use thiserror::Error;

Expand All @@ -20,8 +22,6 @@ mod ffi;
mod run;

pub fn new(cpu: usize, ram: Ram) -> Result<impl Hypervisor, VmmError> {
use std::io::Error;

// Open KVM device.
let kvm = unsafe { open(b"/dev/kvm\0".as_ptr().cast(), O_RDWR) };

Expand All @@ -31,52 +31,38 @@ pub fn new(cpu: usize, ram: Ram) -> Result<impl Hypervisor, VmmError> {

// Check KVM version.
let kvm = unsafe { OwnedFd::from_raw_fd(kvm) };
let mut compat = false;
let version = unsafe { ioctl(kvm.as_raw_fd(), KVM_GET_API_VERSION) };

match unsafe { kvm_check_version(kvm.as_raw_fd(), &mut compat) } {
0 if !compat => {
return Err(VmmError::KvmVersionMismatched);
}
0 => {}
v => return Err(VmmError::GetKvmVersionFailed(Error::from_raw_os_error(v))),
if version < 0 {
return Err(VmmError::GetKvmVersionFailed(Error::last_os_error()));
} else if version != KVM_API_VERSION {
return Err(VmmError::KvmVersionMismatched);
}

// Check max CPU.
let mut max = 0;

match unsafe { kvm_max_vcpus(kvm.as_raw_fd(), &mut max) } {
0 => {}
v => {
return Err(VmmError::GetMaxCpuFailed(Error::from_raw_os_error(v)));
}
}
let max = unsafe { ioctl(kvm.as_raw_fd(), KVM_CHECK_EXTENSION, KVM_CAP_MAX_VCPUS) };

if max < cpu {
if max < 0 {
return Err(VmmError::GetMaxCpuFailed(Error::last_os_error()));
} else if TryInto::<usize>::try_into(max).unwrap() < cpu {
return Err(VmmError::MaxCpuTooLow);
}

// Check KVM_CAP_ONE_REG. KVM_SET_ONE_REG and KVM_GET_ONE_REG are the only API that support all
// architectures.
if unsafe { !kvm_check_extension(kvm.as_raw_fd(), 70) } {
if unsafe { ioctl(kvm.as_raw_fd(), KVM_CHECK_EXTENSION, KVM_CAP_ONE_REG) <= 0 } {
return Err(VmmError::NoKvmOneReg);
}

// Get size of CPU context.
let vcpu_mmap_size = match unsafe { kvm_get_vcpu_mmap_size(kvm.as_raw_fd()) } {
size @ 0.. => size as usize,
_ => return Err(VmmError::GetMmapSizeFailed(Error::last_os_error())),
};

// Create a VM.
let mut vm = -1;
let vcpu_mmap_size = unsafe { ioctl(kvm.as_raw_fd(), KVM_GET_VCPU_MMAP_SIZE, 0) };

match unsafe { kvm_create_vm(kvm.as_raw_fd(), &mut vm) } {
0 => {}
v => return Err(VmmError::CreateVmFailed(Error::from_raw_os_error(v))),
if vcpu_mmap_size < 0 {
return Err(VmmError::GetMmapSizeFailed(Error::last_os_error()));
}

// Set RAM.
let vm = unsafe { OwnedFd::from_raw_fd(vm) };
// Create a VM.
let vm = create_vm(kvm.as_fd())?;
let slot = 0;
let len = ram.len().try_into().unwrap();
let mem = ram.host_addr().cast_mut().cast();
Expand All @@ -87,13 +73,55 @@ pub fn new(cpu: usize, ram: Ram) -> Result<impl Hypervisor, VmmError> {
}

Ok(Kvm {
vcpu_mmap_size,
vcpu_mmap_size: vcpu_mmap_size.try_into().unwrap(),
vm,
ram,
kvm,
})
}

#[cfg(target_arch = "aarch64")]
fn create_vm(kvm: BorrowedFd) -> Result<OwnedFd, VmmError> {
use self::ffi::{KVM_CAP_ARM_VM_IPA_SIZE, KVM_VM_TYPE_ARM_IPA_SIZE};

// Check KVM_CAP_ARM_VM_IPA_SIZE. We cannot use default machine type on AArch64 otherwise
// KVM_CREATE_VM will fails on Apple M1 due to the default IPA size is 40-bits, which M1 does
// not support.
let limit = unsafe {
ioctl(
kvm.as_raw_fd(),
KVM_CHECK_EXTENSION,
KVM_CAP_ARM_VM_IPA_SIZE,
)
};

if limit <= 0 {
return Err(VmmError::NoVmIpaSize);
} else if limit < 36 {
return Err(VmmError::PhysicalAddressTooSmall);
}

// Create a VM.
let vm = unsafe { ioctl(kvm.as_raw_fd(), KVM_CREATE_VM, KVM_VM_TYPE_ARM_IPA_SIZE(36)) };

if vm < 0 {
Err(VmmError::CreateVmFailed(Error::last_os_error()))
} else {
Ok(unsafe { OwnedFd::from_raw_fd(vm) })
}
}

#[cfg(target_arch = "x86_64")]
fn create_vm(kvm: BorrowedFd) -> Result<OwnedFd, VmmError> {
let vm = unsafe { ioctl(kvm.as_raw_fd(), KVM_CREATE_VM, 0) };

if vm < 0 {
Err(VmmError::CreateVmFailed(Error::last_os_error()))
} else {
Ok(unsafe { OwnedFd::from_raw_fd(vm) })
}
}

/// Implementation of [`Hypervisor`] using KVM.
///
/// Fields in this struct need to drop in a correct order (e.g. vm must be dropped before ram).
Expand Down
8 changes: 8 additions & 0 deletions src/core/src/vmm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,14 @@ enum VmmError {
#[error("your OS does not support KVM_CAP_ONE_REG")]
NoKvmOneReg,

#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
#[error("your OS does not support KVM_CAP_ARM_VM_IPA_SIZE")]
NoVmIpaSize,

#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
#[error("physical address supported by your CPU too small")]
PhysicalAddressTooSmall,

#[cfg(target_os = "linux")]
#[error("couldn't create a VM")]
CreateVmFailed(#[source] std::io::Error),
Expand Down
46 changes: 0 additions & 46 deletions src/kvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,6 @@
#include <stdint.h>
#include <string.h>

extern "C" int kvm_check_version(int kvm, bool *compat)
{
auto v = ioctl(kvm, KVM_GET_API_VERSION);

if (v < 0) {
return errno;
}

*compat = (v == KVM_API_VERSION);
return 0;
}

extern "C" bool kvm_check_extension(int fd, int id)
{
return ioctl(fd, KVM_CHECK_EXTENSION, id) > 0;
}

extern "C" int kvm_max_vcpus(int kvm, size_t *max)
{
auto num = ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_MAX_VCPUS);

if (num < 0) {
return errno;
}

*max = static_cast<size_t>(num);
return 0;
}

extern "C" int kvm_create_vm(int kvm, int *fd)
{
auto vm = ioctl(kvm, KVM_CREATE_VM, 0);

if (vm < 0) {
return errno;
}

*fd = vm;
return 0;
}

extern "C" int kvm_set_user_memory_region(
int vm,
uint32_t slot,
Expand All @@ -73,11 +32,6 @@ extern "C" int kvm_set_user_memory_region(
return 0;
}

extern "C" int kvm_get_vcpu_mmap_size(int kvm)
{
return ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, 0);
}

extern "C" int kvm_create_vcpu(int vm, uint32_t id, int *fd)
{
auto vcpu = ioctl(vm, KVM_CREATE_VCPU, id);
Expand Down

0 comments on commit 98825ea

Please sign in to comment.