xref: /cloud-hypervisor/arch/src/aarch64/mod.rs (revision 7d7bfb2034001d4cb15df2ddc56d2d350c8da30f)
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 /// Module for the global interrupt controller configuration.
8 pub mod gic;
9 /// Layout for this aarch64 system.
10 pub mod layout;
11 /// Logic for configuring aarch64 registers.
12 pub mod regs;
13 /// Module for loading UEFI binary.
14 pub mod uefi;
15 
16 pub use self::fdt::DeviceInfoForFdt;
17 use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
18 use gic::GicDevice;
19 use log::{log_enabled, Level};
20 use std::collections::HashMap;
21 use std::convert::TryInto;
22 use std::fmt::Debug;
23 use std::sync::Arc;
24 use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
25 
26 /// Errors thrown while configuring aarch64 system.
27 #[derive(Debug)]
28 pub enum Error {
29     /// Failed to create a FDT.
30     SetupFdt,
31 
32     /// Failed to write FDT to memory.
33     WriteFdtToMemory(fdt::Error),
34 
35     /// Failed to create a GIC.
36     SetupGic(gic::Error),
37 
38     /// Failed to compute the initramfs address.
39     InitramfsAddress,
40 
41     /// Error configuring the general purpose registers
42     RegsConfiguration(regs::Error),
43 
44     /// Error configuring the MPIDR register
45     VcpuRegMpidr(hypervisor::HypervisorCpuError),
46 
47     /// Error initializing PMU for vcpu
48     VcpuInitPmu,
49 }
50 
51 impl From<Error> for super::Error {
52     fn from(e: Error) -> super::Error {
53         super::Error::AArch64Setup(e)
54     }
55 }
56 
57 #[derive(Debug, Copy, Clone)]
58 /// Specifies the entry point address where the guest must start
59 /// executing code.
60 pub struct EntryPoint {
61     /// Address in guest memory where the guest must start execution
62     pub entry_addr: GuestAddress,
63 }
64 
65 /// Configure the specified VCPU, and return its MPIDR.
66 pub fn configure_vcpu(
67     fd: &Arc<dyn hypervisor::Vcpu>,
68     id: u8,
69     kernel_entry_point: Option<EntryPoint>,
70 ) -> super::Result<u64> {
71     if let Some(kernel_entry_point) = kernel_entry_point {
72         regs::setup_regs(fd, id, kernel_entry_point.entry_addr.raw_value())
73             .map_err(Error::RegsConfiguration)?;
74     }
75 
76     let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMpidr)?;
77     Ok(mpidr)
78 }
79 
80 pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
81     let mut regions = vec![
82         // 0 ~ 4 MiB: Reserved for UEFI space
83         (GuestAddress(0), layout::UEFI_SIZE as usize, RegionType::Ram),
84         // 4 MiB ~ 256 MiB: Gic and legacy devices
85         (
86             GuestAddress(layout::UEFI_SIZE),
87             (layout::MEM_32BIT_DEVICES_START.0 - layout::UEFI_SIZE) as usize,
88             RegionType::Reserved,
89         ),
90         // 256 MiB ~ 768 MiB: MMIO space
91         (
92             layout::MEM_32BIT_DEVICES_START,
93             layout::MEM_32BIT_DEVICES_SIZE as usize,
94             RegionType::SubRegion,
95         ),
96         // 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
97         (
98             layout::PCI_MMCONFIG_START,
99             layout::PCI_MMCONFIG_SIZE as usize,
100             RegionType::Reserved,
101         ),
102     ];
103 
104     // Normally UEFI should be loaded to a flash area at the beginning of memory.
105     // But now flash memory type is not supported.
106     // As a workaround, we take 4 MiB memory from the main RAM for UEFI.
107     // As a result, the RAM that the guest can see is less than what has been
108     // assigned in command line, when ACPI and UEFI is enabled.
109     let ram_size = size - layout::UEFI_SIZE;
110     let ram_32bit_space_size =
111         layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START);
112 
113     // RAM space
114     // Case1: guest memory fits before the gap
115     if ram_size as u64 <= ram_32bit_space_size {
116         regions.push((layout::RAM_START, ram_size as usize, RegionType::Ram));
117     // Case2: guest memory extends beyond the gap
118     } else {
119         // Push memory before the gap
120         regions.push((
121             layout::RAM_START,
122             ram_32bit_space_size as usize,
123             RegionType::Ram,
124         ));
125         // Other memory is placed after 4GiB
126         regions.push((
127             layout::RAM_64BIT_START,
128             (ram_size - ram_32bit_space_size) as usize,
129             RegionType::Ram,
130         ));
131     }
132 
133     // Add the 32-bit reserved memory hole as a reserved region
134     regions.push((
135         layout::MEM_32BIT_RESERVED_START,
136         layout::MEM_32BIT_RESERVED_SIZE as usize,
137         RegionType::Reserved,
138     ));
139 
140     regions
141 }
142 
143 /// Configures the system and should be called once per vm before starting vcpu threads.
144 #[allow(clippy::too_many_arguments)]
145 pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
146     guest_mem: &GuestMemoryMmap,
147     cmdline: &str,
148     vcpu_mpidr: Vec<u64>,
149     vcpu_topology: Option<(u8, u8, u8)>,
150     device_info: &HashMap<(DeviceType, String), T, S>,
151     initrd: &Option<super::InitramfsConfig>,
152     pci_space_info: &[PciSpaceInfo],
153     virtio_iommu_bdf: Option<u32>,
154     gic_device: &dyn GicDevice,
155     numa_nodes: &NumaNodes,
156     pmu_supported: bool,
157 ) -> super::Result<()> {
158     let fdt_final = fdt::create_fdt(
159         guest_mem,
160         cmdline,
161         vcpu_mpidr,
162         vcpu_topology,
163         device_info,
164         gic_device,
165         initrd,
166         pci_space_info,
167         numa_nodes,
168         virtio_iommu_bdf,
169         pmu_supported,
170     )
171     .map_err(|_| Error::SetupFdt)?;
172 
173     if log_enabled!(Level::Debug) {
174         fdt::print_fdt(&fdt_final);
175     }
176 
177     fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
178 
179     Ok(())
180 }
181 
182 /// Returns the memory address where the initramfs could be loaded.
183 pub fn initramfs_load_addr(
184     guest_mem: &GuestMemoryMmap,
185     initramfs_size: usize,
186 ) -> super::Result<u64> {
187     let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
188     match guest_mem
189         .last_addr()
190         .checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
191     {
192         Some(offset) => {
193             if guest_mem.address_in_range(offset) {
194                 Ok(offset.raw_value())
195             } else {
196                 Err(super::Error::AArch64Setup(Error::InitramfsAddress))
197             }
198         }
199         None => Err(super::Error::AArch64Setup(Error::InitramfsAddress)),
200     }
201 }
202 
203 pub fn get_host_cpu_phys_bits() -> u8 {
204     // A dummy hypervisor created only for querying the host IPA size and will
205     // be freed after the query.
206     let hv = hypervisor::new().unwrap();
207     let host_cpu_phys_bits = hv.get_host_ipa_limit().try_into().unwrap();
208     if host_cpu_phys_bits == 0 {
209         // Host kernel does not support `get_host_ipa_limit`,
210         // we return the default value 40 here.
211         40
212     } else {
213         host_cpu_phys_bits
214     }
215 }
216 
217 #[cfg(test)]
218 mod tests {
219     use super::*;
220 
221     #[test]
222     fn test_arch_memory_regions_dram_2gb() {
223         let regions = arch_memory_regions((1usize << 31) as u64); //2GB
224         assert_eq!(6, regions.len());
225         assert_eq!(layout::RAM_START, regions[4].0);
226         assert_eq!(((1 << 31) - layout::UEFI_SIZE) as usize, regions[4].1);
227         assert_eq!(RegionType::Ram, regions[4].2);
228         assert_eq!(RegionType::Reserved, regions[5].2);
229     }
230 
231     #[test]
232     fn test_arch_memory_regions_dram_4gb() {
233         let regions = arch_memory_regions((1usize << 32) as u64); //4GB
234         let ram_32bit_space_size =
235             layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START);
236         assert_eq!(7, regions.len());
237         assert_eq!(layout::RAM_START, regions[4].0);
238         assert_eq!(ram_32bit_space_size as usize, regions[4].1);
239         assert_eq!(RegionType::Ram, regions[4].2);
240         assert_eq!(RegionType::Reserved, regions[6].2);
241         assert_eq!(RegionType::Ram, regions[5].2);
242         assert_eq!(
243             ((1 << 32) - layout::UEFI_SIZE - ram_32bit_space_size) as usize,
244             regions[5].1
245         );
246     }
247 }
248