/* * Copyright (c) 2012-2013, 2015, 2017 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) 2004-2005 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. * * Authors: Ali Saidi * Nathan Binkert * Andreas Sandberg */ #ifndef __DEV_DMA_DEVICE_HH__ #define __DEV_DMA_DEVICE_HH__ #include #include #include "base/circlebuf.hh" #include "dev/io_device.hh" #include "params/DmaDevice.hh" #include "sim/drain.hh" #include "sim/system.hh" class DmaPort : public MasterPort, public Drainable { private: /** * Take the first packet of the transmit list and attempt to send * it as a timing request. If it is successful, schedule the * sending of the next packet, otherwise remember that we are * waiting for a retry. */ void trySendTimingReq(); /** * For timing, attempt to send the first item on the transmit * list, and if it is successful and there are more packets * waiting, then schedule the sending of the next packet. For * atomic, simply send and process everything on the transmit * list. */ void sendDma(); /** * Handle a response packet by updating the corresponding DMA * request state to reflect the bytes received, and also update * the pending request counter. If the DMA request that this * packet is part of is complete, then signal the completion event * if present, potentially with a delay added to it. * * @param pkt Response packet to handler * @param delay Additional delay for scheduling the completion event */ void handleResp(PacketPtr pkt, Tick delay = 0); struct DmaReqState : public Packet::SenderState { /** Event to call on the device when this transaction (all packets) * complete. */ Event *completionEvent; /** Total number of bytes that this transaction involves. */ const Addr totBytes; /** Number of bytes that have been acked for this transaction. */ Addr numBytes; /** Amount to delay completion of dma by */ const Tick delay; DmaReqState(Event *ce, Addr tb, Tick _delay) : completionEvent(ce), totBytes(tb), numBytes(0), delay(_delay) {} }; public: /** The device that owns this port. */ MemObject *const device; /** The system that device/port are in. This is used to select which mode * we are currently operating in. */ System *const sys; /** Id for all requests */ const MasterID masterId; protected: /** Use a deque as we never do any insertion or removal in the middle */ std::deque transmitList; /** Event used to schedule a future sending from the transmit list. */ EventFunctionWrapper sendEvent; /** Number of outstanding packets the dma port has. */ uint32_t pendingCount; /** If the port is currently waiting for a retry before it can * send whatever it is that it's sending. */ bool inRetry; protected: bool recvTimingResp(PacketPtr pkt) override; void recvReqRetry() override; void queueDma(PacketPtr pkt); public: DmaPort(MemObject *dev, System *s); RequestPtr dmaAction(Packet::Command cmd, Addr addr, int size, Event *event, uint8_t *data, Tick delay, Request::Flags flag = 0); bool dmaPending() const { return pendingCount > 0; } DrainState drain() override; }; class DmaDevice : public PioDevice { protected: DmaPort dmaPort; public: typedef DmaDeviceParams Params; DmaDevice(const Params *p); virtual ~DmaDevice() { } void dmaWrite(Addr addr, int size, Event *event, uint8_t *data, Tick delay = 0) { dmaPort.dmaAction(MemCmd::WriteReq, addr, size, event, data, delay); } void dmaRead(Addr addr, int size, Event *event, uint8_t *data, Tick delay = 0) { dmaPort.dmaAction(MemCmd::ReadReq, addr, size, event, data, delay); } bool dmaPending() const { return dmaPort.dmaPending(); } void init() override; unsigned int cacheBlockSize() const { return sys->cacheLineSize(); } BaseMasterPort &getMasterPort(const std::string &if_name, PortID idx = InvalidPortID) override; }; /** * DMA callback class. * * Allows one to register for a callback event after a sequence of (potentially * non-contiguous) DMA transfers on a DmaPort completes. Derived classes must * implement the process() method and use getChunkEvent() to allocate a * callback event for each participating DMA. */ class DmaCallback : public Drainable { public: virtual const std::string name() const { return "DmaCallback"; } /** * DmaPort ensures that all oustanding DMA accesses have completed before * it finishes draining. However, DmaChunkEvents scheduled with a delay * might still be sitting on the event queue. Therefore, draining is not * complete until count is 0, which ensures that all outstanding * DmaChunkEvents associated with this DmaCallback have fired. */ DrainState drain() override { return count ? DrainState::Draining : DrainState::Drained; } protected: int count; DmaCallback() : count(0) { } virtual ~DmaCallback() { } /** * Callback function invoked on completion of all chunks. */ virtual void process() = 0; private: /** * Called by DMA engine completion event on each chunk completion. * Since the object may delete itself here, callers should not use * the object pointer after calling this function. */ void chunkComplete() { if (--count == 0) { process(); // Need to notify DrainManager that this object is finished // draining, even though it is immediately deleted. signalDrainDone(); delete this; } } public: /** * Request a chunk event. Chunks events should be provided to each DMA * request that wishes to participate in this DmaCallback. */ Event *getChunkEvent() { ++count; return new EventFunctionWrapper([this]{ chunkComplete(); }, name(), true); } }; /** * Buffered DMA engine helper class * * This class implements a simple DMA engine that feeds a FIFO * buffer. The size of the buffer, the maximum number of pending * requests and the maximum request size are all set when the engine * is instantiated. * * An asynchronous transfer of a block of data * (designated by a start address and a size) is started by calling * the startFill() method. The DMA engine will aggressively try to * keep the internal FIFO full. As soon as there is room in the FIFO * for more data and there are free request slots, a new fill * will be started. * * Data in the FIFO can be read back using the get() and tryGet() * methods. Both request a block of data from the FIFO. However, get() * panics if the block cannot be satisfied, while tryGet() simply * returns false. The latter call makes it possible to implement * custom buffer underrun handling. * * A simple use case would be something like this: * \code{.cpp} * // Create a DMA engine with a 1KiB buffer. Issue up to 8 concurrent * // uncacheable 64 byte (maximum) requests. * DmaReadFifo *dma = new DmaReadFifo(port, 1024, 64, 8, * Request::UNCACHEABLE); * * // Start copying 4KiB data from 0xFF000000 * dma->startFill(0xFF000000, 0x1000); * * // Some time later when there is data in the FIFO. * uint8_t data[8]; * dma->get(data, sizeof(data)) * \endcode * * * The DMA engine allows new blocks to be requested as soon as the * last request for a block has been sent (i.e., there is no need to * wait for pending requests to complete). This can be queried with * the atEndOfBlock() method and more advanced implementations may * override the onEndOfBlock() callback. */ class DmaReadFifo : public Drainable, public Serializable { public: DmaReadFifo(DmaPort &port, size_t size, unsigned max_req_size, unsigned max_pending, Request::Flags flags = 0); ~DmaReadFifo(); public: // Serializable void serialize(CheckpointOut &cp) const override; void unserialize(CheckpointIn &cp) override; public: // Drainable DrainState drain() override; public: // FIFO access /** * @{ * @name FIFO access */ /** * Try to read data from the FIFO. * * This method reads len bytes of data from the FIFO and stores * them in the memory location pointed to by dst. The method * fails, and no data is written to the buffer, if the FIFO * doesn't contain enough data to satisfy the request. * * @param dst Pointer to a destination buffer * @param len Amount of data to read. * @return true on success, false otherwise. */ bool tryGet(uint8_t *dst, size_t len); template bool tryGet(T &value) { return tryGet(static_cast(&value), sizeof(T)); }; /** * Read data from the FIFO and panic on failure. * * @see tryGet() * * @param dst Pointer to a destination buffer * @param len Amount of data to read. */ void get(uint8_t *dst, size_t len); template T get() { T value; get(static_cast(&value), sizeof(T)); return value; }; /** Get the amount of data stored in the FIFO */ size_t size() const { return buffer.size(); } /** Flush the FIFO */ void flush() { buffer.flush(); } /** @} */ public: // FIFO fill control /** * @{ * @name FIFO fill control */ /** * Start filling the FIFO. * * @warn It's considered an error to call start on an active DMA * engine unless the last request from the active block has been * sent (i.e., atEndOfBlock() is true). * * @param start Physical address to copy from. * @param size Size of the block to copy. */ void startFill(Addr start, size_t size); /** * Stop the DMA engine. * * Stop filling the FIFO and ignore incoming responses for pending * requests. The onEndOfBlock() callback will not be called after * this method has been invoked. However, once the last response * has been received, the onIdle() callback will still be called. */ void stopFill(); /** * Has the DMA engine sent out the last request for the active * block? */ bool atEndOfBlock() const { return nextAddr == endAddr; } /** * Is the DMA engine active (i.e., are there still in-flight * accesses)? */ bool isActive() const { return !(pendingRequests.empty() && atEndOfBlock()); } /** @} */ protected: // Callbacks /** * @{ * @name Callbacks */ /** * End of block callback * * This callback is called once after the last access in a * block has been sent. It is legal for a derived class to call * startFill() from this method to initiate a transfer. */ virtual void onEndOfBlock() {}; /** * Last response received callback * * This callback is called when the DMA engine becomes idle (i.e., * there are no pending requests). * * It is possible for a DMA engine to reach the end of block and * become idle at the same tick. In such a case, the * onEndOfBlock() callback will be called first. This callback * will NOT be called if that callback initiates a new DMA transfer. */ virtual void onIdle() {}; /** @} */ private: // Configuration /** Maximum request size in bytes */ const Addr maxReqSize; /** Maximum FIFO size in bytes */ const size_t fifoSize; /** Request flags */ const Request::Flags reqFlags; DmaPort &port; private: class DmaDoneEvent : public Event { public: DmaDoneEvent(DmaReadFifo *_parent, size_t max_size); void kill(); void cancel(); bool canceled() const { return _canceled; } void reset(size_t size); void process(); bool done() const { return _done; } size_t requestSize() const { return _requestSize; } const uint8_t *data() const { return _data.data(); } uint8_t *data() { return _data.data(); } private: DmaReadFifo *parent; bool _done; bool _canceled; size_t _requestSize; std::vector _data; }; typedef std::unique_ptr DmaDoneEventUPtr; /** * DMA request done, handle incoming data and issue new * request. */ void dmaDone(); /** Handle pending requests that have been flagged as done. */ void handlePending(); /** Try to issue new DMA requests or bypass DMA requests*/ void resumeFill(); /** Try to issue new DMA requests during normal execution*/ void resumeFillTiming(); /** Try to bypass DMA requests in KVM execution mode */ void resumeFillFunctional(); private: // Internal state Fifo buffer; Addr nextAddr; Addr endAddr; std::deque pendingRequests; std::deque freeRequests; }; #endif // __DEV_DMA_DEVICE_HH__