Fixes incomplete ID_AA64MMFR0_EL1 on Linux (#999)

This commit is contained in:
Putta Khunchalee 2024-09-28 14:16:48 +07:00 committed by GitHub
parent 98825ea886
commit 80dde69731
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 129 additions and 45 deletions

View File

@ -4,15 +4,26 @@ 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_CREATE_VCPU: c_ulong = _IO(KVMIO, 0x41);
pub const KVM_GET_ONE_REG: c_ulong = _IOW::<KvmOneReg<()>>(KVMIO, 0xab);
#[cfg(target_arch = "aarch64")]
pub const KVM_ARM_VCPU_INIT: c_ulong = _IOW::<KvmVcpuInit>(KVMIO, 0xae);
#[cfg(target_arch = "aarch64")]
pub const KVM_ARM_PREFERRED_TARGET: c_ulong = _IOR::<KvmVcpuInit>(KVMIO, 0xaf);
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;
#[cfg(target_arch = "aarch64")]
pub const KVM_CAP_ARM_VM_IPA_SIZE: c_int = 165;
const KVMIO: c_ulong = 0xAE;
const _IOC_NONE: c_ulong = 0;
const _IOC_WRITE: c_ulong = 1;
const _IOC_READ: c_ulong = 2;
const _IOC_NRSHIFT: c_ulong = 0;
const _IOC_NRBITS: c_ulong = 8;
const _IOC_TYPEBITS: c_ulong = 8;
@ -27,17 +38,53 @@ pub fn KVM_VM_TYPE_ARM_IPA_SIZE(v: c_int) -> c_int {
v & 0xff
}
#[cfg(target_arch = "aarch64")]
#[allow(non_snake_case)]
pub fn ARM64_SYS_REG(op0: u64, op1: u64, crn: u64, crm: u64, op2: u64) -> u64 {
(0x6000000000000000
| 0x0013 << 16
| (op0 << 14) & 0x000000000000c000
| (op1 << 11) & 0x0000000000003800
| (crn << 7) & 0x0000000000000780
| (crm << 3) & 0x0000000000000078
| op2 & 0x0000000000000007)
| 0x0030000000000000
}
#[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 _IOR<T>(ty: c_ulong, nr: c_ulong) -> c_ulong {
_IOC(_IOC_READ, ty, nr, size_of::<T>() as _)
}
#[allow(non_snake_case)]
const fn _IOW<T>(ty: c_ulong, nr: c_ulong) -> c_ulong {
_IOC(_IOC_WRITE, ty, nr, size_of::<T>() as _)
}
#[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)
(dir << _IOC_DIRSHIFT)
| (ty << _IOC_TYPESHIFT)
| (nr << _IOC_NRSHIFT)
| (size << _IOC_SIZESHIFT)
}
#[repr(C)]
pub struct KvmOneReg<'a, T> {
pub id: u64,
pub addr: &'a mut T,
}
#[cfg(target_arch = "aarch64")]
#[repr(C)]
pub struct KvmVcpuInit {
pub target: u32,
pub features: [u32; 7],
}
extern "C" {
@ -48,7 +95,5 @@ extern "C" {
len: u64,
mem: *mut c_void,
) -> c_int;
pub fn kvm_create_vcpu(vm: c_int, id: u32, fd: *mut c_int) -> c_int;
pub fn kvm_run(vcpu: c_int) -> c_int;
}

View File

@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use self::cpu::KvmCpu;
use self::ffi::{
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_set_user_memory_region, KvmOneReg, KVM_API_VERSION, KVM_CAP_MAX_VCPUS, KVM_CAP_ONE_REG,
KVM_CHECK_EXTENSION, KVM_CREATE_VCPU, KVM_CREATE_VM, KVM_GET_API_VERSION, KVM_GET_ONE_REG,
KVM_GET_VCPU_MMAP_SIZE,
};
use super::{CpuFeats, Hypervisor};
@ -63,6 +63,18 @@ pub fn new(cpu: usize, ram: Ram) -> Result<impl Hypervisor, VmmError> {
// Create a VM.
let vm = create_vm(kvm.as_fd())?;
#[cfg(target_arch = "aarch64")]
let preferred_target = unsafe {
let mut v: self::ffi::KvmVcpuInit = std::mem::zeroed();
if ioctl(vm.as_raw_fd(), self::ffi::KVM_ARM_PREFERRED_TARGET, &mut v) < 0 {
return Err(VmmError::GetPreferredTargetFailed(Error::last_os_error()));
}
v
};
// Set RAM.
let slot = 0;
let len = ram.len().try_into().unwrap();
let mem = ram.host_addr().cast_mut().cast();
@ -74,6 +86,8 @@ pub fn new(cpu: usize, ram: Ram) -> Result<impl Hypervisor, VmmError> {
Ok(Kvm {
vcpu_mmap_size: vcpu_mmap_size.try_into().unwrap(),
#[cfg(target_arch = "aarch64")]
preferred_target,
vm,
ram,
kvm,
@ -127,34 +141,70 @@ fn create_vm(kvm: BorrowedFd) -> Result<OwnedFd, VmmError> {
/// Fields in this struct need to drop in a correct order (e.g. vm must be dropped before ram).
struct Kvm {
vcpu_mmap_size: usize,
#[cfg(target_arch = "aarch64")]
preferred_target: self::ffi::KvmVcpuInit,
vm: OwnedFd,
ram: Ram,
#[allow(dead_code)] // kvm are needed by vm.
kvm: OwnedFd,
}
impl Kvm {
#[cfg(target_arch = "aarch64")]
fn create_cpu(&self, id: usize) -> Result<OwnedFd, KvmCpuError> {
use self::ffi::KVM_ARM_VCPU_INIT;
// Create CPU.
let cpu = unsafe { ioctl(self.vm.as_raw_fd(), KVM_CREATE_VCPU, id) };
if cpu < 0 {
return Err(KvmCpuError::CreateCpuFailed(Error::last_os_error()));
}
// Init CPU.
let cpu = unsafe { OwnedFd::from_raw_fd(cpu) };
if unsafe { ioctl(cpu.as_raw_fd(), KVM_ARM_VCPU_INIT, &self.preferred_target) < 0 } {
return Err(KvmCpuError::InitCpuFailed(Error::last_os_error()));
}
Ok(cpu)
}
#[cfg(target_arch = "x86_64")]
fn create_cpu(&self, id: usize) -> Result<OwnedFd, KvmCpuError> {
let cpu = unsafe { ioctl(self.vm.as_raw_fd(), KVM_CREATE_VCPU, id) };
if cpu < 0 {
Err(KvmCpuError::CreateCpuFailed(Error::last_os_error()))
} else {
Ok(unsafe { OwnedFd::from_raw_fd(cpu) })
}
}
}
impl Hypervisor for Kvm {
type Cpu<'a> = KvmCpu<'a>;
type CpuErr = KvmCpuError;
#[cfg(target_arch = "aarch64")]
fn cpu_features(&mut self) -> Result<CpuFeats, Self::CpuErr> {
// See https://www.kernel.org/doc/html/latest/arch/arm64/cpu-feature-registers.html for the
// reason why we can access *_EL1 registers from a user space.
use self::ffi::ARM64_SYS_REG;
use crate::vmm::hv::{Mmfr0, Mmfr1, Mmfr2};
use std::arch::asm;
// ID_AA64MMFR0_EL1.
let mut mmfr0;
unsafe {
asm!(
"mrs {v}, ID_AA64MMFR0_EL1",
v = out(reg) mmfr0,
options(pure, nomem, preserves_flags, nostack)
)
let cpu = self.create_cpu(0)?;
let mut mmfr0 = Mmfr0::default();
let mut req = KvmOneReg {
id: ARM64_SYS_REG(0b11, 0b000, 0b0000, 0b0111, 0b000),
addr: &mut mmfr0,
};
if unsafe { ioctl(cpu.as_raw_fd(), KVM_GET_ONE_REG, &mut req) < 0 } {
return Err(KvmCpuError::ReadMmfr0Failed(Error::last_os_error()));
}
// ID_AA64MMFR1_EL1.
let mut mmfr1;
@ -178,7 +228,7 @@ impl Hypervisor for Kvm {
};
Ok(CpuFeats {
mmfr0: Mmfr0::from_bits(mmfr0),
mmfr0,
mmfr1: Mmfr1::from_bits(mmfr1),
mmfr2: Mmfr2::from_bits(mmfr2),
})
@ -198,17 +248,7 @@ impl Hypervisor for Kvm {
}
fn create_cpu(&self, id: usize) -> Result<Self::Cpu<'_>, Self::CpuErr> {
use std::io::Error;
// Create vCPU.
let id = id.try_into().unwrap();
let mut vcpu = -1;
let vcpu = match unsafe { kvm_create_vcpu(self.vm.as_raw_fd(), id, &mut vcpu) } {
0 => unsafe { OwnedFd::from_raw_fd(vcpu) },
v => return Err(KvmCpuError::CreateVcpuFailed(Error::from_raw_os_error(v))),
};
// Get kvm_run.
let vcpu = self.create_cpu(id)?;
let cx = unsafe {
mmap(
null_mut(),
@ -231,9 +271,17 @@ impl Hypervisor for Kvm {
/// Implementation of [`Hypervisor::CpuErr`].
#[derive(Debug, Error)]
pub enum KvmCpuError {
#[error("failed to create vcpu")]
CreateVcpuFailed(#[source] std::io::Error),
#[error("couldn't create vCPU")]
CreateCpuFailed(#[source] std::io::Error),
#[cfg(target_arch = "aarch64")]
#[error("couldn't initialize vCPU")]
InitCpuFailed(#[source] std::io::Error),
#[error("couldn't get a pointer to kvm_run")]
GetKvmRunFailed(#[source] std::io::Error),
#[cfg(target_arch = "aarch64")]
#[error("couldn't read ID_AA64MMFR0_EL1")]
ReadMmfr0Failed(#[source] std::io::Error),
}

View File

@ -722,6 +722,10 @@ enum VmmError {
#[error("couldn't get the size of vCPU mmap")]
GetMmapSizeFailed(#[source] std::io::Error),
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
#[error("couldn't get preferred CPU target")]
GetPreferredTargetFailed(#[source] std::io::Error),
#[cfg(not(target_os = "macos"))]
#[error("couldn't create Vulkan device")]
CreateVulkanDeviceFailed(#[source] ash::vk::Result),

View File

@ -32,19 +32,6 @@ extern "C" int kvm_set_user_memory_region(
return 0;
}
extern "C" int kvm_create_vcpu(int vm, uint32_t id, int *fd)
{
auto vcpu = ioctl(vm, KVM_CREATE_VCPU, id);
if (vcpu < 0) {
return errno;
}
*fd = vcpu;
return 0;
}
extern "C" int kvm_run(int vcpu)
{
return ioctl(vcpu, KVM_RUN, 0);