aarch64: provide initial vcpu regs via vcpu_init

The code from configure_vcpu_early() is split into two parts:
1. vcpu_features() returns the `VcpuFeature`s required for vcpu.init()
2. vcpu_init() returns the general register state

This makes use of the new generic vcpu_init code path and moves the
general purpose register initialization into configure_vcpu() rather
than the ARM-specific confgiure_vcpu_early() to be consistent with other
architectures. Only the vcpu.init() call needs to happen before irqchip
is finalized on ARM, not other register setup.

BUG=b:237095693
TEST=tools/presubmit --all
TEST=Boot Crostini on trogdor

Change-Id: Ib3eab946ba9f1e407f339c2119d36d280655066f
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3786979
Reviewed-by: Junichi Uekawa <uekawa@chromium.org>
Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Daniel Verkamp 2022-06-27 15:28:01 -07:00 committed by crosvm LUCI
parent 5097b46a62
commit e8fe4b2400
2 changed files with 117 additions and 46 deletions

View file

@ -360,30 +360,39 @@ impl arch::LinuxArch for AArch64 {
} }
}; };
let memory_end = GuestAddress(AARCH64_PHYS_MEM_START + components.memory_size);
let fdt_offset = fdt_address(memory_end, has_bios);
let mut use_pmu = vm let mut use_pmu = vm
.get_hypervisor() .get_hypervisor()
.check_capability(HypervisorCap::ArmPmuV3); .check_capability(HypervisorCap::ArmPmuV3);
let vcpu_count = components.vcpu_count; let vcpu_count = components.vcpu_count;
let mut has_pvtime = true; let mut has_pvtime = true;
let mut vcpus = Vec::with_capacity(vcpu_count); let mut vcpus = Vec::with_capacity(vcpu_count);
let mut vcpu_init = Vec::with_capacity(vcpu_count);
for vcpu_id in 0..vcpu_count { for vcpu_id in 0..vcpu_count {
let vcpu: Vcpu = *vm let vcpu: Vcpu = *vm
.create_vcpu(vcpu_id) .create_vcpu(vcpu_id)
.map_err(Error::CreateVcpu)? .map_err(Error::CreateVcpu)?
.downcast::<Vcpu>() .downcast::<Vcpu>()
.map_err(|_| Error::DowncastVcpu)?; .map_err(|_| Error::DowncastVcpu)?;
Self::configure_vcpu_early( let per_vcpu_init = Self::vcpu_init(
vm.get_memory(),
&vcpu,
vcpu_id, vcpu_id,
use_pmu,
has_bios, has_bios,
fdt_offset,
image_size, image_size,
components.hv_cfg.protection_type, components.hv_cfg.protection_type,
)?; );
has_pvtime &= vcpu.has_pvtime_support(); has_pvtime &= vcpu.has_pvtime_support();
vcpus.push(vcpu); vcpus.push(vcpu);
vcpu_ids.push(vcpu_id); vcpu_ids.push(vcpu_id);
vcpu_init.push(per_vcpu_init);
}
// Initialize Vcpus after all Vcpu objects have been created.
for (vcpu_id, vcpu) in vcpus.iter().enumerate() {
vcpu.init(&Self::vcpu_features(vcpu_id, use_pmu))
.map_err(Error::VcpuInit)?;
} }
irq_chip.finalize().map_err(Error::FinalizeIrqChip)?; irq_chip.finalize().map_err(Error::FinalizeIrqChip)?;
@ -576,7 +585,6 @@ impl arch::LinuxArch for AArch64 {
timeout_sec: VMWDT_DEFAULT_TIMEOUT_SEC, timeout_sec: VMWDT_DEFAULT_TIMEOUT_SEC,
}; };
let memory_end = GuestAddress(AARCH64_PHYS_MEM_START + components.memory_size);
fdt::create_fdt( fdt::create_fdt(
AARCH64_FDT_MAX_SIZE as usize, AARCH64_FDT_MAX_SIZE as usize,
&mem, &mem,
@ -586,7 +594,7 @@ impl arch::LinuxArch for AArch64 {
vcpu_count as u32, vcpu_count as u32,
components.cpu_clusters, components.cpu_clusters,
components.cpu_capacity, components.cpu_capacity,
fdt_address(memory_end, has_bios), fdt_offset,
cmdline.as_str(), cmdline.as_str(),
initrd, initrd,
components.android_fstab, components.android_fstab,
@ -599,8 +607,6 @@ impl arch::LinuxArch for AArch64 {
) )
.map_err(Error::CreateFdt)?; .map_err(Error::CreateFdt)?;
let vcpu_init = vec![VcpuInitAArch64::default(); vcpu_count];
Ok(RunnableLinuxVm { Ok(RunnableLinuxVm {
vm, vm,
vcpu_count, vcpu_count,
@ -631,14 +637,16 @@ impl arch::LinuxArch for AArch64 {
_vm: &V, _vm: &V,
_hypervisor: &dyn Hypervisor, _hypervisor: &dyn Hypervisor,
_irq_chip: &mut dyn IrqChipAArch64, _irq_chip: &mut dyn IrqChipAArch64,
_vcpu: &mut dyn VcpuAArch64, vcpu: &mut dyn VcpuAArch64,
_vcpu_init: VcpuInitAArch64, vcpu_init: VcpuInitAArch64,
_vcpu_id: usize, _vcpu_id: usize,
_num_cpus: usize, _num_cpus: usize,
_has_bios: bool, _has_bios: bool,
_cpu_config: Option<CpuConfigAArch64>, _cpu_config: Option<CpuConfigAArch64>,
) -> std::result::Result<(), Self::Error> { ) -> std::result::Result<(), Self::Error> {
// AArch64 doesn't configure vcpus on the vcpu thread, so nothing to do here. for (reg, value) in vcpu_init.regs.iter() {
vcpu.set_one_reg(*reg, *value).map_err(Error::SetReg)?;
}
Ok(()) Ok(())
} }
@ -808,42 +816,42 @@ impl AArch64 {
Ok(()) Ok(())
} }
/// Sets up `vcpu`. /// Get ARM-specific features for vcpu with index `vcpu_id`.
///
/// AArch64 needs vcpus set up before its kernel IRQ chip is created, so `configure_vcpu_early`
/// is called from `build_vm` on the main thread. `LinuxArch::configure_vcpu`, which is used
/// by X86_64 to do setup later from the vcpu thread, is a no-op on AArch64 since vcpus were
/// already configured here.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `guest_mem` - The guest memory object.
/// * `vcpu` - The vcpu to configure.
/// * `vcpu_id` - The VM's index for `vcpu`. /// * `vcpu_id` - The VM's index for `vcpu`.
/// * `use_pmu` - Should `vcpu` be configured to use the Performance Monitor Unit. /// * `use_pmu` - Should `vcpu` be configured to use the Performance Monitor Unit.
fn configure_vcpu_early( fn vcpu_features(vcpu_id: usize, use_pmu: bool) -> Vec<VcpuFeature> {
guest_mem: &GuestMemory,
vcpu: &dyn VcpuAArch64,
vcpu_id: usize,
use_pmu: bool,
has_bios: bool,
image_size: usize,
protection_type: ProtectionType,
) -> Result<()> {
let mut features = vec![VcpuFeature::PsciV0_2]; let mut features = vec![VcpuFeature::PsciV0_2];
if use_pmu { if use_pmu {
features.push(VcpuFeature::PmuV3); features.push(VcpuFeature::PmuV3);
} }
// Non-boot cpus are powered off initially // Non-boot cpus are powered off initially
if vcpu_id != 0 { if vcpu_id != 0 {
features.push(VcpuFeature::PowerOff) features.push(VcpuFeature::PowerOff);
} }
vcpu.init(&features).map_err(Error::VcpuInit)?;
features
}
/// Get initial register state for vcpu with index `vcpu_id`.
///
/// # Arguments
///
/// * `vcpu_id` - The VM's index for `vcpu`.
fn vcpu_init(
vcpu_id: usize,
has_bios: bool,
fdt_address: GuestAddress,
image_size: usize,
protection_type: ProtectionType,
) -> VcpuInitAArch64 {
let mut regs: BTreeMap<VcpuRegAArch64, u64> = Default::default();
// All interrupts masked // All interrupts masked
let pstate = PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL1H; let pstate = PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL1H;
vcpu.set_one_reg(VcpuRegAArch64::Pstate, pstate) regs.insert(VcpuRegAArch64::Pstate, pstate);
.map_err(Error::SetReg)?;
// Other cpus are powered off initially // Other cpus are powered off initially
if vcpu_id == 0 { if vcpu_id == 0 {
@ -863,28 +871,22 @@ impl AArch64 {
/* PC -- entry point */ /* PC -- entry point */
if let Some(entry) = entry_addr { if let Some(entry) = entry_addr {
vcpu.set_one_reg(VcpuRegAArch64::Pc, entry) regs.insert(VcpuRegAArch64::Pc, entry);
.map_err(Error::SetReg)?;
} }
/* X0 -- fdt address */ /* X0 -- fdt address */
let memory_end = guest_mem.end_addr(); regs.insert(VcpuRegAArch64::X(0), fdt_address.offset());
let fdt_addr = fdt_address(memory_end, has_bios);
vcpu.set_one_reg(VcpuRegAArch64::X(0), fdt_addr.offset())
.map_err(Error::SetReg)?;
if protection_type.runs_firmware() { if protection_type.runs_firmware() {
/* X1 -- payload entry point */ /* X1 -- payload entry point */
vcpu.set_one_reg(VcpuRegAArch64::X(1), image_addr.offset()) regs.insert(VcpuRegAArch64::X(1), image_addr.offset());
.map_err(Error::SetReg)?;
/* X2 -- image size */ /* X2 -- image size */
vcpu.set_one_reg(VcpuRegAArch64::X(2), image_size as u64) regs.insert(VcpuRegAArch64::X(2), image_size as u64);
.map_err(Error::SetReg)?;
} }
} }
Ok(()) VcpuInitAArch64 { regs }
} }
} }
@ -912,3 +914,66 @@ impl MsrHandlers {
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vcpu_init_unprotected_kernel() {
let has_bios = false;
let fdt_address = GuestAddress(0x1234);
let image_size = 0x1000;
let prot = ProtectionType::Unprotected;
let vcpu_init = AArch64::vcpu_init(0, has_bios, fdt_address, image_size, prot);
// PC: kernel image entry point
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::Pc), Some(&0x8080_0000));
// X0: fdt_offset
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::X(0)), Some(&0x1234));
}
#[test]
fn vcpu_init_unprotected_bios() {
let has_bios = true;
let fdt_address = GuestAddress(0x1234);
let image_size = 0x1000;
let prot = ProtectionType::Unprotected;
let vcpu_init = AArch64::vcpu_init(0, has_bios, fdt_address, image_size, prot);
// PC: bios image entry point
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::Pc), Some(&0x8020_0000));
// X0: fdt_offset
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::X(0)), Some(&0x1234));
}
#[test]
fn vcpu_init_protected_kernel() {
let has_bios = false;
let fdt_address = GuestAddress(0x1234);
let image_size = 0x1000;
let prot = ProtectionType::Protected;
let vcpu_init = AArch64::vcpu_init(0, has_bios, fdt_address, image_size, prot);
// The hypervisor provides the initial value of PC, so PC should not be present in the
// vcpu_init register map.
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::Pc), None);
// X0: fdt_offset
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::X(0)), Some(&0x1234));
// X1: kernel image entry point
assert_eq!(
vcpu_init.regs.get(&VcpuRegAArch64::X(1)),
Some(&0x8080_0000)
);
// X2: image size
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::X(2)), Some(&0x1000));
}
}

View file

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
use std::collections::BTreeMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use base::Error; use base::Error;
@ -49,6 +50,7 @@ impl TryFrom<u32> for PsciVersion {
pub const PSCI_0_2: PsciVersion = PsciVersion { major: 0, minor: 2 }; pub const PSCI_0_2: PsciVersion = PsciVersion { major: 0, minor: 2 };
pub const PSCI_1_0: PsciVersion = PsciVersion { major: 1, minor: 0 }; pub const PSCI_1_0: PsciVersion = PsciVersion { major: 1, minor: 0 };
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum VcpuRegAArch64 { pub enum VcpuRegAArch64 {
X(u8), X(u8),
Sp, Sp,
@ -125,9 +127,13 @@ pub trait VcpuAArch64: Vcpu {
impl_downcast!(VcpuAArch64); impl_downcast!(VcpuAArch64);
/// Initial state for AArch64 VCPUs. /// Initial register state for AArch64 VCPUs.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct VcpuInitAArch64 {} pub struct VcpuInitAArch64 {
/// Initial register state as a map of register name to value pairs. Registers that do not have
/// a value specified in this map will retain the original value provided by the hypervisor.
pub regs: BTreeMap<VcpuRegAArch64, u64>,
}
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct CpuConfigAArch64 {} pub struct CpuConfigAArch64 {}