diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index 76b00ef99..586fbcad4 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -7,10 +7,15 @@ mod gicv2; mod gicv3; /// Layout for this aarch64 system. pub mod layout; +/// Logic for configuring aarch64 registers. +pub mod regs; use crate::RegionType; 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)] pub enum Error {} @@ -59,6 +64,21 @@ pub fn get_reserved_mem_addr() -> usize { 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 { // The value returned here is used to determine the physical address space size // for a VM (IPA size). diff --git a/arch/src/aarch64/regs.rs b/arch/src/aarch64/regs.rs new file mode 100644 index 000000000..dac3b6430 --- /dev/null +++ b/arch/src/aarch64/regs.rs @@ -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 = result::Result; + +#[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::()) 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 { + 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); + } +}