mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-08-20 15:01:16 +00:00
aarch64: Porting AArch64 register implementation from Firecracker
As on AArch64 systems we need register mpidr to create the flattened device tree, here in this commit we add ported AArch64 register implementation from Firecracker and related changes to make this commit build. Signed-off-by: Henry Wang <Henry.Wang@arm.com>
This commit is contained in:
parent
d605fda3f7
commit
5a18dd36e2
@ -7,10 +7,15 @@ mod gicv2;
|
|||||||
mod gicv3;
|
mod gicv3;
|
||||||
/// Layout for this aarch64 system.
|
/// Layout for this aarch64 system.
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
/// Logic for configuring aarch64 registers.
|
||||||
|
pub mod regs;
|
||||||
|
|
||||||
use crate::RegionType;
|
use crate::RegionType;
|
||||||
use kvm_ioctls::*;
|
use kvm_ioctls::*;
|
||||||
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize};
|
use std::fmt::Debug;
|
||||||
|
use vm_memory::{
|
||||||
|
Address, GuestAddress, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {}
|
pub enum Error {}
|
||||||
@ -59,6 +64,21 @@ pub fn get_reserved_mem_addr() -> usize {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auxiliary function to get the address where the device tree blob is loaded.
|
||||||
|
fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
|
||||||
|
// If the memory allocated is smaller than the size allocated for the FDT,
|
||||||
|
// we return the start of the DRAM so that
|
||||||
|
// we allow the code to try and load the FDT.
|
||||||
|
|
||||||
|
if let Some(addr) = mem.last_addr().checked_sub(layout::FDT_MAX_SIZE as u64 - 1) {
|
||||||
|
if mem.address_in_range(addr) {
|
||||||
|
return addr.raw_value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout::RAM_64BIT_START
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||||
// The value returned here is used to determine the physical address space size
|
// The value returned here is used to determine the physical address space size
|
||||||
// for a VM (IPA size).
|
// for a VM (IPA size).
|
||||||
|
198
arch/src/aarch64/regs.rs
Normal file
198
arch/src/aarch64/regs.rs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
// Portions Copyright 2017 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 THIRD-PARTY file.
|
||||||
|
|
||||||
|
use std::{mem, result};
|
||||||
|
|
||||||
|
use super::get_fdt_addr;
|
||||||
|
use kvm_bindings::{
|
||||||
|
user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK,
|
||||||
|
KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_CRN_SHIFT,
|
||||||
|
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP1_MASK,
|
||||||
|
KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT,
|
||||||
|
KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
|
||||||
|
};
|
||||||
|
use kvm_ioctls::VcpuFd;
|
||||||
|
|
||||||
|
use vm_memory::GuestMemoryMmap;
|
||||||
|
|
||||||
|
/// Errors thrown while setting aarch64 registers.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Failed to set core register (PC, PSTATE or general purpose ones).
|
||||||
|
SetCoreRegister(kvm_ioctls::Error),
|
||||||
|
/// Failed to get a system register.
|
||||||
|
GetSysRegister(kvm_ioctls::Error),
|
||||||
|
}
|
||||||
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
// PSR (Processor State Register) bits.
|
||||||
|
// Taken from arch/arm64/include/uapi/asm/ptrace.h.
|
||||||
|
const PSR_MODE_EL1h: u64 = 0x0000_0005;
|
||||||
|
const PSR_F_BIT: u64 = 0x0000_0040;
|
||||||
|
const PSR_I_BIT: u64 = 0x0000_0080;
|
||||||
|
const PSR_A_BIT: u64 = 0x0000_0100;
|
||||||
|
const PSR_D_BIT: u64 = 0x0000_0200;
|
||||||
|
// Taken from arch/arm64/kvm/inject_fault.c.
|
||||||
|
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
|
||||||
|
|
||||||
|
// Following are macros that help with getting the ID of a aarch64 core register.
|
||||||
|
// The core register are represented by the user_pt_regs structure. Look for it in
|
||||||
|
// arch/arm64/include/uapi/asm/ptrace.h.
|
||||||
|
|
||||||
|
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
|
||||||
|
// an instance of that structure.
|
||||||
|
// It uses a null pointer to retrieve the offset to the field.
|
||||||
|
// Inspired by C solution: `#define offsetof(str, f) ((size_t)(&((str *)0)->f))`.
|
||||||
|
// Doing `offset__of!(user_pt_regs, pstate)` in our rust code will trigger the following:
|
||||||
|
// unsafe { &(*(0 as *const user_pt_regs)).pstate as *const _ as usize }
|
||||||
|
// The dereference expression produces an lvalue, but that lvalue is not actually read from,
|
||||||
|
// we're just doing pointer math on it, so in theory, it should safe.
|
||||||
|
macro_rules! offset__of {
|
||||||
|
($str:ty, $field:ident) => {
|
||||||
|
unsafe { &(*(0 as *const $str)).$field as *const _ as usize }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! arm64_core_reg {
|
||||||
|
($reg: tt) => {
|
||||||
|
// As per `kvm_arm_copy_reg_indices`, the id of a core register can be obtained like this:
|
||||||
|
// `const u64 core_reg = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | i`, where i is obtained with:
|
||||||
|
// `for (i = 0; i < sizeof(struct kvm_regs) / sizeof(__u32); i++) {`
|
||||||
|
// We are using here `user_pt_regs` since this structure contains the core register and it is at
|
||||||
|
// the start of `kvm_regs`.
|
||||||
|
// struct kvm_regs {
|
||||||
|
// struct user_pt_regs regs; /* sp = sp_el0 */
|
||||||
|
//
|
||||||
|
// __u64 sp_el1;
|
||||||
|
// __u64 elr_el1;
|
||||||
|
//
|
||||||
|
// __u64 spsr[KVM_NR_SPSR];
|
||||||
|
//
|
||||||
|
// struct user_fpsimd_state fp_regs;
|
||||||
|
//};
|
||||||
|
// struct user_pt_regs {
|
||||||
|
// __u64 regs[31];
|
||||||
|
// __u64 sp;
|
||||||
|
// __u64 pc;
|
||||||
|
// __u64 pstate;
|
||||||
|
//};
|
||||||
|
// In our implementation we need: pc, pstate and user_pt_regs->regs[0].
|
||||||
|
KVM_REG_ARM64 as u64
|
||||||
|
| KVM_REG_SIZE_U64 as u64
|
||||||
|
| u64::from(KVM_REG_ARM_CORE)
|
||||||
|
| ((offset__of!(user_pt_regs, $reg) / mem::size_of::<u32>()) as u64)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes the ID of a specific ARM64 system register similar to how
|
||||||
|
// the kernel C macro does.
|
||||||
|
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/uapi/asm/kvm.h#L203
|
||||||
|
macro_rules! arm64_sys_reg {
|
||||||
|
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
||||||
|
const $name: u64 = KVM_REG_ARM64 as u64
|
||||||
|
| KVM_REG_SIZE_U64 as u64
|
||||||
|
| KVM_REG_ARM64_SYSREG as u64
|
||||||
|
| ((($op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT)
|
||||||
|
& KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
|
||||||
|
| ((($op1 as u64) << KVM_REG_ARM64_SYSREG_OP1_SHIFT)
|
||||||
|
& KVM_REG_ARM64_SYSREG_OP1_MASK as u64)
|
||||||
|
| ((($crn as u64) << KVM_REG_ARM64_SYSREG_CRN_SHIFT)
|
||||||
|
& KVM_REG_ARM64_SYSREG_CRN_MASK as u64)
|
||||||
|
| ((($crm as u64) << KVM_REG_ARM64_SYSREG_CRM_SHIFT)
|
||||||
|
& KVM_REG_ARM64_SYSREG_CRM_MASK as u64)
|
||||||
|
| ((($op2 as u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT)
|
||||||
|
& KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant imported from the Linux kernel:
|
||||||
|
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/asm/sysreg.h#L135
|
||||||
|
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
||||||
|
|
||||||
|
/// Configure core registers for a given CPU.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
|
/// * `cpu_id` - Index of current vcpu.
|
||||||
|
/// * `boot_ip` - Starting instruction pointer.
|
||||||
|
/// * `mem` - Reserved DRAM for current VM.
|
||||||
|
pub fn setup_regs(vcpu: &VcpuFd, cpu_id: u8, boot_ip: u64, mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
|
// Get the register index of the PSTATE (Processor State) register.
|
||||||
|
vcpu.set_one_reg(arm64_core_reg!(pstate), PSTATE_FAULT_BITS_64)
|
||||||
|
.map_err(Error::SetCoreRegister)?;
|
||||||
|
|
||||||
|
// Other vCPUs are powered off initially awaiting PSCI wakeup.
|
||||||
|
if cpu_id == 0 {
|
||||||
|
// Setting the PC (Processor Counter) to the current program address (kernel address).
|
||||||
|
vcpu.set_one_reg(arm64_core_reg!(pc), boot_ip)
|
||||||
|
.map_err(Error::SetCoreRegister)?;
|
||||||
|
|
||||||
|
// Last mandatory thing to set -> the address pointing to the FDT (also called DTB).
|
||||||
|
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
|
||||||
|
// not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||||
|
// We are choosing to place it the end of DRAM. See `get_fdt_addr`.
|
||||||
|
vcpu.set_one_reg(arm64_core_reg!(regs), get_fdt_addr(mem) as u64)
|
||||||
|
.map_err(Error::SetCoreRegister)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the MPIDR - Multiprocessor Affinity Register.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
|
pub fn read_mpidr(vcpu: &VcpuFd) -> Result<u64> {
|
||||||
|
vcpu.get_one_reg(MPIDR_EL1).map_err(Error::GetSysRegister)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::aarch64::layout;
|
||||||
|
use kvm_ioctls::Kvm;
|
||||||
|
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setup_regs() {
|
||||||
|
let kvm = Kvm::new().unwrap();
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
let mut regions = Vec::new();
|
||||||
|
regions.push((
|
||||||
|
GuestAddress(layout::RAM_64BIT_START),
|
||||||
|
(layout::FDT_MAX_SIZE + 0x1000) as usize,
|
||||||
|
));
|
||||||
|
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||||
|
|
||||||
|
match setup_regs(&vcpu, 0, 0x0, &mem).unwrap_err() {
|
||||||
|
Error::SetCoreRegister(ref e) => assert_eq!(e.errno(), libc::ENOEXEC),
|
||||||
|
_ => panic!("Expected to receive Error::SetCoreRegister"),
|
||||||
|
}
|
||||||
|
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||||
|
vm.get_preferred_target(&mut kvi).unwrap();
|
||||||
|
vcpu.vcpu_init(&kvi).unwrap();
|
||||||
|
|
||||||
|
assert!(setup_regs(&vcpu, 0, 0x0, &mem).is_ok());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_read_mpidr() {
|
||||||
|
let kvm = Kvm::new().unwrap();
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||||
|
vm.get_preferred_target(&mut kvi).unwrap();
|
||||||
|
|
||||||
|
// Must fail when vcpu is not initialized yet.
|
||||||
|
assert!(read_mpidr(&vcpu).is_err());
|
||||||
|
|
||||||
|
vcpu.vcpu_init(&kvi).unwrap();
|
||||||
|
assert_eq!(read_mpidr(&vcpu).unwrap(), 0x80000000);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user