text: /*
   * Copyright (c) 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.
   *
   * 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: Giacomo Travaglini
   */
  
  #include "dev/arm/gic_v3_its.hh"
  
  #include "debug/AddrRanges.hh"
  #include "debug/Drain.hh"
  #include "debug/GIC.hh"
  #include "debug/ITS.hh"
  #include "dev/arm/gic_v3.hh"
  #include "dev/arm/gic_v3_distributor.hh"
  #include "dev/arm/gic_v3_redistributor.hh"
  #include "mem/packet_access.hh"
  
  #define COMMAND(x, method) { x, DispatchEntry(#x, method) }
  
  const AddrRange Gicv3Its::GITS_BASER(0x0100, 0x0140);
  
  const uint32_t Gicv3Its::CTLR_QUIESCENT = 0x80000000;
  
  ItsProcess::ItsProcess(Gicv3Its &_its)
    : its(_its), coroutine(nullptr)
  {
  }
  
  ItsProcess::~ItsProcess()
  {
  }
  
  void
  ItsProcess::reinit()
  {
      coroutine.reset(new Coroutine(
          std::bind(&ItsProcess::main, this, std::placeholders::_1)));
  }
  
  const std::string
  ItsProcess::name() const
  {
      return its.name();
  }
  
  ItsAction
  ItsProcess::run(PacketPtr pkt)
  {
      assert(coroutine != nullptr);
      assert(*coroutine);
      return (*coroutine)(pkt).get();
  }
  
  void
  ItsProcess::doRead(Yield &yield, Addr addr, void *ptr, size_t size)
  {
      ItsAction a;
      a.type = ItsActionType::SEND_REQ;
  
      RequestPtr req = std::make_shared<Request>(
          addr, size, 0, its.masterId);
  
      req->taskId(ContextSwitchTaskId::DMA);
  
      a.pkt = new Packet(req, MemCmd::ReadReq);
      a.pkt->dataStatic(ptr);
  
      a.delay = 0;
  
      PacketPtr pkt = yield(a).get();
  
      assert(pkt);
      assert(pkt->getSize() >= size);
  
      delete pkt;
  }
  
  void
  ItsProcess::doWrite(Yield &yield, Addr addr, void *ptr, size_t size)
  {
      ItsAction a;
      a.type = ItsActionType::SEND_REQ;
  
      RequestPtr req = std::make_shared<Request>(
          addr, size, 0, its.masterId);
  
      req->taskId(ContextSwitchTaskId::DMA);
  
      a.pkt = new Packet(req, MemCmd::WriteReq);
      a.pkt->dataStatic(ptr);
  
      a.delay = 0;
  
      PacketPtr pkt = yield(a).get();
  
      assert(pkt);
      assert(pkt->getSize() >= size);
  
      delete pkt;
  }
  
  void
  ItsProcess::terminate(Yield &yield)
  {
      ItsAction a;
      a.type = ItsActionType::TERMINATE;
      a.pkt = NULL;
      a.delay = 0;
      yield(a);
  }
  
  void
  ItsProcess::writeDeviceTable(Yield &yield, uint32_t device_id, DTE dte)
  {
      const Addr base = its.pageAddress(Gicv3Its::DEVICE_TABLE);
      const Addr address = base + (device_id * sizeof(dte));
  
      DPRINTF(ITS, "Writing DTE at address %#x: %#x\n", address, dte);
  
      doWrite(yield, address, &dte, sizeof(dte));
  }
  
  void
  ItsProcess::writeIrqTranslationTable(
      Yield &yield, const Addr itt_base, uint32_t event_id, ITTE itte)
  {
      const Addr address = itt_base + (event_id * sizeof(itte));
  
      doWrite(yield, address, &itte, sizeof(itte));
  
      DPRINTF(ITS, "Writing ITTE at address %#x: %#x\n", address, itte);
  }
  
  void
  ItsProcess::writeIrqCollectionTable(
      Yield &yield, uint32_t collection_id, CTE cte)
  {
      const Addr base = its.pageAddress(Gicv3Its::COLLECTION_TABLE);
      const Addr address = base + (collection_id * sizeof(cte));
  
      doWrite(yield, address, &cte, sizeof(cte));
  
      DPRINTF(ITS, "Writing CTE at address %#x: %#x\n", address, cte);
  }
  
  uint64_t
  ItsProcess::readDeviceTable(Yield &yield, uint32_t device_id)
  {
      uint64_t dte;
      const Addr base = its.pageAddress(Gicv3Its::DEVICE_TABLE);
      const Addr address = base + (device_id * sizeof(dte));
  
      doRead(yield, address, &dte, sizeof(dte));
  
      DPRINTF(ITS, "Reading DTE at address %#x: %#x\n", address, dte);
      return dte;
  }
  
  uint64_t
  ItsProcess::readIrqTranslationTable(
      Yield &yield, const Addr itt_base, uint32_t event_id)
  {
      uint64_t itte;
      const Addr address = itt_base + (event_id * sizeof(itte));
  
      doRead(yield, address, &itte, sizeof(itte));
  
      DPRINTF(ITS, "Reading ITTE at address %#x: %#x\n", address, itte);
      return itte;
  }
  
  uint64_t
  ItsProcess::readIrqCollectionTable(Yield &yield, uint32_t collection_id)
  {
      uint64_t cte;
      const Addr base = its.pageAddress(Gicv3Its::COLLECTION_TABLE);
      const Addr address = base + (collection_id * sizeof(cte));
  
      doRead(yield, address, &cte, sizeof(cte));
  
      DPRINTF(ITS, "Reading CTE at address %#x: %#x\n", address, cte);
      return cte;
  }
  
  ItsTranslation::ItsTranslation(Gicv3Its &_its)
    : ItsProcess(_its)
  {
      reinit();
      its.pendingTranslations++;
      its.gitsControl.quiescent = 0;
  }
  
  ItsTranslation::~ItsTranslation()
  {
      assert(its.pendingTranslations >= 1);
      its.pendingTranslations--;
      if (!its.pendingTranslations && !its.pendingCommands)
          its.gitsControl.quiescent = 1;
  }
  
  void
  ItsTranslation::main(Yield &yield)
  {
      PacketPtr pkt = yield.get();
  
      const uint32_t device_id = pkt->req->streamId();
      const uint32_t event_id = pkt->getLE<uint32_t>();
  
      auto result = translateLPI(yield, device_id, event_id);
  
      uint32_t intid = result.first;
      Gicv3Redistributor *redist = result.second;
  
      // Set the LPI in the redistributor
      redist->setClrLPI(intid, true);
  
      // Update the value in GITS_TRANSLATER only once we know
      // there was no error in the tranlation process (before
      // terminating the translation
      its.gitsTranslater = event_id;
  
      terminate(yield);
  }
  
  std::pair<uint32_t, Gicv3Redistributor *>
  ItsTranslation::translateLPI(Yield &yield, uint32_t device_id,
                               uint32_t event_id)
  {
      if (its.deviceOutOfRange(device_id)) {
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, device_id);
  
      if (!dte.valid || its.idOutOfRange(event_id, dte.ittRange)) {
          terminate(yield);
      }
  
      ITTE itte = readIrqTranslationTable(yield, dte.ittAddress, event_id);
      const auto collection_id = itte.icid;
  
      if (!itte.valid || its.collectionOutOfRange(collection_id)) {
          terminate(yield);
      }
  
      CTE cte = readIrqCollectionTable(yield, collection_id);
  
      if (!cte.valid) {
          terminate(yield);
      }
  
      // Returning the INTID and the target Redistributor
      return std::make_pair(itte.intNum, its.getRedistributor(cte));
  }
  
  ItsCommand::DispatchTable ItsCommand::cmdDispatcher =
  {
      COMMAND(CLEAR, &ItsCommand::clear),
      COMMAND(DISCARD, &ItsCommand::discard),
      COMMAND(INT, &ItsCommand::doInt),
      COMMAND(INV, &ItsCommand::inv),
      COMMAND(INVALL, &ItsCommand::invall),
      COMMAND(MAPC, &ItsCommand::mapc),
      COMMAND(MAPD, &ItsCommand::mapd),
      COMMAND(MAPI, &ItsCommand::mapi),
      COMMAND(MAPTI, &ItsCommand::mapti),
      COMMAND(MOVALL, &ItsCommand::movall),
      COMMAND(MOVI, &ItsCommand::movi),
      COMMAND(SYNC, &ItsCommand::sync),
      COMMAND(VINVALL, &ItsCommand::vinvall),
      COMMAND(VMAPI, &ItsCommand::vmapi),
      COMMAND(VMAPP, &ItsCommand::vmapp),
      COMMAND(VMAPTI, &ItsCommand::vmapti),
      COMMAND(VMOVI, &ItsCommand::vmovi),
      COMMAND(VMOVP, &ItsCommand::vmovp),
      COMMAND(VSYNC, &ItsCommand::vsync),
  };
  
  ItsCommand::ItsCommand(Gicv3Its &_its)
    : ItsProcess(_its)
  {
      reinit();
      its.pendingCommands = true;
  
      its.gitsControl.quiescent = 0;
  }
  
  ItsCommand::~ItsCommand()
  {
      its.pendingCommands = false;
  
      if (!its.pendingTranslations)
          its.gitsControl.quiescent = 1;
  }
  
  std::string
  ItsCommand::commandName(uint32_t cmd)
  {
      const auto entry = cmdDispatcher.find(cmd);
      return entry != cmdDispatcher.end() ? entry->second.name : "INVALID";
  }
  
  void
  ItsCommand::main(Yield &yield)
  {
      ItsAction a;
      a.type = ItsActionType::INITIAL_NOP;
      a.pkt = nullptr;
      a.delay = 0;
      yield(a);
  
      while (its.gitsCwriter.offset != its.gitsCreadr.offset) {
          CommandEntry command;
  
          // Reading the command from CMDQ
          readCommand(yield, command);
  
          processCommand(yield, command);
  
          its.incrementReadPointer();
      }
  
      terminate(yield);
  }
  
  void
  ItsCommand::readCommand(Yield &yield, CommandEntry &command)
  {
      // read the command pointed by GITS_CREADR
      const Addr cmd_addr =
          (its.gitsCbaser.physAddr << 12) + (its.gitsCreadr.offset << 5);
  
      doRead(yield, cmd_addr, &command, sizeof(command));
  
      DPRINTF(ITS, "Command %s read from queue at address: %#x\n",
              commandName(command.type), cmd_addr);
      DPRINTF(ITS, "dw0: %#x dw1: %#x dw2: %#x dw3: %#x\n",
              command.raw[0], command.raw[1], command.raw[2], command.raw[3]);
  }
  
  void
  ItsCommand::processCommand(Yield &yield, CommandEntry &command)
  {
      const auto entry = cmdDispatcher.find(command.type);
  
      if (entry != cmdDispatcher.end()) {
          // Execute the command
          entry->second.exec(this, yield, command);
      } else {
          panic("Unrecognized command type: %u", command.type);
      }
  }
  
  void
  ItsCommand::clear(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, command.deviceId);
  
      if (!dte.valid || idOutOfRange(command, dte)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      ITTE itte = readIrqTranslationTable(
          yield, dte.ittAddress, command.eventId);
  
      if (!itte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      const auto collection_id = itte.icid;
      CTE cte = readIrqCollectionTable(yield, collection_id);
  
      if (!cte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      // Clear the LPI in the redistributor
      its.getRedistributor(cte)->setClrLPI(itte.intNum, false);
  }
  
  void
  ItsCommand::discard(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, command.deviceId);
  
      if (!dte.valid || idOutOfRange(command, dte)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      ITTE itte = readIrqTranslationTable(
          yield, dte.ittAddress, command.eventId);
  
      if (!itte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      const auto collection_id = itte.icid;
      Gicv3Its::CTE cte = readIrqCollectionTable(yield, collection_id);
  
      if (!cte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      its.getRedistributor(cte)->setClrLPI(itte.intNum, false);
  
      // Then removes the mapping from the ITT (invalidating)
      itte.valid = 0;
      writeIrqTranslationTable(
          yield, dte.ittAddress, command.eventId, itte);
  }
  
  void
  ItsCommand::doInt(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, command.deviceId);
  
      if (!dte.valid || idOutOfRange(command, dte)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      ITTE itte = readIrqTranslationTable(
          yield, dte.ittAddress, command.eventId);
  
      if (!itte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      const auto collection_id = itte.icid;
      CTE cte = readIrqCollectionTable(yield, collection_id);
  
      if (!cte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      // Set the LPI in the redistributor
      its.getRedistributor(cte)->setClrLPI(itte.intNum, true);
  }
  
  void
  ItsCommand::inv(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, command.deviceId);
  
      if (!dte.valid || idOutOfRange(command, dte)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      ITTE itte = readIrqTranslationTable(
          yield, dte.ittAddress, command.eventId);
  
      if (!itte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      const auto collection_id = itte.icid;
      CTE cte = readIrqCollectionTable(yield, collection_id);
  
      if (!cte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
      // Do nothing since caching is currently not supported in
      // Redistributor
  }
  
  void
  ItsCommand::invall(Yield &yield, CommandEntry &command)
  {
      if (collectionOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      const auto icid = bits(command.raw[2], 15, 0);
  
      CTE cte = readIrqCollectionTable(yield, icid);
  
      if (!cte.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
      // Do nothing since caching is currently not supported in
      // Redistributor
  }
  
  void
  ItsCommand::mapc(Yield &yield, CommandEntry &command)
  {
      if (collectionOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      CTE cte = 0;
      cte.valid = bits(command.raw[2], 63);
      cte.rdBase = bits(command.raw[2], 50, 16);
  
      const auto icid = bits(command.raw[2], 15, 0);
  
      writeIrqCollectionTable(yield, icid, cte);
  }
  
  void
  ItsCommand::mapd(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command) || sizeOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = 0;
      dte.valid = bits(command.raw[2], 63);
      dte.ittAddress = mbits(command.raw[2], 51, 8);
      dte.ittRange = bits(command.raw[1], 4, 0);
  
      writeDeviceTable(yield, command.deviceId, dte);
  }
  
  void
  ItsCommand::mapi(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      if (collectionOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, command.deviceId);
  
      if (!dte.valid || idOutOfRange(command, dte) ||
          its.lpiOutOfRange(command.eventId)) {
  
          its.incrementReadPointer();
          terminate(yield);
      }
  
      Gicv3Its::ITTE itte = readIrqTranslationTable(
          yield, dte.ittAddress, command.eventId);
  
      itte.valid = 1;
      itte.intType = Gicv3Its::PHYSICAL_INTERRUPT;
      itte.intNum = command.eventId;
      itte.icid = bits(command.raw[2], 15, 0);
  
      writeIrqTranslationTable(
          yield, dte.ittAddress, command.eventId, itte);
  }
  
  void
  ItsCommand::mapti(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      if (collectionOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, command.deviceId);
  
      const auto pintid = bits(command.raw[1], 63, 32);
  
      if (!dte.valid || idOutOfRange(command, dte) ||
          its.lpiOutOfRange(pintid)) {
  
          its.incrementReadPointer();
          terminate(yield);
      }
  
      ITTE itte = readIrqTranslationTable(
          yield, dte.ittAddress, command.eventId);
  
      itte.valid = 1;
      itte.intType = Gicv3Its::PHYSICAL_INTERRUPT;
      itte.intNum = pintid;
      itte.icid = bits(command.raw[2], 15, 0);
  
      writeIrqTranslationTable(
          yield, dte.ittAddress, command.eventId, itte);
  }
  
  void
  ItsCommand::movall(Yield &yield, CommandEntry &command)
  {
      const uint64_t rd1 = bits(command.raw[2], 50, 16);
      const uint64_t rd2 = bits(command.raw[3], 50, 16);
  
      if (rd1 != rd2) {
          Gicv3Redistributor * redist1 = its.getRedistributor(rd1);
          Gicv3Redistributor * redist2 = its.getRedistributor(rd2);
  
          its.moveAllPendingState(redist1, redist2);
      }
  }
  
  void
  ItsCommand::movi(Yield &yield, CommandEntry &command)
  {
      if (deviceOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      if (collectionOutOfRange(command)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      DTE dte = readDeviceTable(yield, command.deviceId);
  
      if (!dte.valid || idOutOfRange(command, dte)) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      ITTE itte = readIrqTranslationTable(
          yield, dte.ittAddress, command.eventId);
  
      if (!itte.valid || itte.intType == Gicv3Its::VIRTUAL_INTERRUPT) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      const auto collection_id1 = itte.icid;
      CTE cte1 = readIrqCollectionTable(yield, collection_id1);
  
      if (!cte1.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      const auto collection_id2 = bits(command.raw[2], 15, 0);
      CTE cte2 = readIrqCollectionTable(yield, collection_id2);
  
      if (!cte2.valid) {
          its.incrementReadPointer();
          terminate(yield);
      }
  
      Gicv3Redistributor *first_redist = its.getRedistributor(cte1);
      Gicv3Redistributor *second_redist = its.getRedistributor(cte2);
  
      if (second_redist != first_redist) {
          // move pending state of the interrupt from one redistributor
          // to the other.
          if (first_redist->isPendingLPI(itte.intNum)) {
              first_redist->setClrLPI(itte.intNum, false);
              second_redist->setClrLPI(itte.intNum, true);
          }
      }
  
      itte.icid = collection_id2;
      writeIrqTranslationTable(
          yield, dte.ittAddress, command.eventId, itte);
  }
  
  void
  ItsCommand::sync(Yield &yield, CommandEntry &command)
  {
      warn("ITS %s command unimplemented", __func__);
  }
  
  void
  ItsCommand::vinvall(Yield &yield, CommandEntry &command)
  {
      panic("ITS %s command unimplemented", __func__);
  }
  
  void
  ItsCommand::vmapi(Yield &yield, CommandEntry &command)
  {
      panic("ITS %s command unimplemented", __func__);
  }
  
  void
  ItsCommand::vmapp(Yield &yield, CommandEntry &command)
  {
      panic("ITS %s command unimplemented", __func__);
  }
  
  void
  ItsCommand::vmapti(Yield &yield, CommandEntry &command)
  {
      panic("ITS %s command unimplemented", __func__);
  }
  
  void
  ItsCommand::vmovi(Yield &yield, CommandEntry &command)
  {
      panic("ITS %s command unimplemented", __func__);
  }
  
  void
  ItsCommand::vmovp(Yield &yield, CommandEntry &command)
  {
      panic("ITS %s command unimplemented", __func__);
  }
  
  void
  ItsCommand::vsync(Yield &yield, CommandEntry &command)
  {
      panic("ITS %s command unimplemented", __func__);
  }
  
  Gicv3Its::Gicv3Its(const Gicv3ItsParams *params)
   : BasicPioDevice(params, params->pio_size),
     dmaPort(name() + ".dma", *this),
     gitsControl(CTLR_QUIESCENT),
     gitsTyper(params->gits_typer),
     gitsCbaser(0), gitsCreadr(0),
     gitsCwriter(0), gitsIidr(0),
     tableBases(NUM_BASER_REGS, 0),
     masterId(params->system->getMasterId(this)),
     gic(nullptr),
     commandEvent([this] { checkCommandQueue(); }, name()),
     pendingCommands(false),
     pendingTranslations(0)
  {
      BASER device_baser = 0;
      device_baser.type = DEVICE_TABLE;
      device_baser.entrySize = sizeof(uint64_t) - 1;
      tableBases[0] = device_baser;
  
      BASER icollect_baser = 0;
      icollect_baser.type = COLLECTION_TABLE;
      icollect_baser.entrySize = sizeof(uint64_t) - 1;
      tableBases[1] = icollect_baser;
  }
  
  void
  Gicv3Its::setGIC(Gicv3 *_gic)
  {
      assert(!gic);
      gic = _gic;
  }
  
  AddrRangeList
  Gicv3Its::getAddrRanges() const
  {
      assert(pioSize != 0);
      AddrRangeList ranges;
      DPRINTF(AddrRanges, "registering range: %#x-%#x\n", pioAddr, pioSize);
      ranges.push_back(RangeSize(pioAddr, pioSize));
      return ranges;
  }
  
  Tick
  Gicv3Its::read(PacketPtr pkt)
  {
      const Addr addr = pkt->getAddr() - pioAddr;
      uint64_t value = 0;
  
      DPRINTF(GIC, "%s register at addr: %#x\n", __func__, addr);
  
      switch (addr) {
        case GITS_CTLR:
          value = gitsControl;
          break;
  
        case GITS_IIDR:
          value = gitsIidr;
          break;
  
        case GITS_TYPER:
          value = gitsTyper;
          break;
  
        case GITS_TYPER + 4:
          value = gitsTyper.high;
          break;
  
        case GITS_CBASER:
          value = gitsCbaser;
          break;
  
        case GITS_CBASER + 4:
          value = gitsCbaser.high;
          break;
  
        case GITS_CWRITER:
          value = gitsCwriter;
          break;
  
        case GITS_CWRITER + 4:
          value = gitsCwriter.high;
          break;
  
        case GITS_CREADR:
          value = gitsCreadr;
          break;
  
        case GITS_CREADR + 4:
          value = gitsCreadr.high;
          break;
  
        case GITS_PIDR2:
          value = gic->getDistributor()->gicdPidr2;
          break;
  
        case GITS_TRANSLATER:
          value = gitsTranslater;
          break;
  
        default:
          if (GITS_BASER.contains(addr)) {
              auto relative_addr = addr - GITS_BASER.start();
              auto baser_index = relative_addr / sizeof(uint64_t);
  
              value = tableBases[baser_index];
              break;
          } else {
              panic("Unrecognized register access\n");
          }
      }
  
      pkt->setUintX(value, LittleEndianByteOrder);
      pkt->makeAtomicResponse();
      return pioDelay;
  }
  
  Tick
  Gicv3Its::write(PacketPtr pkt)
  {
      Addr addr = pkt->getAddr() - pioAddr;
  
      DPRINTF(GIC, "%s register at addr: %#x\n", __func__, addr);
  
      switch (addr) {
        case GITS_CTLR:
          assert(pkt->getSize() == sizeof(uint32_t));
          gitsControl = (pkt->getLE<uint32_t>() & ~CTLR_QUIESCENT);
          // We should check here if the ITS has been disabled, and if
          // that's the case, flush GICv3 caches to external memory.
          // This is not happening now, since LPI caching is not
          // currently implemented in gem5.
          break;
  
        case GITS_IIDR:
          panic("GITS_IIDR is Read Only\n");
  
        case GITS_TYPER:
          panic("GITS_TYPER is Read Only\n");
  
        case GITS_CBASER:
          if (pkt->getSize() == sizeof(uint32_t)) {
              gitsCbaser.low = pkt->getLE<uint32_t>();
          } else {
              assert(pkt->getSize() == sizeof(uint64_t));
              gitsCbaser = pkt->getLE<uint64_t>();
          }
  
          gitsCreadr = 0; // Cleared when CBASER gets written
  
          checkCommandQueue();
          break;
  
        case GITS_CBASER + 4:
          assert(pkt->getSize() == sizeof(uint32_t));
          gitsCbaser.high = pkt->getLE<uint32_t>();
  
          gitsCreadr = 0; // Cleared when CBASER gets written
  
          checkCommandQueue();
          break;
  
        case GITS_CWRITER:
          if (pkt->getSize() == sizeof(uint32_t)) {
              gitsCwriter.low = pkt->getLE<uint32_t>();
          } else {
              assert(pkt->getSize() == sizeof(uint64_t));
              gitsCwriter = pkt->getLE<uint64_t>();
          }
  
          checkCommandQueue();
          break;
  
        case GITS_CWRITER + 4:
          assert(pkt->getSize() == sizeof(uint32_t));
          gitsCwriter.high = pkt->getLE<uint32_t>();
  
          checkCommandQueue();
          break;
  
        case GITS_CREADR:
          panic("GITS_READR is Read Only\n");
  
        case GITS_TRANSLATER:
          if (gitsControl.enabled) {
              translate(pkt);
          }
          break;
  
        default:
          if (GITS_BASER.contains(addr)) {
              auto relative_addr = addr - GITS_BASER.start();
              auto baser_index = relative_addr / sizeof(uint64_t);
  
              const uint64_t table_base = tableBases[baser_index];
              const uint64_t w_mask = tableBases[baser_index].type ?
                  BASER_WMASK : BASER_WMASK_UNIMPL;
              const uint64_t val = pkt->getLE<uint64_t>() & w_mask;
  
              tableBases[baser_index] = table_base | val;
              break;
          } else {
              panic("Unrecognized register access\n");
          }
      }
  
      pkt->makeAtomicResponse();
      return pioDelay;
  }
  
  bool
  Gicv3Its::idOutOfRange(uint32_t event_id, uint8_t itt_range) const
  {
      const uint32_t id_bits = gitsTyper.idBits;
      return event_id >= (1ULL << (id_bits + 1)) ||
          event_id >= ((1ULL << itt_range) + 1);
  }
  
  bool
  Gicv3Its::deviceOutOfRange(uint32_t device_id) const
  {
      return device_id >= (1ULL << (gitsTyper.devBits + 1));
  }
  
  bool
  Gicv3Its::sizeOutOfRange(uint32_t size) const
  {
      return size > gitsTyper.idBits;
  }
  
  bool
  Gicv3Its::collectionOutOfRange(uint32_t collection_id) const
  {
      // If GITS_TYPER.CIL == 0, ITS supports 16-bit CollectionID
      // Otherwise, #bits is specified by GITS_TYPER.CIDbits
      const auto cid_bits = gitsTyper.cil == 0 ?
          16 : gitsTyper.cidBits + 1;
  
      return collection_id >= (1ULL << cid_bits);
  }
  
  bool
  Gicv3Its::lpiOutOfRange(uint32_t intid) const
  {
      return intid >= (1ULL << (Gicv3Distributor::IDBITS + 1)) ||
             (intid < Gicv3Redistributor::SMALLEST_LPI_ID &&
              intid != Gicv3::INTID_SPURIOUS);
  }
  
  DrainState
  Gicv3Its::drain()
  {
      if (!pendingCommands && !pendingTranslations) {
          return DrainState::Drained;
      } else {
          DPRINTF(Drain, "GICv3 ITS not drained\n");
          return DrainState::Draining;
      }
  }
  
  void
  Gicv3Its::serialize(CheckpointOut & cp) const
  {
      SERIALIZE_SCALAR(gitsControl);
      SERIALIZE_SCALAR(gitsTyper);
      SERIALIZE_SCALAR(gitsCbaser);
      SERIALIZE_SCALAR(gitsCreadr);
      SERIALIZE_SCALAR(gitsCwriter);
      SERIALIZE_SCALAR(gitsIidr);
  
      SERIALIZE_CONTAINER(tableBases);
  }
  
  void
  Gicv3Its::unserialize(CheckpointIn & cp)
  {
      UNSERIALIZE_SCALAR(gitsControl);
      UNSERIALIZE_SCALAR(gitsTyper);
      UNSERIALIZE_SCALAR(gitsCbaser);
      UNSERIALIZE_SCALAR(gitsCreadr);
      UNSERIALIZE_SCALAR(gitsCwriter);
      UNSERIALIZE_SCALAR(gitsIidr);
  
      UNSERIALIZE_CONTAINER(tableBases);
  }
  
  void
  Gicv3Its::incrementReadPointer()
  {
      // Make the reader point to the next element
      gitsCreadr.offset = gitsCreadr.offset + 1;
  
      // Check for wrapping
      if (gitsCreadr.offset == maxCommands()) {
          gitsCreadr.offset = 0;
      }
  }
  
  uint64_t
  Gicv3Its::maxCommands() const
  {
      return (4096 * (gitsCbaser.size + 1)) / sizeof(ItsCommand::CommandEntry);
  }
  
  void
  Gicv3Its::checkCommandQueue()
  {
      if (!gitsControl.enabled || !gitsCbaser.valid)
          return;
  
      // If GITS_CWRITER gets set by sw to a value bigger than the
      // allowed one, the command queue should stop processing commands
      // until the register gets reset to an allowed one
      if (gitsCwriter.offset >= maxCommands()) {
          return;
      }
  
      if (gitsCwriter.offset != gitsCreadr.offset) {
          // writer and reader pointing to different command
          // entries: queue not empty.
          DPRINTF(ITS, "Reading command from queue\n");
  
          if (!pendingCommands) {
              auto *cmd_proc = new ItsCommand(*this);
  
              runProcess(cmd_proc, nullptr);
          } else {
              DPRINTF(ITS, "Waiting for pending command to finish\n");
          }
      }
  }
  
  Port &
  Gicv3Its::getPort(const std::string &if_name, PortID idx)
  {
      if (if_name == "dma") {
          return dmaPort;
      }
      return BasicPioDevice::getPort(if_name, idx);
  }
  
  void
  Gicv3Its::recvReqRetry()
  {
      assert(!packetsToRetry.empty());
  
      while (!packetsToRetry.empty()) {
          ItsAction a = packetsToRetry.front();
  
          assert(a.type == ItsActionType::SEND_REQ);
  
          if (!dmaPort.sendTimingReq(a.pkt))
              break;
  
          packetsToRetry.pop();
      }
  }
  
  bool
  Gicv3Its::recvTimingResp(PacketPtr pkt)
  {
      // @todo: We need to pay for this and not just zero it out
      pkt->headerDelay = pkt->payloadDelay = 0;
  
      ItsProcess *proc =
          safe_cast<ItsProcess *>(pkt->popSenderState());
  
      runProcessTiming(proc, pkt);
  
      return true;
  }
  
  ItsAction
  Gicv3Its::runProcess(ItsProcess *proc, PacketPtr pkt)
  {
      if (sys->isAtomicMode()) {
          return runProcessAtomic(proc, pkt);
      } else if (sys->isTimingMode()) {
          return runProcessTiming(proc, pkt);
      } else {
          panic("Not in timing or atomic mode\n");
      }
  }
  
  ItsAction
  Gicv3Its::runProcessTiming(ItsProcess *proc, PacketPtr pkt)
  {
      ItsAction action = proc->run(pkt);
  
      switch (action.type) {
        case ItsActionType::SEND_REQ:
          action.pkt->pushSenderState(proc);
  
          if (packetsToRetry.empty() &&
              dmaPort.sendTimingReq(action.pkt)) {
  
          } else {
              packetsToRetry.push(action);
          }
          break;
  
        case ItsActionType::TERMINATE:
          delete proc;
          if (!pendingCommands && !commandEvent.scheduled()) {
              schedule(commandEvent, clockEdge());
          }
          break;
  
        default:
          panic("Unknown action\n");
      }
  
      return action;
  }
  
  ItsAction
  Gicv3Its::runProcessAtomic(ItsProcess *proc, PacketPtr pkt)
  {
      ItsAction action;
      Tick delay = 0;
      bool terminate = false;
  
      do {
          action = proc->run(pkt);
  
          switch (action.type) {
            case ItsActionType::SEND_REQ:
              delay += dmaPort.sendAtomic(action.pkt);
              pkt = action.pkt;
              break;
  
            case ItsActionType::TERMINATE:
              delete proc;
              terminate = true;
              break;
  
            default:
              panic("Unknown action\n");
          }
  
      } while (!terminate);
  
      action.delay = delay;
  
      return action;
  }
  
  void
  Gicv3Its::translate(PacketPtr pkt)
  {
      DPRINTF(ITS, "Starting Translation Request\n");
  
      auto *proc = new ItsTranslation(*this);
      runProcess(proc, pkt);
  }
  
  Gicv3Redistributor*
  Gicv3Its::getRedistributor(uint64_t rd_base)
  {
      if (gitsTyper.pta == 1) {
          // RDBase is a redistributor address
          return gic->getRedistributorByAddr(rd_base << 16);
      } else {
          // RDBase is a redistributor number
          return gic->getRedistributor(rd_base);
      }
  }
  
  Addr
  Gicv3Its::pageAddress(Gicv3Its::ItsTables table)
  {
      auto base_it = std::find_if(
          tableBases.begin(), tableBases.end(),
          [table] (const BASER &b) { return b.type == table; }
      );
  
      panic_if(base_it == tableBases.end(),
          "ITS Table not recognised\n");
  
      const BASER base = *base_it;
  
      // real address depends on page size
      switch (base.pageSize) {
        case SIZE_4K:
        case SIZE_16K:
          return mbits(base, 47, 12);
        case SIZE_64K:
          return mbits(base, 47, 16) | (bits(base, 15, 12) << 48);
        default:
          panic("Unsupported page size\n");
      }
  }
  
  void
  Gicv3Its::moveAllPendingState(
      Gicv3Redistributor *rd1, Gicv3Redistributor *rd2)
  {
      const uint64_t largest_lpi_id = 1ULL << (rd1->lpiIDBits + 1);
      uint8_t lpi_pending_table[largest_lpi_id / 8];
  
      // Copying the pending table from redistributor 1 to redistributor 2
      rd1->memProxy->readBlob(
          rd1->lpiPendingTablePtr, (uint8_t *)lpi_pending_table,
          sizeof(lpi_pending_table));
  
      rd2->memProxy->writeBlob(
          rd2->lpiPendingTablePtr, (uint8_t *)lpi_pending_table,
          sizeof(lpi_pending_table));
  
      // Clearing pending table in redistributor 2
      rd1->memProxy->memsetBlob(
          rd1->lpiPendingTablePtr,
          0, sizeof(lpi_pending_table));
  
      rd2->updateDistributor();
  }
  
  Gicv3Its *
  Gicv3ItsParams::create()
  {
      return new Gicv3Its(this);
  }
  