2020-02-13 01:50:11 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2019-2020 Atmosphère-NX
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2020-02-14 01:19:26 +00:00
|
|
|
#include <mutex>
|
2020-02-13 01:50:11 +00:00
|
|
|
#include "hvisor_virtual_gic.hpp"
|
2020-02-15 02:05:54 +00:00
|
|
|
#include "cpu/hvisor_cpu_instructions.hpp"
|
2020-02-13 01:50:11 +00:00
|
|
|
|
2020-02-16 01:19:19 +00:00
|
|
|
#include "platform/interrupt_config.h" // TODO remove
|
|
|
|
|
2020-02-14 01:19:26 +00:00
|
|
|
#define GICDOFF(field) (offsetof(GicV2Distributor, field))
|
|
|
|
|
2020-02-13 01:50:11 +00:00
|
|
|
namespace ams::hvisor {
|
|
|
|
|
|
|
|
|
|
|
|
VirtualGic::VirqQueue::iterator VirtualGic::VirqQueue::insert(VirtualGic::VirqQueue::iterator pos, VirtualGic::VirqState &elem)
|
|
|
|
{
|
|
|
|
// Insert before
|
|
|
|
ENSURE(!elem.IsQueued());
|
|
|
|
|
|
|
|
// Empty list
|
|
|
|
if (begin() == end()) {
|
|
|
|
m_first = m_last = &elem;
|
|
|
|
elem.listPrev = elem.listNext = virqListEndIndex;
|
|
|
|
return begin();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pos == end()) {
|
|
|
|
// Insert after last
|
|
|
|
VirqState &prev = back();
|
|
|
|
elem.listPrev = GetStateIndex(prev);
|
|
|
|
elem.listNext = prev.listNext;
|
|
|
|
prev.listNext = GetStateIndex(elem);
|
|
|
|
m_last = &elem;
|
|
|
|
} else {
|
|
|
|
u32 idx = GetStateIndex(elem);
|
|
|
|
u32 posidx = GetStateIndex(*pos);
|
|
|
|
u32 previdx = elem.listPrev;
|
|
|
|
|
|
|
|
elem.listNext = posidx;
|
|
|
|
elem.listPrev = previdx;
|
|
|
|
|
|
|
|
pos->listPrev = idx;
|
|
|
|
|
|
|
|
if (pos == begin()) {
|
|
|
|
m_first = &elem;
|
|
|
|
} else {
|
|
|
|
--pos;
|
|
|
|
pos->listNext = idx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return iterator{&elem, m_storage};
|
|
|
|
}
|
|
|
|
|
|
|
|
VirtualGic::VirqQueue::iterator VirtualGic::VirqQueue::insert(VirtualGic::VirqState &elem)
|
|
|
|
{
|
|
|
|
// Insert in a stable sorted way
|
|
|
|
// Lower priority number is higher; we sort by descending priority, ie. ascending priority number
|
|
|
|
// Put the interrupts that were previously in the LR before the one which don't
|
|
|
|
return insert(
|
|
|
|
std::find_if(begin(), end(), [&a = elem](const VirqState &b) {
|
|
|
|
return a.priority == b.priority ? a.handled && !b.handled : a.priority < b.priority;
|
|
|
|
}),
|
|
|
|
elem
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::SetInterruptEnabledState(u32 id)
|
|
|
|
{
|
|
|
|
VirqState &state = GetVirqState(id);
|
|
|
|
|
|
|
|
if (id < 16 || !IrqManager::IsGuestInterrupt(id) || state.enabled) {
|
|
|
|
// Nothing to do...
|
|
|
|
// Also, ignore for SGIs
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Similar effects to setting the target list to non-0 when it was 0...
|
|
|
|
if (state.IsPending()) {
|
|
|
|
NotifyOtherCoreList(state.targetList);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.enabled = true;
|
|
|
|
IrqManager::SetInterruptEnabled(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::ClearInterruptEnabledState(u32 id)
|
|
|
|
{
|
|
|
|
VirqState &state = GetVirqState(id);
|
|
|
|
|
|
|
|
if (id < 16 || !IrqManager::IsGuestInterrupt(id) || !state.enabled) {
|
|
|
|
// Nothing to do...
|
|
|
|
// Also, ignore for SGIs
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Similar effects to setting the target list to 0, we may need to notify the core
|
|
|
|
// handling the interrupt if it's pending
|
|
|
|
if (state.handled) {
|
|
|
|
NotifyOtherCoreList(BIT(state.coreId));
|
|
|
|
}
|
|
|
|
|
|
|
|
state.enabled = false;
|
|
|
|
IrqManager::ClearInterruptEnabled(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::SetInterruptPriorityByte(u32 id, u8 priority)
|
|
|
|
{
|
|
|
|
if (!IrqManager::IsGuestInterrupt(id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 32 priority levels max, bits [7:3]
|
|
|
|
priority >>= priorityShift;
|
|
|
|
|
|
|
|
if (id >= 16) {
|
|
|
|
// Ensure we have the correct priority on the physical distributor...
|
|
|
|
IrqManager::GetInstance().SetInterruptPriority(id, IrqManager::guestPriority);
|
|
|
|
}
|
|
|
|
|
|
|
|
VirqState &state = GetVirqState(id);
|
|
|
|
if (priority == state.priority) {
|
|
|
|
// Nothing to do...
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.priority = priority;
|
|
|
|
u32 targets = state.targetList;
|
|
|
|
if (targets != 0 && state.IsPending()) {
|
|
|
|
NotifyOtherCoreList(targets);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::SetInterruptTargets(u32 id, u8 coreList)
|
|
|
|
{
|
|
|
|
// Ignored for SGIs and PPIs, and non-guest interrupts
|
|
|
|
if (id < 32 || !IrqManager::IsGuestInterrupt(id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Interrupt not pending (inactive or active-only): nothing much to do (see reference manual)
|
|
|
|
// Otherwise, we may need to migrate the interrupt.
|
|
|
|
// In our model, while a physical interrupt can be pending on multiple cores, we decide that a pending SPI
|
|
|
|
// can only be handled on a single core (either it's in a LR, or in the global list), therefore we need to
|
|
|
|
// send a signal to (oldList XOR newList) to either put the interrupt back in the global list or potentially handle it
|
|
|
|
|
|
|
|
// Note that we take into account that the interrupt may be disabled.
|
|
|
|
VirqState &state = GetVirqState(id);
|
|
|
|
if (state.IsPending()) {
|
|
|
|
u8 oldList = state.targetList;
|
2020-02-23 01:30:35 +00:00
|
|
|
u8 diffList = (oldList ^ coreList) & CoreContext::GetActiveCoreMask();
|
2020-02-13 01:50:11 +00:00
|
|
|
if (diffList != 0) {
|
|
|
|
NotifyOtherCoreList(diffList);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state.targetList = coreList;
|
|
|
|
IrqManager::SetInterruptTargets(id, state.targetList);
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::SetInterruptConfigBits(u32 id, u32 config)
|
|
|
|
{
|
|
|
|
// Ignored for SGIs, implementation defined for PPIs
|
|
|
|
if (id < 32 || !IrqManager::IsGuestInterrupt(id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
VirqState &state = GetVirqState(id);
|
|
|
|
|
|
|
|
// Expose bit(2n) as nonprogrammable to the guest no matter what the physical distributor actually behaves
|
2020-02-15 02:05:54 +00:00
|
|
|
bool newEdgeTriggered = ((config & 2) << GicV2Distributor::GetCfgrShift(id)) != 0;
|
2020-02-13 01:50:11 +00:00
|
|
|
|
2020-02-15 02:05:54 +00:00
|
|
|
if (state.edgeTriggered != newEdgeTriggered) {
|
|
|
|
state.edgeTriggered = newEdgeTriggered;
|
|
|
|
IrqManager::SetInterruptMode(id, newEdgeTriggered);
|
2020-02-13 01:50:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::SetSgiPendingState(u32 id, u32 coreId, u32 srcCoreId)
|
|
|
|
{
|
|
|
|
VirqState &state = GetVirqState(coreId, id);
|
|
|
|
m_incomingSgiPendingSources[coreId][id] |= BIT(srcCoreId);
|
|
|
|
if (!state.handled && !state.IsQueued()) {
|
|
|
|
// The SGI was inactive on the target core...
|
|
|
|
state.SetPending();
|
|
|
|
state.srcCoreId = srcCoreId;
|
|
|
|
m_incomingSgiPendingSources[coreId][id] &= ~BIT(srcCoreId);
|
|
|
|
m_virqPendingQueue.insert(state);
|
|
|
|
NotifyOtherCoreList(BIT(coreId));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::SendSgi(u32 id, GicV2Distributor::SgirTargetListFilter filter, u32 coreList)
|
|
|
|
{
|
|
|
|
switch (filter) {
|
|
|
|
case GicV2Distributor::ForwardToTargetList:
|
|
|
|
// Forward to coreList
|
|
|
|
break;
|
|
|
|
case GicV2Distributor::ForwardToAllOthers:
|
|
|
|
// Forward to all but current core
|
2020-02-23 01:30:35 +00:00
|
|
|
coreList = ~BIT(currentCoreCtx->GetCoreId());
|
2020-02-13 01:50:11 +00:00
|
|
|
break;
|
|
|
|
case GicV2Distributor::ForwardToSelf:
|
|
|
|
// Forward to current core only
|
2020-02-23 01:30:35 +00:00
|
|
|
coreList = BIT(currentCoreCtx->GetCoreId());
|
2020-02-13 01:50:11 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEBUG("Emulated GCID_SGIR: invalid TargetListFilter value!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-02-23 01:30:35 +00:00
|
|
|
coreList &= CoreContext::GetActiveCoreMask();
|
2020-02-13 01:50:11 +00:00
|
|
|
for (u32 dstCore: util::BitsOf{coreList}) {
|
2020-02-23 01:30:35 +00:00
|
|
|
SetSgiPendingState(id, dstCore, currentCoreCtx->GetCoreId());
|
2020-02-13 01:50:11 +00:00
|
|
|
}
|
2020-02-14 01:19:26 +00:00
|
|
|
}
|
2020-02-13 01:50:11 +00:00
|
|
|
|
2020-02-14 01:19:26 +00:00
|
|
|
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<u32>(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<u32>(offset - GICDOFF(icenabler));
|
|
|
|
for(u32 pos: util::BitsOf{val}) {
|
|
|
|
SetInterruptEnabledState(base + pos);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case GICDOFF(ipriorityr) ... GICDOFF(ipriorityr) + maxIrqId: {
|
|
|
|
u32 base = static_cast<u32>(offset - GICDOFF(ipriorityr));
|
|
|
|
for (u32 i = 0; i < static_cast<u32>(sz); i++) {
|
|
|
|
SetInterruptPriorityByte(base + i, static_cast<u8>(val));
|
|
|
|
val >>= 8;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case GICDOFF(itargetsr) + 32 ... GICDOFF(itargetsr) + maxIrqId: {
|
|
|
|
u32 base = static_cast<u32>(offset - GICDOFF(itargetsr));
|
|
|
|
for (u32 i = 0; i < static_cast<u32>(sz); i++) {
|
|
|
|
SetInterruptTargets(base + i, static_cast<u8>(val));
|
|
|
|
val >>= 8;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case GICDOFF(icfgr) + 32/4 ... GICDOFF(icfgr) + maxIrqId/4: {
|
|
|
|
u32 base = 4 * static_cast<u32>(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<GicV2Distributor::SgirTargetListFilter>((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<u32>(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<u32>(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<u32>(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<u32>(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.
|
|
|
|
*/
|
|
|
|
|
2020-02-15 02:05:54 +00:00
|
|
|
if (state.edgeTriggered || !state.IsPending()) {
|
2020-02-14 01:19:26 +00:00
|
|
|
// 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) {
|
2020-02-23 01:30:35 +00:00
|
|
|
if (state.irqId < 32 && state.coreId != currentCoreCtx->GetCoreId()) {
|
2020-02-14 01:19:26 +00:00
|
|
|
// We can't handle SGIs/PPIs of other cores.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-02-23 01:30:35 +00:00
|
|
|
return state.enabled && (state.irqId < 32 || (state.targetList & BIT(currentCoreCtx->GetCoreId())) != 0);
|
2020-02-14 01:19:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
for (VirqState &state: m_virqPendingQueue) {
|
|
|
|
if (pred(state)) {
|
|
|
|
chosen[numChosen++] = &state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < numChosen; i++) {
|
|
|
|
chosen[i]->handled = true;
|
2020-02-23 01:30:35 +00:00
|
|
|
chosen[i]->coreId = currentCoreCtx->GetCoreId();
|
2020-02-14 01:19:26 +00:00
|
|
|
m_virqPendingQueue.erase(*chosen[i]);
|
|
|
|
}
|
2020-02-13 01:50:11 +00:00
|
|
|
}
|
2020-02-15 02:05:54 +00:00
|
|
|
|
|
|
|
void VirtualGic::PushListRegisters(VirqState *chosen[], size_t num)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < num; i++) {
|
|
|
|
VirqState &state = *chosen[i];
|
|
|
|
u32 irqId = state.irqId;
|
|
|
|
|
|
|
|
GicV2VirtualInterfaceController::ListRegister lr = {0};
|
|
|
|
lr.grp1 = false; // group0
|
|
|
|
lr.priority = state.priority;
|
|
|
|
lr.virtualId = irqId;
|
|
|
|
|
|
|
|
// We only add new pending interrupts here...
|
|
|
|
lr.pending = true;
|
|
|
|
lr.active = false;
|
|
|
|
|
|
|
|
// We don't support guests setting the pending latch, so the logic is probably simpler...
|
|
|
|
|
|
|
|
if (irqId < 16) {
|
|
|
|
// SGI
|
|
|
|
lr.physicalId = BIT(9) /* EOI notification bit */ | state.srcCoreId;
|
|
|
|
// ^ IDK how kvm gets away with not setting the EOI notif bits in some cases,
|
|
|
|
// what they do seems to be prone to drop interrupts, etc.
|
|
|
|
|
|
|
|
lr.hw = false; // software
|
|
|
|
} else {
|
|
|
|
// Actual physical interrupt
|
|
|
|
lr.hw = true;
|
|
|
|
lr.physicalId = irqId;
|
|
|
|
}
|
|
|
|
|
|
|
|
volatile auto *freeLr = AllocateListRegister();
|
|
|
|
ENSURE(freeLr != nullptr);
|
|
|
|
freeLr->raw = lr.raw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VirtualGic::UpdateListRegister(volatile GicV2VirtualInterfaceController::ListRegister *lr)
|
|
|
|
{
|
|
|
|
GicV2VirtualInterfaceController::ListRegister lrCopy = { .raw = lr->raw };
|
|
|
|
|
|
|
|
u32 irqId = lrCopy.virtualId;
|
|
|
|
|
|
|
|
// Note: this give priority to multi-SGIs than can be immediately handled
|
|
|
|
|
|
|
|
// Update the state
|
|
|
|
VirqState &state = GetVirqState(irqId);
|
|
|
|
ENSURE(state.handled);
|
|
|
|
|
|
|
|
u32 srcCoreId = state.coreId;
|
2020-02-23 01:30:35 +00:00
|
|
|
u32 coreId = currentCoreCtx->GetCoreId();
|
2020-02-15 02:05:54 +00:00
|
|
|
|
|
|
|
state.active = lrCopy.active;
|
|
|
|
|
|
|
|
if (lrCopy.active) {
|
|
|
|
// We don't dequeue active interrupts
|
|
|
|
|
|
|
|
if (irqId < 16) {
|
|
|
|
// We can allow SGIs to be marked active-pending if it's been made pending from the same source again
|
|
|
|
// For hw interrupts, the active-pending state is tracked in the real GICD
|
|
|
|
if (m_incomingSgiPendingSources[coreId][irqId] & BIT(srcCoreId)) {
|
|
|
|
lrCopy.pending = true;
|
|
|
|
m_incomingSgiPendingSources[coreId][irqId] &= ~BIT(srcCoreId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the vIRQ goes from pending to active, it has been acknowledged: clear line level and pending latch
|
|
|
|
// SGIs are always edge-triggered, so line level doesn't matter & that's why we handle them above to simplify the code
|
|
|
|
if (!lrCopy.pending) {
|
|
|
|
state.ClearPendingOnAck();
|
|
|
|
}
|
|
|
|
lr->raw = lrCopy.raw;
|
|
|
|
return true;
|
|
|
|
} else if (lrCopy.pending) {
|
|
|
|
// New interrupts might have come, pending status might have been changed, etc.
|
|
|
|
// We need to put the interrupt back in the pending list (which we clean up afterwards)
|
|
|
|
state.handled = false;
|
|
|
|
m_virqPendingQueue.insert(state);
|
|
|
|
lr->raw = 0;
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
// Interrupt is inactive. This means it has been acked and handled.
|
|
|
|
// SGIs are always edge-triggered, so line level doesn't matter & that's why we handle them above to simplify the code
|
|
|
|
|
|
|
|
if (irqId < 16) {
|
|
|
|
// Special case for multi-SGIs if they can be immediately handled
|
|
|
|
if (m_incomingSgiPendingSources[coreId][irqId] != 0) {
|
|
|
|
srcCoreId = __builtin_ctz(m_incomingSgiPendingSources[coreId][irqId]);
|
|
|
|
state.srcCoreId = srcCoreId;
|
|
|
|
m_incomingSgiPendingSources[coreId][irqId] &= ~BIT(srcCoreId);
|
|
|
|
lrCopy.physicalId = BIT(9) /* EOI notification bit */ | srcCoreId;
|
|
|
|
|
|
|
|
lrCopy.pending = true;
|
|
|
|
lr->raw = lrCopy.raw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!lrCopy.pending) {
|
|
|
|
// Inactive interrupt, cleanup
|
|
|
|
state.ClearPendingOnAck();
|
|
|
|
state.handled = false;
|
|
|
|
lr->raw = 0;
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::UpdateState()
|
|
|
|
{
|
|
|
|
GicV2VirtualInterfaceController::HypervisorControlRegister hcr = { .raw = gich->hcr.raw };
|
2020-02-23 01:30:35 +00:00
|
|
|
u32 coreId = currentCoreCtx->GetCoreId();
|
2020-02-15 02:05:54 +00:00
|
|
|
|
|
|
|
// First, put back inactive interrupts into the queue, handle some SGI stuff
|
|
|
|
// Need to handle the LRs in reverse order to keep list stability
|
|
|
|
u64 usedMap = cpu::rbit(m_usedLrMap[coreId]);
|
|
|
|
for (auto pos: util::BitsOf{usedMap}) {
|
|
|
|
if (!UpdateListRegister(&gich->lr[63 - pos])) {
|
|
|
|
usedMap &= ~BITL(pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_usedLrMap[coreId] = cpu::rbit(usedMap);
|
|
|
|
|
|
|
|
// Then, clean the list up
|
|
|
|
CleanupPendingQueue();
|
|
|
|
|
|
|
|
size_t numFreeLr = GetNumberOfFreeListRegisters();
|
|
|
|
VirqState *chosen[64];
|
|
|
|
|
|
|
|
// Choose interrupts...
|
|
|
|
size_t numChosen = ChoosePendingInterrupts(chosen, numFreeLr);
|
|
|
|
|
|
|
|
// ...and push them
|
|
|
|
PushListRegisters(chosen, numChosen);
|
|
|
|
|
|
|
|
// Enable underflow interrupt when appropriate to do so
|
|
|
|
hcr.uie = m_numListRegisters - GetNumberOfFreeListRegisters() > 1;
|
|
|
|
gich->hcr.raw = hcr.raw;
|
|
|
|
}
|
|
|
|
|
2020-02-16 01:19:19 +00:00
|
|
|
std::optional<bool> VirtualGic::InterruptTopHalfHandler(u32 irqId, u32)
|
2020-02-15 02:05:54 +00:00
|
|
|
{
|
2020-02-16 01:19:19 +00:00
|
|
|
if (irqId == IrqManager::VgicUpdateSgi) {
|
|
|
|
// This SGI is just there to trigger the state update
|
|
|
|
return false;
|
|
|
|
} else if (irqId != GIC_IRQID_MAINTENANCE) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Maintenance interrupt handler:
|
2020-02-15 02:05:54 +00:00
|
|
|
GicV2VirtualInterfaceController::MaintenanceIntStatRegister misr = { .raw = gich->misr.raw };
|
|
|
|
|
|
|
|
// Force GICV_CTRL to behave like ns-GICC_CTLR, with group 1 being replaced by group 0
|
|
|
|
// Ensure we aren't spammed by maintenance interrupts, either.
|
|
|
|
if (misr.vgrp0e || misr.vgrp0d || misr.vgrp1e || misr.vgrp1d) {
|
|
|
|
GicV2VirtualInterfaceController::VmControlRegister vmcr = { .raw = gich->vmcr.raw };
|
|
|
|
vmcr.cbpr = 0;
|
|
|
|
vmcr.fiqEn = 0;
|
|
|
|
vmcr.ackCtl = 0;
|
|
|
|
vmcr.enableGrp1 = 0;
|
|
|
|
gich->vmcr.raw = vmcr.raw;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (misr.vgrp0e) {
|
2020-02-23 01:30:35 +00:00
|
|
|
DEBUG("EL2 [core %d]: Group 0 enabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
|
2020-02-15 02:05:54 +00:00
|
|
|
gich->hcr.vgrp0eie = false;
|
|
|
|
gich->hcr.vgrp0die = true;
|
|
|
|
} else if (misr.vgrp0d) {
|
2020-02-23 01:30:35 +00:00
|
|
|
DEBUG("EL2 [core %d]: Group 0 disabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
|
2020-02-15 02:05:54 +00:00
|
|
|
gich->hcr.vgrp0eie = true;
|
|
|
|
gich->hcr.vgrp0die = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Already handled the following 2 above:
|
|
|
|
if (misr.vgrp1e) {
|
2020-02-23 01:30:35 +00:00
|
|
|
DEBUG("EL2 [core %d]: Group 1 enabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
|
2020-02-15 02:05:54 +00:00
|
|
|
}
|
|
|
|
if (misr.vgrp1d) {
|
2020-02-23 01:30:35 +00:00
|
|
|
DEBUG("EL2 [core %d]: Group 1 disabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
|
2020-02-15 02:05:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (misr.eoi) {
|
2020-02-23 01:30:35 +00:00
|
|
|
//DEBUG("EL2 [core %d]: SGI EOI maintenance interrupt\n", currentCoreCtx->GetCoreId());
|
2020-02-15 02:05:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (misr.u) {
|
2020-02-23 01:30:35 +00:00
|
|
|
//DEBUG("EL2 [core %d]: Underflow maintenance interrupt\n", currentCoreCtx->GetCoreId());
|
2020-02-15 02:05:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ENSURE2(!misr.lrenp, "List Register Entry Not Present maintenance interrupt!\n");
|
|
|
|
|
|
|
|
// The rest should be handled by the main loop...
|
2020-02-16 01:19:19 +00:00
|
|
|
return false;
|
2020-02-15 02:05:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::EnqueuePhysicalIrq(u32 id)
|
|
|
|
{
|
|
|
|
VirqState &state = GetVirqState(id);
|
|
|
|
state.SetPending();
|
|
|
|
m_virqPendingQueue.insert(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualGic::Initialize()
|
|
|
|
{
|
2020-02-23 01:30:35 +00:00
|
|
|
if (currentCoreCtx->IsBootCore()) {
|
2020-02-15 02:05:54 +00:00
|
|
|
m_virqPendingQueue.Initialize(m_virqStates.data());
|
|
|
|
m_numListRegisters = static_cast<u8>(1 + (gich->vtr & 0x3F));
|
|
|
|
|
|
|
|
// All fields are reset to 0 on reset and deep sleep exit
|
|
|
|
|
|
|
|
for (VirqState &state: m_virqStates) {
|
|
|
|
state.listPrev = state.listNext = virqListInvalidIndex;
|
|
|
|
state.priority = lowestPriority;
|
|
|
|
}
|
|
|
|
|
|
|
|
// SPIs (+ reserved interrupts just in case)
|
|
|
|
for (u32 i = 32; i < 1024; i++) {
|
|
|
|
GetVirqState(0, i).irqId = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// SGIs, PPIs
|
|
|
|
for (u32 coreId = 0; coreId < MAX_CORE; coreId++) {
|
|
|
|
for (u32 i = 0; i < 32; i++) {
|
|
|
|
VirqState &state = GetVirqState(coreId, i);
|
|
|
|
state.coreId = coreId;
|
|
|
|
state.irqId = i;
|
|
|
|
if (i < 16) {
|
|
|
|
state.edgeTriggered = true;
|
|
|
|
state.enabled = true;
|
|
|
|
} else {
|
|
|
|
state.edgeTriggered = IrqManager::IsInterruptEdgeTriggered(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All guest interrupts are initially configured as disabled
|
|
|
|
// All guest SPIs are initially configured as level-sensitive with no targets
|
|
|
|
}
|
|
|
|
|
2020-02-16 01:19:19 +00:00
|
|
|
auto &mgr = IrqManager::GetInstance();
|
|
|
|
|
|
|
|
mgr.Register(*this, GIC_IRQID_MAINTENANCE, true);
|
|
|
|
mgr.Register(*this, IrqManager::VgicUpdateSgi, false);
|
|
|
|
|
2020-02-15 02:05:54 +00:00
|
|
|
// Clear the list registers (they reset to 0, though)
|
|
|
|
for (u8 i = 0; i < m_numListRegisters; i++) {
|
|
|
|
gich->lr[i].raw = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enable a few maintenance interrupts. Enable the virtual interface.
|
|
|
|
GicV2VirtualInterfaceController::HypervisorControlRegister hcr = {};
|
|
|
|
hcr.vgrp1eie = true,
|
|
|
|
hcr.vgrp0eie = true,
|
|
|
|
hcr.lrenpie = true,
|
|
|
|
hcr.en = true,
|
|
|
|
gich->hcr.raw = hcr.raw;
|
|
|
|
}
|
2020-02-13 01:50:11 +00:00
|
|
|
}
|