diff --git a/thermosphere/src/hvisor_irq_manager.cpp b/thermosphere/src/hvisor_irq_manager.cpp index 8b0f36b6a..017df9cea 100644 --- a/thermosphere/src/hvisor_irq_manager.cpp +++ b/thermosphere/src/hvisor_irq_manager.cpp @@ -67,11 +67,10 @@ namespace ams::hvisor { bool IrqManager::IsGuestInterrupt(u32 id) { // We don't care about the interrupts we don't use + // Special interrupts id (eg. spurious interrupt id 1023) are also reserved to us + // because the virtual interface hw itself will generate it for the guest. - bool ret = true; - ret = ret && id != GIC_IRQID_MAINTENANCE; - ret = ret && id != GIC_IRQID_NS_PHYS_HYP_TIMER; - + bool ret = id <= GicV2Distributor::maxIrqId && id != GIC_IRQID_MAINTENANCE && id != GIC_IRQID_NS_PHYS_HYP_TIMER; ret = ret && transportInterfaceFindByIrqId(id) == NULL; return ret; } diff --git a/thermosphere/src/hvisor_irq_manager.hpp b/thermosphere/src/hvisor_irq_manager.hpp index 3784ee7bc..ddec81578 100644 --- a/thermosphere/src/hvisor_irq_manager.hpp +++ b/thermosphere/src/hvisor_irq_manager.hpp @@ -40,7 +40,9 @@ namespace ams::hvisor { static u32 GetTypeRegister() { return gicd->typer; } static void SetInterruptEnabled(u32 id) { gicd->isenabler[id / 32] = BIT(id % 32); } static void ClearInterruptEnabled(u32 id) { gicd->icenabler[id / 32] = BIT(id % 32); } + static bool IsInterruptPending(u32 id) { return (gicd->ispendr[id / 32] & BIT(id % 32)) != 0;} static void ClearInterruptPending(u32 id) { gicd->icpendr[id / 32] = BIT(id % 32); } + static void ClearInterruptActive(u32 id) { gicd->icactiver[id / 32] = BIT(id % 32); } static void SetInterruptShiftedPriority(u32 id, u8 prio) { gicd->ipriorityr[id] = prio; } static void SetInterruptTargets(u32 id, u8 targetList) { gicd->itargetsr[id] = targetList; } static bool IsInterruptLevelSensitive(u32 id) diff --git a/thermosphere/src/hvisor_virtual_gic.cpp b/thermosphere/src/hvisor_virtual_gic.cpp index 7cccc6554..586ba7217 100644 --- a/thermosphere/src/hvisor_virtual_gic.cpp +++ b/thermosphere/src/hvisor_virtual_gic.cpp @@ -14,8 +14,11 @@ * along with this program. If not, see . */ +#include #include "hvisor_virtual_gic.hpp" +#define GICDOFF(field) (offsetof(GicV2Distributor, field)) + namespace ams::hvisor { @@ -72,29 +75,6 @@ namespace ams::hvisor { ); } - VirtualGic::VirqQueue::iterator VirtualGic::VirqQueue::erase(VirtualGic::VirqQueue::iterator startPos, VirtualGic::VirqQueue::iterator endPos) - { - VirqState &prev = m_storage[startPos->listPrev]; - VirqState &next = *endPos; - u32 nextPos = GetStateIndex(*endPos); - - if (startPos->listPrev != virqListEndIndex) { - prev.listNext = nextPos; - } else { - m_first = &next; - } - - if (nextPos != virqListEndIndex) { - next.listPrev = startPos->listPrev; - } else { - m_last = &prev; - } - - for (auto it = startPos; it != endPos; ++it) { - it->listPrev = it->listNext = virqListInvalidIndex; - } - } - void VirtualGic::SetInterruptEnabledState(u32 id) { VirqState &state = GetVirqState(id); @@ -243,6 +223,263 @@ namespace ams::hvisor { for (u32 dstCore: util::BitsOf{coreList}) { SetSgiPendingState(id, dstCore, currentCoreCtx->coreId); } + } + bool VirtualGic::ValidateGicdRegisterAccess(size_t offset, size_t sz) + { + // ipriorityr, itargetsr, *pendsgir are byte-accessible + // Report a fault on accessing fields for + if ( + !(offset >= GICDOFF(ipriorityr) && offset < GICDOFF(ipriorityr) + GicV2Distributor::maxIrqId) && + !(offset >= GICDOFF(itargetsr) && offset < GICDOFF(itargetsr) + GicV2Distributor::maxIrqId) && + !(offset >= GICDOFF(cpendsgir) && offset < GICDOFF(cpendsgir) + 16) && + !(offset >= GICDOFF(spendsgir) && offset < GICDOFF(spendsgir) + 16) + ) { + return (offset & 3) == 0 && sz == 4; + } else { + return sz == 1 || (sz == 4 && ((offset & 3) != 0)); + } + } + + void VirtualGic::WriteGicdRegister(u32 val, size_t offset, size_t sz) + { + static constexpr auto maxIrqId = GicV2Distributor::maxIrqId; + std::scoped_lock lk{IrqManager::GetInstance().m_lock}; + + switch (offset) { + case GICDOFF(typer): + case GICDOFF(iidr): + case GICDOFF(icpidr2): + case GICDOFF(itargetsr) ... GICDOFF(itargetsr) + 31: + // Write ignored (read-only registers) + break; + case GICDOFF(icfgr) ... GICDOFF(icfgr) + 31/4: + // Write ignored because of an implementation-defined choice + break; + case GICDOFF(igroupr) ... GICDOFF(igroupr) + maxIrqId/8: + // Write ignored because we don't implement Group 1 here + break; + case GICDOFF(ispendr) ... GICDOFF(ispendr) + maxIrqId/8: + case GICDOFF(icpendr) ... GICDOFF(icpendr) + maxIrqId/8: + case GICDOFF(isactiver) ... GICDOFF(isactiver) + maxIrqId/8: + case GICDOFF(icactiver) ... GICDOFF(icactiver) + maxIrqId/8: + case GICDOFF(cpendsgir) ... GICDOFF(cpendsgir) + 15: + case GICDOFF(spendsgir) ... GICDOFF(spendsgir) + 15: + // Write ignored, not implemented (at least not yet, TODO) + break; + + case GICDOFF(ctlr): { + SetDistributorControlRegister(val); + break; + } + + case GICDOFF(isenabler) ... GICDOFF(isenabler) + maxIrqId/8: { + u32 base = 8 * static_cast(offset - GICDOFF(isenabler)); + for(u32 pos: util::BitsOf{val}) { + SetInterruptEnabledState(base + pos); + } + break; + } + case GICDOFF(icenabler) ... GICDOFF(icenabler) + maxIrqId/8: { + u32 base = 8 * static_cast(offset - GICDOFF(icenabler)); + for(u32 pos: util::BitsOf{val}) { + SetInterruptEnabledState(base + pos); + } + break; + } + + case GICDOFF(ipriorityr) ... GICDOFF(ipriorityr) + maxIrqId: { + u32 base = static_cast(offset - GICDOFF(ipriorityr)); + for (u32 i = 0; i < static_cast(sz); i++) { + SetInterruptPriorityByte(base + i, static_cast(val)); + val >>= 8; + } + break; + } + + case GICDOFF(itargetsr) + 32 ... GICDOFF(itargetsr) + maxIrqId: { + u32 base = static_cast(offset - GICDOFF(itargetsr)); + for (u32 i = 0; i < static_cast(sz); i++) { + SetInterruptTargets(base + i, static_cast(val)); + val >>= 8; + } + break; + } + + case GICDOFF(icfgr) + 32/4 ... GICDOFF(icfgr) + maxIrqId/4: { + u32 base = 4 * static_cast(offset & 0xFF); + for (u32 i = 0; i < 16; i++) { + SetInterruptConfigBits(base + i, val & 3); + val >>= 2; + } + break; + } + + case GICDOFF(sgir): { + SendSgi(val & 0xF, static_cast((val >> 24) & 3), (val >> 16) & 0xFF); + break; + } + + default: + DEBUG("Write to GICD reserved/implementation-defined register offset=0x%03lx value=0x%08lx", offset, val); + break; + } + + UpdateState(); + } + + + u32 VirtualGic::ReadGicdRegister(size_t offset, size_t sz) + { + static constexpr auto maxIrqId = GicV2Distributor::maxIrqId; + std::scoped_lock lk{IrqManager::GetInstance().m_lock}; + + //DEBUG("gicd read off 0x%03llx sz %lx\n", offset, sz); + u32 val = 0; + + switch (offset) { + case GICDOFF(icfgr) ... GICDOFF(icfgr) + 31/4: + // RAZ because of an implementation-defined choice + break; + case GICDOFF(igroupr) ... GICDOFF(igroupr) + maxIrqId/8: + // RAZ because we don't implement Group 1 here + break; + case GICDOFF(ispendr) ... GICDOFF(ispendr) + maxIrqId/8: + case GICDOFF(icpendr) ... GICDOFF(icpendr) + maxIrqId/8: + case GICDOFF(isactiver) ... GICDOFF(isactiver) + maxIrqId/8: + case GICDOFF(icactiver) ... GICDOFF(icactiver) + maxIrqId/8: + case GICDOFF(cpendsgir) ... GICDOFF(cpendsgir) + 15: + case GICDOFF(spendsgir) ... GICDOFF(spendsgir) + 15: + // RAZ, not implemented (at least not yet, TODO) + break; + + case GICDOFF(ctlr): { + val = GetDistributorControlRegister(); + break; + } + case GICDOFF(typer): { + val = GetDistributorTypeRegister(); + break; + } + case GICDOFF(iidr): { + val = GetDistributorImplementerIdentificationRegister(); + break; + } + + case GICDOFF(isenabler) ... GICDOFF(isenabler) + maxIrqId/8: + case GICDOFF(icenabler) ... GICDOFF(icenabler) + maxIrqId/8: { + u32 base = 8 * static_cast(offset & 0x7F); + for (u32 i = 0; i < 32; i++) { + val |= GetInterruptEnabledState(base + i) ? BIT(i) : 0; + } + break; + } + + case GICDOFF(ipriorityr) ... GICDOFF(ipriorityr) + maxIrqId: { + u32 base = static_cast(offset - GICDOFF(ipriorityr)); + for (u32 i = 0; i < sz; i++) { + val |= GetInterruptPriorityByte(base + i) << (8 * i); + } + break; + } + + case GICDOFF(itargetsr) ... GICDOFF(itargetsr) + maxIrqId: { + u32 base = static_cast(offset - GICDOFF(itargetsr)); + for (u32 i = 0; i < sz; i++) { + val |= GetInterruptTargets(base + i) << (8 * i); + } + break; + } + + case GICDOFF(icfgr) + 32/4 ... GICDOFF(icfgr) + maxIrqId/4: { + u32 base = 4 * static_cast(offset & 0xFF); + for (u32 i = 0; i < 16; i++) { + val |= GetInterruptConfigBits(base + i) << (2 * i); + } + break; + } + + case GICDOFF(sgir): + // Write-only register + DEBUG("Read from write-only register GCID_SGIR\n"); + break; + + case GICDOFF(icpidr2): { + val = GetPeripheralId2Register(); + break; + } + + default: + DEBUG("Read from GICD reserved/implementation-defined register offset=0x%03lx\n", offset); + break; + } + + UpdateState(); + return val; + } + + void VirtualGic::ResampleVirqLevel(VirtualGic::VirqState &state) + { + /* + For hardware interrupts, we have kept the interrupt active on the physical GICD + For level-sensitive interrupts, we need to check if they're also still physically pending (resampling). + If not, there's nothing to service anymore, and therefore we have to deactivate them, so that + we're notified when they become pending again. + */ + + if (!state.levelSensitive || !state.IsPending()) { + // Nothing to do for edge-triggered interrupts and non-pending interrupts + return; + } + + u32 irqId = state.irqId; + + // Can't do anything for level-sensitive PPIs from other cores either + if (irqId < 32 && state.coreId != currentCoreCtx->coreId) { + return; + } + + bool lineLevel = IrqManager::IsInterruptPending(irqId); + if (!lineLevel) { + IrqManager::ClearInterruptActive(irqId); + state.ClearPendingLine(); + } + } + + void VirtualGic::CleanupPendingQueue() + { + // SGIs are pruned elsewhere + + // Resample line level for level-sensitive interrupts + for (VirqState &state: m_virqPendingQueue) { + ResampleVirqLevel(state); + } + + // Cleanup the list + m_virqPendingQueue.erase_if([](const VirqState &state) { return !state.IsPending(); }); + } + + size_t VirtualGic::ChoosePendingInterrupts(VirtualGic::VirqState *chosen[], size_t maxNum) + { + size_t numChosen = 0; + auto pred = [](const VirqState &state) { + if (state.irqId < 32 && state.coreId != currentCoreCtx->coreId) { + // We can't handle SGIs/PPIs of other cores. + return false; + } + + return state.enabled && (state.irqId < 32 || (state.targetList & BIT(currentCoreCtx->coreId)) != 0); + }; + + for (VirqState &state: m_virqPendingQueue) { + if (pred(state)) { + chosen[numChosen++] = &state; + } + } + + for (size_t i = 0; i < numChosen; i++) { + chosen[i]->handled = true; + m_virqPendingQueue.erase(*chosen[i]); + } } } diff --git a/thermosphere/src/hvisor_virtual_gic.hpp b/thermosphere/src/hvisor_virtual_gic.hpp index a1cd5732d..c3c51a53e 100644 --- a/thermosphere/src/hvisor_virtual_gic.hpp +++ b/thermosphere/src/hvisor_virtual_gic.hpp @@ -161,8 +161,6 @@ namespace ams::hvisor { using difference_type = ptrdiff_t; using iterator = Iterator; using const_iterator = Iterator; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; constexpr void Initialize(VirqState *storage) { m_storage = storage; } @@ -181,24 +179,47 @@ namespace ams::hvisor { constexpr iterator begin() { return iterator{m_first, m_storage}; } constexpr iterator end() { return iterator{&m_storage[virqListEndIndex], m_storage}; } - constexpr const_reverse_iterator crbegin() const { - return const_reverse_iterator{const_iterator{m_last, m_storage}}; - } - constexpr const_reverse_iterator crend() const { return const_reverse_iterator{cend()}; } - - constexpr const_reverse_iterator rbegin() const { return crbegin(); } - constexpr const_reverse_iterator rend() const { return crend(); } - - constexpr reverse_iterator rbegin() { return reverse_iterator{iterator{m_first, m_storage}}; } - constexpr reverse_iterator rend() { return reverse_iterator{end()}; } - - iterator insert(iterator pos, VirqState &elem); iterator insert(VirqState &elem); - iterator erase(iterator startPos, iterator endPos); + constexpr iterator erase(iterator startPos, iterator endPos) + { + VirqState &prev = m_storage[startPos->listPrev]; + VirqState &next = *endPos; + u32 nextPos = GetStateIndex(*endPos); - iterator erase(iterator pos) { return erase(pos, std::next(pos)); } + ENSURE(startPos->IsQueued()); + if (startPos->listPrev != virqListEndIndex) { + prev.listNext = nextPos; + } else { + m_first = &next; + } + + if (nextPos != virqListEndIndex) { + next.listPrev = startPos->listPrev; + } else { + m_last = &prev; + } + + for (iterator it = startPos; it != endPos; ++it) { + it->listPrev = it->listNext = virqListInvalidIndex; + } + } + + constexpr iterator erase(iterator pos) { return erase(pos, std::next(pos)); } + constexpr iterator erase(VirqState &pos) { return erase(iterator{&pos, m_storage}); } + + template + void erase_if(Pred p) + { + for (iterator it = begin(); l = end(); i != l) { + if(p(*it)) { + it = erase(it); + } else { + ++it; + } + } + } }; @@ -225,7 +246,6 @@ namespace ams::hvisor { bool m_distributorEnabled = false; private: - constexpr VirqState &GetVirqState(u32 coreId, u32 id) { if (id >= 32) { @@ -253,12 +273,12 @@ namespace ams::hvisor { } } - u32 vgicGetDistributorControlRegister(void) + u32 GetDistributorControlRegister(void) { return m_distributorEnabled ? 1 : 0; } - u32 vgicGetDistributorTypeRegister(void) + u32 GetDistributorTypeRegister(void) { // See above comment. // Therefore, LSPI = 0, SecurityExtn = 0, rest = from physical distributor @@ -309,6 +329,18 @@ namespace ams::hvisor { void SetSgiPendingState(u32 id, u32 coreId, u32 srcCoreId); void SendSgi(u32 id, GicV2Distributor::SgirTargetListFilter filter, u32 coreList); + void ResampleVirqLevel(VirqState &state); + void CleanupPendingQueue(); + size_t ChoosePendingInterrupts(VirqState *chosen[], size_t maxNum); + + void UpdateState(); + + public: + static bool ValidateGicdRegisterAccess(size_t offset, size_t sz); + public: + void WriteGicdRegister(u32 val, size_t offset, size_t sz); + u32 ReadGicdRegister(size_t offset, size_t sz); + }; }