| /* |
| * 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. |
| */ |
| |
| #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.requestorId); |
| |
| 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.requestorId); |
| |
| 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), |
| requestorId(params->system->getRequestorId(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, ByteOrder::little); |
| 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); |
| } |