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