arch-arm: Add basic support for KVM_CAP_ARM_USER_IRQ
authorHsuan Hsu <hsuan.hsu@mediatek.com>
Sun, 5 Jul 2020 14:56:07 +0000 (22:56 +0800)
committerHsuan Hsu <kugwa2000@gmail.com>
Fri, 10 Jul 2020 18:03:15 +0000 (18:03 +0000)
KVM_CAP_ARM_USER_IRQ is a KVM extension introduced in newer versions of
Linux (>= 4.12). It supports delivering interrupt from the kernel-space
timer to the user-space GIC, which means that it will be unnecessary to
use the memory-mapped timer and emulate it in gem5 anymore.

Using the option provided by this change, Linux is able to boot with 1
CPU successfully, and the speed is slightly faster then the memory-
mapped timer option. However, multicore seems to hang during boot and
still needs more investigation to be enabled.

JIRA: https://gem5.atlassian.net/browse/GEM5-663

Change-Id: I146bbcce3cf66f8f5ebee04ea5f1b9f54868721a
Signed-off-by: Hsuan Hsu <hsuan.hsu@mediatek.com>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/30921
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Maintainer: Andreas Sandberg <andreas.sandberg@arm.com>

src/arch/arm/kvm/base_cpu.cc
src/arch/arm/kvm/base_cpu.hh

index c028106b3b37215c95a4a5ee7774901a5c69b4bf..04c5d0fd0576302ba6cdedb0ee7f09ed41054b56 100644 (file)
@@ -41,7 +41,9 @@
 
 #include "arch/arm/interrupts.hh"
 #include "debug/KvmInt.hh"
+#include "dev/arm/generic_timer.hh"
 #include "params/BaseArmKvmCPU.hh"
+#include "params/GenericTimer.hh"
 
 #define INTERRUPT_ID(type, vcpu, irq) (                    \
         ((type) << KVM_ARM_IRQ_TYPE_SHIFT) |               \
@@ -57,7 +59,8 @@
 
 BaseArmKvmCPU::BaseArmKvmCPU(BaseArmKvmCPUParams *params)
     : BaseKvmCPU(params),
-      irqAsserted(false), fiqAsserted(false)
+      irqAsserted(false), fiqAsserted(false),
+      virtTimerPin(nullptr), prevDeviceIRQLevel(0)
 {
 }
 
@@ -82,6 +85,10 @@ BaseArmKvmCPU::startup()
         target_config.features[0] |= (1 << KVM_ARM_VCPU_EL1_32BIT);
     }
     kvmArmVCpuInit(target_config);
+
+    if (!vm.hasKernelIRQChip())
+        virtTimerPin = static_cast<ArmSystem *>(system)\
+            ->getGenericTimer()->params()->int_virt->get(tc);
 }
 
 Tick
@@ -113,7 +120,29 @@ BaseArmKvmCPU::kvmRun(Tick ticks)
     irqAsserted = simIRQ;
     fiqAsserted = simFIQ;
 
-    return BaseKvmCPU::kvmRun(ticks);
+    Tick kvmRunTicks = BaseKvmCPU::kvmRun(ticks);
+
+    if (!vm.hasKernelIRQChip()) {
+        uint64_t device_irq_level =
+            getKvmRunState()->s.regs.device_irq_level;
+
+        if (!(prevDeviceIRQLevel & KVM_ARM_DEV_EL1_VTIMER) &&
+            (device_irq_level & KVM_ARM_DEV_EL1_VTIMER)) {
+
+            DPRINTF(KvmInt, "In-kernel vtimer IRQ asserted\n");
+            prevDeviceIRQLevel |= KVM_ARM_DEV_EL1_VTIMER;
+            virtTimerPin->raise();
+
+        } else if ((prevDeviceIRQLevel & KVM_ARM_DEV_EL1_VTIMER) &&
+                   !(device_irq_level & KVM_ARM_DEV_EL1_VTIMER)) {
+
+            DPRINTF(KvmInt, "In-kernel vtimer IRQ disasserted\n");
+            prevDeviceIRQLevel &= ~KVM_ARM_DEV_EL1_VTIMER;
+            virtTimerPin->clear();
+        }
+    }
+
+    return kvmRunTicks;
 }
 
 const BaseArmKvmCPU::RegIndexVector &
index 4ee35ca92c2907c3752f3dd094db26ca2428d002..028cd3903733ead5a68813efa8939f762006e8f5 100644 (file)
@@ -41,6 +41,7 @@
 #include <vector>
 
 #include "cpu/kvm/base.hh"
+#include "dev/arm/base_gic.hh"
 
 struct BaseArmKvmCPUParams;
 
@@ -61,6 +62,23 @@ class BaseArmKvmCPU : public BaseKvmCPU
     /** Cached state of the FIQ line */
     bool fiqAsserted;
 
+    /**
+     * If the user-space GIC and the kernel-space timer are used
+     * simultaneously, set up this interrupt pin to forward interrupt from
+     * the timer to the GIC when timer IRQ level change is intercepted.
+     */
+    ArmInterruptPin *virtTimerPin;
+
+    /**
+     * KVM records whether each in-kernel device IRQ is asserted or
+     * disasserted in the kvmRunState->s.regs.device_irq_level bit map,
+     * and guarantees at least one KVM exit when the level changes. We
+     * use only the KVM_ARM_DEV_EL1_VTIMER bit field currently to track
+     * the level of the in-kernel timer, and preserve the last level in
+     * this class member.
+     */
+    uint64_t prevDeviceIRQLevel;
+
   protected:
     typedef std::vector<uint64_t> RegIndexVector;