xref: /cloud-hypervisor/arch/src/aarch64/mod.rs (revision f67b3f79ea19c9a66e04074cbbf5d292f6529e43)
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, RegionType};
18 use gic::GicDevice;
19 use log::{log_enabled, Level};
20 use std::collections::HashMap;
21 use std::convert::TryInto;
22 use std::ffi::CStr;
23 use std::fmt::Debug;
24 use std::sync::Arc;
25 use vm_memory::{
26     Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestUsize,
27 };
28 
29 /// Errors thrown while configuring aarch64 system.
30 #[derive(Debug)]
31 pub enum Error {
32     /// Failed to create a FDT.
33     SetupFdt,
34 
35     /// Failed to write FDT to memory.
36     WriteFdtToMemory(fdt::Error),
37 
38     /// Failed to create a GIC.
39     SetupGic(gic::Error),
40 
41     /// Failed to compute the initramfs address.
42     InitramfsAddress,
43 
44     /// Error configuring the general purpose registers
45     RegsConfiguration(regs::Error),
46 
47     /// Error configuring the MPIDR register
48     VcpuRegMpidr(hypervisor::HypervisorCpuError),
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     vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
71 ) -> super::Result<u64> {
72     if let Some(kernel_entry_point) = kernel_entry_point {
73         regs::setup_regs(
74             fd,
75             id,
76             kernel_entry_point.entry_addr.raw_value(),
77             &vm_memory.memory(),
78         )
79         .map_err(Error::RegsConfiguration)?;
80     }
81 
82     let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMpidr)?;
83     Ok(mpidr)
84 }
85 
86 pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
87     // Normally UEFI should be loaded to a flash area at the beginning of memory.
88     // But now flash memory type is not supported.
89     // As a workaround, we take 4 MiB memory from the main RAM for UEFI.
90     // As a result, the RAM that the guest can see is less than what has been
91     // assigned in command line, when ACPI and UEFI is enabled.
92     let ram_deduction = if cfg!(feature = "acpi") {
93         layout::UEFI_SIZE
94     } else {
95         0
96     };
97 
98     vec![
99         // 0 ~ 4 MiB: Reserved for UEFI space
100         #[cfg(feature = "acpi")]
101         (GuestAddress(0), layout::UEFI_SIZE as usize, RegionType::Ram),
102         #[cfg(not(feature = "acpi"))]
103         (
104             GuestAddress(0),
105             layout::UEFI_SIZE as usize,
106             RegionType::Reserved,
107         ),
108         // 4 MiB ~ 256 MiB: Gic and legacy devices
109         (
110             GuestAddress(layout::UEFI_SIZE),
111             (layout::MEM_32BIT_DEVICES_START.0 - layout::UEFI_SIZE) as usize,
112             RegionType::Reserved,
113         ),
114         // 256 MiB ~ 768 MiB: MMIO space
115         (
116             layout::MEM_32BIT_DEVICES_START,
117             layout::MEM_32BIT_DEVICES_SIZE as usize,
118             RegionType::SubRegion,
119         ),
120         // 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
121         (
122             layout::PCI_MMCONFIG_START,
123             layout::PCI_MMCONFIG_SIZE as usize,
124             RegionType::Reserved,
125         ),
126         // 1 GiB ~ : Ram
127         (
128             GuestAddress(layout::RAM_64BIT_START),
129             (size - ram_deduction) as usize,
130             RegionType::Ram,
131         ),
132     ]
133 }
134 
135 /// Configures the system and should be called once per vm before starting vcpu threads.
136 #[allow(clippy::too_many_arguments)]
137 pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
138     guest_mem: &GuestMemoryMmap,
139     cmdline_cstring: &CStr,
140     vcpu_mpidr: Vec<u64>,
141     vcpu_topology: Option<(u8, u8, u8)>,
142     device_info: &HashMap<(DeviceType, String), T, S>,
143     initrd: &Option<super::InitramfsConfig>,
144     pci_space_address: &(u64, u64),
145     gic_device: &dyn GicDevice,
146     numa_nodes: &NumaNodes,
147 ) -> super::Result<()> {
148     let fdt_final = fdt::create_fdt(
149         guest_mem,
150         cmdline_cstring,
151         vcpu_mpidr,
152         vcpu_topology,
153         device_info,
154         gic_device,
155         initrd,
156         pci_space_address,
157         numa_nodes,
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