mem: Add support for handling CMOs in the MSHRs

To add support for cache maintenance operations (CMOs) in the MSHRs,
this change adds the following functionality:
- If a CMO request hits in the MSHRs, we deferred as we can't
  coalesce it with any other requests.
- When we promote any deferred targets, we promote them in order and
  stop if we encounter a CMO request. If the CMO request is at the
  beginning of the deferred targets list it will be the only promoted
  target.

Change-Id: I10d1f7e16bd6d522d917279c5d408a3f0cee4286
Reviewed-by: Stephan Diestelhorst <stephan.diestelhorst@arm.com>
Reviewed-on: https://gem5-review.googlesource.com/5050
Maintainer: Nikos Nikoleris <nikos.nikoleris@arm.com>
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
diff --git a/src/mem/cache/mshr.cc b/src/mem/cache/mshr.cc
index fa21f5c..d89adef 100644
--- a/src/mem/cache/mshr.cc
+++ b/src/mem/cache/mshr.cc
@@ -318,16 +318,24 @@
     // - there are other targets already deferred
     // - there's a pending invalidate to be applied after the response
     //   comes back (but before this target is processed)
+    // - the MSHR's first (and only) non-deferred target is a cache
+    //   maintenance packet
+    // - the new target is a cache maintenance packet (this is probably
+    //   overly conservative but certainly safe)
     // - this target requires a writable block and either we're not
     //   getting a writable block back or we have already snooped
     //   another read request that will downgrade our writable block
     //   to non-writable (Shared or Owned)
-    if (inService &&
-        (!deferredTargets.empty() || hasPostInvalidate() ||
-         (pkt->needsWritable() &&
-          (!isPendingModified() || hasPostDowngrade() || isForward)))) {
+    PacketPtr tgt_pkt = targets.front().pkt;
+    if (pkt->req->isCacheMaintenance() ||
+        tgt_pkt->req->isCacheMaintenance() ||
+        !deferredTargets.empty() ||
+        (inService &&
+         (hasPostInvalidate() ||
+          (pkt->needsWritable() &&
+           (!isPendingModified() || hasPostDowngrade() || isForward))))) {
         // need to put on deferred list
-        if (hasPostInvalidate())
+        if (inService && hasPostInvalidate())
             replaceUpgrade(pkt);
         deferredTargets.add(pkt, whenReady, _order, Target::FromCPU, true,
                             alloc_on_fill);
@@ -349,7 +357,8 @@
     // when we snoop packets the needsWritable and isInvalidate flags
     // should always be the same, however, this assumes that we never
     // snoop writes as they are currently not marked as invalidations
-    panic_if(pkt->needsWritable() != pkt->isInvalidate(),
+    panic_if((pkt->needsWritable() != pkt->isInvalidate()) &&
+             !pkt->req->isCacheMaintenance(),
              "%s got snoop %s where needsWritable, "
              "does not match isInvalidate", name(), pkt->print());
 
@@ -367,7 +376,7 @@
         // That is, even though the upper-level cache got out on its
         // local bus first, some other invalidating transaction
         // reached the global bus before the upgrade did.
-        if (pkt->needsWritable()) {
+        if (pkt->needsWritable() || pkt->req->isCacheInvalidate()) {
             targets.replaceUpgrades();
             deferredTargets.replaceUpgrades();
         }
@@ -377,16 +386,18 @@
 
     // From here on down, the request issued by this MSHR logically
     // precedes the request we're snooping.
-    if (pkt->needsWritable()) {
+    if (pkt->needsWritable() || pkt->req->isCacheInvalidate()) {
         // snooped request still precedes the re-request we'll have to
         // issue for deferred targets, if any...
         deferredTargets.replaceUpgrades();
     }
 
-    if (hasPostInvalidate()) {
-        // a prior snoop has already appended an invalidation, so
-        // logically we don't have the block anymore; no need for
-        // further snooping.
+    PacketPtr tgt_pkt = targets.front().pkt;
+    if (hasPostInvalidate() || tgt_pkt->req->isCacheInvalidate()) {
+        // a prior snoop has already appended an invalidation or a
+        // cache invalidation operation is in progress, so logically
+        // we don't have the block anymore; no need for further
+        // snooping.
         return true;
     }
 
@@ -401,7 +412,8 @@
 
         // Start by determining if we will eventually respond or not,
         // matching the conditions checked in Cache::handleSnoop
-        bool will_respond = isPendingModified() && pkt->needsResponse();
+        bool will_respond = isPendingModified() && pkt->needsResponse() &&
+                      !pkt->isClean();
 
         // The packet we are snooping may be deleted by the time we
         // actually process the target, and we consequently need to
@@ -435,10 +447,14 @@
         targets.add(cp_pkt, curTick(), _order, Target::FromSnoop,
                     downstreamPending && targets.needsWritable, false);
 
-        if (pkt->needsWritable()) {
+        if (pkt->needsWritable() || pkt->isInvalidate()) {
             // This transaction will take away our pending copy
             postInvalidate = true;
         }
+
+        if (pkt->isClean()) {
+            pkt->setSatisfied();
+        }
     }
 
     if (!pkt->needsWritable() && !pkt->req->isUncacheable()) {
@@ -490,23 +506,36 @@
 bool
 MSHR::promoteDeferredTargets()
 {
-    if (targets.empty())  {
-        if (deferredTargets.empty()) {
-            return false;
-        }
-
-        std::swap(targets, deferredTargets);
-    } else {
-        // If the targets list is not empty then we have one targets
-        // from the deferredTargets list to the targets list. A new
-        // request will then service the targets list.
-        targets.splice(targets.end(), deferredTargets);
-        targets.populateFlags();
+    if (targets.empty() && deferredTargets.empty()) {
+        // nothing to promote
+        return false;
     }
 
-    // clear deferredTargets flags
-    deferredTargets.resetFlags();
+    // the deferred targets can be generally promoted unless they
+    // contain a cache maintenance request
 
+    // find the first target that is a cache maintenance request
+    auto it = std::find_if(deferredTargets.begin(), deferredTargets.end(),
+                           [](MSHR::Target &t) {
+                               return t.pkt->req->isCacheMaintenance();
+                           });
+    if (it == deferredTargets.begin()) {
+        // if the first deferred target is a cache maintenance packet
+        // then we can promote provided the targets list is empty and
+        // we can service it on its own
+        if (targets.empty()) {
+            targets.splice(targets.end(), deferredTargets, it);
+        }
+    } else {
+        // if a cache maintenance operation exists, we promote all the
+        // deferred targets that precede it, or all deferred targets
+        // otherwise
+        targets.splice(targets.end(), deferredTargets,
+                       deferredTargets.begin(), it);
+    }
+
+    deferredTargets.populateFlags();
+    targets.populateFlags();
     order = targets.front().order;
     readyTime = std::max(curTick(), targets.front().readyTime);