crosvm/hypervisor/src/kvm/aarch64.rs
Andrew Walbran e79aba1026 Check KVM_CAP_ARM_PROTECTED_VM after creating a protected VM.
This should have been updated with the pKVM ABI change in
https://crrev.com/c/3330204, as protected mode is now selected when
creating the VM, and pVM firmmware is optional.

BUG=b:209794844

Change-Id: I2850b4c2ef523a15b8215df070eb9da066a67cb5
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3420330
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Andrew Walbran <qwandor@google.com>
2022-01-28 16:16:26 +00:00

398 lines
14 KiB
Rust

// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use libc::{EINVAL, ENOMEM, ENOSYS, ENXIO};
use base::{
errno_result, error, ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val, Error,
MemoryMappingBuilder, Result,
};
use kvm_sys::*;
use vm_memory::GuestAddress;
use super::{Kvm, KvmCap, KvmVcpu, KvmVm};
use crate::{
ClockState, DeviceKind, Hypervisor, IrqSourceChip, ProtectionType, PsciVersion, VcpuAArch64,
VcpuFeature, Vm, VmAArch64, VmCap,
};
impl Kvm {
// Compute the machine type, which should be the IPA range for the VM
// Ideally, this would take a description of the memory map and return
// the closest machine type for this VM. Here, we just return the maximum
// the kernel support.
pub fn get_vm_type(&self, protection_type: ProtectionType) -> Result<u32> {
// Safe because we know self is a real kvm fd
let ipa_size = match unsafe {
ioctl_with_val(self, KVM_CHECK_EXTENSION(), KVM_CAP_ARM_VM_IPA_SIZE.into())
} {
// Not supported? Use 0 as the machine type, which implies 40bit IPA
ret if ret < 0 => 0,
ipa => ipa as u32,
};
let protection_flag = match protection_type {
ProtectionType::Unprotected => 0,
ProtectionType::Protected | ProtectionType::ProtectedWithoutFirmware => {
KVM_VM_TYPE_ARM_PROTECTED
}
};
// Use the lower 8 bits representing the IPA space as the machine type
Ok((ipa_size & KVM_VM_TYPE_ARM_IPA_SIZE_MASK) | protection_flag)
}
/// Get the size of guest physical addresses (IPA) in bits.
pub fn get_guest_phys_addr_bits(&self) -> u8 {
// Safe because we know self is a real kvm fd
let vm_ipa_size = match unsafe {
ioctl_with_val(self, KVM_CHECK_EXTENSION(), KVM_CAP_ARM_VM_IPA_SIZE.into())
} {
// Default physical address size is 40 bits if the extension is not supported.
ret if ret <= 0 => 40,
ipa => ipa as u8,
};
vm_ipa_size
}
}
impl KvmVm {
/// Checks if a particular `VmCap` is available, or returns None if arch-independent
/// Vm.check_capability() should handle the check.
pub fn check_capability_arch(&self, _c: VmCap) -> Option<bool> {
None
}
/// Returns the params to pass to KVM_CREATE_DEVICE for a `kind` device on this arch, or None to
/// let the arch-independent `KvmVm::create_device` handle it.
pub fn get_device_params_arch(&self, kind: DeviceKind) -> Option<kvm_create_device> {
match kind {
DeviceKind::ArmVgicV2 => Some(kvm_create_device {
type_: kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V2,
fd: 0,
flags: 0,
}),
DeviceKind::ArmVgicV3 => Some(kvm_create_device {
type_: kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3,
fd: 0,
flags: 0,
}),
_ => None,
}
}
/// Arch-specific implementation of `Vm::get_pvclock`. Always returns an error on AArch64.
pub fn get_pvclock_arch(&self) -> Result<ClockState> {
Err(Error::new(ENXIO))
}
/// Arch-specific implementation of `Vm::set_pvclock`. Always returns an error on AArch64.
pub fn set_pvclock_arch(&self, _state: &ClockState) -> Result<()> {
Err(Error::new(ENXIO))
}
fn get_protected_vm_info(&self) -> Result<KvmProtectedVmInfo> {
let mut info = KvmProtectedVmInfo {
firmware_size: 0,
reserved: [0; 7],
};
// Safe because we allocated the struct and we know the kernel won't write beyond the end of
// the struct or keep a pointer to it.
unsafe {
self.enable_raw_capability(
KvmCap::ArmProtectedVm,
KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO,
&[&mut info as *mut KvmProtectedVmInfo as u64, 0, 0, 0],
)
}?;
Ok(info)
}
fn set_protected_vm_firmware_ipa(&self, fw_addr: GuestAddress) -> Result<()> {
// Safe because none of the args are pointers.
unsafe {
self.enable_raw_capability(
KvmCap::ArmProtectedVm,
KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA,
&[fw_addr.0, 0, 0, 0],
)
}
}
}
#[repr(C)]
struct KvmProtectedVmInfo {
firmware_size: u64,
reserved: [u64; 7],
}
impl VmAArch64 for KvmVm {
fn get_hypervisor(&self) -> &dyn Hypervisor {
&self.kvm
}
fn load_protected_vm_firmware(
&mut self,
fw_addr: GuestAddress,
fw_max_size: u64,
) -> Result<()> {
let info = self.get_protected_vm_info()?;
if info.firmware_size == 0 {
Err(Error::new(EINVAL))
} else {
if info.firmware_size > fw_max_size {
return Err(Error::new(ENOMEM));
}
let mem = MemoryMappingBuilder::new(info.firmware_size as usize)
.build()
.map_err(|_| Error::new(EINVAL))?;
self.add_memory_region(fw_addr, Box::new(mem), false, false)?;
self.set_protected_vm_firmware_ipa(fw_addr)
}
}
fn create_vcpu(&self, id: usize) -> Result<Box<dyn VcpuAArch64>> {
// create_vcpu is declared separately in VmAArch64 and VmX86, so it can return VcpuAArch64
// or VcpuX86. But both use the same implementation in KvmVm::create_vcpu.
Ok(Box::new(KvmVm::create_vcpu(self, id)?))
}
}
impl KvmVcpu {
/// Arch-specific implementation of `Vcpu::pvclock_ctrl`. Always returns an error on AArch64.
pub fn pvclock_ctrl_arch(&self) -> Result<()> {
Err(Error::new(ENXIO))
}
}
impl VcpuAArch64 for KvmVcpu {
fn init(&self, features: &[VcpuFeature]) -> Result<()> {
let mut kvi = kvm_vcpu_init {
target: KVM_ARM_TARGET_GENERIC_V8,
features: [0; 7],
};
// Safe because we allocated the struct and we know the kernel will write exactly the size
// of the struct.
let ret = unsafe { ioctl_with_mut_ref(&self.vm, KVM_ARM_PREFERRED_TARGET(), &mut kvi) };
if ret != 0 {
return errno_result();
}
for f in features {
let shift = match f {
VcpuFeature::PsciV0_2 => KVM_ARM_VCPU_PSCI_0_2,
VcpuFeature::PmuV3 => KVM_ARM_VCPU_PMU_V3,
VcpuFeature::PowerOff => KVM_ARM_VCPU_POWER_OFF,
};
kvi.features[0] |= 1 << shift;
}
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, KVM_ARM_VCPU_INIT(), &kvi) };
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
fn init_pmu(&self, irq: u64) -> Result<()> {
let irq_addr = &irq as *const u64;
// The in-kernel PMU virtualization is initialized by setting the irq
// with KVM_ARM_VCPU_PMU_V3_IRQ and then by KVM_ARM_VCPU_PMU_V3_INIT.
let irq_attr = kvm_device_attr {
group: KVM_ARM_VCPU_PMU_V3_CTRL,
attr: KVM_ARM_VCPU_PMU_V3_IRQ as u64,
addr: irq_addr as u64,
flags: 0,
};
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, kvm_sys::KVM_HAS_DEVICE_ATTR(), &irq_attr) };
if ret < 0 {
return errno_result();
}
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, kvm_sys::KVM_SET_DEVICE_ATTR(), &irq_attr) };
if ret < 0 {
return errno_result();
}
let init_attr = kvm_device_attr {
group: KVM_ARM_VCPU_PMU_V3_CTRL,
attr: KVM_ARM_VCPU_PMU_V3_INIT as u64,
addr: 0,
flags: 0,
};
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, kvm_sys::KVM_SET_DEVICE_ATTR(), &init_attr) };
if ret < 0 {
return errno_result();
}
Ok(())
}
fn has_pvtime_support(&self) -> bool {
// The in-kernel PV time structure is initialized by setting the base
// address with KVM_ARM_VCPU_PVTIME_IPA
let pvtime_attr = kvm_device_attr {
group: KVM_ARM_VCPU_PVTIME_CTRL,
attr: KVM_ARM_VCPU_PVTIME_IPA as u64,
addr: 0,
flags: 0,
};
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, kvm_sys::KVM_HAS_DEVICE_ATTR(), &pvtime_attr) };
if ret < 0 {
return false;
}
return true;
}
fn init_pvtime(&self, pvtime_ipa: u64) -> Result<()> {
let pvtime_ipa_addr = &pvtime_ipa as *const u64;
// The in-kernel PV time structure is initialized by setting the base
// address with KVM_ARM_VCPU_PVTIME_IPA
let pvtime_attr = kvm_device_attr {
group: KVM_ARM_VCPU_PVTIME_CTRL,
attr: KVM_ARM_VCPU_PVTIME_IPA as u64,
addr: pvtime_ipa_addr as u64,
flags: 0,
};
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, kvm_sys::KVM_SET_DEVICE_ATTR(), &pvtime_attr) };
if ret < 0 {
return errno_result();
}
Ok(())
}
fn set_one_reg(&self, reg_id: u64, data: u64) -> Result<()> {
let data_ref = &data as *const u64;
let onereg = kvm_one_reg {
id: reg_id,
addr: data_ref as u64,
};
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, KVM_SET_ONE_REG(), &onereg) };
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
fn get_one_reg(&self, reg_id: u64) -> Result<u64> {
let val: u64 = 0;
let mut onereg = kvm_one_reg {
id: reg_id,
addr: (&val as *const u64) as u64,
};
// Safe because we allocated the struct and we know the kernel will read exactly the size of
// the struct.
let ret = unsafe { ioctl_with_ref(self, KVM_GET_ONE_REG(), &mut onereg) };
if ret == 0 {
Ok(val)
} else {
return errno_result();
}
}
fn get_psci_version(&self) -> Result<PsciVersion> {
// The definition of KVM_REG_ARM_PSCI_VERSION is in arch/arm64/include/uapi/asm/kvm.h.
const KVM_REG_ARM_PSCI_VERSION: u64 =
KVM_REG_ARM64 | (KVM_REG_SIZE_U64 as u64) | (KVM_REG_ARM_FW as u64);
match self.get_one_reg(KVM_REG_ARM_PSCI_VERSION) {
Ok(v) => {
let major = (v >> PSCI_VERSION_MAJOR_SHIFT) as u32;
let minor = (v as u32) & PSCI_VERSION_MINOR_MASK;
Ok(PsciVersion { major, minor })
}
Err(_) => {
// When `KVM_REG_ARM_PSCI_VERSION` is not supported, we can return PSCI 0.2, as vCPU
// has been initialized with `KVM_ARM_VCPU_PSCI_0_2` successfully.
Ok(PsciVersion { major: 0, minor: 2 })
}
}
}
}
// This function translates an IrqSrouceChip to the kvm u32 equivalent. It has a different
// implementation between x86_64 and aarch64 because the irqchip KVM constants are not defined on
// all architectures.
pub(super) fn chip_to_kvm_chip(chip: IrqSourceChip) -> u32 {
match chip {
// ARM does not have a constant for this, but the default routing
// setup seems to set this to 0
IrqSourceChip::Gic => 0,
_ => {
error!("Invalid IrqChipSource for ARM {:?}", chip);
0
}
}
}
#[cfg(test)]
mod tests {
use super::super::Kvm;
use super::*;
use crate::{IrqRoute, IrqSource, IrqSourceChip};
use vm_memory::{GuestAddress, GuestMemory};
#[test]
fn set_gsi_routing() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.create_irq_chip().unwrap();
vm.set_gsi_routing(&[]).unwrap();
vm.set_gsi_routing(&[IrqRoute {
gsi: 1,
source: IrqSource::Irqchip {
chip: IrqSourceChip::Gic,
pin: 3,
},
}])
.unwrap();
vm.set_gsi_routing(&[IrqRoute {
gsi: 1,
source: IrqSource::Msi {
address: 0xf000000,
data: 0xa0,
},
}])
.unwrap();
vm.set_gsi_routing(&[
IrqRoute {
gsi: 1,
source: IrqSource::Irqchip {
chip: IrqSourceChip::Gic,
pin: 3,
},
},
IrqRoute {
gsi: 2,
source: IrqSource::Msi {
address: 0xf000000,
data: 0xa0,
},
},
])
.unwrap();
}
}