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