dev-arm: Fix <timer>_CTL_EL<x>.ISTATUS when masking the irq
authorGiacomo Travaglini <giacomo.travaglini@arm.com>
Thu, 23 Jul 2020 12:19:49 +0000 (13:19 +0100)
committerGiacomo Travaglini <giacomo.travaglini@arm.com>
Mon, 10 Aug 2020 15:35:54 +0000 (15:35 +0000)
According to the ArmArm:

"When the value of the ENABLE bit is 1, ISTATUS indicates whether the
timer condition is met.  ISTATUS takes no account of the value of the
IMASK bit. If the value of ISTATUS is 1 and the value of IMASK is 0 then
the timer interrupt is asserted."

Since ISTATUS is simply flagging that timer conditions are met, an
interrupt mask (via the <timer>_CTL_EL<x>.IMASK) shouldn't reset the
field to 0.
Clearing the ISTATUS bit leads to the following problem
as an example:

1) virtual timer (EL1) issuing a physical interrupt to the GIC

2) hypervisor handling the physical interrupt; setting the
CNTV_CTL_EL0.IMASK to 1 before issuing the virtual interrupt
to the VM

3) The VM receives the virtual interrupt but it gets confused
since CNTV_CTL_EL0.ISTATUS is 0 (due to point 2)

What happens when we disable the timer?

"When the value of the ENABLE bit is 0, the ISTATUS field is UNKNOWN."

So we are allowed to not clear the ISTATUS bit if the timer gets
disabled

Change-Id: I8eb32459a3ef6829c1910cf63815e102e2705566
Signed-off-by: Giacomo Travaglini <giacomo.travaglini@arm.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-by: Adrian Herrera <adrian.herrera@arm.com>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/31775
Reviewed-by: Hsuan Hsu <kugwa2000@gmail.com>
Tested-by: kokoro <noreply+kokoro@google.com>
src/dev/arm/generic_timer.cc

index 7bb2defef7a11e0dbfb6c1421a96fb8941edf9db..a620eec6c74b4aa0780a8c9f890c5e616e58a9ec 100644 (file)
@@ -281,11 +281,14 @@ ArchTimer::updateCounter()
     if (value() >= _counterLimit) {
         counterLimitReached();
     } else {
-        if (_control.istatus) {
+        // Clear the interurpt when timers conditions are not met
+        if (_interrupt->active()) {
             DPRINTF(Timer, "Clearing interrupt\n");
             _interrupt->clear();
-            _control.istatus = 0;
         }
+
+        _control.istatus = 0;
+
         if (scheduleEvents()) {
             _parent.schedule(_counterLimitReachedEvent,
                              whenValue(_counterLimit));
@@ -320,10 +323,16 @@ ArchTimer::setControl(uint32_t val)
     // Timer masked or disabled
     else if ((!old_ctl.imask && new_ctl.imask) ||
              (old_ctl.enable && !new_ctl.enable)) {
-        if (_control.istatus) {
+
+        if (_interrupt->active()) {
             DPRINTF(Timer, "Clearing interrupt\n");
+            // We are clearing the interrupt but we are not
+            // setting istatus to 0 as we are doing
+            // in the updateCounter.
+            // istatus signals that Timer conditions are met.
+            // It shouldn't depend on masking.
+            // if enable is zero. istatus is unknown.
             _interrupt->clear();
-            _control.istatus = 0;
         }
     }
 }