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