diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs new file mode 100644 index 000000000..74901f1fb --- /dev/null +++ b/arch/src/aarch64/fdt.rs @@ -0,0 +1,610 @@ +// Copyright 2020 Arm Limited (or its affiliates). All rights reserved. +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the THIRD-PARTY file. + +use libc::{c_char, c_int, c_void}; +use std::collections::HashMap; +use std::ffi::{CStr, CString, NulError}; +use std::fmt::Debug; +use std::ptr::null; +use std::{io, result}; + +use super::super::DeviceType; +use super::super::InitrdConfig; +use super::get_fdt_addr; +use super::gic::GICDevice; +use super::layout::FDT_MAX_SIZE; +use crate::aarch64::fdt::Error::CstringFDTTransform; +use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap}; + +// This is a value for uniquely identifying the FDT node declaring the interrupt controller. +const GIC_PHANDLE: u32 = 1; +// This is a value for uniquely identifying the FDT node containing the clock definition. +const CLOCK_PHANDLE: u32 = 2; +// Read the documentation specified when appending the root node to the FDT. +const ADDRESS_CELLS: u32 = 0x2; +const SIZE_CELLS: u32 = 0x2; + +// As per kvm tool and +// https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt +// Look for "The 1st cell..." +const GIC_FDT_IRQ_TYPE_SPI: u32 = 0; +const GIC_FDT_IRQ_TYPE_PPI: u32 = 1; + +// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17 +const IRQ_TYPE_EDGE_RISING: u32 = 1; +const IRQ_TYPE_LEVEL_HI: u32 = 4; + +// This links to libfdt which handles the creation of the binary blob +// flattened device tree (fdt) that is passed to the kernel and indicates +// the hardware configuration of the machine. +#[link(name = "fdt")] +extern "C" { + fn fdt_create(buf: *mut c_void, bufsize: c_int) -> c_int; + fn fdt_finish_reservemap(fdt: *mut c_void) -> c_int; + fn fdt_begin_node(fdt: *mut c_void, name: *const c_char) -> c_int; + fn fdt_property(fdt: *mut c_void, name: *const c_char, val: *const c_void, len: c_int) + -> c_int; + fn fdt_end_node(fdt: *mut c_void) -> c_int; + fn fdt_open_into(fdt: *const c_void, buf: *mut c_void, bufsize: c_int) -> c_int; + fn fdt_finish(fdt: *const c_void) -> c_int; + fn fdt_pack(fdt: *mut c_void) -> c_int; +} + +/// Trait for devices to be added to the Flattened Device Tree. +pub trait DeviceInfoForFDT { + /// Returns the address where this device will be loaded. + fn addr(&self) -> u64; + /// Returns the associated interrupt for this device. + fn irq(&self) -> u32; + /// Returns the amount of memory that needs to be reserved for this device. + fn length(&self) -> u64; +} + +/// Errors thrown while configuring the Flattened Device Tree for aarch64. +#[derive(Debug)] +pub enum Error { + /// Failed to append node to the FDT. + AppendFDTNode(io::Error), + /// Failed to append a property to the FDT. + AppendFDTProperty(io::Error), + /// Syscall for creating FDT failed. + CreateFDT(io::Error), + /// Failed to obtain a C style string. + CstringFDTTransform(NulError), + /// Failure in calling syscall for terminating this FDT. + FinishFDTReserveMap(io::Error), + /// Failure in writing FDT in memory. + WriteFDTToMemory(GuestMemoryError), +} +type Result = result::Result; + +/// Creates the flattened device tree for this aarch64 VM. +pub fn create_fdt( + guest_mem: &GuestMemoryMmap, + cmdline: &CStr, + vcpu_mpidr: Vec, + device_info: &HashMap<(DeviceType, String), T>, + gic_device: &Box, + initrd: &Option, +) -> Result> { + // Alocate stuff necessary for the holding the blob. + let mut fdt = vec![0; FDT_MAX_SIZE]; + + allocate_fdt(&mut fdt)?; + + // For an explanation why these nodes were introduced in the blob take a look at + // https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845 + // Look for "Required nodes and properties". + + // Header or the root node as per above mentioned documentation. + append_begin_node(&mut fdt, "")?; + append_property_string(&mut fdt, "compatible", "linux,dummy-virt")?; + // For info on #address-cells and size-cells read "Note about cells and address representation" + // from the above mentioned txt file. + append_property_u32(&mut fdt, "#address-cells", ADDRESS_CELLS)?; + append_property_u32(&mut fdt, "#size-cells", SIZE_CELLS)?; + // This is not mandatory but we use it to point the root node to the node + // containing description of the interrupt controller for this VM. + append_property_u32(&mut fdt, "interrupt-parent", GIC_PHANDLE)?; + create_cpu_nodes(&mut fdt, &vcpu_mpidr)?; + create_memory_node(&mut fdt, guest_mem)?; + create_chosen_node(&mut fdt, cmdline, initrd)?; + create_gic_node(&mut fdt, gic_device)?; + create_timer_node(&mut fdt)?; + create_clock_node(&mut fdt)?; + create_psci_node(&mut fdt)?; + create_devices_node(&mut fdt, device_info)?; + + // End Header node. + append_end_node(&mut fdt)?; + + // Allocate another buffer so we can format and then write fdt to guest. + let mut fdt_final = vec![0; FDT_MAX_SIZE]; + finish_fdt(&mut fdt, &mut fdt_final)?; + + // Write FDT to memory. + let fdt_address = GuestAddress(get_fdt_addr(&guest_mem)); + guest_mem + .write_slice(fdt_final.as_slice(), fdt_address) + .map_err(Error::WriteFDTToMemory)?; + Ok(fdt_final) +} + +// Following are auxiliary functions for allocating and finishing the FDT. +fn allocate_fdt(fdt: &mut Vec) -> Result<()> { + // Safe since we allocated this array with FDT_MAX_SIZE. + let mut fdt_ret = unsafe { fdt_create(fdt.as_mut_ptr() as *mut c_void, FDT_MAX_SIZE as c_int) }; + + if fdt_ret != 0 { + return Err(Error::CreateFDT(io::Error::last_os_error())); + } + + // The flattened device trees created with fdt_create() contains a list of + // reserved memory areas. We need to call `fdt_finish_reservemap` so as to make sure that there is a + // terminator in the reservemap list and whatever happened to be at the + // start of the FDT data section would end up being interpreted as + // reservemap entries. + // Safe since we previously allocated this array. + fdt_ret = unsafe { fdt_finish_reservemap(fdt.as_mut_ptr() as *mut c_void) }; + if fdt_ret != 0 { + return Err(Error::FinishFDTReserveMap(io::Error::last_os_error())); + } + Ok(()) +} + +fn finish_fdt(from_fdt: &mut Vec, to_fdt: &mut Vec) -> Result<()> { + // Safe since we allocated `fdt_final` and previously passed in its size. + let mut fdt_ret = unsafe { fdt_finish(from_fdt.as_mut_ptr() as *mut c_void) }; + if fdt_ret != 0 { + return Err(Error::FinishFDTReserveMap(io::Error::last_os_error())); + } + + // Safe because we allocated both arrays with the correct size. + fdt_ret = unsafe { + fdt_open_into( + from_fdt.as_mut_ptr() as *mut c_void, + to_fdt.as_mut_ptr() as *mut c_void, + FDT_MAX_SIZE as i32, + ) + }; + if fdt_ret != 0 { + return Err(Error::FinishFDTReserveMap(io::Error::last_os_error())); + } + + // Safe since we allocated `to_fdt`. + fdt_ret = unsafe { fdt_pack(to_fdt.as_mut_ptr() as *mut c_void) }; + if fdt_ret != 0 { + return Err(Error::FinishFDTReserveMap(io::Error::last_os_error())); + } + Ok(()) +} + +// Following are auxiliary functions for appending nodes to FDT. +fn append_begin_node(fdt: &mut Vec, name: &str) -> Result<()> { + let cstr_name = CString::new(name).map_err(CstringFDTTransform)?; + + // Safe because we allocated fdt and converted name to a CString + let fdt_ret = unsafe { fdt_begin_node(fdt.as_mut_ptr() as *mut c_void, cstr_name.as_ptr()) }; + if fdt_ret != 0 { + return Err(Error::AppendFDTNode(io::Error::last_os_error())); + } + Ok(()) +} + +fn append_end_node(fdt: &mut Vec) -> Result<()> { + // Safe because we allocated fdt. + let fdt_ret = unsafe { fdt_end_node(fdt.as_mut_ptr() as *mut c_void) }; + if fdt_ret != 0 { + return Err(Error::AppendFDTNode(io::Error::last_os_error())); + } + Ok(()) +} + +// Following are auxiliary functions for appending property nodes to the nodes of the FDT. +fn append_property_u32(fdt: &mut Vec, name: &str, val: u32) -> Result<()> { + append_property(fdt, name, &to_be32(val)) +} + +fn append_property_u64(fdt: &mut Vec, name: &str, val: u64) -> Result<()> { + append_property(fdt, name, &to_be64(val)) +} + +fn append_property_string(fdt: &mut Vec, name: &str, value: &str) -> Result<()> { + let cstr_value = CString::new(value).map_err(CstringFDTTransform)?; + append_property_cstring(fdt, name, &cstr_value) +} + +fn append_property_cstring(fdt: &mut Vec, name: &str, cstr_value: &CStr) -> Result<()> { + let value_bytes = cstr_value.to_bytes_with_nul(); + let cstr_name = CString::new(name).map_err(CstringFDTTransform)?; + // Safe because we allocated fdt, converted name and value to CStrings + let fdt_ret = unsafe { + fdt_property( + fdt.as_mut_ptr() as *mut c_void, + cstr_name.as_ptr(), + value_bytes.as_ptr() as *mut c_void, + value_bytes.len() as i32, + ) + }; + if fdt_ret != 0 { + return Err(Error::AppendFDTProperty(io::Error::last_os_error())); + } + Ok(()) +} + +fn append_property_null(fdt: &mut Vec, name: &str) -> Result<()> { + let cstr_name = CString::new(name).map_err(CstringFDTTransform)?; + + // Safe because we allocated fdt, converted name to a CString + let fdt_ret = unsafe { + fdt_property( + fdt.as_mut_ptr() as *mut c_void, + cstr_name.as_ptr(), + null(), + 0, + ) + }; + if fdt_ret != 0 { + return Err(Error::AppendFDTProperty(io::Error::last_os_error())); + } + Ok(()) +} + +fn append_property(fdt: &mut Vec, name: &str, val: &[u8]) -> Result<()> { + let cstr_name = CString::new(name).map_err(CstringFDTTransform)?; + let val_ptr = val.as_ptr() as *const c_void; + + // Safe because we allocated fdt and converted name to a CString + let fdt_ret = unsafe { + fdt_property( + fdt.as_mut_ptr() as *mut c_void, + cstr_name.as_ptr(), + val_ptr, + val.len() as i32, + ) + }; + if fdt_ret != 0 { + return Err(Error::AppendFDTProperty(io::Error::last_os_error())); + } + Ok(()) +} + +// Auxiliary functions for writing u32/u64 numbers in big endian order. +fn to_be32(input: u32) -> [u8; 4] { + u32::to_be_bytes(input) +} + +fn to_be64(input: u64) -> [u8; 8] { + u64::to_be_bytes(input) +} + +// Helper functions for generating a properly formatted byte vector using 32-bit/64-bit cells. +fn generate_prop32(cells: &[u32]) -> Vec { + let mut ret: Vec = Vec::new(); + for &e in cells { + ret.extend(to_be32(e).iter()); + } + ret +} + +fn generate_prop64(cells: &[u64]) -> Vec { + let mut ret: Vec = Vec::new(); + for &e in cells { + ret.extend(to_be64(e).iter()); + } + ret +} + +// Following are the auxiliary function for creating the different nodes that we append to our FDT. +fn create_cpu_nodes(fdt: &mut Vec, vcpu_mpidr: &Vec) -> Result<()> { + // See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml. + append_begin_node(fdt, "cpus")?; + // As per documentation, on ARM v8 64-bit systems value should be set to 2. + append_property_u32(fdt, "#address-cells", 0x02)?; + append_property_u32(fdt, "#size-cells", 0x0)?; + let num_cpus = vcpu_mpidr.len(); + + for cpu_index in 0..num_cpus { + let cpu_name = format!("cpu@{:x}", cpu_index); + append_begin_node(fdt, &cpu_name)?; + append_property_string(fdt, "device_type", "cpu")?; + append_property_string(fdt, "compatible", "arm,arm-v8")?; + if num_cpus > 1 { + // This is required on armv8 64-bit. See aforementioned documentation. + append_property_string(fdt, "enable-method", "psci")?; + } + // Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register. + // See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html. + append_property_u64(fdt, "reg", vcpu_mpidr[cpu_index] & 0x7FFFFF)?; + append_end_node(fdt)?; + } + append_end_node(fdt)?; + Ok(()) +} + +fn create_memory_node(fdt: &mut Vec, guest_mem: &GuestMemoryMmap) -> Result<()> { + let mem_size = guest_mem.last_addr().raw_value() - super::layout::RAM_64BIT_START + 1; + // See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960 + // for an explanation of this. + let mem_reg_prop = generate_prop64(&[super::layout::RAM_64BIT_START as u64, mem_size as u64]); + + append_begin_node(fdt, "memory")?; + append_property_string(fdt, "device_type", "memory")?; + append_property(fdt, "reg", &mem_reg_prop)?; + append_end_node(fdt)?; + Ok(()) +} + +fn create_chosen_node( + fdt: &mut Vec, + cmdline: &CStr, + initrd: &Option, +) -> Result<()> { + append_begin_node(fdt, "chosen")?; + append_property_cstring(fdt, "bootargs", cmdline)?; + + if let Some(initrd_config) = initrd { + append_property_u64( + fdt, + "linux,initrd-start", + initrd_config.address.raw_value() as u64, + )?; + append_property_u64( + fdt, + "linux,initrd-end", + initrd_config.address.raw_value() + initrd_config.size as u64, + )?; + } + + append_end_node(fdt)?; + + Ok(()) +} + +fn create_gic_node(fdt: &mut Vec, gic_device: &Box) -> Result<()> { + let gic_reg_prop = generate_prop64(gic_device.device_properties()); + + append_begin_node(fdt, "intc")?; + append_property_string(fdt, "compatible", gic_device.fdt_compatibility())?; + append_property_null(fdt, "interrupt-controller")?; + // "interrupt-cells" field specifies the number of cells needed to encode an + // interrupt source. The type shall be a and the value shall be 3 if no PPI affinity description + // is required. + append_property_u32(fdt, "#interrupt-cells", 3)?; + append_property(fdt, "reg", &gic_reg_prop)?; + append_property_u32(fdt, "phandle", GIC_PHANDLE)?; + append_property_u32(fdt, "#address-cells", 2)?; + append_property_u32(fdt, "#size-cells", 2)?; + append_property_null(fdt, "ranges")?; + let gic_intr = [ + GIC_FDT_IRQ_TYPE_PPI, + gic_device.fdt_maint_irq(), + IRQ_TYPE_LEVEL_HI, + ]; + let gic_intr_prop = generate_prop32(&gic_intr); + + append_property(fdt, "interrupts", &gic_intr_prop)?; + append_end_node(fdt)?; + + Ok(()) +} + +fn create_clock_node(fdt: &mut Vec) -> Result<()> { + // The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture + // (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power + // consumption and reduced interface complexity. + // PCLK is the clock source and this node defines exactly the clock for the APB. + append_begin_node(fdt, "apb-pclk")?; + append_property_string(fdt, "compatible", "fixed-clock")?; + append_property_u32(fdt, "#clock-cells", 0x0)?; + append_property_u32(fdt, "clock-frequency", 24000000)?; + append_property_string(fdt, "clock-output-names", "clk24mhz")?; + append_property_u32(fdt, "phandle", CLOCK_PHANDLE)?; + append_end_node(fdt)?; + + Ok(()) +} + +fn create_timer_node(fdt: &mut Vec) -> Result<()> { + // See + // https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt + // These are fixed interrupt numbers for the timer device. + let irqs = [13, 14, 11, 10]; + let compatible = "arm,armv8-timer"; + + let mut timer_reg_cells: Vec = Vec::new(); + for &irq in irqs.iter() { + timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI); + timer_reg_cells.push(irq); + timer_reg_cells.push(IRQ_TYPE_LEVEL_HI); + } + let timer_reg_prop = generate_prop32(timer_reg_cells.as_slice()); + + append_begin_node(fdt, "timer")?; + append_property_string(fdt, "compatible", compatible)?; + append_property_null(fdt, "always-on")?; + append_property(fdt, "interrupts", &timer_reg_prop)?; + append_end_node(fdt)?; + + Ok(()) +} + +fn create_psci_node(fdt: &mut Vec) -> Result<()> { + let compatible = "arm,psci-0.2"; + append_begin_node(fdt, "psci")?; + append_property_string(fdt, "compatible", compatible)?; + // Two methods available: hvc and smc. + // As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC. + // So, since we are using kvm, we need to use hvc. + append_property_string(fdt, "method", "hvc")?; + append_end_node(fdt)?; + + Ok(()) +} + +fn create_virtio_node( + fdt: &mut Vec, + dev_info: &T, +) -> Result<()> { + let device_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]); + let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]); + + append_begin_node(fdt, &format!("virtio_mmio@{:x}", dev_info.addr()))?; + append_property_string(fdt, "compatible", "virtio,mmio")?; + append_property(fdt, "reg", &device_reg_prop)?; + append_property(fdt, "interrupts", &irq)?; + append_property_u32(fdt, "interrupt-parent", GIC_PHANDLE)?; + append_end_node(fdt)?; + + Ok(()) +} + +fn create_serial_node( + fdt: &mut Vec, + dev_info: &T, +) -> Result<()> { + let serial_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]); + let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]); + + append_begin_node(fdt, &format!("uart@{:x}", dev_info.addr()))?; + append_property_string(fdt, "compatible", "ns16550a")?; + append_property(fdt, "reg", &serial_reg_prop)?; + append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?; + append_property_string(fdt, "clock-names", "apb_pclk")?; + append_property(fdt, "interrupts", &irq)?; + append_end_node(fdt)?; + + Ok(()) +} + +fn create_rtc_node( + fdt: &mut Vec, + dev_info: &T, +) -> Result<()> { + let compatible = b"arm,pl031\0arm,primecell\0"; + let rtc_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]); + let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_LEVEL_HI]); + append_begin_node(fdt, &format!("rtc@{:x}", dev_info.addr()))?; + append_property(fdt, "compatible", compatible)?; + append_property(fdt, "reg", &rtc_reg_prop)?; + append_property(fdt, "interrupts", &irq)?; + append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?; + append_property_string(fdt, "clock-names", "apb_pclk")?; + append_end_node(fdt)?; + + Ok(()) +} + +fn create_devices_node( + fdt: &mut Vec, + dev_info: &HashMap<(DeviceType, String), T>, +) -> Result<()> { + // Create one temp Vec to store all virtio devices + let mut ordered_virtio_device: Vec<&T> = Vec::new(); + + for ((device_type, _device_id), info) in dev_info { + match device_type { + DeviceType::RTC => create_rtc_node(fdt, info)?, + DeviceType::Serial => create_serial_node(fdt, info)?, + DeviceType::Virtio(_) => { + ordered_virtio_device.push(info); + } + } + } + + // Sort out virtio devices by address from low to high and insert them into fdt table. + ordered_virtio_device.sort_by(|a, b| a.addr().cmp(&b.addr())); + for ordered_device_info in ordered_virtio_device.drain(..) { + create_virtio_node(fdt, ordered_device_info)?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::aarch64::gic::create_gic; + use crate::aarch64::layout; + use kvm_ioctls::Kvm; + + const LEN: u64 = 4096; + + #[derive(Clone, Debug)] + pub struct MMIODeviceInfo { + addr: u64, + irq: u32, + } + + impl DeviceInfoForFDT for MMIODeviceInfo { + fn addr(&self) -> u64 { + self.addr + } + fn irq(&self) -> u32 { + self.irq + } + fn length(&self) -> u64 { + LEN + } + } + + // The `load` function from the `device_tree` will mistakenly check the actual size + // of the buffer with the allocated size. This works around that. + fn set_size(buf: &mut [u8], pos: usize, val: usize) { + buf[pos] = ((val >> 24) & 0xff) as u8; + buf[pos + 1] = ((val >> 16) & 0xff) as u8; + buf[pos + 2] = ((val >> 8) & 0xff) as u8; + buf[pos + 3] = (val & 0xff) as u8; + } + + #[test] + fn test_create_fdt_with_devices() { + let mut regions = Vec::new(); + regions.push(( + GuestAddress(layout::RAM_64BIT_START), + (layout::FDT_MAX_SIZE + 0x1000) as usize, + )); + let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory"); + + let dev_info: HashMap<(DeviceType, std::string::String), MMIODeviceInfo> = [ + ( + (DeviceType::Serial, DeviceType::Serial.to_string()), + MMIODeviceInfo { addr: 0x00, irq: 1 }, + ), + ( + (DeviceType::Virtio(1), "virtio".to_string()), + MMIODeviceInfo { + addr: 0x00 + LEN, + irq: 2, + }, + ), + ( + (DeviceType::RTC, "rtc".to_string()), + MMIODeviceInfo { + addr: 0x00 + 2 * LEN, + irq: 3, + }, + ), + ] + .iter() + .cloned() + .collect(); + let kvm = Kvm::new().unwrap(); + let vm = kvm.create_vm().unwrap(); + let gic = create_gic(&vm, 1).unwrap(); + assert!(create_fdt( + &mem, + &CString::new("console=tty0").unwrap(), + vec![0], + &dev_info, + &gic, + &None, + ) + .is_ok()) + } +} diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index 586fbcad4..9747b50f0 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -1,6 +1,9 @@ +// Copyright 2020 Arm Limited (or its affiliates). All rights reserved. // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +/// Module for the flattened device tree. +pub mod fdt; /// Module for the global interrupt controller configuration. pub mod gic; mod gicv2; @@ -11,26 +14,15 @@ pub mod layout; pub mod regs; use crate::RegionType; +use aarch64::gic::GICDevice; use kvm_ioctls::*; +use std::collections::HashMap; +use std::ffi::CStr; use std::fmt::Debug; use vm_memory::{ Address, GuestAddress, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize, }; -#[derive(Debug)] -pub enum Error {} - -impl From for super::Error { - fn from(e: Error) -> super::Error { - super::Error::AArch64Setup(e) - } -} - -/// Stub function that needs to be implemented when aarch64 functionality is added. -pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> { - vec![(GuestAddress(0), size as usize, RegionType::Ram)] -} - #[derive(Debug, Copy, Clone)] /// Specifies the entry point address where the guest must start /// executing code. @@ -48,20 +40,88 @@ pub fn configure_vcpu( unimplemented!(); } -/// Stub function that needs to be implemented when aarch64 functionality is added. -pub fn configure_system( - _guest_mem: &GuestMemoryMmap, - _cmdline_addr: GuestAddress, - _cmdline_size: usize, - _num_cpus: u8, - _rsdp_addr: Option, +/// Errors thrown while configuring aarch64 system. +#[derive(Debug)] +pub enum Error { + /// Failed to create a Flattened Device Tree for this aarch64 VM. + SetupFDT(fdt::Error), + /// Failed to compute the initrd address. + InitrdAddress, +} + +impl From for super::Error { + fn from(e: Error) -> super::Error { + super::Error::AArch64Setup(e) + } +} + +pub use self::fdt::DeviceInfoForFDT; +use crate::DeviceType; + +pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> { + let mut regions = Vec::new(); + // 0 ~ 256 MiB: Reserved + regions.push(( + GuestAddress(0), + layout::PCI_DEVICES_MAPPED_IO_START as usize, + RegionType::Reserved, + )); + + // 256 MiB ~ 1 G: MMIO space + regions.push(( + GuestAddress(layout::PCI_DEVICES_MAPPED_IO_START), + layout::PCI_DEVICES_MAPPED_IO_SIZE as usize, + RegionType::SubRegion, + )); + + // 1G ~ 2G: reserved. The leading 256M for PCIe MMCONFIG space + regions.push(( + layout::PCI_MMCONFIG_START, + (layout::RAM_64BIT_START - layout::PCI_MMCONFIG_START.0) as usize, + RegionType::Reserved, + )); + + regions.push(( + GuestAddress(layout::RAM_64BIT_START), + size as usize, + RegionType::Ram, + )); + + regions +} + +/// Configures the system and should be called once per vm before starting vcpu threads. +/// +/// # Arguments +/// +/// * `guest_mem` - The memory to be used by the guest. +/// * `num_cpus` - Number of virtual CPUs the guest will have. +#[allow(clippy::too_many_arguments)] +#[allow(unused_variables)] +pub fn configure_system( + guest_mem: &GuestMemoryMmap, + cmdline_cstring: &CStr, + vcpu_mpidr: Vec, + device_info: &HashMap<(DeviceType, String), T>, + gic_device: &Box, + initrd: &Option, ) -> super::Result<()> { + let dtb = fdt::create_fdt( + guest_mem, + cmdline_cstring, + vcpu_mpidr, + device_info, + gic_device, + initrd, + ) + .map_err(Error::SetupFDT)?; + Ok(()) } -/// Stub function that needs to be implemented when aarch64 functionality is added. -pub fn get_reserved_mem_addr() -> usize { - 0 +/// Returns the memory address where the kernel could be loaded. +pub fn get_kernel_start() -> u64 { + layout::RAM_64BIT_START } // Auxiliary function to get the address where the device tree blob is loaded. @@ -104,3 +164,46 @@ pub fn check_required_kvm_extensions(kvm: &Kvm) -> super::Result<()> { } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_arch_memory_regions_dram() { + let regions = arch_memory_regions((1usize << 32) as u64); //4GB + assert_eq!(4, regions.len()); + assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[3].0); + assert_eq!(1usize << 32, regions[3].1); + assert_eq!(RegionType::Ram, regions[3].2); + } + + #[test] + fn test_get_fdt_addr() { + let mut regions = Vec::new(); + + regions.push(( + GuestAddress(layout::RAM_64BIT_START), + (layout::FDT_MAX_SIZE - 0x1000) as usize, + )); + let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory"); + assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START); + regions.clear(); + + regions.push(( + GuestAddress(layout::RAM_64BIT_START), + (layout::FDT_MAX_SIZE) as usize, + )); + let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory"); + assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START); + regions.clear(); + + regions.push(( + GuestAddress(layout::RAM_64BIT_START), + (layout::FDT_MAX_SIZE + 0x1000) as usize, + )); + let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory"); + assert_eq!(get_fdt_addr(&mem), 0x1000 + layout::RAM_64BIT_START); + regions.clear(); + } +} diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 561978703..4c6d29d86 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -4,6 +4,8 @@ // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +//! Implements platform specific functionality. +//! Supported platforms: x86_64, aarch64. #![allow( clippy::unreadable_literal, clippy::redundant_static_lifetimes, @@ -14,18 +16,21 @@ extern crate byteorder; extern crate kvm_bindings; +extern crate kvm_ioctls; extern crate libc; +extern crate vm_memory; + #[cfg(feature = "acpi")] extern crate acpi_tables; extern crate arch_gen; -extern crate kvm_ioctls; extern crate linux_loader; -extern crate vm_memory; use kvm_ioctls::*; +use std::fmt; use std::result; +/// Type for returning error code. #[derive(Debug)] pub enum Error { #[cfg(target_arch = "x86_64")] @@ -55,9 +60,12 @@ pub enum Error { /// Capability missing CapabilityMissing(Cap), } + +/// Type for returning public functions outcome. pub type Result = result::Result; -#[derive(PartialEq)] +/// Type for memory region types. +#[derive(PartialEq, Debug)] pub enum RegionType { /// RAM type Ram, @@ -75,14 +83,15 @@ pub enum RegionType { Reserved, } +/// Module for aarch64 related functionality. #[cfg(target_arch = "aarch64")] pub mod aarch64; #[cfg(target_arch = "aarch64")] pub use aarch64::{ arch_memory_regions, check_required_kvm_extensions, configure_system, configure_vcpu, - get_host_cpu_phys_bits, get_reserved_mem_addr, layout, layout::CMDLINE_MAX_SIZE, - layout::IRQ_BASE, layout::IRQ_MAX, EntryPoint, + fdt::DeviceInfoForFDT, get_host_cpu_phys_bits, get_kernel_start, layout, + layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, layout::IRQ_MAX, EntryPoint, }; #[cfg(target_arch = "x86_64")] @@ -110,3 +119,54 @@ pub struct InitramfsConfig { /// Size of initramfs in guest memory pub size: usize, } + +/// Types of devices that can get attached to this platform. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] +pub enum DeviceType { + /// Device Type: Virtio. + Virtio(u32), + /// Device Type: Serial. + #[cfg(target_arch = "aarch64")] + Serial, + /// Device Type: RTC. + #[cfg(target_arch = "aarch64")] + RTC, +} + +/// Type for passing information about the initrd in the guest memory. +pub struct InitrdConfig { + /// Load address of initrd in guest memory + pub address: vm_memory::GuestAddress, + /// Size of initrd in guest memory + pub size: usize, +} + +/// Default (smallest) memory page size for the supported architectures. +pub const PAGE_SIZE: usize = 4096; + +impl fmt::Display for DeviceType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Structure to describe MMIO device information +#[derive(Clone, Debug)] +#[cfg(target_arch = "aarch64")] +pub struct MMIODeviceInfo { + pub addr: u64, + pub irq: u32, +} + +#[cfg(target_arch = "aarch64")] +impl DeviceInfoForFDT for MMIODeviceInfo { + fn addr(&self) -> u64 { + self.addr + } + fn irq(&self) -> u32 { + self.irq + } + fn length(&self) -> u64 { + 4096 as u64 + } +}