xref: /cloud-hypervisor/arch/src/aarch64/mod.rs (revision 190a11f2124b0b60a2d44e85b7c9988373acfb6d)
1 // Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
2 // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 // SPDX-License-Identifier: Apache-2.0
4 
5 /// Module for the flattened device tree.
6 pub mod fdt;
7 /// Layout for this aarch64 system.
8 pub mod layout;
9 /// Module for loading UEFI binary.
10 pub mod uefi;
11 
12 use std::collections::HashMap;
13 use std::fmt::Debug;
14 use std::sync::{Arc, Mutex};
15 
16 use hypervisor::arch::aarch64::gic::Vgic;
17 use hypervisor::arch::aarch64::regs::MPIDR_EL1;
18 use log::{log_enabled, Level};
19 use thiserror::Error;
20 use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
21 
22 pub use self::fdt::DeviceInfoForFdt;
23 use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
24 
25 pub const _NSIG: i32 = 65;
26 
27 /// Errors thrown while configuring aarch64 system.
28 #[derive(Debug, Error)]
29 pub enum Error {
30     /// Failed to create a FDT.
31     #[error("Failed to create a FDT")]
32     SetupFdt,
33 
34     /// Failed to write FDT to memory.
35     #[error("Failed to write FDT to memory")]
36     WriteFdtToMemory(#[source] fdt::Error),
37 
38     /// Failed to create a GIC.
39     #[error("Failed to create a GIC")]
40     SetupGic,
41 
42     /// Failed to compute the initramfs address.
43     #[error("Failed to compute the initramfs address")]
44     InitramfsAddress,
45 
46     /// Error configuring the general purpose registers
47     #[error("Error configuring the general purpose registers")]
48     RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
49 
50     /// Error configuring the MPIDR register
51     #[error("Error configuring the MPIDR register")]
52     VcpuRegMpidr(#[source] hypervisor::HypervisorCpuError),
53 
54     /// Error initializing PMU for vcpu
55     #[error("Error initializing PMU for vcpu")]
56     VcpuInitPmu,
57 }
58 
59 #[derive(Debug, Copy, Clone)]
60 /// Specifies the entry point address where the guest must start
61 /// executing code.
62 pub struct EntryPoint {
63     /// Address in guest memory where the guest must start execution
64     pub entry_addr: GuestAddress,
65 }
66 
67 /// Configure the specified VCPU, and return its MPIDR.
68 pub fn configure_vcpu(
69     vcpu: &Arc<dyn hypervisor::Vcpu>,
70     id: u8,
71     boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
72 ) -> super::Result<u64> {
73     if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
74         vcpu.setup_regs(
75             id,
76             kernel_entry_point.entry_addr.raw_value(),
77             super::layout::FDT_START.raw_value(),
78         )
79         .map_err(Error::RegsConfiguration)?;
80     }
81 
82     let mpidr = vcpu.get_sys_reg(MPIDR_EL1).map_err(Error::VcpuRegMpidr)?;
83     Ok(mpidr)
84 }
85 
86 pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
87     vec![
88         // 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
89         (
90             GuestAddress(0),
91             layout::MEM_32BIT_DEVICES_START.0 as usize,
92             RegionType::Reserved,
93         ),
94         // 256 MiB ~ 768 MiB: MMIO space
95         (
96             layout::MEM_32BIT_DEVICES_START,
97             layout::MEM_32BIT_DEVICES_SIZE as usize,
98             RegionType::SubRegion,
99         ),
100         // 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
101         (
102             layout::PCI_MMCONFIG_START,
103             layout::PCI_MMCONFIG_SIZE as usize,
104             RegionType::Reserved,
105         ),
106         // 1GiB ~ 4032 MiB: RAM before the gap
107         (
108             layout::RAM_START,
109             layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
110             RegionType::Ram,
111         ),
112         // 4GiB ~ inf: RAM after the gap
113         (layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
114         // Add the 32-bit reserved memory hole as a reserved region
115         (
116             layout::MEM_32BIT_RESERVED_START,
117             layout::MEM_32BIT_RESERVED_SIZE as usize,
118             RegionType::Reserved,
119         ),
120     ]
121 }
122 
123 /// Configures the system and should be called once per vm before starting vcpu threads.
124 #[allow(clippy::too_many_arguments)]
125 pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
126     guest_mem: &GuestMemoryMmap,
127     cmdline: &str,
128     vcpu_mpidr: Vec<u64>,
129     vcpu_topology: Option<(u8, u8, u8)>,
130     device_info: &HashMap<(DeviceType, String), T, S>,
131     initrd: &Option<super::InitramfsConfig>,
132     pci_space_info: &[PciSpaceInfo],
133     virtio_iommu_bdf: Option<u32>,
134     gic_device: &Arc<Mutex<dyn Vgic>>,
135     numa_nodes: &NumaNodes,
136     pmu_supported: bool,
137 ) -> super::Result<()> {
138     let fdt_final = fdt::create_fdt(
139         guest_mem,
140         cmdline,
141         vcpu_mpidr,
142         vcpu_topology,
143         device_info,
144         gic_device,
145         initrd,
146         pci_space_info,
147         numa_nodes,
148         virtio_iommu_bdf,
149         pmu_supported,
150     )
151     .map_err(|_| Error::SetupFdt)?;
152 
153     if log_enabled!(Level::Debug) {
154         fdt::print_fdt(&fdt_final);
155     }
156 
157     fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
158 
159     Ok(())
160 }
161 
162 /// Returns the memory address where the initramfs could be loaded.
163 pub fn initramfs_load_addr(
164     guest_mem: &GuestMemoryMmap,
165     initramfs_size: usize,
166 ) -> super::Result<u64> {
167     let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
168     match guest_mem
169         .last_addr()
170         .checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
171     {
172         Some(offset) => {
173             if guest_mem.address_in_range(offset) {
174                 Ok(offset.raw_value())
175             } else {
176                 Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
177             }
178         }
179         None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
180     }
181 }
182 
183 pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
184     let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
185     if host_cpu_phys_bits == 0 {
186         // Host kernel does not support `get_host_ipa_limit`,
187         // we return the default value 40 here.
188         40
189     } else {
190         host_cpu_phys_bits
191     }
192 }
193 
194 #[cfg(test)]
195 mod tests {
196     use super::*;
197 
198     #[test]
199     fn test_arch_memory_regions_dram() {
200         let regions = arch_memory_regions();
201         assert_eq!(6, regions.len());
202         assert_eq!(layout::RAM_START, regions[3].0);
203         assert_eq!(RegionType::Ram, regions[3].2);
204         assert_eq!(RegionType::Reserved, regions[5].2);
205         assert_eq!(RegionType::Ram, regions[4].2);
206     }
207 }
208