xref: /cloud-hypervisor/arch/src/aarch64/mod.rs (revision f7f2f25a574b1b2dba22c094fc8226d404157d15)
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;
18 use crate::GuestMemoryMmap;
19 use crate::RegionType;
20 use gic::GicDevice;
21 use log::{log_enabled, Level};
22 use std::collections::HashMap;
23 use std::convert::TryInto;
24 use std::ffi::CStr;
25 use std::fmt::Debug;
26 use std::sync::Arc;
27 use vm_memory::{
28     Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestUsize,
29 };
30 
31 /// Errors thrown while configuring aarch64 system.
32 #[derive(Debug)]
33 pub enum Error {
34     /// Failed to create a FDT.
35     SetupFdt,
36 
37     /// Failed to write FDT to memory.
38     WriteFdtToMemory(fdt::Error),
39 
40     /// Failed to create a GIC.
41     SetupGic(gic::Error),
42 
43     /// Failed to compute the initramfs address.
44     InitramfsAddress,
45 
46     /// Error configuring the general purpose registers
47     RegsConfiguration(regs::Error),
48 
49     /// Error configuring the MPIDR register
50     VcpuRegMpidr(hypervisor::HypervisorCpuError),
51 }
52 
53 impl From<Error> for super::Error {
54     fn from(e: Error) -> super::Error {
55         super::Error::AArch64Setup(e)
56     }
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     fd: &Arc<dyn hypervisor::Vcpu>,
70     id: u8,
71     kernel_entry_point: Option<EntryPoint>,
72     vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
73 ) -> super::Result<u64> {
74     if let Some(kernel_entry_point) = kernel_entry_point {
75         regs::setup_regs(
76             fd,
77             id,
78             kernel_entry_point.entry_addr.raw_value(),
79             &vm_memory.memory(),
80         )
81         .map_err(Error::RegsConfiguration)?;
82     }
83 
84     let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMpidr)?;
85     Ok(mpidr)
86 }
87 
88 pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
89     // Normally UEFI should be loaded to a flash area at the beginning of memory.
90     // But now flash memory type is not supported.
91     // As a workaround, we take 4 MiB memory from the main RAM for UEFI.
92     // As a result, the RAM that the guest can see is less than what has been
93     // assigned in command line, when ACPI and UEFI is enabled.
94     let ram_deduction = if cfg!(feature = "acpi") {
95         layout::UEFI_SIZE
96     } else {
97         0
98     };
99 
100     vec![
101         // 0 ~ 4 MiB: Reserved for UEFI space
102         #[cfg(feature = "acpi")]
103         (GuestAddress(0), layout::UEFI_SIZE as usize, RegionType::Ram),
104         #[cfg(not(feature = "acpi"))]
105         (
106             GuestAddress(0),
107             layout::UEFI_SIZE as usize,
108             RegionType::Reserved,
109         ),
110         // 4 MiB ~ 256 MiB: Gic and legacy devices
111         (
112             GuestAddress(layout::UEFI_SIZE),
113             (layout::MEM_32BIT_DEVICES_START.0 - layout::UEFI_SIZE) as usize,
114             RegionType::Reserved,
115         ),
116         // 256 MiB ~ 768 MiB: MMIO space
117         (
118             layout::MEM_32BIT_DEVICES_START,
119             layout::MEM_32BIT_DEVICES_SIZE as usize,
120             RegionType::SubRegion,
121         ),
122         // 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
123         (
124             layout::PCI_MMCONFIG_START,
125             layout::PCI_MMCONFIG_SIZE as usize,
126             RegionType::Reserved,
127         ),
128         // 1 GiB ~ : Ram
129         (
130             GuestAddress(layout::RAM_64BIT_START),
131             (size - ram_deduction) as usize,
132             RegionType::Ram,
133         ),
134     ]
135 }
136 
137 /// Configures the system and should be called once per vm before starting vcpu threads.
138 #[allow(clippy::too_many_arguments)]
139 pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
140     guest_mem: &GuestMemoryMmap,
141     cmdline_cstring: &CStr,
142     vcpu_mpidr: Vec<u64>,
143     vcpu_topology: Option<(u8, u8, u8)>,
144     device_info: &HashMap<(DeviceType, String), T, S>,
145     initrd: &Option<super::InitramfsConfig>,
146     pci_space_address: &(u64, u64),
147     gic_device: &dyn GicDevice,
148 ) -> super::Result<()> {
149     let fdt_final = fdt::create_fdt(
150         guest_mem,
151         cmdline_cstring,
152         vcpu_mpidr,
153         vcpu_topology,
154         device_info,
155         gic_device,
156         initrd,
157         pci_space_address,
158     )
159     .map_err(|_| Error::SetupFdt)?;
160 
161     if log_enabled!(Level::Debug) {
162         fdt::print_fdt(&fdt_final);
163     }
164 
165     fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
166 
167     Ok(())
168 }
169 
170 /// Returns the memory address where the initramfs could be loaded.
171 pub fn initramfs_load_addr(
172     guest_mem: &GuestMemoryMmap,
173     initramfs_size: usize,
174 ) -> super::Result<u64> {
175     let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
176     match guest_mem
177         .last_addr()
178         .checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
179     {
180         Some(offset) => {
181             if guest_mem.address_in_range(offset) {
182                 Ok(offset.raw_value())
183             } else {
184                 Err(super::Error::AArch64Setup(Error::InitramfsAddress))
185             }
186         }
187         None => Err(super::Error::AArch64Setup(Error::InitramfsAddress)),
188     }
189 }
190 
191 /// Returns the memory address where the kernel could be loaded.
192 pub fn get_kernel_start() -> u64 {
193     layout::KERNEL_START
194 }
195 
196 ///Return guest memory address where the uefi should be loaded.
197 pub fn get_uefi_start() -> u64 {
198     layout::UEFI_START
199 }
200 
201 // Auxiliary function to get the address where the device tree blob is loaded.
202 fn get_fdt_addr() -> u64 {
203     layout::FDT_START
204 }
205 
206 pub fn get_host_cpu_phys_bits() -> u8 {
207     // A dummy hypervisor created only for querying the host IPA size and will
208     // be freed after the query.
209     let hv = hypervisor::new().unwrap();
210     let host_cpu_phys_bits = hv.get_host_ipa_limit().try_into().unwrap();
211     if host_cpu_phys_bits == 0 {
212         // Host kernel does not support `get_host_ipa_limit`,
213         // we return the default value 40 here.
214         40
215     } else {
216         host_cpu_phys_bits
217     }
218 }
219 
220 #[cfg(test)]
221 mod tests {
222     use super::*;
223 
224     #[test]
225     fn test_arch_memory_regions_dram() {
226         let regions = arch_memory_regions((1usize << 32) as u64); //4GB
227         assert_eq!(5, regions.len());
228         assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[4].0);
229         assert_eq!(1usize << 32, regions[4].1);
230         assert_eq!(RegionType::Ram, regions[4].2);
231     }
232 }
233