Apply the gem5 namespace to the codebase. Some anonymous namespaces could theoretically be removed, but since this change's main goal was to keep conflicts at a minimum, it was decided not to modify much the general shape of the files. A few missing comments of the form "// namespace X" that occurred before the newly added "} // namespace gem5" have been added for consistency. std out should not be included in the gem5 namespace, so they weren't. ProtoMessage has not been included in the gem5 namespace, since I'm not familiar with how proto works. Regarding the SystemC files, although they belong to gem5, they actually perform integration between gem5 and SystemC; therefore, it deserved its own separate namespace. Files that are automatically generated have been included in the gem5 namespace. The .isa files currently are limited to a single namespace. This limitation should be later removed to make it easier to accomodate a better API. Regarding the files in util, gem5:: was prepended where suitable. Notice that this patch was tested as much as possible given that most of these were already not previously compiling. Change-Id: Ia53d404ec79c46edaa98f654e23bc3b0e179fe2d Signed-off-by: Daniel R. Carvalho <odanrc@yahoo.com.br> Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/46323 Maintainer: Bobby R. Bruce <bbruce@ucdavis.edu> Reviewed-by: Bobby R. Bruce <bbruce@ucdavis.edu> Reviewed-by: Matthew Poremba <matthew.poremba@amd.com> Tested-by: kokoro <noreply+kokoro@google.com>
605 lines
18 KiB
C++
605 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2012, 2015, 2017, 2019 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) 2006 The Regents of The University of Michigan
|
|
* 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.
|
|
*/
|
|
|
|
#include "dev/dma_device.hh"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <utility>
|
|
|
|
#include "base/logging.hh"
|
|
#include "base/trace.hh"
|
|
#include "debug/DMA.hh"
|
|
#include "debug/Drain.hh"
|
|
#include "sim/clocked_object.hh"
|
|
#include "sim/system.hh"
|
|
|
|
namespace gem5
|
|
{
|
|
|
|
DmaPort::DmaPort(ClockedObject *dev, System *s,
|
|
uint32_t sid, uint32_t ssid)
|
|
: RequestPort(dev->name() + ".dma", dev),
|
|
device(dev), sys(s), requestorId(s->getRequestorId(dev)),
|
|
sendEvent([this]{ sendDma(); }, dev->name()),
|
|
defaultSid(sid), defaultSSid(ssid), cacheLineSize(s->cacheLineSize())
|
|
{ }
|
|
|
|
void
|
|
DmaPort::handleRespPacket(PacketPtr pkt, Tick delay)
|
|
{
|
|
// Should always see a response with a sender state.
|
|
assert(pkt->isResponse());
|
|
|
|
// Get the DMA sender state.
|
|
auto *state = dynamic_cast<DmaReqState*>(pkt->senderState);
|
|
assert(state);
|
|
|
|
handleResp(state, pkt->getAddr(), pkt->req->getSize(), delay);
|
|
|
|
delete pkt;
|
|
}
|
|
|
|
void
|
|
DmaPort::handleResp(DmaReqState *state, Addr addr, Addr size, Tick delay)
|
|
{
|
|
DPRINTF(DMA, "Received response %s for addr: %#x size: %d nb: %d," \
|
|
" tot: %d sched %d\n",
|
|
MemCmd(state->cmd).toString(), addr, size,
|
|
state->numBytes, state->totBytes,
|
|
state->completionEvent ?
|
|
state->completionEvent->scheduled() : 0);
|
|
|
|
// Update the number of bytes received based on the request rather
|
|
// than the packet as the latter could be rounded up to line sizes.
|
|
state->numBytes += size;
|
|
assert(state->totBytes >= state->numBytes);
|
|
|
|
// If we have reached the total number of bytes for this DMA request,
|
|
// then signal the completion and delete the sate.
|
|
if (state->totBytes == state->numBytes) {
|
|
assert(pendingCount != 0);
|
|
pendingCount--;
|
|
if (state->completionEvent) {
|
|
delay += state->delay;
|
|
device->schedule(state->completionEvent, curTick() + delay);
|
|
}
|
|
delete state;
|
|
}
|
|
|
|
// We might be drained at this point, if so signal the drain event.
|
|
if (pendingCount == 0)
|
|
signalDrainDone();
|
|
}
|
|
|
|
PacketPtr
|
|
DmaPort::DmaReqState::createPacket()
|
|
{
|
|
RequestPtr req = std::make_shared<Request>(
|
|
gen.addr(), gen.size(), flags, id);
|
|
req->setStreamId(sid);
|
|
req->setSubstreamId(ssid);
|
|
req->taskId(context_switch_task_id::DMA);
|
|
|
|
PacketPtr pkt = new Packet(req, cmd);
|
|
|
|
if (data)
|
|
pkt->dataStatic(data + gen.complete());
|
|
|
|
pkt->senderState = this;
|
|
return pkt;
|
|
}
|
|
|
|
bool
|
|
DmaPort::recvTimingResp(PacketPtr pkt)
|
|
{
|
|
// We shouldn't ever get a cacheable block in Modified state.
|
|
assert(pkt->req->isUncacheable() ||
|
|
!(pkt->cacheResponding() && !pkt->hasSharers()));
|
|
|
|
handleRespPacket(pkt);
|
|
|
|
return true;
|
|
}
|
|
|
|
DmaDevice::DmaDevice(const Params &p)
|
|
: PioDevice(p), dmaPort(this, sys, p.sid, p.ssid)
|
|
{ }
|
|
|
|
void
|
|
DmaDevice::init()
|
|
{
|
|
panic_if(!dmaPort.isConnected(),
|
|
"DMA port of %s not connected to anything!", name());
|
|
PioDevice::init();
|
|
}
|
|
|
|
DrainState
|
|
DmaPort::drain()
|
|
{
|
|
if (pendingCount == 0) {
|
|
return DrainState::Drained;
|
|
} else {
|
|
DPRINTF(Drain, "DmaPort not drained\n");
|
|
return DrainState::Draining;
|
|
}
|
|
}
|
|
|
|
void
|
|
DmaPort::recvReqRetry()
|
|
{
|
|
assert(transmitList.size());
|
|
trySendTimingReq();
|
|
}
|
|
|
|
void
|
|
DmaPort::dmaAction(Packet::Command cmd, Addr addr, int size, Event *event,
|
|
uint8_t *data, uint32_t sid, uint32_t ssid, Tick delay,
|
|
Request::Flags flag)
|
|
{
|
|
DPRINTF(DMA, "Starting DMA for addr: %#x size: %d sched: %d\n", addr, size,
|
|
event ? event->scheduled() : -1);
|
|
|
|
// One DMA request sender state for every action, that is then
|
|
// split into many requests and packets based on the block size,
|
|
// i.e. cache line size.
|
|
transmitList.push_back(
|
|
new DmaReqState(cmd, addr, cacheLineSize, size,
|
|
data, flag, requestorId, sid, ssid, event, delay));
|
|
pendingCount++;
|
|
|
|
// In zero time, also initiate the sending of the packets for the request
|
|
// we have just created. For atomic this involves actually completing all
|
|
// the requests.
|
|
sendDma();
|
|
}
|
|
|
|
void
|
|
DmaPort::dmaAction(Packet::Command cmd, Addr addr, int size, Event *event,
|
|
uint8_t *data, Tick delay, Request::Flags flag)
|
|
{
|
|
dmaAction(cmd, addr, size, event, data,
|
|
defaultSid, defaultSSid, delay, flag);
|
|
}
|
|
|
|
void
|
|
DmaPort::trySendTimingReq()
|
|
{
|
|
// Send the next packet for the first DMA request on the transmit list,
|
|
// and schedule the following send if it is successful
|
|
DmaReqState *state = transmitList.front();
|
|
|
|
PacketPtr pkt = inRetry ? inRetry : state->createPacket();
|
|
inRetry = nullptr;
|
|
|
|
DPRINTF(DMA, "Trying to send %s addr %#x\n", pkt->cmdString(),
|
|
pkt->getAddr());
|
|
|
|
// Check if this was the last packet now, since hypothetically the packet
|
|
// response may come immediately, and state may be deleted.
|
|
bool last = state->gen.last();
|
|
if (!sendTimingReq(pkt))
|
|
inRetry = pkt;
|
|
if (!inRetry) {
|
|
// If that was the last packet from this request, pop it from the list.
|
|
if (last)
|
|
transmitList.pop_front();
|
|
else
|
|
state->gen.next();
|
|
DPRINTF(DMA, "-- Done\n");
|
|
// If there is more to do, then do so.
|
|
if (!transmitList.empty()) {
|
|
// This should ultimately wait for as many cycles as the device
|
|
// needs to send the packet, but currently the port does not have
|
|
// any known width so simply wait a single cycle.
|
|
device->schedule(sendEvent, device->clockEdge(Cycles(1)));
|
|
}
|
|
} else {
|
|
DPRINTF(DMA, "-- Failed, waiting for retry\n");
|
|
}
|
|
|
|
DPRINTF(DMA, "TransmitList: %d, inRetry: %d\n",
|
|
transmitList.size(), inRetry ? 1 : 0);
|
|
}
|
|
|
|
bool
|
|
DmaPort::sendAtomicReq(DmaReqState *state)
|
|
{
|
|
PacketPtr pkt = state->createPacket();
|
|
DPRINTF(DMA, "Sending DMA for addr: %#x size: %d\n",
|
|
state->gen.addr(), state->gen.size());
|
|
Tick lat = sendAtomic(pkt);
|
|
|
|
// Check if we're done, since handleResp may delete state.
|
|
bool done = !state->gen.next();
|
|
handleRespPacket(pkt, lat);
|
|
return done;
|
|
}
|
|
|
|
bool
|
|
DmaPort::sendAtomicBdReq(DmaReqState *state)
|
|
{
|
|
bool done = false;
|
|
|
|
auto bd_it = memBackdoors.contains(state->gen.addr());
|
|
if (bd_it == memBackdoors.end()) {
|
|
// We don't have a backdoor for this address, so use a packet.
|
|
|
|
PacketPtr pkt = state->createPacket();
|
|
DPRINTF(DMA, "Sending DMA for addr: %#x size: %d\n",
|
|
state->gen.addr(), state->gen.size());
|
|
|
|
MemBackdoorPtr bd = nullptr;
|
|
Tick lat = sendAtomicBackdoor(pkt, bd);
|
|
|
|
// If we got a backdoor, record it.
|
|
if (bd && memBackdoors.insert(bd->range(), bd) != memBackdoors.end()) {
|
|
// Invalidation callback which finds this backdoor and removes it.
|
|
auto callback = [this](const MemBackdoor &backdoor) {
|
|
for (auto it = memBackdoors.begin();
|
|
it != memBackdoors.end(); it++) {
|
|
if (it->second == &backdoor) {
|
|
memBackdoors.erase(it);
|
|
return;
|
|
}
|
|
}
|
|
panic("Got invalidation for unknown memory backdoor.");
|
|
};
|
|
bd->addInvalidationCallback(callback);
|
|
}
|
|
|
|
// Check if we're done now, since handleResp may delete state.
|
|
done = !state->gen.next();
|
|
handleRespPacket(pkt, lat);
|
|
} else {
|
|
// We have a backdoor that can at least partially satisfy this request.
|
|
DPRINTF(DMA, "Handling DMA for addr: %#x size %d through backdoor\n",
|
|
state->gen.addr(), state->gen.size());
|
|
|
|
const auto *bd = bd_it->second;
|
|
// Offset of this access into the backdoor.
|
|
const Addr offset = state->gen.addr() - bd->range().start();
|
|
// How many bytes we still need.
|
|
const Addr remaining = state->totBytes - state->gen.complete();
|
|
// How many bytes this backdoor can provide, starting from offset.
|
|
const Addr available = bd->range().size() - offset;
|
|
|
|
// How many bytes we're going to handle through this backdoor.
|
|
const Addr handled = std::min(remaining, available);
|
|
|
|
// If there's a buffer for data, read/write it.
|
|
if (state->data) {
|
|
uint8_t *bd_data = bd->ptr() + offset;
|
|
uint8_t *state_data = state->data + state->gen.complete();
|
|
if (MemCmd(state->cmd).isRead())
|
|
memcpy(state_data, bd_data, handled);
|
|
else
|
|
memcpy(bd_data, state_data, handled);
|
|
}
|
|
|
|
// Advance the chunk generator past this region of memory.
|
|
state->gen.setNext(state->gen.addr() + handled);
|
|
|
|
// Check if we're done now, since handleResp may delete state.
|
|
done = !state->gen.next();
|
|
handleResp(state, state->gen.addr(), handled);
|
|
}
|
|
|
|
return done;
|
|
}
|
|
|
|
void
|
|
DmaPort::sendDma()
|
|
{
|
|
// Some kind of selection between access methods. More work is going to
|
|
// have to be done to make switching actually work.
|
|
assert(transmitList.size());
|
|
|
|
if (sys->isTimingMode()) {
|
|
// If we are either waiting for a retry or are still waiting after
|
|
// sending the last packet, then do not proceed.
|
|
if (inRetry || sendEvent.scheduled()) {
|
|
DPRINTF(DMA, "Can't send immediately, waiting to send\n");
|
|
return;
|
|
}
|
|
|
|
trySendTimingReq();
|
|
} else if (sys->isAtomicMode()) {
|
|
const bool bypass = sys->bypassCaches();
|
|
|
|
// Send everything there is to send in zero time.
|
|
while (!transmitList.empty()) {
|
|
DmaReqState *state = transmitList.front();
|
|
transmitList.pop_front();
|
|
|
|
bool done = state->gen.done();
|
|
while (!done)
|
|
done = bypass ? sendAtomicBdReq(state) : sendAtomicReq(state);
|
|
}
|
|
} else {
|
|
panic("Unknown memory mode.");
|
|
}
|
|
}
|
|
|
|
Port &
|
|
DmaDevice::getPort(const std::string &if_name, PortID idx)
|
|
{
|
|
if (if_name == "dma") {
|
|
return dmaPort;
|
|
}
|
|
return PioDevice::getPort(if_name, idx);
|
|
}
|
|
|
|
DmaReadFifo::DmaReadFifo(DmaPort &_port, size_t size,
|
|
unsigned max_req_size,
|
|
unsigned max_pending,
|
|
Request::Flags flags)
|
|
: maxReqSize(max_req_size), fifoSize(size),
|
|
reqFlags(flags), port(_port), cacheLineSize(port.sys->cacheLineSize()),
|
|
buffer(size)
|
|
{
|
|
freeRequests.resize(max_pending);
|
|
for (auto &e : freeRequests)
|
|
e.reset(new DmaDoneEvent(this, max_req_size));
|
|
|
|
}
|
|
|
|
DmaReadFifo::~DmaReadFifo()
|
|
{
|
|
for (auto &p : pendingRequests) {
|
|
DmaDoneEvent *e(p.release());
|
|
|
|
if (e->done()) {
|
|
delete e;
|
|
} else {
|
|
// We can't kill in-flight DMAs, so we'll just transfer
|
|
// ownership to the event queue so that they get freed
|
|
// when they are done.
|
|
e->kill();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::serialize(CheckpointOut &cp) const
|
|
{
|
|
assert(pendingRequests.empty());
|
|
|
|
SERIALIZE_CONTAINER(buffer);
|
|
SERIALIZE_SCALAR(endAddr);
|
|
SERIALIZE_SCALAR(nextAddr);
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::unserialize(CheckpointIn &cp)
|
|
{
|
|
UNSERIALIZE_CONTAINER(buffer);
|
|
UNSERIALIZE_SCALAR(endAddr);
|
|
UNSERIALIZE_SCALAR(nextAddr);
|
|
}
|
|
|
|
bool
|
|
DmaReadFifo::tryGet(uint8_t *dst, size_t len)
|
|
{
|
|
if (buffer.size() >= len) {
|
|
buffer.read(dst, len);
|
|
resumeFill();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::get(uint8_t *dst, size_t len)
|
|
{
|
|
panic_if(!tryGet(dst, len), "Buffer underrun in DmaReadFifo::get()");
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::startFill(Addr start, size_t size)
|
|
{
|
|
assert(atEndOfBlock());
|
|
|
|
nextAddr = start;
|
|
endAddr = start + size;
|
|
resumeFill();
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::stopFill()
|
|
{
|
|
// Prevent new DMA requests by setting the next address to the end
|
|
// address. Pending requests will still complete.
|
|
nextAddr = endAddr;
|
|
|
|
// Flag in-flight accesses as canceled. This prevents their data
|
|
// from being written to the FIFO.
|
|
for (auto &p : pendingRequests)
|
|
p->cancel();
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::resumeFill()
|
|
{
|
|
// Don't try to fetch more data if we are draining. This ensures
|
|
// that the DMA engine settles down before we checkpoint it.
|
|
if (drainState() == DrainState::Draining)
|
|
return;
|
|
|
|
const bool old_eob(atEndOfBlock());
|
|
|
|
if (port.sys->bypassCaches())
|
|
resumeFillBypass();
|
|
else
|
|
resumeFillTiming();
|
|
|
|
if (!old_eob && atEndOfBlock())
|
|
onEndOfBlock();
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::resumeFillBypass()
|
|
{
|
|
const size_t fifo_space = buffer.capacity() - buffer.size();
|
|
if (fifo_space >= cacheLineSize || buffer.capacity() < cacheLineSize) {
|
|
const size_t block_remaining = endAddr - nextAddr;
|
|
const size_t xfer_size = std::min(fifo_space, block_remaining);
|
|
std::vector<uint8_t> tmp_buffer(xfer_size);
|
|
|
|
assert(pendingRequests.empty());
|
|
DPRINTF(DMA, "Direct bypass startAddr=%#x xfer_size=%#x " \
|
|
"fifo_space=%#x block_remaining=%#x\n",
|
|
nextAddr, xfer_size, fifo_space, block_remaining);
|
|
|
|
port.dmaAction(MemCmd::ReadReq, nextAddr, xfer_size, nullptr,
|
|
tmp_buffer.data(), 0, reqFlags);
|
|
|
|
buffer.write(tmp_buffer.begin(), xfer_size);
|
|
nextAddr += xfer_size;
|
|
}
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::resumeFillTiming()
|
|
{
|
|
size_t size_pending(0);
|
|
for (auto &e : pendingRequests)
|
|
size_pending += e->requestSize();
|
|
|
|
while (!freeRequests.empty() && !atEndOfBlock()) {
|
|
const size_t req_size(std::min(maxReqSize, endAddr - nextAddr));
|
|
if (buffer.size() + size_pending + req_size > fifoSize)
|
|
break;
|
|
|
|
DmaDoneEventUPtr event(std::move(freeRequests.front()));
|
|
freeRequests.pop_front();
|
|
assert(event);
|
|
|
|
event->reset(req_size);
|
|
port.dmaAction(MemCmd::ReadReq, nextAddr, req_size, event.get(),
|
|
event->data(), 0, reqFlags);
|
|
nextAddr += req_size;
|
|
size_pending += req_size;
|
|
|
|
pendingRequests.emplace_back(std::move(event));
|
|
}
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::dmaDone()
|
|
{
|
|
const bool old_active(isActive());
|
|
|
|
handlePending();
|
|
resumeFill();
|
|
|
|
if (old_active && !isActive())
|
|
onIdle();
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::handlePending()
|
|
{
|
|
while (!pendingRequests.empty() && pendingRequests.front()->done()) {
|
|
// Get the first finished pending request
|
|
DmaDoneEventUPtr event(std::move(pendingRequests.front()));
|
|
pendingRequests.pop_front();
|
|
|
|
if (!event->canceled())
|
|
buffer.write(event->data(), event->requestSize());
|
|
|
|
// Move the event to the list of free requests
|
|
freeRequests.emplace_back(std::move(event));
|
|
}
|
|
|
|
if (pendingRequests.empty())
|
|
signalDrainDone();
|
|
}
|
|
|
|
DrainState
|
|
DmaReadFifo::drain()
|
|
{
|
|
return pendingRequests.empty() ?
|
|
DrainState::Drained : DrainState::Draining;
|
|
}
|
|
|
|
|
|
DmaReadFifo::DmaDoneEvent::DmaDoneEvent(DmaReadFifo *_parent, size_t max_size)
|
|
: parent(_parent), _data(max_size, 0)
|
|
{
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::DmaDoneEvent::kill()
|
|
{
|
|
parent = nullptr;
|
|
setFlags(AutoDelete);
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::DmaDoneEvent::cancel()
|
|
{
|
|
_canceled = true;
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::DmaDoneEvent::reset(size_t size)
|
|
{
|
|
assert(size <= _data.size());
|
|
_done = false;
|
|
_canceled = false;
|
|
_requestSize = size;
|
|
}
|
|
|
|
void
|
|
DmaReadFifo::DmaDoneEvent::process()
|
|
{
|
|
if (!parent)
|
|
return;
|
|
|
|
assert(!_done);
|
|
_done = true;
|
|
parent->dmaDone();
|
|
}
|
|
|
|
} // namespace gem5
|