From 98825ea8863aba1bc4288bf4b376661a46a8103d Mon Sep 17 00:00:00 2001 From: Putta Khunchalee Date: Fri, 27 Sep 2024 02:11:53 +0700 Subject: [PATCH] Fixes KVM_CREATE_VM fails on Apple M1 (#998) --- src/core/src/vmm/hv/linux/ffi.rs | 48 ++++++++++++--- src/core/src/vmm/hv/linux/mod.rs | 100 ++++++++++++++++++++----------- src/core/src/vmm/mod.rs | 8 +++ src/kvm.cpp | 46 -------------- 4 files changed, 113 insertions(+), 89 deletions(-) diff --git a/src/core/src/vmm/hv/linux/ffi.rs b/src/core/src/vmm/hv/linux/ffi.rs index 1d826cefa..e81090e6d 100644 --- a/src/core/src/vmm/hv/linux/ffi.rs +++ b/src/core/src/vmm/hv/linux/ffi.rs @@ -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, diff --git a/src/core/src/vmm/hv/linux/mod.rs b/src/core/src/vmm/hv/linux/mod.rs index d01b3c150..c8e947f43 100644 --- a/src/core/src/vmm/hv/linux/mod.rs +++ b/src/core/src/vmm/hv/linux/mod.rs @@ -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; @@ -20,8 +22,6 @@ mod ffi; mod run; pub fn new(cpu: usize, ram: Ram) -> Result { - use std::io::Error; - // Open KVM device. let kvm = unsafe { open(b"/dev/kvm\0".as_ptr().cast(), O_RDWR) }; @@ -31,52 +31,38 @@ pub fn new(cpu: usize, ram: Ram) -> Result { // 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::::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(); @@ -87,13 +73,55 @@ pub fn new(cpu: usize, ram: Ram) -> Result { } 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 { + 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 { + 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). diff --git a/src/core/src/vmm/mod.rs b/src/core/src/vmm/mod.rs index 5e9813c16..7faf83c28 100644 --- a/src/core/src/vmm/mod.rs +++ b/src/core/src/vmm/mod.rs @@ -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), diff --git a/src/kvm.cpp b/src/kvm.cpp index a49a289bb..b9ec74506 100644 --- a/src/kvm.cpp +++ b/src/kvm.cpp @@ -9,47 +9,6 @@ #include #include -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(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, @@ -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);