text: /*
   * Copyright (c) 2018-2020 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.
   *
   * 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 "mem/qos/mem_sink.hh"
  
  #include "base/logging.hh"
  #include "base/trace.hh"
  #include "debug/Drain.hh"
  #include "debug/QOS.hh"
  #include "mem/qos/q_policy.hh"
  #include "params/QoSMemSinkInterface.hh"
  
  namespace gem5
  {
  
  namespace memory
  {
  
  GEM5_DEPRECATED_NAMESPACE(QoS, qos);
  namespace qos
  {
  
  MemSinkCtrl::MemSinkCtrl(const QoSMemSinkCtrlParams &p)
    : MemCtrl(p), requestLatency(p.request_latency),
      responseLatency(p.response_latency),
      memoryPacketSize(p.memory_packet_size),
      readBufferSize(p.read_buffer_size),
      writeBufferSize(p.write_buffer_size), port(name() + ".port", *this),
      interface(p.interface),
      retryRdReq(false), retryWrReq(false), nextRequest(0), nextReqEvent(this),
      stats(this)
  {
      // Resize read and write queue to allocate space
      // for configured QoS priorities
      readQueue.resize(numPriorities());
      writeQueue.resize(numPriorities());
  
      interface->setMemCtrl(this);
  }
  
  MemSinkCtrl::~MemSinkCtrl()
  {}
  
  void
  MemSinkCtrl::init()
  {
      MemCtrl::init();
  
      // Allow unconnected memories as this is used in several ruby
      // systems at the moment
      if (port.isConnected()) {
          port.sendRangeChange();
      }
  }
  
  bool
  MemSinkCtrl::readQueueFull(const uint64_t packets) const
  {
      return (totalReadQueueSize + packets > readBufferSize);
  }
  
  bool
  MemSinkCtrl::writeQueueFull(const uint64_t packets) const
  {
      return (totalWriteQueueSize + packets > writeBufferSize);
  }
  
  Tick
  MemSinkCtrl::recvAtomic(PacketPtr pkt)
  {
      panic_if(pkt->cacheResponding(),
               "%s Should not see packets where cache is responding\n",
               __func__);
  
      interface->access(pkt);
      return responseLatency;
  }
  
  void
  MemSinkCtrl::recvFunctional(PacketPtr pkt)
  {
      pkt->pushLabel(name());
  
      interface->functionalAccess(pkt);
  
      pkt->popLabel();
  }
  
  Port &
  MemSinkCtrl::getPort(const std::string &interface, PortID idx)
  {
      if (interface != "port") {
          return MemCtrl::getPort(interface, idx);
      } else {
          return port;
      }
  }
  
  bool
  MemSinkCtrl::recvTimingReq(PacketPtr pkt)
  {
      // Request accepted
      bool req_accepted = true;
  
      panic_if(!(pkt->isRead() || pkt->isWrite()),
               "%s. Should only see "
               "read and writes at memory controller\n",
               __func__);
  
      panic_if(pkt->cacheResponding(),
               "%s. Should not see packets where cache is responding\n",
               __func__);
  
      DPRINTF(QOS,
              "%s: REQUESTOR %s request %s addr %lld size %d\n",
              __func__,
              _system->getRequestorName(pkt->req->requestorId()),
              pkt->cmdString(), pkt->getAddr(), pkt->getSize());
  
      uint64_t required_entries = divCeil(pkt->getSize(), memoryPacketSize);
  
      assert(required_entries);
  
      // Schedule packet
      uint8_t pkt_priority = qosSchedule({&readQueue, &writeQueue},
                                         memoryPacketSize, pkt);
  
      if (pkt->isRead()) {
          if (readQueueFull(required_entries)) {
              DPRINTF(QOS,
                      "%s Read queue full, not accepting\n", __func__);
              // Remember that we have to retry this port
              retryRdReq = true;
              stats.numReadRetries++;
              req_accepted = false;
          } else {
              // Enqueue the incoming packet into corresponding
              // QoS priority queue
              readQueue.at(pkt_priority).push_back(pkt);
              queuePolicy->enqueuePacket(pkt);
          }
      } else {
          if (writeQueueFull(required_entries)) {
              DPRINTF(QOS,
                      "%s Write queue full, not accepting\n", __func__);
              // Remember that we have to retry this port
              retryWrReq = true;
              stats.numWriteRetries++;
              req_accepted = false;
          } else {
              // Enqueue the incoming packet into corresponding QoS
              // priority queue
              writeQueue.at(pkt_priority).push_back(pkt);
              queuePolicy->enqueuePacket(pkt);
          }
      }
  
      if (req_accepted) {
          // The packet is accepted - log it
          logRequest(pkt->isRead()? READ : WRITE,
                     pkt->req->requestorId(),
                     pkt->qosValue(),
                     pkt->getAddr(),
                     required_entries);
      }
  
      // Check if we have to process next request event
      if (!nextReqEvent.scheduled()) {
          DPRINTF(QOS,
                  "%s scheduling next request at "
                  "time %d (next is %d)\n", __func__,
                  std::max(curTick(), nextRequest), nextRequest);
          schedule(nextReqEvent, std::max(curTick(), nextRequest));
      }
      return req_accepted;
  }
  
  void
  MemSinkCtrl::processNextReqEvent()
  {
      PacketPtr pkt = nullptr;
  
      // Evaluate bus direction
      busStateNext = selectNextBusState();
  
      // Record turnaround stats and update current state direction
      recordTurnaroundStats();
  
      // Set current bus state
      setCurrentBusState();
  
      // Access current direction buffer
      std::vector<PacketQueue>* queue_ptr = (busState == READ ? &readQueue :
                                                                &writeQueue);
  
      DPRINTF(QOS,
              "%s DUMPING %s queues status\n", __func__,
              (busState == WRITE ? "WRITE" : "READ"));
  
      if (debug::QOS) {
          for (uint8_t i = 0; i < numPriorities(); ++i) {
              std::string plist = "";
              for (auto& e : (busState == WRITE ? writeQueue[i]: readQueue[i])) {
                  plist += (std::to_string(e->req->requestorId())) + " ";
              }
              DPRINTF(QOS,
                      "%s priority Queue [%i] contains %i elements, "
                      "packets are: [%s]\n", __func__, i,
                      busState == WRITE ? writeQueueSizes[i] :
                                          readQueueSizes[i],
                      plist);
          }
      }
  
      uint8_t curr_prio = numPriorities();
  
      for (auto queue = (*queue_ptr).rbegin();
           queue != (*queue_ptr).rend(); ++queue) {
  
          curr_prio--;
  
          DPRINTF(QOS,
                  "%s checking %s queue [%d] priority [%d packets]\n",
                  __func__, (busState == READ? "READ" : "WRITE"),
                  curr_prio, queue->size());
  
          if (!queue->empty()) {
              // Call the queue policy to select packet from priority queue
              auto p_it = queuePolicy->selectPacket(&(*queue));
              pkt = *p_it;
              queue->erase(p_it);
  
              DPRINTF(QOS,
                      "%s scheduling packet address %d for requestor %s from "
                      "priority queue %d\n", __func__, pkt->getAddr(),
                      _system->getRequestorName(pkt->req->requestorId()),
                      curr_prio);
              break;
          }
      }
  
      assert(pkt);
  
      // Setup next request service time - do it here as retry request
      // hands over control to the port
      nextRequest = curTick() + requestLatency;
  
      uint64_t removed_entries = divCeil(pkt->getSize(), memoryPacketSize);
  
      DPRINTF(QOS,
              "%s scheduled packet address %d for requestor %s size is %d, "
              "corresponds to %d memory packets\n", __func__, pkt->getAddr(),
              _system->getRequestorName(pkt->req->requestorId()),
              pkt->getSize(), removed_entries);
  
      // Schedule response
      panic_if(!pkt->needsResponse(),
          "%s response not required\n", __func__);
  
      // Do the actual memory access which also turns the packet
      // into a response
      interface->access(pkt);
  
      // Log the response
      logResponse(pkt->isRead()? READ : WRITE,
                  pkt->req->requestorId(),
                  pkt->qosValue(),
                  pkt->getAddr(),
                  removed_entries, responseLatency);
  
      // Schedule the response
      port.schedTimingResp(pkt, curTick() + responseLatency);
      DPRINTF(QOS,
              "%s response scheduled at time %d\n",
              __func__, curTick() + responseLatency);
  
      // Finally - handle retry requests - this handles control
      // to the port, so do it last
      if (busState == READ && retryRdReq) {
          retryRdReq = false;
          port.sendRetryReq();
      } else if (busState == WRITE && retryWrReq) {
          retryWrReq = false;
          port.sendRetryReq();
      }
  
      // Check if we have to schedule another request event
      if ((totalReadQueueSize || totalWriteQueueSize) &&
          !nextReqEvent.scheduled()) {
  
          schedule(nextReqEvent, curTick() + requestLatency);
          DPRINTF(QOS,
                  "%s scheduling next request event at tick %d\n",
                  __func__, curTick() + requestLatency);
      }
  }
  
  DrainState
  MemSinkCtrl::drain()
  {
      if (totalReadQueueSize || totalWriteQueueSize) {
          DPRINTF(Drain,
                  "%s queues have requests, waiting to drain\n",
                  __func__);
          return DrainState::Draining;
      } else {
          return DrainState::Drained;
      }
  }
  
  MemSinkCtrl::MemSinkCtrlStats::MemSinkCtrlStats(statistics::Group *parent)
      : statistics::Group(parent),
        ADD_STAT(numReadRetries, statistics::units::Count::get(),
                 "Number of read retries"),
        ADD_STAT(numWriteRetries, statistics::units::Count::get(),
                 "Number of write retries")
  {
  }
  
  MemSinkCtrl::MemoryPort::MemoryPort(const std::string& n,
                                      MemSinkCtrl& m)
    : QueuedResponsePort(n, &m, queue, true),
     mem(m), queue(mem, *this, true)
  {}
  
  AddrRangeList
  MemSinkCtrl::MemoryPort::getAddrRanges() const
  {
      AddrRangeList ranges;
      ranges.push_back(mem.interface->getAddrRange());
      return ranges;
  }
  
  Tick
  MemSinkCtrl::MemoryPort::recvAtomic(PacketPtr pkt)
  {
      return mem.recvAtomic(pkt);
  }
  
  void
  MemSinkCtrl::MemoryPort::recvFunctional(PacketPtr pkt)
  {
      pkt->pushLabel(mem.name());
  
      if (!queue.trySatisfyFunctional(pkt)) {
          // Default implementation of SimpleTimingPort::recvFunctional()
          // calls recvAtomic() and throws away the latency; we can save a
          // little here by just not calculating the latency.
          mem.recvFunctional(pkt);
      }
  
      pkt->popLabel();
  }
  
  bool
  MemSinkCtrl::MemoryPort::recvTimingReq(PacketPtr pkt)
  {
      return mem.recvTimingReq(pkt);
  }
  
  MemSinkInterface::MemSinkInterface(const QoSMemSinkInterfaceParams &_p)
      : AbstractMemory(_p)
  {
  }
  
  } // namespace qos
  } // namespace memory
  } // namespace gem5
  