summaryrefslogtreecommitdiff
path: root/src/mem/cache/mshr.cc
blob: 4f170e6b3a036208a821266154c4a3a554ac89ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
/*
 * Copyright (c) 2012-2013, 2015-2018 ARM Limited
 * All rights reserved.
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Copyright (c) 2002-2005 The Regents of The University of Michigan
 * Copyright (c) 2010 Advanced Micro Devices, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Erik Hallnor
 *          Dave Greene
 */

/**
 * @file
 * Miss Status and Handling Register (MSHR) definitions.
 */

#include "mem/cache/mshr.hh"

#include <algorithm>
#include <cassert>
#include <string>
#include <vector>

#include "base/logging.hh"
#include "base/types.hh"
#include "debug/Cache.hh"
#include "mem/cache/cache.hh"
#include "sim/core.hh"

MSHR::MSHR() : downstreamPending(false),
               pendingModified(false),
               postInvalidate(false), postDowngrade(false),
               isForward(false)
{
}

MSHR::TargetList::TargetList()
    : needsWritable(false), hasUpgrade(false), allocOnFill(false),
      hasFromCache(false)
{}


void
MSHR::TargetList::updateFlags(PacketPtr pkt, Target::Source source,
                              bool alloc_on_fill)
{
    if (source != Target::FromSnoop) {
        if (pkt->needsWritable()) {
            needsWritable = true;
        }

        // StoreCondReq is effectively an upgrade if it's in an MSHR
        // since it would have been failed already if we didn't have a
        // read-only copy
        if (pkt->isUpgrade() || pkt->cmd == MemCmd::StoreCondReq) {
            hasUpgrade = true;
        }

        // potentially re-evaluate whether we should allocate on a fill or
        // not
        allocOnFill = allocOnFill || alloc_on_fill;

        if (source != Target::FromPrefetcher) {
            hasFromCache = hasFromCache || pkt->fromCache();
        }
    }
}

void
MSHR::TargetList::populateFlags()
{
    resetFlags();
    for (auto& t: *this) {
        updateFlags(t.pkt, t.source, t.allocOnFill);
    }
}

inline void
MSHR::TargetList::add(PacketPtr pkt, Tick readyTime,
                      Counter order, Target::Source source, bool markPending,
                      bool alloc_on_fill)
{
    updateFlags(pkt, source, alloc_on_fill);
    if (markPending) {
        // Iterate over the SenderState stack and see if we find
        // an MSHR entry. If we do, set the downstreamPending
        // flag. Otherwise, do nothing.
        MSHR *mshr = pkt->findNextSenderState<MSHR>();
        if (mshr != nullptr) {
            assert(!mshr->downstreamPending);
            mshr->downstreamPending = true;
        } else {
            // No need to clear downstreamPending later
            markPending = false;
        }
    }

    emplace_back(pkt, readyTime, order, source, markPending, alloc_on_fill);
}


static void
replaceUpgrade(PacketPtr pkt)
{
    // remember if the current packet has data allocated
    bool has_data = pkt->hasData() || pkt->hasRespData();

    if (pkt->cmd == MemCmd::UpgradeReq) {
        pkt->cmd = MemCmd::ReadExReq;
        DPRINTF(Cache, "Replacing UpgradeReq with ReadExReq\n");
    } else if (pkt->cmd == MemCmd::SCUpgradeReq) {
        pkt->cmd = MemCmd::SCUpgradeFailReq;
        DPRINTF(Cache, "Replacing SCUpgradeReq with SCUpgradeFailReq\n");
    } else if (pkt->cmd == MemCmd::StoreCondReq) {
        pkt->cmd = MemCmd::StoreCondFailReq;
        DPRINTF(Cache, "Replacing StoreCondReq with StoreCondFailReq\n");
    }

    if (!has_data) {
        // there is no sensible way of setting the data field if the
        // new command actually would carry data
        assert(!pkt->hasData());

        if (pkt->hasRespData()) {
            // we went from a packet that had no data (neither request,
            // nor response), to one that does, and therefore we need to
            // actually allocate space for the data payload
            pkt->allocate();
        }
    }
}


void
MSHR::TargetList::replaceUpgrades()
{
    if (!hasUpgrade)
        return;

    for (auto& t : *this) {
        replaceUpgrade(t.pkt);
    }

    hasUpgrade = false;
}


void
MSHR::TargetList::clearDownstreamPending()
{
    for (auto& t : *this) {
        if (t.markedPending) {
            // Iterate over the SenderState stack and see if we find
            // an MSHR entry. If we find one, clear the
            // downstreamPending flag by calling
            // clearDownstreamPending(). This recursively clears the
            // downstreamPending flag in all caches this packet has
            // passed through.
            MSHR *mshr = t.pkt->findNextSenderState<MSHR>();
            if (mshr != nullptr) {
                mshr->clearDownstreamPending();
            }
            t.markedPending = false;
        }
    }
}


bool
MSHR::TargetList::checkFunctional(PacketPtr pkt)
{
    for (auto& t : *this) {
        if (pkt->checkFunctional(t.pkt)) {
            return true;
        }
    }

    return false;
}


void
MSHR::TargetList::print(std::ostream &os, int verbosity,
                        const std::string &prefix) const
{
    for (auto& t : *this) {
        const char *s;
        switch (t.source) {
          case Target::FromCPU:
            s = "FromCPU";
            break;
          case Target::FromSnoop:
            s = "FromSnoop";
            break;
          case Target::FromPrefetcher:
            s = "FromPrefetcher";
            break;
          default:
            s = "";
            break;
        }
        ccprintf(os, "%s%s: ", prefix, s);
        t.pkt->print(os, verbosity, "");
        ccprintf(os, "\n");
    }
}


void
MSHR::allocate(Addr blk_addr, unsigned blk_size, PacketPtr target,
               Tick when_ready, Counter _order, bool alloc_on_fill)
{
    blkAddr = blk_addr;
    blkSize = blk_size;
    isSecure = target->isSecure();
    readyTime = when_ready;
    order = _order;
    assert(target);
    isForward = false;
    _isUncacheable = target->req->isUncacheable();
    inService = false;
    downstreamPending = false;
    assert(targets.isReset());
    // Don't know of a case where we would allocate a new MSHR for a
    // snoop (mem-side request), so set source according to request here
    Target::Source source = (target->cmd == MemCmd::HardPFReq) ?
        Target::FromPrefetcher : Target::FromCPU;
    targets.add(target, when_ready, _order, source, true, alloc_on_fill);
    assert(deferredTargets.isReset());
}


void
MSHR::clearDownstreamPending()
{
    assert(downstreamPending);
    downstreamPending = false;
    // recursively clear flag on any MSHRs we will be forwarding
    // responses to
    targets.clearDownstreamPending();
}

void
MSHR::markInService(bool pending_modified_resp)
{
    assert(!inService);

    inService = true;
    pendingModified = targets.needsWritable || pending_modified_resp;
    postInvalidate = postDowngrade = false;

    if (!downstreamPending) {
        // let upstream caches know that the request has made it to a
        // level where it's going to get a response
        targets.clearDownstreamPending();
    }
}


void
MSHR::deallocate()
{
    assert(targets.empty());
    targets.resetFlags();
    assert(deferredTargets.isReset());
    inService = false;
}

/*
 * Adds a target to an MSHR
 */
void
MSHR::allocateTarget(PacketPtr pkt, Tick whenReady, Counter _order,
                     bool alloc_on_fill)
{
    // assume we'd never issue a prefetch when we've got an
    // outstanding miss
    assert(pkt->cmd != MemCmd::HardPFReq);

    // uncacheable accesses always allocate a new MSHR, and cacheable
    // accesses ignore any uncacheable MSHRs, thus we should never
    // have targets addded if originally allocated uncacheable
    assert(!_isUncacheable);

    // if there's a request already in service for this MSHR, we will
    // have to defer the new target until after the response if any of
    // the following are true:
    // - 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)
    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 (inService && hasPostInvalidate())
            replaceUpgrade(pkt);
        deferredTargets.add(pkt, whenReady, _order, Target::FromCPU, true,
                            alloc_on_fill);
    } else {
        // No request outstanding, or still OK to append to
        // outstanding request: append to regular target list.  Only
        // mark pending if current request hasn't been issued yet
        // (isn't in service).
        targets.add(pkt, whenReady, _order, Target::FromCPU, !inService,
                    alloc_on_fill);
    }
}

bool
MSHR::handleSnoop(PacketPtr pkt, Counter _order)
{
    DPRINTF(Cache, "%s for %s\n", __func__, pkt->print());

    // 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()) &&
             !pkt->req->isCacheMaintenance(),
             "%s got snoop %s where needsWritable, "
             "does not match isInvalidate", name(), pkt->print());

    if (!inService || (pkt->isExpressSnoop() && downstreamPending)) {
        // Request has not been issued yet, or it's been issued
        // locally but is buffered unissued at some downstream cache
        // which is forwarding us this snoop.  Either way, the packet
        // we're snooping logically precedes this MSHR's request, so
        // the snoop has no impact on the MSHR, but must be processed
        // in the standard way by the cache.  The only exception is
        // that if we're an L2+ cache buffering an UpgradeReq from a
        // higher-level cache, and the snoop is invalidating, then our
        // buffered upgrades must be converted to read exclusives,
        // since the upper-level cache no longer has a valid copy.
        // 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() || pkt->req->isCacheInvalidate()) {
            targets.replaceUpgrades();
            deferredTargets.replaceUpgrades();
        }

        return false;
    }

    // From here on down, the request issued by this MSHR logically
    // precedes the request we're snooping.
    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();
    }

    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;
    }

    if (isPendingModified() || pkt->isInvalidate()) {
        // We need to save and replay the packet in two cases:
        // 1. We're awaiting a writable copy (Modified or Exclusive),
        //    so this MSHR is the orgering point, and we need to respond
        //    after we receive data.
        // 2. It's an invalidation (e.g., UpgradeReq), and we need
        //    to forward the snoop up the hierarchy after the current
        //    transaction completes.

        // Start by determining if we will eventually respond or not,
        // matching the conditions checked in Cache::handleSnoop
        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
        // save a copy here. Clear flags and also allocate new data as
        // the original packet data storage may have been deleted by
        // the time we get to process this packet. In the cases where
        // we are not responding after handling the snoop we also need
        // to create a copy of the request to be on the safe side. In
        // the latter case the cache is responsible for deleting both
        // the packet and the request as part of handling the deferred
        // snoop.
        PacketPtr cp_pkt = will_respond ? new Packet(pkt, true, true) :
            new Packet(new Request(*pkt->req), pkt->cmd, blkSize, pkt->id);

        if (will_respond) {
            // we are the ordering point, and will consequently
            // respond, and depending on whether the packet
            // needsWritable or not we either pass a Shared line or a
            // Modified line
            pkt->setCacheResponding();

            // inform the cache hierarchy that this cache had the line
            // in the Modified state, even if the response is passed
            // as Shared (and thus non-writable)
            pkt->setResponderHadWritable();

            // in the case of an uncacheable request there is no need
            // to set the responderHadWritable flag, but since the
            // recipient does not care there is no harm in doing so
        }
        targets.add(cp_pkt, curTick(), _order, Target::FromSnoop,
                    downstreamPending && targets.needsWritable, false);

        if (pkt->needsWritable() || pkt->isInvalidate()) {
            // This transaction will take away our pending copy
            postInvalidate = true;
        }

        if (isPendingModified() && pkt->isClean()) {
            pkt->setSatisfied();
        }
    }

    if (!pkt->needsWritable() && !pkt->req->isUncacheable()) {
        // This transaction will get a read-shared copy, downgrading
        // our copy if we had a writable one
        postDowngrade = true;
        // make sure that any downstream cache does not respond with a
        // writable (and dirty) copy even if it has one, unless it was
        // explicitly asked for one
        pkt->setHasSharers();
    }

    return true;
}

MSHR::TargetList
MSHR::extractServiceableTargets(PacketPtr pkt)
{
    TargetList ready_targets;
    // If the downstream MSHR got an invalidation request then we only
    // service the first of the FromCPU targets and any other
    // non-FromCPU target. This way the remaining FromCPU targets
    // issue a new request and get a fresh copy of the block and we
    // avoid memory consistency violations.
    if (pkt->cmd == MemCmd::ReadRespWithInvalidate) {
        auto it = targets.begin();
        assert((it->source == Target::FromCPU) ||
               (it->source == Target::FromPrefetcher));
        ready_targets.push_back(*it);
        it = targets.erase(it);
        while (it != targets.end()) {
            if (it->source == Target::FromCPU) {
                it++;
            } else {
                assert(it->source == Target::FromSnoop);
                ready_targets.push_back(*it);
                it = targets.erase(it);
            }
        }
        ready_targets.populateFlags();
    } else {
        std::swap(ready_targets, targets);
    }
    targets.populateFlags();

    return ready_targets;
}

bool
MSHR::promoteDeferredTargets()
{
    if (targets.empty() && deferredTargets.empty()) {
        // nothing to promote
        return false;
    }

    // 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);

    return true;
}


void
MSHR::promoteWritable()
{
    if (deferredTargets.needsWritable &&
        !(hasPostInvalidate() || hasPostDowngrade())) {
        // We got a writable response, but we have deferred targets
        // which are waiting to request a writable copy (not because
        // of a pending invalidate).  This can happen if the original
        // request was for a read-only block, but we got a writable
        // response anyway. Since we got the writable copy there's no
        // need to defer the targets, so move them up to the regular
        // target list.
        assert(!targets.needsWritable);
        targets.needsWritable = true;
        // if any of the deferred targets were upper-level cache
        // requests marked downstreamPending, need to clear that
        assert(!downstreamPending);  // not pending here anymore
        deferredTargets.clearDownstreamPending();
        // this clears out deferredTargets too
        targets.splice(targets.end(), deferredTargets);
        deferredTargets.resetFlags();
    }
}


bool
MSHR::checkFunctional(PacketPtr pkt)
{
    // For printing, we treat the MSHR as a whole as single entity.
    // For other requests, we iterate over the individual targets
    // since that's where the actual data lies.
    if (pkt->isPrint()) {
        pkt->checkFunctional(this, blkAddr, isSecure, blkSize, nullptr);
        return false;
    } else {
        return (targets.checkFunctional(pkt) ||
                deferredTargets.checkFunctional(pkt));
    }
}

bool
MSHR::sendPacket(Cache &cache)
{
    return cache.sendMSHRQueuePacket(this);
}

void
MSHR::print(std::ostream &os, int verbosity, const std::string &prefix) const
{
    ccprintf(os, "%s[%#llx:%#llx](%s) %s %s %s state: %s %s %s %s %s %s\n",
             prefix, blkAddr, blkAddr + blkSize - 1,
             isSecure ? "s" : "ns",
             isForward ? "Forward" : "",
             allocOnFill() ? "AllocOnFill" : "",
             needsWritable() ? "Wrtbl" : "",
             _isUncacheable ? "Unc" : "",
             inService ? "InSvc" : "",
             downstreamPending ? "DwnPend" : "",
             postInvalidate ? "PostInv" : "",
             postDowngrade ? "PostDowngr" : "",
             hasFromCache() ? "HasFromCache" : "");

    if (!targets.empty()) {
        ccprintf(os, "%s  Targets:\n", prefix);
        targets.print(os, verbosity, prefix + "    ");
    }
    if (!deferredTargets.empty()) {
        ccprintf(os, "%s  Deferred Targets:\n", prefix);
        deferredTargets.print(os, verbosity, prefix + "      ");
    }
}

std::string
MSHR::print() const
{
    std::ostringstream str;
    print(str);
    return str.str();
}