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;