From d605fda3f71097900485d236f94986394f0d640c Mon Sep 17 00:00:00 2001 From: Henry Wang Date: Mon, 20 Apr 2020 10:03:00 +0800 Subject: [PATCH] aarch64: Porting GIC source files from Firecracker This commit adds ported code of Generic Interrupt Controller (GIC) software implementation for AArch64, including both GICv2 and GICv3 devices. These GIC devices are actually emulated by the host kernel through KVM and will be used in the guest VM as the interrupt controller for AArch64. Signed-off-by: Henry Wang --- arch/src/aarch64/gic.rs | 159 ++++++++++++++++++++++++++++++++++++++ arch/src/aarch64/gicv2.rs | 114 +++++++++++++++++++++++++++ arch/src/aarch64/gicv3.rs | 117 ++++++++++++++++++++++++++++ arch/src/aarch64/mod.rs | 5 ++ 4 files changed, 395 insertions(+) create mode 100644 arch/src/aarch64/gic.rs create mode 100644 arch/src/aarch64/gicv2.rs create mode 100644 arch/src/aarch64/gicv3.rs diff --git a/arch/src/aarch64/gic.rs b/arch/src/aarch64/gic.rs new file mode 100644 index 000000000..ded6d769c --- /dev/null +++ b/arch/src/aarch64/gic.rs @@ -0,0 +1,159 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::{boxed::Box, result}; + +use kvm_ioctls::{DeviceFd, VmFd}; + +use super::gicv2::GICv2; +use super::gicv3::GICv3; + +/// Errors thrown while setting up the GIC. +#[derive(Debug)] +pub enum Error { + /// Error while calling KVM ioctl for setting up the global interrupt controller. + CreateGIC(kvm_ioctls::Error), + /// Error while setting device attributes for the GIC. + SetDeviceAttribute(kvm_ioctls::Error), +} +type Result = result::Result; + +/// Trait for GIC devices. +pub trait GICDevice: Send + Sync { + /// Returns the file descriptor of the GIC device + fn device_fd(&self) -> &DeviceFd; + + /// Returns an array with GIC device properties + fn device_properties(&self) -> &[u64]; + + /// Returns the number of vCPUs this GIC handles + fn vcpu_count(&self) -> u64; + + /// Returns the fdt compatibility property of the device + fn fdt_compatibility(&self) -> &str; + + /// Returns the maint_irq fdt property of the device + fn fdt_maint_irq(&self) -> u32; + + /// Returns the GIC version of the device + fn version() -> u32 + where + Self: Sized; + + /// Create the GIC device object + fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box + where + Self: Sized; + + /// Setup the device-specific attributes + fn init_device_attributes(gic_device: &Box) -> Result<()> + where + Self: Sized; + + /// Initialize a GIC device + fn init_device(vm: &VmFd) -> Result + where + Self: Sized, + { + let mut gic_device = kvm_bindings::kvm_create_device { + type_: Self::version(), + fd: 0, + flags: 0, + }; + + vm.create_device(&mut gic_device).map_err(Error::CreateGIC) + } + + /// Set a GIC device attribute + fn set_device_attribute( + fd: &DeviceFd, + group: u32, + attr: u64, + addr: u64, + flags: u32, + ) -> Result<()> + where + Self: Sized, + { + let attr = kvm_bindings::kvm_device_attr { + group: group, + attr: attr, + addr: addr, + flags: flags, + }; + fd.set_device_attr(&attr) + .map_err(Error::SetDeviceAttribute)?; + + Ok(()) + } + + /// Finalize the setup of a GIC device + fn finalize_device(gic_device: &Box) -> Result<()> + where + Self: Sized, + { + /* We need to tell the kernel how many irqs to support with this vgic. + * See the `layout` module for details. + */ + let nr_irqs: u32 = super::layout::IRQ_MAX - super::layout::IRQ_BASE + 1; + let nr_irqs_ptr = &nr_irqs as *const u32; + Self::set_device_attribute( + gic_device.device_fd(), + kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS, + 0, + nr_irqs_ptr as u64, + 0, + )?; + + /* Finalize the GIC. + * See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211. + */ + Self::set_device_attribute( + gic_device.device_fd(), + kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL, + u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT), + 0, + 0, + )?; + + Ok(()) + } + + /// Method to initialize the GIC device + fn new(vm: &VmFd, vcpu_count: u64) -> Result> + where + Self: Sized, + { + let vgic_fd = Self::init_device(vm)?; + + let device = Self::create_device(vgic_fd, vcpu_count); + + Self::init_device_attributes(&device)?; + + Self::finalize_device(&device)?; + + Ok(device) + } +} + +/// Create a GIC device. +/// +/// It will try to create by default a GICv3 device. If that fails it will try +/// to fall-back to a GICv2 device. +pub fn create_gic(vm: &VmFd, vcpu_count: u64) -> Result> { + GICv3::new(vm, vcpu_count).or_else(|_| GICv2::new(vm, vcpu_count)) +} + +#[cfg(test)] +mod tests { + + use super::*; + use kvm_ioctls::Kvm; + + #[test] + fn test_create_gic() { + let kvm = Kvm::new().unwrap(); + let vm = kvm.create_vm().unwrap(); + assert!(create_gic(&vm, 1).is_ok()); + } +} diff --git a/arch/src/aarch64/gicv2.rs b/arch/src/aarch64/gicv2.rs new file mode 100644 index 000000000..b68ab8c85 --- /dev/null +++ b/arch/src/aarch64/gicv2.rs @@ -0,0 +1,114 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::{boxed::Box, result}; + +use kvm_ioctls::DeviceFd; + +use super::gic::{Error, GICDevice}; + +type Result = result::Result; + +/// Represent a GIC v2 device +pub struct GICv2 { + /// The file descriptor for the KVM device + fd: DeviceFd, + + /// GIC device properties, to be used for setting up the fdt entry + properties: [u64; 4], + + /// Number of CPUs handled by the device + vcpu_count: u64, +} + +impl GICv2 { + // Unfortunately bindgen omits defines that are based on other defines. + // See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel. + const KVM_VGIC_V2_DIST_SIZE: u64 = 0x1000; + const KVM_VGIC_V2_CPU_SIZE: u64 = 0x2000; + + // Device trees specific constants + const ARCH_GIC_V2_MAINT_IRQ: u32 = 8; + + /// Get the address of the GICv2 distributor. + const fn get_dist_addr() -> u64 { + super::layout::MAPPED_IO_START - GICv2::KVM_VGIC_V2_DIST_SIZE + } + + /// Get the size of the GIC_v2 distributor. + const fn get_dist_size() -> u64 { + GICv2::KVM_VGIC_V2_DIST_SIZE + } + + /// Get the address of the GIC_v2 CPU. + const fn get_cpu_addr() -> u64 { + GICv2::get_dist_addr() - GICv2::KVM_VGIC_V2_CPU_SIZE + } + + /// Get the size of the GIC_v2 CPU. + const fn get_cpu_size() -> u64 { + GICv2::KVM_VGIC_V2_CPU_SIZE + } +} + +impl GICDevice for GICv2 { + fn version() -> u32 { + kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V2 + } + + fn device_fd(&self) -> &DeviceFd { + &self.fd + } + + fn device_properties(&self) -> &[u64] { + &self.properties + } + + fn vcpu_count(&self) -> u64 { + self.vcpu_count + } + + fn fdt_compatibility(&self) -> &str { + "arm,gic-400" + } + + fn fdt_maint_irq(&self) -> u32 { + GICv2::ARCH_GIC_V2_MAINT_IRQ + } + + fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box { + Box::new(GICv2 { + fd: fd, + properties: [ + GICv2::get_dist_addr(), + GICv2::get_dist_size(), + GICv2::get_cpu_addr(), + GICv2::get_cpu_size(), + ], + vcpu_count: vcpu_count, + }) + } + + fn init_device_attributes(gic_device: &Box) -> Result<()> { + /* Setting up the distributor attribute. + We are placing the GIC below 1GB so we need to substract the size of the distributor. */ + Self::set_device_attribute( + &gic_device.device_fd(), + kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR, + u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_DIST), + &GICv2::get_dist_addr() as *const u64 as u64, + 0, + )?; + + /* Setting up the CPU attribute. */ + Self::set_device_attribute( + &gic_device.device_fd(), + kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR, + u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_CPU), + &GICv2::get_cpu_addr() as *const u64 as u64, + 0, + )?; + + Ok(()) + } +} diff --git a/arch/src/aarch64/gicv3.rs b/arch/src/aarch64/gicv3.rs new file mode 100644 index 000000000..6c0d8c30c --- /dev/null +++ b/arch/src/aarch64/gicv3.rs @@ -0,0 +1,117 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::{boxed::Box, result}; + +use kvm_ioctls::DeviceFd; + +use super::gic::{Error, GICDevice}; + +type Result = result::Result; + +pub struct GICv3 { + /// The file descriptor for the KVM device + fd: DeviceFd, + + /// GIC device properties, to be used for setting up the fdt entry + properties: [u64; 4], + + /// Number of CPUs handled by the device + vcpu_count: u64, +} + +impl GICv3 { + // Unfortunately bindgen omits defines that are based on other defines. + // See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel. + const SZ_64K: u64 = 0x0001_0000; + const KVM_VGIC_V3_DIST_SIZE: u64 = GICv3::SZ_64K; + const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * GICv3::SZ_64K); + + // Device trees specific constants + const ARCH_GIC_V3_MAINT_IRQ: u32 = 9; + + /// Get the address of the GIC distributor. + fn get_dist_addr() -> u64 { + super::layout::MAPPED_IO_START - GICv3::KVM_VGIC_V3_DIST_SIZE + } + + /// Get the size of the GIC distributor. + fn get_dist_size() -> u64 { + GICv3::KVM_VGIC_V3_DIST_SIZE + } + + /// Get the address of the GIC redistributors. + fn get_redists_addr(vcpu_count: u64) -> u64 { + GICv3::get_dist_addr() - GICv3::get_redists_size(vcpu_count) + } + + /// Get the size of the GIC redistributors. + fn get_redists_size(vcpu_count: u64) -> u64 { + vcpu_count * GICv3::KVM_VGIC_V3_REDIST_SIZE + } +} + +impl GICDevice for GICv3 { + fn version() -> u32 { + kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3 + } + + fn device_fd(&self) -> &DeviceFd { + &self.fd + } + + fn device_properties(&self) -> &[u64] { + &self.properties + } + + fn vcpu_count(&self) -> u64 { + self.vcpu_count + } + + fn fdt_compatibility(&self) -> &str { + "arm,gic-v3" + } + + fn fdt_maint_irq(&self) -> u32 { + GICv3::ARCH_GIC_V3_MAINT_IRQ + } + + fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box { + Box::new(GICv3 { + fd: fd, + properties: [ + GICv3::get_dist_addr(), + GICv3::get_dist_size(), + GICv3::get_redists_addr(vcpu_count), + GICv3::get_redists_size(vcpu_count), + ], + vcpu_count: vcpu_count, + }) + } + + fn init_device_attributes(gic_device: &Box) -> Result<()> { + /* Setting up the distributor attribute. + We are placing the GIC below 1GB so we need to substract the size of the distributor. + */ + Self::set_device_attribute( + &gic_device.device_fd(), + kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR, + u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST), + &GICv3::get_dist_addr() as *const u64 as u64, + 0, + )?; + + /* Setting up the redistributors' attribute. + We are calculating here the start of the redistributors address. We have one per CPU. + */ + Self::set_device_attribute( + &gic_device.device_fd(), + kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR, + u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST), + &GICv3::get_redists_addr(u64::from(gic_device.vcpu_count())) as *const u64 as u64, + 0, + )?; + + Ok(()) + } +} diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index 55d0c2929..76b00ef99 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -1,6 +1,11 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +/// Module for the global interrupt controller configuration. +pub mod gic; +mod gicv2; +mod gicv3; +/// Layout for this aarch64 system. pub mod layout; use crate::RegionType;