dev-arm: Rewrite GICv3 update

The GICv3 update methods are method which are invoked anytime the model
needs to evaluate a change in its state, which most of the time means
managing the state of an interrupt (forwarding it to a PE, deasserting
it, etc).
The way it is currently done is a little bit obscure and doesn't
handle correctly IRQ prioritization.
Example:
An IRQ which is handled by the redistributor (PPI or LPI) was not
competing with any pending interrupts coming from the distributor (SPIs)
once raised by a peripheral.

Also the way the pending state of an interrupt was removed at the
cpu interface level wasn't happening in place where this was actually
happening (E.g. when activating it), but happened with a weird
fullUpdate semantic, where if there was a pending interrupt in a
cpu interface, all cpu interfaces had their pending interrupt (if any)
been disabled.

With this patch, state update always starts at the distributor, and
it goes down until the cpu interface where a Gicv3CPUInterface::update
method selects the winning interrupt coming from distributor/redistributor
to be forwarded to the PE.

Change-Id: I1c517cbc4bf107cc2d7ae7beb2692e3cf5187a40
Signed-off-by: Giacomo Travaglini <giacomo.travaglini@arm.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/20614
Maintainer: Andreas Sandberg <andreas.sandberg@arm.com>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/src/dev/arm/gic_v3_cpu_interface.cc b/src/dev/arm/gic_v3_cpu_interface.cc
index 97d9145..b4c271d 100644
--- a/src/dev/arm/gic_v3_cpu_interface.cc
+++ b/src/dev/arm/gic_v3_cpu_interface.cc
@@ -1819,15 +1819,19 @@
     if (int_id < Gicv3::SGI_MAX + Gicv3::PPI_MAX) {
         // SGI or PPI, redistributor
         redistributor->activateIRQ(int_id);
-        redistributor->updateAndInformCPUInterface();
     } else if (int_id < Gicv3::INTID_SECURE) {
         // SPI, distributor
         distributor->activateIRQ(int_id);
-        distributor->updateAndInformCPUInterfaces();
     } else if (int_id >= Gicv3Redistributor::SMALLEST_LPI_ID) {
         // LPI, Redistributor
         redistributor->setClrLPI(int_id, false);
     }
+
+    // By setting the priority to 0xff we are effectively
+    // making the int_id not pending anymore at the cpu
+    // interface.
+    hppi.prio = 0xff;
+    updateDistributor();
 }
 
 void
@@ -1857,15 +1861,12 @@
     if (int_id < Gicv3::SGI_MAX + Gicv3::PPI_MAX) {
         // SGI or PPI, redistributor
         redistributor->deactivateIRQ(int_id);
-        redistributor->updateAndInformCPUInterface();
     } else if (int_id < Gicv3::INTID_SECURE) {
         // SPI, distributor
         distributor->deactivateIRQ(int_id);
-        distributor->updateAndInformCPUInterfaces();
-    } else {
-        // LPI, redistributor, shouldn't deactivate
-        redistributor->updateAndInformCPUInterface();
     }
+
+    updateDistributor();
 }
 
 void
@@ -1992,6 +1993,12 @@
 }
 
 void
+Gicv3CPUInterface::updateDistributor()
+{
+    distributor->update();
+}
+
+void
 Gicv3CPUInterface::update()
 {
     bool signal_IRQ = false;
diff --git a/src/dev/arm/gic_v3_cpu_interface.hh b/src/dev/arm/gic_v3_cpu_interface.hh
index 5c66fd7..ed432bc 100644
--- a/src/dev/arm/gic_v3_cpu_interface.hh
+++ b/src/dev/arm/gic_v3_cpu_interface.hh
@@ -326,6 +326,7 @@
     void serialize(CheckpointOut & cp) const override;
     void unserialize(CheckpointIn & cp) override;
     void update();
+    void updateDistributor();
     void virtualActivateIRQ(uint32_t lrIdx);
     void virtualDeactivateIRQ(int lrIdx);
     uint8_t virtualDropPriority();
diff --git a/src/dev/arm/gic_v3_distributor.cc b/src/dev/arm/gic_v3_distributor.cc
index f85474c..0211bec 100644
--- a/src/dev/arm/gic_v3_distributor.cc
+++ b/src/dev/arm/gic_v3_distributor.cc
@@ -638,7 +638,7 @@
             }
         }
 
-        updateAndInformCPUInterfaces();
+        update();
         return;
     } else if (GICD_ICPENDR.contains(addr)) {
         // Interrupt Clear-Pending Registers
@@ -663,10 +663,11 @@
 
             if (clear) {
                 irqPending[int_id] = false;
+                clearIrqCpuInterface(int_id);
             }
         }
 
-        updateAndInformCPUInterfaces();
+        update();
         return;
     } else if (GICD_ISACTIVER.contains(addr)) {
         // Interrupt Set-Active Registers
@@ -947,7 +948,7 @@
     irqPending[int_id] = true;
     DPRINTF(GIC, "Gicv3Distributor::sendInt(): "
             "int_id %d (SPI) pending bit set\n", int_id);
-    updateAndInformCPUInterfaces();
+    update();
 }
 
 void
@@ -956,40 +957,59 @@
     panic_if(int_id < Gicv3::SGI_MAX + Gicv3::PPI_MAX, "Invalid SPI!");
     panic_if(int_id > itLines, "Invalid SPI!");
     irqPending[int_id] = false;
-    updateAndInformCPUInterfaces();
+    clearIrqCpuInterface(int_id);
+
+    update();
 }
 
-void
-Gicv3Distributor::updateAndInformCPUInterfaces()
+Gicv3CPUInterface*
+Gicv3Distributor::route(uint32_t int_id)
 {
-    update();
+    IROUTER affinity_routing = irqAffinityRouting[int_id];
+    Gicv3Redistributor * target_redistributor = nullptr;
 
-    for (int i = 0; i < gic->getSystem()->numContexts(); i++) {
-        gic->getCPUInterface(i)->update();
+    const Gicv3::GroupId int_group = getIntGroup(int_id);
+
+    if (affinity_routing.IRM) {
+        // Interrupts routed to any PE defined as a participating node
+        for (int i = 0; i < gic->getSystem()->numContexts(); i++) {
+            Gicv3Redistributor * redistributor_i =
+                gic->getRedistributor(i);
+
+            if (redistributor_i->
+                    canBeSelectedFor1toNInterrupt(int_group)) {
+                target_redistributor = redistributor_i;
+                break;
+            }
+        }
+    } else {
+        uint32_t affinity = (affinity_routing.Aff3 << 24) |
+                            (affinity_routing.Aff2 << 16) |
+                            (affinity_routing.Aff1 << 8) |
+                            (affinity_routing.Aff0 << 0);
+        target_redistributor =
+            gic->getRedistributorByAffinity(affinity);
+    }
+
+    if (!target_redistributor) {
+        // Interrrupts targeting not present cpus must remain pending
+        return nullptr;
+    } else {
+        return target_redistributor->getCPUInterface();
     }
 }
 
 void
-Gicv3Distributor::fullUpdate()
+Gicv3Distributor::clearIrqCpuInterface(uint32_t int_id)
 {
-    for (int i = 0; i < gic->getSystem()->numContexts(); i++) {
-        Gicv3CPUInterface * cpu_interface_i = gic->getCPUInterface(i);
-        cpu_interface_i->hppi.prio = 0xff;
-    }
-
-    update();
-
-    for (int i = 0; i < gic->getSystem()->numContexts(); i++) {
-        Gicv3Redistributor * redistributor_i = gic->getRedistributor(i);
-        redistributor_i->update();
-    }
+    auto cpu_interface = route(int_id);
+    if (cpu_interface)
+        cpu_interface->hppi.prio = 0xff;
 }
 
 void
 Gicv3Distributor::update()
 {
-    std::vector<bool> new_hppi(gic->getSystem()->numContexts(), false);
-
     // Find the highest priority pending SPI
     for (int int_id = Gicv3::SGI_MAX + Gicv3::PPI_MAX; int_id < itLines;
          int_id++) {
@@ -998,65 +1018,27 @@
 
         if (irqPending[int_id] && irqEnabled[int_id] &&
             !irqActive[int_id] && group_enabled) {
-            IROUTER affinity_routing = irqAffinityRouting[int_id];
-            Gicv3Redistributor * target_redistributor = nullptr;
 
-            if (affinity_routing.IRM) {
-                // Interrupts routed to any PE defined as a participating node
-                for (int i = 0; i < gic->getSystem()->numContexts(); i++) {
-                    Gicv3Redistributor * redistributor_i =
-                        gic->getRedistributor(i);
+            // Find the cpu interface where to route the interrupt
+            Gicv3CPUInterface *target_cpu_interface = route(int_id);
 
-                    if (redistributor_i->
-                            canBeSelectedFor1toNInterrupt(int_group)) {
-                        target_redistributor = redistributor_i;
-                        break;
-                    }
-                }
-            } else {
-                uint32_t affinity = (affinity_routing.Aff3 << 24) |
-                                    (affinity_routing.Aff3 << 16) |
-                                    (affinity_routing.Aff1 << 8) |
-                                    (affinity_routing.Aff0 << 0);
-                target_redistributor =
-                    gic->getRedistributorByAffinity(affinity);
-            }
-
-            if (!target_redistributor) {
-                // Interrrupts targeting not present cpus must remain pending
-                return;
-            }
-
-            Gicv3CPUInterface * target_cpu_interface =
-                target_redistributor->getCPUInterface();
-            uint32_t target_cpu = target_redistributor->cpuId;
+            // Invalid routing
+            if (!target_cpu_interface) continue;
 
             if ((irqPriority[int_id] < target_cpu_interface->hppi.prio) ||
-                /*
-                 * Multiple pending ints with same priority.
-                 * Implementation choice which one to signal.
-                 * Our implementation selects the one with the lower id.
-                 */
                 (irqPriority[int_id] == target_cpu_interface->hppi.prio &&
                 int_id < target_cpu_interface->hppi.intid)) {
+
                 target_cpu_interface->hppi.intid = int_id;
                 target_cpu_interface->hppi.prio = irqPriority[int_id];
                 target_cpu_interface->hppi.group = int_group;
-                new_hppi[target_cpu] = true;
             }
         }
     }
 
+    // Update all redistributors
     for (int i = 0; i < gic->getSystem()->numContexts(); i++) {
-        Gicv3Redistributor * redistributor_i = gic->getRedistributor(i);
-        Gicv3CPUInterface * cpu_interface_i =
-            redistributor_i->getCPUInterface();
-
-        if (!new_hppi[i] && cpu_interface_i->hppi.prio != 0xff &&
-            cpu_interface_i->hppi.intid >= (Gicv3::SGI_MAX + Gicv3::PPI_MAX) &&
-            cpu_interface_i->hppi.intid < Gicv3::INTID_SECURE) {
-            fullUpdate();
-        }
+        gic->getRedistributor(i)->update();
     }
 }
 
diff --git a/src/dev/arm/gic_v3_distributor.hh b/src/dev/arm/gic_v3_distributor.hh
index 0cf8d37..76ab6dd 100644
--- a/src/dev/arm/gic_v3_distributor.hh
+++ b/src/dev/arm/gic_v3_distributor.hh
@@ -222,13 +222,14 @@
     void serialize(CheckpointOut & cp) const override;
     void unserialize(CheckpointIn & cp) override;
     void update();
-    void updateAndInformCPUInterfaces();
+    Gicv3CPUInterface* route(uint32_t int_id);
 
   public:
 
     Gicv3Distributor(Gicv3 * gic, uint32_t it_lines);
 
     void deassertSPI(uint32_t int_id);
+    void clearIrqCpuInterface(uint32_t int_id);
     void init();
     void initState();
     uint64_t read(Addr addr, size_t size, bool is_secure_access);
diff --git a/src/dev/arm/gic_v3_its.cc b/src/dev/arm/gic_v3_its.cc
index f822042..4108cc7 100644
--- a/src/dev/arm/gic_v3_its.cc
+++ b/src/dev/arm/gic_v3_its.cc
@@ -1271,7 +1271,7 @@
         rd1->lpiPendingTablePtr,
         0, sizeof(lpi_pending_table));
 
-    rd2->updateAndInformCPUInterface();
+    rd2->updateDistributor();
 }
 
 Gicv3Its *
diff --git a/src/dev/arm/gic_v3_redistributor.cc b/src/dev/arm/gic_v3_redistributor.cc
index 71e74bf..e22ea70 100644
--- a/src/dev/arm/gic_v3_redistributor.cc
+++ b/src/dev/arm/gic_v3_redistributor.cc
@@ -536,7 +536,7 @@
             }
         }
 
-        updateAndInformCPUInterface();
+        updateDistributor();
         break;
 
       case GICR_ICPENDR0:// Interrupt Clear-Pending Register 0
@@ -733,7 +733,7 @@
     irqPending[int_id] = true;
     DPRINTF(GIC, "Gicv3Redistributor::sendPPInt(): "
             "int_id %d (PPI) pending bit set\n", int_id);
-    updateAndInformCPUInterface();
+    updateDistributor();
 }
 
 void
@@ -770,7 +770,7 @@
     irqPending[int_id] = true;
     DPRINTF(GIC, "Gicv3ReDistributor::sendSGI(): "
             "int_id %d (SGI) pending bit set\n", int_id);
-    updateAndInformCPUInterface();
+    updateDistributor();
 }
 
 Gicv3::IntStatus
@@ -791,6 +791,12 @@
     }
 }
 
+void
+Gicv3Redistributor::updateDistributor()
+{
+    distributor->update();
+}
+
 /*
  * Recalculate the highest priority pending interrupt after a
  * change to redistributor state.
@@ -798,8 +804,6 @@
 void
 Gicv3Redistributor::update()
 {
-    bool new_hppi = false;
-
     for (int int_id = 0; int_id < Gicv3::SGI_MAX + Gicv3::PPI_MAX; int_id++) {
         Gicv3::GroupId int_group = getIntGroup(int_id);
         bool group_enabled = distributor->groupEnabled(int_group);
@@ -817,7 +821,6 @@
                 cpuInterface->hppi.intid = int_id;
                 cpuInterface->hppi.prio = irqPriority[int_id];
                 cpuInterface->hppi.group = int_group;
-                new_hppi = true;
             }
         }
     }
@@ -866,17 +869,12 @@
                     cpuInterface->hppi.intid = lpi_id;
                     cpuInterface->hppi.prio = lpi_priority;
                     cpuInterface->hppi.group = lpi_group;
-                    new_hppi = true;
                 }
             }
         }
     }
 
-    if (!new_hppi && cpuInterface->hppi.prio != 0xff &&
-        (cpuInterface->hppi.intid < Gicv3::SGI_MAX + Gicv3::PPI_MAX ||
-         cpuInterface->hppi.intid > SMALLEST_LPI_ID)) {
-        distributor->fullUpdate();
-    }
+    cpuInterface->update();
 }
 
 uint8_t
@@ -958,14 +956,7 @@
 
     writeEntryLPI(lpi_id, lpi_pending_entry);
 
-    updateAndInformCPUInterface();
-}
-
-void
-Gicv3Redistributor::updateAndInformCPUInterface()
-{
-    update();
-    cpuInterface->update();
+    updateDistributor();
 }
 
 Gicv3::GroupId
diff --git a/src/dev/arm/gic_v3_redistributor.hh b/src/dev/arm/gic_v3_redistributor.hh
index 29ff867..c59c341 100644
--- a/src/dev/arm/gic_v3_redistributor.hh
+++ b/src/dev/arm/gic_v3_redistributor.hh
@@ -210,7 +210,7 @@
     void serialize(CheckpointOut & cp) const override;
     void unserialize(CheckpointIn & cp) override;
     void update();
-    void updateAndInformCPUInterface();
+    void updateDistributor();
 
   public: