Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
1967 lines
61 KiB
C
1967 lines
61 KiB
C
/*
|
|
* ARM GICv5 emulation: Interrupt Routing Service (IRS)
|
|
*
|
|
* Copyright (c) 2025 Linaro Limited
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* The IRS is defined in IHI 111701
|
|
* (ARM Generic Interrupt Controller Architecture Specification,
|
|
* GIC architecture version 5):
|
|
* https://developer.arm.com/documentation/111701/latest
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "hw/core/registerfields.h"
|
|
#include "hw/intc/arm_gicv5.h"
|
|
#include "hw/intc/arm_gicv5_stream.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/log.h"
|
|
#include "trace.h"
|
|
#include "migration/blocker.h"
|
|
|
|
OBJECT_DEFINE_TYPE(GICv5, gicv5, ARM_GICV5, ARM_GICV5_COMMON)
|
|
|
|
static const char *domain_name[] = {
|
|
[GICV5_ID_S] = "Secure",
|
|
[GICV5_ID_NS] = "NonSecure",
|
|
[GICV5_ID_EL3] = "EL3",
|
|
[GICV5_ID_REALM] = "Realm",
|
|
};
|
|
|
|
static const char *inttype_name(GICv5IntType t)
|
|
{
|
|
/*
|
|
* We have to be more cautious with getting human readable names
|
|
* for a GICv5IntType for trace strings than we do with the domain
|
|
* enum, because here the value can come from a guest register
|
|
* field.
|
|
*/
|
|
static const char *names[] = {
|
|
[GICV5_PPI] = "PPI",
|
|
[GICV5_LPI] = "LPI",
|
|
[GICV5_SPI] = "SPI",
|
|
};
|
|
if (t >= ARRAY_SIZE(names) || !names[t]) {
|
|
return "RESERVED";
|
|
}
|
|
return names[t];
|
|
}
|
|
|
|
REG32(IRS_IDR0, 0x0)
|
|
FIELD(IRS_IDR0, INT_DOM, 0, 2)
|
|
FIELD(IRS_IDR0, PA_RANGE, 2, 4)
|
|
FIELD(IRS_IDR0, VIRT, 6, 1)
|
|
FIELD(IRS_IDR0, ONE_N, 7, 1)
|
|
FIELD(IRS_IDR0, VIRT_ONE_N, 8, 1)
|
|
FIELD(IRS_IDR0, SETLPI, 9, 1)
|
|
FIELD(IRS_IDR0, MEC, 10, 1)
|
|
FIELD(IRS_IDR0, MPAM, 11, 1)
|
|
FIELD(IRS_IDR0, SWE, 12, 1)
|
|
FIELD(IRS_IDR0, IRSID, 16, 16)
|
|
|
|
REG32(IRS_IDR1, 0x4)
|
|
FIELD(IRS_IDR1, PE_CNT, 0, 16)
|
|
FIELD(IRS_IDR1, IAFFID_BITS, 16, 4)
|
|
FIELD(IRS_IDR1, PRI_BITS, 20, 3)
|
|
|
|
REG32(IRS_IDR2, 0x8)
|
|
FIELD(IRS_IDR2, ID_BITS, 0, 5)
|
|
FIELD(IRS_IDR2, LPI, 5, 1)
|
|
FIELD(IRS_IDR2, MIN_LPI_ID_BITS, 6, 4)
|
|
FIELD(IRS_IDR2, IST_LEVELS, 10, 1)
|
|
FIELD(IRS_IDR2, IST_L2SZ, 11, 3)
|
|
FIELD(IRS_IDR2, IST_MD, 14, 1)
|
|
FIELD(IRS_IDR2, ISTMD_SZ, 15, 5)
|
|
|
|
REG32(IRS_IDR3, 0xc)
|
|
FIELD(IRS_IDR3, VMD, 0, 1)
|
|
FIELD(IRS_IDR3, VMD_SZ, 1, 4)
|
|
FIELD(IRS_IDR3, VM_ID_BITS, 5, 5)
|
|
FIELD(IRS_IDR3, VMT_LEVELS, 10, 1)
|
|
|
|
REG32(IRS_IDR4, 0x10)
|
|
FIELD(IRS_IDR4, VPED_SZ, 0, 6)
|
|
FIELD(IRS_IDR4, VPE_ID_BITS, 6, 4)
|
|
|
|
REG32(IRS_IDR5, 0x14)
|
|
FIELD(IRS_IDR5, SPI_RANGE, 0, 25)
|
|
|
|
REG32(IRS_IDR6, 0x18)
|
|
FIELD(IRS_IDR6, SPI_IRS_RANGE, 0, 25)
|
|
|
|
REG32(IRS_IDR7, 0x1c)
|
|
FIELD(IRS_IDR7, SPI_BASE, 0, 24)
|
|
|
|
REG32(IRS_IIDR, 0x40)
|
|
FIELD(IRS_IIDR, IMPLEMENTER, 0, 12)
|
|
FIELD(IRS_IIDR, REVISION, 12, 4)
|
|
FIELD(IRS_IIDR, VARIANT, 16, 4)
|
|
FIELD(IRS_IIDR, PRODUCTID, 20, 12)
|
|
|
|
REG32(IRS_AIDR, 0x44)
|
|
FIELD(IRS_AIDR, ARCHMINORREV, 0, 4)
|
|
FIELD(IRS_AIDR, ARCHMAJORREV, 4, 4)
|
|
FIELD(IRS_AIDR, COMPONENT, 8, 4)
|
|
|
|
REG32(IRS_CR0, 0x80)
|
|
FIELD(IRS_CR0, IRSEN, 0, 1)
|
|
FIELD(IRS_CR0, IDLE, 1, 1)
|
|
|
|
REG32(IRS_CR1, 0x84)
|
|
FIELD(IRS_CR1, SH, 0, 2)
|
|
FIELD(IRS_CR1, OC, 2, 2)
|
|
FIELD(IRS_CR1, IC, 4, 2)
|
|
FIELD(IRS_CR1, IST_RA, 6, 1)
|
|
FIELD(IRS_CR1, IST_WA, 7, 1)
|
|
FIELD(IRS_CR1, VMT_RA, 8, 1)
|
|
FIELD(IRS_CR1, VMT_WA, 9, 1)
|
|
FIELD(IRS_CR1, VPET_RA, 10, 1)
|
|
FIELD(IRS_CR1, VPET_WA, 11, 1)
|
|
FIELD(IRS_CR1, VMD_RA, 12, 1)
|
|
FIELD(IRS_CR1, VMD_WA, 13, 1)
|
|
FIELD(IRS_CR1, VPED_RA, 14, 1)
|
|
FIELD(IRS_CR1, VPED_WA, 15, 1)
|
|
|
|
REG32(IRS_SYNCR, 0xc0)
|
|
FIELD(IRS_SYNCR, SYNC, 31, 1)
|
|
|
|
REG32(IRS_SYNC_STATUSR, 0xc4)
|
|
FIELD(IRS_SYNC_STATUSR, IDLE, 0, 1)
|
|
|
|
REG64(IRS_SPI_VMR, 0x100)
|
|
FIELD(IRS_SPI_VMR, VM_ID, 0, 16)
|
|
FIELD(IRS_SPI_VMR, VIRT, 63, 1)
|
|
|
|
REG32(IRS_SPI_SELR, 0x108)
|
|
FIELD(IRS_SPI_SELR, ID, 0, 24)
|
|
|
|
REG32(IRS_SPI_DOMAINR, 0x10c)
|
|
FIELD(IRS_SPI_DOMAINR, DOMAIN, 0, 2)
|
|
|
|
REG32(IRS_SPI_RESAMPLER, 0x110)
|
|
FIELD(IRS_SPI_RESAMPLER, SPI_ID, 0, 24)
|
|
|
|
REG32(IRS_SPI_CFGR, 0x114)
|
|
FIELD(IRS_SPI_CFGR, TM, 0, 1)
|
|
|
|
REG32(IRS_SPI_STATUSR, 0x118)
|
|
FIELD(IRS_SPI_STATUSR, IDLE, 0, 1)
|
|
FIELD(IRS_SPI_STATUSR, V, 1, 1)
|
|
|
|
REG32(IRS_PE_SELR, 0x140)
|
|
FIELD(IRS_PE_SELR, IAFFID, 0, 16)
|
|
|
|
REG32(IRS_PE_STATUSR, 0x144)
|
|
FIELD(IRS_PE_STATUSR, IDLE, 0, 1)
|
|
FIELD(IRS_PE_STATUSR, V, 1, 1)
|
|
FIELD(IRS_PE_STATUSR, ONLINE, 2, 1)
|
|
|
|
REG32(IRS_PE_CR0, 0x148)
|
|
FIELD(IRS_PE_CR0, DPS, 0, 1)
|
|
|
|
REG64(IRS_IST_BASER, 0x180)
|
|
FIELD(IRS_IST_BASER, VALID, 0, 1)
|
|
FIELD(IRS_IST_BASER, ADDR, 6, 50)
|
|
|
|
REG32(IRS_IST_CFGR, 0x190)
|
|
FIELD(IRS_IST_CFGR, LPI_ID_BITS, 0, 5)
|
|
FIELD(IRS_IST_CFGR, L2SZ, 5, 2)
|
|
FIELD(IRS_IST_CFGR, ISTSZ, 7, 2)
|
|
FIELD(IRS_IST_CFGR, STRUCTURE, 16, 1)
|
|
|
|
REG32(IRS_IST_STATUSR, 0x194)
|
|
FIELD(IRS_IST_STATUSR, IDLE, 0, 1)
|
|
|
|
REG32(IRS_MAP_L2_ISTR, 0x1c0)
|
|
FIELD(IRS_MAP_L2_ISTR, ID, 0, 24)
|
|
|
|
REG64(IRS_VMT_BASER, 0x200)
|
|
FIELD(IRS_VMT_BASER, VALID, 0, 1)
|
|
FIELD(IRS_VMT_BASER, ADDR, 3, 53)
|
|
|
|
REG32(IRS_VMT_CFGR, 0x210)
|
|
FIELD(IRS_VMT_CFGR, VM_ID_BITS, 0, 5)
|
|
FIELD(IRS_VMT_CFGR, STRUCTURE, 16, 1)
|
|
|
|
REG32(IRS_VMT_STATUSR, 0x124)
|
|
FIELD(IRS_VMT_STATUSR, IDLE, 0, 1)
|
|
|
|
REG64(IRS_VPE_SELR, 0x240)
|
|
FIELD(IRS_VPE_SELR, VM_ID, 0, 16)
|
|
FIELD(IRS_VPE_SELR, VPE_ID, 32, 16)
|
|
FIELD(IRS_VPE_SELR, S, 63, 1)
|
|
|
|
REG64(IRS_VPE_DBR, 0x248)
|
|
FIELD(IRS_VPE_DBR, INTID, 0, 24)
|
|
FIELD(IRS_VPE_DBR, DBPM, 32, 5)
|
|
FIELD(IRS_VPE_DBR, REQ_DB, 62, 1)
|
|
FIELD(IRS_VPE_DBR, DBV, 63, 1)
|
|
|
|
REG32(IRS_VPE_HPPIR, 0x250)
|
|
FIELD(IRS_VPE_HPPIR, ID, 0, 24)
|
|
FIELD(IRS_VPE_HPPIR, TYPE, 29, 3)
|
|
FIELD(IRS_VPE_HPPIR, HPPIV, 32, 1)
|
|
|
|
REG32(IRS_VPE_CR0, 0x258)
|
|
FIELD(IRS_VPE_CR0, DPS, 0, 1)
|
|
|
|
REG32(IRS_VPE_STATUSR, 0x25c)
|
|
FIELD(IRS_VPE_STATUSR, IDLE, 0, 1)
|
|
FIELD(IRS_VPE_STATUSR, V, 1, 1)
|
|
|
|
REG64(IRS_VM_DBR, 0x280)
|
|
FIELD(IRS_VM_DBR, VPE_ID, 0, 16)
|
|
FIELD(IRS_VM_DBR, EN, 63, 1)
|
|
|
|
REG32(IRS_VM_SELR, 0x288)
|
|
FIELD(IRS_VM_SELR, VM_ID, 0, 16)
|
|
|
|
REG32(IRS_VM_STATUSR, 0x28c)
|
|
FIELD(IRS_VM_STATUSR, IDLE, 0, 1)
|
|
FIELD(IRS_VM_STATUSR, V, 1, 1)
|
|
|
|
REG64(IRS_VMAP_L2_VMTR, 0x2c0)
|
|
FIELD(IRS_VMAP_L2_VMTR, VM_ID, 0, 16)
|
|
FIELD(IRS_VMAP_L2_VMTR, M, 63, 1)
|
|
|
|
REG64(IRS_VMAP_VMR, 0x2c8)
|
|
FIELD(IRS_VMAP_VMR, VM_ID, 0, 16)
|
|
FIELD(IRS_VMAP_VMR, U, 62, 1)
|
|
FIELD(IRS_VMAP_VMR, M, 63, 1)
|
|
|
|
REG64(IRS_VMAP_VISTR, 0x2d0)
|
|
FIELD(IRS_VMAP_VISTR, TYPE, 29, 3)
|
|
FIELD(IRS_VMAP_VISTR, VM_ID, 32, 16)
|
|
FIELD(IRS_VMAP_VISTR, U, 62, 1)
|
|
FIELD(IRS_VMAP_VISTR, M, 63, 1)
|
|
|
|
REG64(IRS_VMAP_L2_VISTR, 0x2d8)
|
|
FIELD(IRS_VMAP_L2_VISTR, ID, 0, 24)
|
|
FIELD(IRS_VMAP_L2_VISTR, TYPE, 29, 3)
|
|
FIELD(IRS_VMAP_L2_VISTR, VM_ID, 32, 16)
|
|
FIELD(IRS_VMAP_L2_VISTR, M, 63, 1)
|
|
|
|
REG64(IRS_VMAP_VPER, 0x2e0)
|
|
FIELD(IRS_VMAP_VPER, VPE_ID, 0, 16)
|
|
FIELD(IRS_VMAP_VPER, VM_ID, 32, 16)
|
|
FIELD(IRS_VMAP_VPER, M, 63, 1)
|
|
|
|
REG64(IRS_SAVE_VMR, 0x300)
|
|
FIELD(IRS_SAVE_VMR, VM_ID, 0, 16)
|
|
FIELD(IRS_SAVE_VMR, Q, 62, 1)
|
|
FIELD(IRS_SAVE_VMR, S, 63, 1)
|
|
|
|
REG32(IRS_SAVE_VM_STATUSR, 0x308)
|
|
FIELD(IRS_SAVE_VM_STATUSR, IDLE, 0, 1)
|
|
FIELD(IRS_SAVE_VM_STATUSR, Q, 1, 1)
|
|
|
|
REG32(IRS_MEC_IDR, 0x340)
|
|
FIELD(IRS_MEC_IDR, MECIDSIZE, 0, 4)
|
|
|
|
REG32(IRS_MEC_MECID_R, 0x344)
|
|
FIELD(IRS_MEC_MICID_R, MECID, 0, 16)
|
|
|
|
REG32(IRS_MPAM_IDR, 0x380)
|
|
FIELD(IRS_MPAM_IDR, PARTID_MAX, 0, 16)
|
|
FIELD(IRS_MPAM_IDR, PMG_MAX, 16, 8)
|
|
FIELD(IRS_MPAM_IDR, HAS_MPAM_SP, 24, 1)
|
|
|
|
REG32(IRS_MPAM_PARTID_R, 0x384)
|
|
FIELD(IRS_MPAM_IDR, PARTID, 0, 16)
|
|
FIELD(IRS_MPAM_IDR, PMG, 16, 8)
|
|
FIELD(IRS_MPAM_IDR, MPAM_SP, 24, 2)
|
|
FIELD(IRS_MPAM_IDR, IDLE, 31, 1)
|
|
|
|
REG64(IRS_SWERR_STATUSR, 0x3c0)
|
|
FIELD(IRS_SWERR_STATUSR, V, 0, 1)
|
|
FIELD(IRS_SWERR_STATUSR, S0V, 1, 1)
|
|
FIELD(IRS_SWERR_STATUSR, S1V, 2, 1)
|
|
FIELD(IRS_SWERR_STATUSR, OF, 3, 1)
|
|
FIELD(IRS_SWERR_STATUSR, EC, 16, 8)
|
|
FIELD(IRS_SWERR_STATUSR, IMP_EC, 24, 8)
|
|
|
|
REG64(IRS_SWERR_SYNDROMER0, 0x3c8)
|
|
FIELD(IRS_SWERR_SYNDROMER0, VM_ID, 0, 16)
|
|
FIELD(IRS_SWERR_SYNDROMER0, ID, 32, 24)
|
|
FIELD(IRS_SWERR_SYNDROMER0, TYPE, 60, 3)
|
|
FIELD(IRS_SWERR_SYNDROMER0, VIRTUAL, 63, 1)
|
|
|
|
REG64(IRS_SWERR_SYNDROMER1, 0x3d0)
|
|
FIELD(IRS_SWERR_SYNDROMER2, ADDR, 3, 53)
|
|
|
|
REG32(IRS_IDREGS, 0xffd0)
|
|
REG32(IRS_DEVARCH, 0xffbc)
|
|
|
|
FIELD(L1_ISTE, VALID, 0, 1)
|
|
FIELD(L1_ISTE, L2_ADDR, 12, 44)
|
|
|
|
FIELD(L2_ISTE, PENDING, 0, 1)
|
|
FIELD(L2_ISTE, ACTIVE, 1, 1)
|
|
FIELD(L2_ISTE, HM, 2, 1)
|
|
FIELD(L2_ISTE, ENABLE, 3, 1)
|
|
FIELD(L2_ISTE, IRM, 4, 1)
|
|
FIELD(L2_ISTE, HWU, 9, 2)
|
|
FIELD(L2_ISTE, PRIORITY, 11, 5)
|
|
FIELD(L2_ISTE, IAFFID, 16, 16)
|
|
|
|
/*
|
|
* Format used for gicv5_request_config() return value, which matches
|
|
* the ICC_ICSR_EL1 bit layout.
|
|
*/
|
|
FIELD(ICSR, F, 0, 1)
|
|
FIELD(ICSR, ENABLED, 1, 1)
|
|
FIELD(ICSR, PENDING, 2, 1)
|
|
FIELD(ICSR, IRM, 3, 1)
|
|
FIELD(ICSR, ACTIVE, 4, 1)
|
|
FIELD(ICSR, HM, 5, 1)
|
|
FIELD(ICSR, PRIORITY, 11, 5)
|
|
FIELD(ICSR, IAFFID, 32, 16)
|
|
|
|
#define IRS_DEVARCH_VALUE ((0x23b << 31) | (0x1 << 20) | 0x5a19)
|
|
|
|
static uint32_t gicv5_idreg(int regoffset)
|
|
{
|
|
/*
|
|
* As with the main IRS_IIDR, we don't identify as a specific
|
|
* hardware GICv5 implementation. Arm suggests that the
|
|
* Implementer, Product, etc in IRS_IIDR should also be reported
|
|
* here, so we do that.
|
|
*/
|
|
static const uint8_t gic_ids[] = {
|
|
QEMU_GICV5_IMPLEMENTER >> 8, 0x00, 0x00, 0x00, /* PIDR4..PIDR7 */
|
|
QEMU_GICV5_PRODUCTID & 0xff, /* PIDR0 */
|
|
((QEMU_GICV5_PRODUCTID >> 8) |
|
|
((QEMU_GICV5_IMPLEMENTER & 0xf) << 4)), /* PIDR1 */
|
|
((QEMU_GICV5_REVISION << 4) | (1 << 3) |
|
|
((QEMU_GICV5_IMPLEMENTER & 0x70) >> 4)), /* PIDR2 */
|
|
QEMU_GICV5_VARIANT << 4, /* PIDR3 */
|
|
0x0D, 0xF0, 0x05, 0xB1, /* CIDR0..CIDR3 */
|
|
};
|
|
|
|
regoffset /= 4;
|
|
return gic_ids[regoffset];
|
|
}
|
|
|
|
static GICv5SPIState *spi_for_selr(GICv5Common *cs, GICv5Domain domain)
|
|
{
|
|
/*
|
|
* If the IRS_SPI_SELR value specifies an SPI that can be managed in
|
|
* this domain, return a pointer to its GICv5SPIState; otherwise
|
|
* return NULL.
|
|
*/
|
|
uint32_t id = FIELD_EX32(cs->irs_spi_selr[domain], IRS_SPI_SELR, ID);
|
|
GICv5SPIState *spi = gicv5_raw_spi_state(cs, id);
|
|
|
|
if (spi && (domain == GICV5_ID_EL3 || domain == spi->domain)) {
|
|
return spi;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static MemTxAttrs irs_txattrs(GICv5Common *cs, GICv5Domain domain)
|
|
{
|
|
/*
|
|
* Return a MemTxAttrs to use for IRS memory accesses. IRS_CR1
|
|
* has the usual Arm cacheability/shareability attributes, but
|
|
* QEMU doesn't care about those. All we need to specify here is
|
|
* the correct security attributes, which depend on the interrupt
|
|
* domain. Conveniently, our GICv5Domain encoding matches the
|
|
* ARMSecuritySpace one (because both follow an architecturally
|
|
* specified field). The exception is that the EL3 domain must be
|
|
* Secure instead of Root if we don't implement Realm.
|
|
*/
|
|
if (domain == GICV5_ID_EL3 &&
|
|
!gicv5_domain_implemented(cs, GICV5_ID_REALM)) {
|
|
domain = GICV5_ID_S;
|
|
}
|
|
return (MemTxAttrs) {
|
|
.space = domain,
|
|
.secure = domain == GICV5_ID_S || domain == GICV5_ID_EL3,
|
|
};
|
|
}
|
|
|
|
/* Data we need to pass through to lpi_cache_get_hppi() */
|
|
typedef struct GetHPPIUserData {
|
|
GICv5PendingIrq *best;
|
|
uint32_t iaffid;
|
|
} GetHPPIUserData;
|
|
|
|
static void lpi_cache_get_hppi(gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
uint64_t id = GPOINTER_TO_INT(key);
|
|
uint64_t l2_iste = *(uint64_t *)value;
|
|
uint32_t prio, iaffid;
|
|
GetHPPIUserData *ud = user_data;
|
|
|
|
if ((l2_iste & (R_L2_ISTE_PENDING_MASK | R_L2_ISTE_ACTIVE_MASK | R_L2_ISTE_ENABLE_MASK))
|
|
!= (R_L2_ISTE_PENDING_MASK | R_L2_ISTE_ENABLE_MASK)) {
|
|
return;
|
|
}
|
|
prio = FIELD_EX32(l2_iste, L2_ISTE, PRIORITY);
|
|
iaffid = FIELD_EX32(l2_iste, L2_ISTE, IAFFID);
|
|
if (iaffid == ud->iaffid && prio < ud->best->prio) {
|
|
id = FIELD_DP32(id, INTID, TYPE, GICV5_LPI);
|
|
ud->best->intid = id;
|
|
ud->best->prio = prio;
|
|
}
|
|
}
|
|
|
|
static int irs_cpuidx_from_iaffid(GICv5Common *cs, uint32_t iaffid)
|
|
{
|
|
for (int i = 0; i < cs->num_cpus; i++) {
|
|
if (cs->cpu_iaffids[i] == iaffid) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void irs_recalc_hppi(GICv5 *s, GICv5Domain domain, uint32_t iaffid)
|
|
{
|
|
/*
|
|
* Recalculate the highest priority pending interrupt for the
|
|
* specified domain and cpuif. HPPI candidates must be pending,
|
|
* inactive and enabled.
|
|
*/
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
int cpuidx = irs_cpuidx_from_iaffid(cs, iaffid);
|
|
ARMCPU *cpu = cpuidx >= 0 ? cs->cpus[cpuidx] : NULL;
|
|
GICv5PendingIrq best;
|
|
|
|
best = GICV5_PENDING_IRQ_NONE;
|
|
|
|
if (!cpu) {
|
|
/* Nothing happens for iaffids targeting nonexistent CPUs */
|
|
trace_gicv5_irs_recalc_hppi_fail(domain_name[domain], iaffid,
|
|
"IAFFID doesn't match any CPU");
|
|
return;
|
|
}
|
|
|
|
if (!FIELD_EX32(cs->irs_cr0[domain], IRS_CR0, IRSEN)) {
|
|
/* When the IRS is disabled we don't forward HPPIs */
|
|
trace_gicv5_irs_recalc_hppi_fail(domain_name[domain], iaffid,
|
|
"IRS_CR0.IRSEN is zero");
|
|
return;
|
|
}
|
|
|
|
if (s->phys_lpi_config[domain].valid) {
|
|
GetHPPIUserData ud;
|
|
|
|
ud.best = &best;
|
|
ud.iaffid = iaffid;
|
|
g_hash_table_foreach(s->phys_lpi_config[domain].lpi_cache,
|
|
lpi_cache_get_hppi, &ud);
|
|
}
|
|
|
|
/*
|
|
* OPT: consider also caching the SPI interrupt information,
|
|
* similarly to how we handle LPIs, if iterating through the whole
|
|
* SPI array every time is too expensive.
|
|
*/
|
|
for (int i = 0; i < cs->spi_irs_range; i++) {
|
|
GICv5SPIState *spi = &cs->spi[i];
|
|
|
|
if (spi->active || !spi->pending || !spi->enabled) {
|
|
continue;
|
|
}
|
|
if (spi->domain != domain || spi->iaffid != iaffid) {
|
|
continue;
|
|
}
|
|
if (spi->priority < best.prio) {
|
|
uint32_t intid = 0;
|
|
intid = FIELD_DP32(intid, INTID, ID, i);
|
|
intid = FIELD_DP32(intid, INTID, TYPE, GICV5_SPI);
|
|
best.intid = intid;
|
|
best.prio = spi->priority;
|
|
}
|
|
}
|
|
|
|
trace_gicv5_irs_recalc_hppi(domain_name[domain], iaffid,
|
|
best.intid, best.prio);
|
|
|
|
s->hppi[domain][cpuidx] = best;
|
|
/*
|
|
* Now present the HPPI to the cpuif. In the real hardware stream
|
|
* protocol, the connection between IRS and cpuif is asynchronous,
|
|
* and so both ends track their idea of the current HPPI, with a
|
|
* back-and-forth sequence so they stay in sync and more
|
|
* interaction when the cpuif resets. For QEMU, we are strictly
|
|
* synchronous and the cpuif asking the IRS for data is a cheap
|
|
* function call, so we simplify this:
|
|
* - the IRS knows what the current HPPI is
|
|
* - s->hppi[][] is a cache we can recalculate
|
|
* - the IRS merely tells the cpuif "something changed", and
|
|
* the cpuif asks for the current HPPI when it needs it
|
|
* - the cpuif does not cache the HPPI on its end
|
|
*/
|
|
gicv5_forward_interrupt(cpu, domain);
|
|
}
|
|
|
|
static void irs_recalc_hppi_all_cpus(GICv5 *s, GICv5Domain domain)
|
|
{
|
|
/*
|
|
* Recalculate the HPPI for every CPU for this domain. This is
|
|
* not as efficient as it could be because we will scan through
|
|
* the LPI cached hash table and the SPI array for each CPU rather
|
|
* than doing a single combined scan, but we only need to do this
|
|
* very rarely, when the guest enables or disables the IST, so we
|
|
* implement this the simple way.
|
|
*/
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
for (int i = 0; i < cs->num_cpus; i++) {
|
|
irs_recalc_hppi(s, domain, cs->cpu_iaffids[i]);
|
|
}
|
|
}
|
|
|
|
static void irs_recall_hppis(GICv5 *s, GICv5Domain domain)
|
|
{
|
|
/*
|
|
* The IRS was just disabled -- we must recall any pending HPPIs
|
|
* we have sent to the CPU interfaces. For us this means that we
|
|
* clear our cached HPPI data and tell the cpuif that it has
|
|
* changed.
|
|
*/
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
|
|
for (int i = 0; i < cs->num_cpus; i++) {
|
|
s->hppi[domain][i] = GICV5_PENDING_IRQ_NONE;
|
|
gicv5_forward_interrupt(cs->cpus[i], domain);
|
|
}
|
|
}
|
|
|
|
GICv5PendingIrq gicv5_get_hppi(GICv5Common *cs, GICv5Domain domain,
|
|
uint32_t iaffid)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
int cpuidx = irs_cpuidx_from_iaffid(cs, iaffid);
|
|
|
|
assert(cpuidx >= 0);
|
|
return s->hppi[domain][cpuidx];
|
|
}
|
|
|
|
static hwaddr l1_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg,
|
|
uint32_t id)
|
|
{
|
|
/*
|
|
* In a 2-level IST configuration, return the address of the L1
|
|
* IST entry for this interrupt ID. The bottom l2_idx_bits of the
|
|
* ID value are the index into the L2 table, and the higher bits
|
|
* of the ID index the L1 table.
|
|
*/
|
|
uint32_t l1_index = id >> cfg->l2_idx_bits;
|
|
return cfg->base + (l1_index * 8);
|
|
}
|
|
|
|
static bool get_l2_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg,
|
|
uint32_t id, hwaddr *l2_iste_addr)
|
|
{
|
|
/*
|
|
* Get the address of the L2 interrupt state table entry for this
|
|
* interrupt. On success, fill in l2_iste_addr and return true.
|
|
* On failure, return false.
|
|
*/
|
|
hwaddr l2_base;
|
|
|
|
if (!cfg->valid) {
|
|
return false;
|
|
}
|
|
|
|
if (id >= (1 << cfg->id_bits)) {
|
|
return false;
|
|
}
|
|
|
|
if (cfg->structure) {
|
|
/*
|
|
* 2-level table: read the L1 IST. The bottom l2_idx_bits of
|
|
* the ID value are the index into the L2 table, and the
|
|
* higher bits of the ID index the L1 table. There is always
|
|
* at least one L1 table entry.
|
|
*/
|
|
hwaddr l1_addr = l1_iste_addr(cs, cfg, id);
|
|
uint64_t l1_iste;
|
|
MemTxResult res;
|
|
|
|
l1_iste = address_space_ldq_le(&cs->dma_as, l1_addr,
|
|
cfg->txattrs, &res);
|
|
if (res != MEMTX_OK) {
|
|
/* Reportable with EC=0x01 if sw error reporting implemented */
|
|
qemu_log_mask(LOG_GUEST_ERROR, "L1 ISTE lookup failed for ID 0x%x"
|
|
" at physical address 0x" HWADDR_FMT_plx "\n",
|
|
id, l1_addr);
|
|
return false;
|
|
}
|
|
if (!FIELD_EX64(l1_iste, L1_ISTE, VALID)) {
|
|
return false;
|
|
}
|
|
l2_base = l1_iste & R_L1_ISTE_L2_ADDR_MASK;
|
|
id = extract32(id, 0, cfg->l2_idx_bits);
|
|
} else {
|
|
/* 1-level table */
|
|
l2_base = cfg->base;
|
|
}
|
|
|
|
*l2_iste_addr = l2_base + (id * cfg->istsz);
|
|
return true;
|
|
}
|
|
|
|
static bool read_l2_iste_mem(GICv5Common *cs, const GICv5ISTConfig *cfg,
|
|
hwaddr addr, uint32_t *l2_iste)
|
|
{
|
|
MemTxResult res;
|
|
|
|
*l2_iste = address_space_ldl_le(&cs->dma_as, addr, cfg->txattrs, &res);
|
|
if (res != MEMTX_OK) {
|
|
/* Reportable with EC=0x02 if sw error reporting implemented */
|
|
qemu_log_mask(LOG_GUEST_ERROR, "L2 ISTE read failed at physical "
|
|
"address 0x" HWADDR_FMT_plx "\n", addr);
|
|
}
|
|
return res == MEMTX_OK;
|
|
}
|
|
|
|
static bool write_l2_iste_mem(GICv5Common *cs, const GICv5ISTConfig *cfg,
|
|
hwaddr addr, uint32_t l2_iste)
|
|
{
|
|
MemTxResult res;
|
|
|
|
address_space_stl_le(&cs->dma_as, addr, l2_iste, cfg->txattrs, &res);
|
|
if (res != MEMTX_OK) {
|
|
/* Reportable with EC=0x02 if sw error reporting implemented */
|
|
qemu_log_mask(LOG_GUEST_ERROR, "L2 ISTE write failed at physical "
|
|
"address 0x" HWADDR_FMT_plx "\n", addr);
|
|
}
|
|
return res == MEMTX_OK;
|
|
}
|
|
|
|
/*
|
|
* This is returned by get_l2_iste() and has everything we need to do
|
|
* the writeback of the L2 ISTE word in put_l2_iste(). Not all these
|
|
* fields are always valid; they are private to the implementation of
|
|
* get_l2_iste() and put_l2_iste().
|
|
*/
|
|
typedef struct L2_ISTE_Handle {
|
|
/* Guest memory address of the L2 ISTE; valid only if !hashed */
|
|
hwaddr l2_iste_addr;
|
|
union {
|
|
/* Actual L2_ISTE word; valid only if !hashed */
|
|
uint32_t l2_iste;
|
|
/* Pointer to L2 ISTE word; valid only if hashed */
|
|
uint32_t *l2_iste_p;
|
|
};
|
|
uint32_t id;
|
|
/* True if this ISTE is currently in the cache */
|
|
bool hashed;
|
|
} L2_ISTE_Handle;
|
|
|
|
static uint32_t *get_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg,
|
|
uint32_t id, L2_ISTE_Handle *h)
|
|
{
|
|
/*
|
|
* Find the L2 ISTE for the interrupt @id.
|
|
*
|
|
* We return a pointer to the ISTE: the caller can freely read and
|
|
* modify the uint64_t pointed to to update the ISTE. If the
|
|
* caller modifies the L2 ISTE word, it must call put_l2_iste(),
|
|
* passing it @h, to write back the ISTE. If the caller is only
|
|
* reading the L2 ISTE, it does not need to call put_l2_iste().
|
|
*
|
|
* We fill in @h with information needed for put_l2_iste().
|
|
*
|
|
* If the ISTE could not be read (typically because of a memory
|
|
* error), return NULL.
|
|
*/
|
|
uint32_t *hashvalue;
|
|
|
|
if (!cfg->valid) {
|
|
/* Catch invalid config early, it has no lpi_cache */
|
|
return NULL;
|
|
}
|
|
|
|
hashvalue = g_hash_table_lookup(cfg->lpi_cache,
|
|
GINT_TO_POINTER(id));
|
|
|
|
h->id = id;
|
|
|
|
if (hashvalue) {
|
|
h->hashed = true;
|
|
h->l2_iste_p = hashvalue;
|
|
return hashvalue;
|
|
}
|
|
|
|
h->hashed = false;
|
|
if (!get_l2_iste_addr(cs, cfg, id, &h->l2_iste_addr) ||
|
|
!read_l2_iste_mem(cs, cfg, h->l2_iste_addr, &h->l2_iste)) {
|
|
return NULL;
|
|
}
|
|
return &h->l2_iste;
|
|
}
|
|
|
|
static void put_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg,
|
|
L2_ISTE_Handle *h)
|
|
{
|
|
/*
|
|
* Write back the modified L2_ISTE word found with get_l2_iste().
|
|
* Once this has been called the L2_ISTE_Handle @h and the pointer
|
|
* to the L2 ISTE word are no longer valid.
|
|
*/
|
|
if (h->hashed) {
|
|
uint32_t l2_iste = *h->l2_iste_p;
|
|
if (!FIELD_EX32(l2_iste, L2_ISTE, PENDING)) {
|
|
/*
|
|
* We just made this not pending: remove from hash table
|
|
* and write back to memory.
|
|
*/
|
|
hwaddr l2_iste_addr;
|
|
|
|
g_hash_table_remove(cfg->lpi_cache, GINT_TO_POINTER(h->id));
|
|
if (get_l2_iste_addr(cs, cfg, h->id, &l2_iste_addr)) {
|
|
write_l2_iste_mem(cs, cfg, l2_iste_addr, l2_iste);
|
|
/* Writeback errors are ignored. */
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (FIELD_EX32(h->l2_iste, L2_ISTE, PENDING)) {
|
|
/*
|
|
* We just made this pending: add it to the hash table, and
|
|
* don't bother writing it back to memory.
|
|
*/
|
|
uint32_t *hashvalue = g_new(uint32_t, 1);
|
|
*hashvalue = h->l2_iste;
|
|
g_hash_table_insert(cfg->lpi_cache, GINT_TO_POINTER(h->id), hashvalue);
|
|
return;
|
|
}
|
|
write_l2_iste_mem(cs, cfg, h->l2_iste_addr, h->l2_iste);
|
|
}
|
|
|
|
void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority,
|
|
GICv5Domain domain, GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint32_t iaffid;
|
|
|
|
trace_gicv5_set_priority(domain_name[domain], inttype_name(type), virtual,
|
|
id, priority);
|
|
/* We must ignore unimplemented low-order priority bits */
|
|
priority &= MAKE_64BIT_MASK(5 - QEMU_GICV5_PRI_BITS, QEMU_GICV5_PRI_BITS);
|
|
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set "
|
|
"priority of a virtual interrupt\n");
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
L2_ISTE_Handle h;
|
|
uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
|
|
if (!l2_iste_p) {
|
|
return;
|
|
}
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PRIORITY, priority);
|
|
iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
|
|
put_l2_iste(cs, cfg, &h);
|
|
break;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set "
|
|
"priority of unreachable SPI %d\n", id);
|
|
return;
|
|
}
|
|
|
|
spi->priority = priority;
|
|
iaffid = spi->iaffid;
|
|
break;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set "
|
|
"priority of bad interrupt type %d\n", type);
|
|
return;
|
|
}
|
|
|
|
irs_recalc_hppi(s, domain, iaffid);
|
|
}
|
|
|
|
void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled,
|
|
GICv5Domain domain, GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint32_t iaffid;
|
|
|
|
trace_gicv5_set_enabled(domain_name[domain], inttype_name(type), virtual,
|
|
id, enabled);
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_enabled: tried to set "
|
|
"enable state of a virtual interrupt\n");
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
L2_ISTE_Handle h;
|
|
uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
|
|
if (!l2_iste_p) {
|
|
return;
|
|
}
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ENABLE, enabled);
|
|
iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
|
|
put_l2_iste(cs, cfg, &h);
|
|
break;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_enabled: tried to set "
|
|
"enable state of unreachable SPI %d\n", id);
|
|
return;
|
|
}
|
|
|
|
spi->enabled = true;
|
|
iaffid = spi->iaffid;
|
|
break;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_enabled: tried to set "
|
|
"enable state of bad interrupt type %d\n", type);
|
|
return;
|
|
}
|
|
|
|
irs_recalc_hppi(s, domain, iaffid);
|
|
}
|
|
|
|
void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending,
|
|
GICv5Domain domain, GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint32_t iaffid;
|
|
|
|
trace_gicv5_set_pending(domain_name[domain], inttype_name(type), virtual,
|
|
id, pending);
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_pending: tried to set "
|
|
"pending state of a virtual interrupt\n");
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
L2_ISTE_Handle h;
|
|
uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
|
|
if (!l2_iste_p) {
|
|
return;
|
|
}
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PENDING, pending);
|
|
iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
|
|
put_l2_iste(cs, cfg, &h);
|
|
break;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_pending: tried to set "
|
|
"pending state of unreachable SPI %d\n", id);
|
|
return;
|
|
}
|
|
|
|
spi->pending = true;
|
|
iaffid = spi->iaffid;
|
|
break;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_pending: tried to set "
|
|
"pending state of bad interrupt type %d\n", type);
|
|
return;
|
|
}
|
|
|
|
irs_recalc_hppi(s, domain, iaffid);
|
|
}
|
|
|
|
void gicv5_set_handling(GICv5Common *cs, uint32_t id,
|
|
GICv5HandlingMode handling, GICv5Domain domain,
|
|
GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
|
|
trace_gicv5_set_handling(domain_name[domain], inttype_name(type), virtual,
|
|
id, handling);
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_handling: tried to set "
|
|
"handling mode of a virtual interrupt\n");
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
L2_ISTE_Handle h;
|
|
uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
|
|
if (!l2_iste_p) {
|
|
return;
|
|
}
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, HM, handling);
|
|
put_l2_iste(cs, cfg, &h);
|
|
break;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_handling: tried to set "
|
|
"priority of unreachable SPI %d\n", id);
|
|
return;
|
|
}
|
|
|
|
spi->hm = handling;
|
|
break;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_handling: tried to set "
|
|
"handling mode of bad interrupt type %d\n", type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid,
|
|
GICv5RoutingMode irm, GICv5Domain domain,
|
|
GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint32_t old_iaffid;
|
|
|
|
trace_gicv5_set_target(domain_name[domain], inttype_name(type), virtual,
|
|
id, iaffid, irm);
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set "
|
|
"target of a virtual interrupt\n");
|
|
return;
|
|
}
|
|
if (irm != GICV5_TARGETED) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set "
|
|
"1-of-N routing\n");
|
|
/*
|
|
* In the cpuif insn "GIC CDAFF", IRM is RES0 for a GIC which
|
|
* does not support 1-of-N routing. So warn, and fall through
|
|
* to treat IRM=1 the same as IRM=0.
|
|
*/
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
L2_ISTE_Handle h;
|
|
uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
|
|
if (!l2_iste_p) {
|
|
return;
|
|
}
|
|
/*
|
|
* For QEMU we do not implement 1-of-N routing, and so
|
|
* L2_ISTE.IRM is RES0. We never read it, and we can skip
|
|
* explicitly writing it to zero here.
|
|
*/
|
|
old_iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, IAFFID, iaffid);
|
|
put_l2_iste(cs, cfg, &h);
|
|
break;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set "
|
|
"target of unreachable SPI %d\n", id);
|
|
return;
|
|
}
|
|
|
|
old_iaffid = spi->iaffid;
|
|
spi->iaffid = iaffid;
|
|
break;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set "
|
|
"target of bad interrupt type %d\n", type);
|
|
return;
|
|
}
|
|
|
|
irs_recalc_hppi(s, domain, old_iaffid);
|
|
irs_recalc_hppi(s, domain, iaffid);
|
|
}
|
|
|
|
static uint64_t l2_iste_to_icsr(GICv5Common *cs, const GICv5ISTConfig *cfg,
|
|
uint32_t id)
|
|
{
|
|
uint64_t icsr = 0;
|
|
const uint32_t *l2_iste_p;
|
|
L2_ISTE_Handle h;
|
|
|
|
l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
if (!l2_iste_p) {
|
|
return R_ICSR_F_MASK;
|
|
}
|
|
|
|
/*
|
|
* The field locations in the L2 ISTE do not line up with the
|
|
* corresponding fields in the ICC_ICSR_EL1 register, so we need
|
|
* to extract and deposit them individually.
|
|
*/
|
|
icsr = FIELD_DP64(icsr, ICSR, F, 0);
|
|
icsr = FIELD_DP64(icsr, ICSR, ENABLED, FIELD_EX32(*l2_iste_p, L2_ISTE, ENABLE));
|
|
icsr = FIELD_DP64(icsr, ICSR, PENDING, FIELD_EX32(*l2_iste_p, L2_ISTE, PENDING));
|
|
icsr = FIELD_DP64(icsr, ICSR, IRM, FIELD_EX32(*l2_iste_p, L2_ISTE, IRM));
|
|
icsr = FIELD_DP64(icsr, ICSR, ACTIVE, FIELD_EX32(*l2_iste_p, L2_ISTE, ACTIVE));
|
|
icsr = FIELD_DP64(icsr, ICSR, HM, FIELD_EX32(*l2_iste_p, L2_ISTE, HM));
|
|
icsr = FIELD_DP64(icsr, ICSR, PRIORITY, FIELD_EX32(*l2_iste_p, L2_ISTE, PRIORITY));
|
|
icsr = FIELD_DP64(icsr, ICSR, IAFFID, FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID));
|
|
|
|
return icsr;
|
|
}
|
|
|
|
static uint64_t spi_state_to_icsr(GICv5SPIState *spi)
|
|
{
|
|
uint64_t icsr = 0;
|
|
|
|
icsr = FIELD_DP64(icsr, ICSR, F, 0);
|
|
icsr = FIELD_DP64(icsr, ICSR, ENABLED, spi->enabled);
|
|
icsr = FIELD_DP64(icsr, ICSR, PENDING, spi->pending);
|
|
icsr = FIELD_DP64(icsr, ICSR, IRM, spi->irm);
|
|
icsr = FIELD_DP64(icsr, ICSR, ACTIVE, spi->active);
|
|
icsr = FIELD_DP64(icsr, ICSR, HM, spi->hm);
|
|
icsr = FIELD_DP64(icsr, ICSR, PRIORITY, spi->priority);
|
|
icsr = FIELD_DP64(icsr, ICSR, IAFFID, spi->iaffid);
|
|
|
|
return icsr;
|
|
}
|
|
|
|
uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain,
|
|
GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint64_t icsr;
|
|
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_request_config: tried to "
|
|
"read config of a virtual interrupt\n");
|
|
return R_ICSR_F_MASK;
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
|
|
icsr = l2_iste_to_icsr(cs, cfg, id);
|
|
trace_gicv5_request_config(domain_name[domain], inttype_name(type),
|
|
virtual, id, icsr);
|
|
return icsr;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_request_config: tried to "
|
|
"read config of unreachable SPI %d\n", id);
|
|
return R_ICSR_F_MASK;
|
|
}
|
|
|
|
icsr = spi_state_to_icsr(spi);
|
|
trace_gicv5_request_config(domain_name[domain], inttype_name(type),
|
|
virtual, id, icsr);
|
|
return icsr;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_request_config: tried to "
|
|
"read config of bad interrupt type %d\n", type);
|
|
return R_ICSR_F_MASK;
|
|
}
|
|
}
|
|
|
|
void gicv5_activate(GICv5Common *cs, uint32_t id, GICv5Domain domain,
|
|
GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint32_t iaffid;
|
|
|
|
trace_gicv5_activate(domain_name[domain], inttype_name(type), virtual, id);
|
|
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_activate: tried to "
|
|
"activate a virtual interrupt\n");
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
L2_ISTE_Handle h;
|
|
uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
|
|
if (!l2_iste_p) {
|
|
return;
|
|
}
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ACTIVE, true);
|
|
if (FIELD_EX32(*l2_iste_p, L2_ISTE, HM) == GICV5_EDGE) {
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PENDING, false);
|
|
}
|
|
iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
|
|
put_l2_iste(cs, cfg, &h);
|
|
break;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_activate: tried to "
|
|
"activate unreachable SPI %d\n", id);
|
|
return;
|
|
}
|
|
|
|
spi->active = true;
|
|
if (spi->hm == GICV5_EDGE) {
|
|
spi->pending = false;
|
|
}
|
|
iaffid = spi->iaffid;
|
|
break;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_activate: tried to "
|
|
"activate bad interrupt type %d\n", type);
|
|
return;
|
|
}
|
|
|
|
irs_recalc_hppi(s, domain, iaffid);
|
|
}
|
|
|
|
void gicv5_deactivate(GICv5Common *cs, uint32_t id, GICv5Domain domain,
|
|
GICv5IntType type, bool virtual)
|
|
{
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint32_t iaffid;
|
|
|
|
trace_gicv5_deactivate(domain_name[domain], inttype_name(type), virtual, id);
|
|
|
|
if (virtual) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_deactivate: tried to "
|
|
"deactivate a virtual interrupt\n");
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case GICV5_LPI:
|
|
{
|
|
const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
L2_ISTE_Handle h;
|
|
uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
|
|
|
|
if (!l2_iste_p) {
|
|
return;
|
|
}
|
|
*l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ACTIVE, false);
|
|
iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
|
|
put_l2_iste(cs, cfg, &h);
|
|
break;
|
|
}
|
|
case GICV5_SPI:
|
|
{
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (!spi) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_deactivate: tried to "
|
|
"deactivate unreachable SPI %d\n", id);
|
|
return;
|
|
}
|
|
|
|
spi->active = false;
|
|
iaffid = spi->iaffid;
|
|
break;
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "gicv5_deactivate: tried to "
|
|
"deactivate bad interrupt type %d\n", type);
|
|
return;
|
|
}
|
|
|
|
irs_recalc_hppi(s, domain, iaffid);
|
|
}
|
|
|
|
static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value)
|
|
{
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
uint32_t intid = FIELD_EX32(value, IRS_MAP_L2_ISTR, ID);
|
|
hwaddr l1_addr;
|
|
uint64_t l1_iste;
|
|
MemTxResult res;
|
|
|
|
if (!FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID) ||
|
|
!cfg->structure) {
|
|
/* WI if no IST set up or it is not 2-level */
|
|
return;
|
|
}
|
|
|
|
/* Find the relevant L1 ISTE and set its VALID bit */
|
|
l1_addr = l1_iste_addr(cs, cfg, intid);
|
|
|
|
l1_iste = address_space_ldq_le(&cs->dma_as, l1_addr, cfg->txattrs, &res);
|
|
if (res != MEMTX_OK) {
|
|
goto txfail;
|
|
}
|
|
|
|
l1_iste = FIELD_DP64(l1_iste, L1_ISTE, VALID, 1);
|
|
|
|
address_space_stq_le(&cs->dma_as, l1_addr, l1_iste, cfg->txattrs, &res);
|
|
if (res != MEMTX_OK) {
|
|
goto txfail;
|
|
}
|
|
/*
|
|
* It's CONSTRAINED UNPREDICTABLE to make an L2 IST valid when
|
|
* some of its entries have Pending already set, so we don't need
|
|
* to go through looking for Pending bits and pulling them into
|
|
* the cache, and we don't need to recalc our HPPI.
|
|
*/
|
|
return;
|
|
|
|
txfail:
|
|
/* Reportable with EC=0x0 if sw error reporting implemented */
|
|
qemu_log_mask(LOG_GUEST_ERROR, "L1 ISTE update failed for ID 0x%x at "
|
|
"physical address 0x" HWADDR_FMT_plx "\n", intid, l1_addr);
|
|
}
|
|
|
|
/* Data we need to pass through to irs_clean_lpi_cache_entry() */
|
|
typedef struct CleanLPICacheUserData {
|
|
GICv5Common *cs;
|
|
GICv5ISTConfig *cfg;
|
|
} CleanLPICacheUserData;
|
|
|
|
static gboolean irs_clean_lpi_cache_entry(gpointer key, gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
/* Drop this entry from the LPI cache, writing it back to guest memory. */
|
|
CleanLPICacheUserData *ud = user_data;
|
|
hwaddr l2_iste_addr;
|
|
uint64_t id = GPOINTER_TO_INT(key);
|
|
uint32_t l2_iste = *(uint32_t *)value;
|
|
|
|
if (!get_l2_iste_addr(ud->cs, ud->cfg, id, &l2_iste_addr) ||
|
|
!write_l2_iste_mem(ud->cs, ud->cfg, l2_iste_addr, l2_iste)) {
|
|
/* We drop the cached entry regardless of writeback errors */
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void irs_clean_lpi_cache(GICv5Common *cs, GICv5ISTConfig *cfg)
|
|
{
|
|
/* Write everything in the LPI cache out to guest memory */
|
|
CleanLPICacheUserData ud;
|
|
ud.cs = cs;
|
|
ud.cfg = cfg;
|
|
|
|
g_hash_table_foreach_remove(cfg->lpi_cache, irs_clean_lpi_cache_entry, &ud);
|
|
}
|
|
|
|
static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value)
|
|
{
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
|
|
if (FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID)) {
|
|
/* If VALID is set, ADDR is RO and we can only update VALID */
|
|
bool valid = FIELD_EX64(value, IRS_IST_BASER, VALID);
|
|
if (valid) {
|
|
/* Ignore 1->1 transition */
|
|
return;
|
|
}
|
|
irs_clean_lpi_cache(cs, &s->phys_lpi_config[domain]);
|
|
cs->irs_ist_baser[domain] = FIELD_DP64(cs->irs_ist_baser[domain],
|
|
IRS_IST_BASER, VALID, valid);
|
|
s->phys_lpi_config[domain].valid = false;
|
|
trace_gicv5_ist_invalid(domain_name[domain]);
|
|
irs_recalc_hppi_all_cpus(s, domain);
|
|
return;
|
|
}
|
|
cs->irs_ist_baser[domain] = value;
|
|
|
|
if (FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID)) {
|
|
/*
|
|
* If the guest just set VALID then capture data into config struct,
|
|
* sanitize the reserved values, and expand fields out into byte counts.
|
|
*/
|
|
GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
|
|
uint8_t istbits, l2bits, l2_idx_bits;
|
|
uint8_t id_bits = FIELD_EX64(cs->irs_ist_cfgr[domain],
|
|
IRS_IST_CFGR, LPI_ID_BITS);
|
|
id_bits = MIN(MAX(id_bits, QEMU_GICV5_MIN_LPI_ID_BITS), QEMU_GICV5_ID_BITS);
|
|
|
|
switch (FIELD_EX64(cs->irs_ist_cfgr[domain], IRS_IST_CFGR, ISTSZ)) {
|
|
case 0:
|
|
case 3: /* reserved: acts like the minimum required size */
|
|
istbits = 2;
|
|
break;
|
|
case 1:
|
|
istbits = 3;
|
|
break;
|
|
case 2:
|
|
istbits = 4;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
switch (FIELD_EX64(cs->irs_ist_cfgr[domain], IRS_IST_CFGR, L2SZ)) {
|
|
case 0:
|
|
case 3: /* reserved; CONSTRAINED UNPREDICTABLE */
|
|
l2bits = 12; /* 4K: 12 bits */
|
|
break;
|
|
case 1:
|
|
l2bits = 14; /* 16K: 14 bits */
|
|
break;
|
|
case 2:
|
|
l2bits = 16; /* 64K: 16 bits */
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
/*
|
|
* Calculate how many bits of an ID index the L2 table
|
|
* (e.g. if we need 14 bits to index each byte in a 16K L2 table,
|
|
* but each entry is 4 bytes wide then we need 14 - 2 = 12 bits
|
|
* to index an entry in the table).
|
|
*/
|
|
l2_idx_bits = l2bits - istbits;
|
|
cfg->base = cs->irs_ist_baser[domain] & R_IRS_IST_BASER_ADDR_MASK;
|
|
cfg->txattrs = irs_txattrs(cs, domain),
|
|
cfg->id_bits = id_bits;
|
|
cfg->istsz = 1 << istbits;
|
|
cfg->l2_idx_bits = l2_idx_bits;
|
|
cfg->structure = FIELD_EX64(cs->irs_ist_cfgr[domain],
|
|
IRS_IST_CFGR, STRUCTURE);
|
|
if (!cfg->lpi_cache) {
|
|
/*
|
|
* Keys are GINT_TO_POINTER(intid), so we want the g_direct_hash
|
|
* and g_direct_equal hash and equality functions. We don't
|
|
* want to free the keys, but we do want to free the values
|
|
* (which are pointer-to-uint32_t).
|
|
*/
|
|
cfg->lpi_cache = g_hash_table_new_full(NULL, NULL, NULL, g_free);
|
|
}
|
|
cfg->valid = true;
|
|
trace_gicv5_ist_valid(domain_name[domain], cfg->base, cfg->id_bits,
|
|
cfg->l2_idx_bits, cfg->istsz, cfg->structure);
|
|
irs_recalc_hppi_all_cpus(s, domain);
|
|
}
|
|
}
|
|
|
|
static void spi_sample(GICv5SPIState *spi)
|
|
{
|
|
/*
|
|
* Sample the state of the SPI input line; this generates
|
|
* SET_EDGE, SET_LEVEL or CLEAR events which update the SPI's
|
|
* pending state and handling mode per R_HHKMN. The logic is the
|
|
* same for "the input line changed" (R_QBXXV) and "software asked
|
|
* us to resample" (R_DMTFM).
|
|
*/
|
|
if (spi->level) {
|
|
/*
|
|
* SET_LEVEL or SET_EDGE: interrupt becomes pending, and the
|
|
* handling mode is updated to match the trigger mode.
|
|
*/
|
|
spi->pending = true;
|
|
spi->hm = spi->tm == GICV5_TRIGGER_EDGE ? GICV5_EDGE : GICV5_LEVEL;
|
|
} else if (spi->tm == GICV5_TRIGGER_LEVEL) {
|
|
/* falling edges only trigger a CLEAR event for level-triggered */
|
|
spi->pending = false;
|
|
}
|
|
}
|
|
|
|
static bool irs_pe_selr_valid(GICv5Common *cs, GICv5Domain domain)
|
|
{
|
|
/*
|
|
* Return true if IRS_PE_SELR has a valid AFFID in it. We don't
|
|
* expect the guest to do this except perhaps once at startup, so
|
|
* do a simple linear scan through the cpu_iaffids array.
|
|
*/
|
|
for (int i = 0; i < cs->num_cpu_iaffids; i++) {
|
|
if (cs->irs_pe_selr[domain] == cs->cpu_iaffids[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset,
|
|
uint64_t *data, MemTxAttrs attrs)
|
|
{
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
uint32_t v = 0;
|
|
|
|
switch (offset) {
|
|
case A_IRS_IDR0:
|
|
v = cs->irs_idr0;
|
|
/* INT_DOM reports the domain this register is for */
|
|
v = FIELD_DP32(v, IRS_IDR0, INT_DOM, domain);
|
|
if (domain != GICV5_ID_REALM) {
|
|
/* MEC field RES0 except for the Realm domain */
|
|
v &= ~R_IRS_IDR0_MEC_MASK;
|
|
}
|
|
if (domain == GICV5_ID_EL3) {
|
|
/* VIRT is RES0 for EL3 domain */
|
|
v &= ~R_IRS_IDR0_VIRT_MASK;
|
|
/* ...which means VIRT_ONE_N is also RES0 */
|
|
v &= ~R_IRS_IDR0_VIRT_ONE_N_MASK;
|
|
}
|
|
return true;
|
|
|
|
case A_IRS_IDR1:
|
|
*data = cs->irs_idr1;
|
|
return true;
|
|
|
|
case A_IRS_IDR2:
|
|
*data = cs->irs_idr2;
|
|
return true;
|
|
|
|
case A_IRS_IDR3:
|
|
/* In EL3 IDR0.VIRT is 0 so this is RES0 */
|
|
*data = domain == GICV5_ID_EL3 ? 0 : cs->irs_idr3;
|
|
return true;
|
|
|
|
case A_IRS_IDR4:
|
|
/* In EL3 IDR0.VIRT is 0 so this is RES0 */
|
|
*data = domain == GICV5_ID_EL3 ? 0 : cs->irs_idr4;
|
|
return true;
|
|
|
|
case A_IRS_IDR5:
|
|
*data = cs->irs_idr5;
|
|
return true;
|
|
|
|
case A_IRS_IDR6:
|
|
*data = cs->irs_idr6;
|
|
return true;
|
|
|
|
case A_IRS_IDR7:
|
|
*data = cs->irs_idr7;
|
|
return true;
|
|
|
|
case A_IRS_IIDR:
|
|
*data = cs->irs_iidr;
|
|
return true;
|
|
|
|
case A_IRS_AIDR:
|
|
*data = cs->irs_aidr;
|
|
return true;
|
|
|
|
case A_IRS_IST_BASER:
|
|
*data = extract64(cs->irs_ist_baser[domain], 0, 32);
|
|
return true;
|
|
|
|
case A_IRS_IST_BASER + 4:
|
|
*data = extract64(cs->irs_ist_baser[domain], 32, 32);
|
|
return true;
|
|
|
|
case A_IRS_IST_STATUSR:
|
|
/*
|
|
* For QEMU writes to IRS_IST_BASER and IRS_MAP_L2_ISTR take effect
|
|
* instantaneously, and the guest can never see the IDLE bit as 0.
|
|
*/
|
|
*data = R_IRS_IST_STATUSR_IDLE_MASK;
|
|
return true;
|
|
|
|
case A_IRS_IST_CFGR:
|
|
*data = cs->irs_ist_cfgr[domain];
|
|
return true;
|
|
|
|
case A_IRS_SPI_STATUSR:
|
|
/*
|
|
* QEMU writes to IRS_SPI_{CFGR,DOMAINR,SELR,VMR} take effect
|
|
* instantaneously, so the guest can never see the IDLE bit as 0.
|
|
*/
|
|
v = FIELD_DP32(v, IRS_SPI_STATUSR, V,
|
|
spi_for_selr(cs, domain) != NULL);
|
|
v = FIELD_DP32(v, IRS_SPI_STATUSR, IDLE, 1);
|
|
*data = v;
|
|
return true;
|
|
|
|
case A_IRS_SPI_CFGR:
|
|
{
|
|
GICv5SPIState *spi = spi_for_selr(cs, domain);
|
|
|
|
if (spi) {
|
|
v = FIELD_DP32(v, IRS_SPI_CFGR, TM, spi->tm);
|
|
}
|
|
*data = v;
|
|
return true;
|
|
}
|
|
case A_IRS_SPI_DOMAINR:
|
|
if (domain == GICV5_ID_EL3) {
|
|
/* This is RAZ/WI except for the EL3 domain */
|
|
GICv5SPIState *spi = spi_for_selr(cs, domain);
|
|
if (spi) {
|
|
v = FIELD_DP32(v, IRS_SPI_DOMAINR, DOMAIN, spi->domain);
|
|
}
|
|
}
|
|
*data = v;
|
|
return true;
|
|
case A_IRS_CR0:
|
|
/* Enabling is instantaneous for us so IDLE is always 1 */
|
|
*data = cs->irs_cr0[domain] | R_IRS_CR0_IDLE_MASK;
|
|
if (FIELD_EX32(cs->irs_cr0[domain], IRS_CR0, IRSEN)) {
|
|
irs_recalc_hppi_all_cpus(s, domain);
|
|
} else {
|
|
irs_recall_hppis(s, domain);
|
|
}
|
|
return true;
|
|
case A_IRS_CR1:
|
|
*data = cs->irs_cr1[domain];
|
|
return true;
|
|
case A_IRS_SYNC_STATUSR:
|
|
/* Sync is a no-op for QEMU: we are always IDLE */
|
|
*data = R_IRS_SYNC_STATUSR_IDLE_MASK;
|
|
return true;
|
|
case A_IRS_PE_SELR:
|
|
*data = cs->irs_pe_selr[domain];
|
|
return true;
|
|
case A_IRS_PE_CR0:
|
|
/* We don't implement 1ofN, so this is RAZ/WI for us */
|
|
*data = 0;
|
|
return true;
|
|
case A_IRS_PE_STATUSR:
|
|
/*
|
|
* Our CPUs are always online, so we're really just reporting
|
|
* whether the guest wrote a valid AFFID to IRS_PE_SELR
|
|
*/
|
|
v = R_IRS_PE_STATUSR_IDLE_MASK;
|
|
if (irs_pe_selr_valid(cs, domain)) {
|
|
v |= R_IRS_PE_STATUSR_V_MASK | R_IRS_PE_STATUSR_ONLINE_MASK;
|
|
}
|
|
*data = v;
|
|
return true;
|
|
case A_IRS_DEVARCH:
|
|
*data = IRS_DEVARCH_VALUE;
|
|
return true;
|
|
case A_IRS_IDREGS ... A_IRS_IDREGS + 0x2f:
|
|
/* CoreSight ID registers */
|
|
*data = gicv5_idreg(offset - A_IRS_IDREGS);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset,
|
|
uint64_t data, MemTxAttrs attrs)
|
|
{
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
|
|
switch (offset) {
|
|
case A_IRS_IST_BASER:
|
|
irs_ist_baser_write(s, domain,
|
|
deposit64(cs->irs_ist_baser[domain], 0, 32, data));
|
|
return true;
|
|
case A_IRS_IST_BASER + 4:
|
|
irs_ist_baser_write(s, domain,
|
|
deposit64(cs->irs_ist_baser[domain], 32, 32, data));
|
|
return true;
|
|
case A_IRS_IST_CFGR:
|
|
if (FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"guest tried to write IRS_IST_CFGR for %s config frame "
|
|
"while IST_BASER.VALID set\n", domain_name[domain]);
|
|
} else {
|
|
cs->irs_ist_cfgr[domain] = data;
|
|
}
|
|
return true;
|
|
case A_IRS_MAP_L2_ISTR:
|
|
irs_map_l2_istr_write(s, domain, data);
|
|
return true;
|
|
case A_IRS_SPI_SELR:
|
|
cs->irs_spi_selr[domain] = data;
|
|
return true;
|
|
case A_IRS_SPI_CFGR:
|
|
{
|
|
GICv5SPIState *spi = spi_for_selr(cs, domain);
|
|
if (spi) {
|
|
GICv5TriggerMode old_tm = spi->tm;
|
|
spi->tm = FIELD_EX32(data, IRS_SPI_CFGR, TM);
|
|
if (spi->tm != old_tm) {
|
|
/*
|
|
* R_KBPXL: updates to SPI trigger mode can generate CLEAR or
|
|
* SET_LEVEL events. This is not the same logic as spi_sample().
|
|
*/
|
|
if (spi->tm == GICV5_TRIGGER_LEVEL) {
|
|
if (spi->level) {
|
|
spi->pending = true;
|
|
spi->hm = GICV5_LEVEL;
|
|
} else {
|
|
spi->pending = false;
|
|
}
|
|
} else if (spi->level) {
|
|
spi->pending = false;
|
|
}
|
|
irs_recalc_hppi(s, spi->domain, spi->iaffid);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case A_IRS_SPI_DOMAINR:
|
|
if (domain == GICV5_ID_EL3) {
|
|
/* this is RAZ/WI except for the EL3 domain */
|
|
GICv5SPIState *spi = spi_for_selr(cs, domain);
|
|
if (spi) {
|
|
GICv5Domain old_domain = spi->domain;
|
|
spi->domain = FIELD_EX32(data, IRS_SPI_DOMAINR, DOMAIN);
|
|
if (spi->domain != old_domain) {
|
|
irs_recalc_hppi(s, old_domain, spi->iaffid);
|
|
irs_recalc_hppi(s, spi->domain, spi->iaffid);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
case A_IRS_SPI_RESAMPLER:
|
|
{
|
|
uint32_t id = FIELD_EX32(data, IRS_SPI_RESAMPLER, SPI_ID);
|
|
GICv5SPIState *spi = gicv5_spi_state(cs, id, domain);
|
|
|
|
if (spi) {
|
|
spi_sample(spi);
|
|
irs_recalc_hppi(s, spi->domain, spi->iaffid);
|
|
trace_gicv5_spi_state(id, spi->level, spi->pending, spi->active);
|
|
}
|
|
return true;
|
|
}
|
|
case A_IRS_CR0:
|
|
cs->irs_cr0[domain] = data & R_IRS_CR0_IRSEN_MASK;
|
|
return true;
|
|
case A_IRS_CR1:
|
|
cs->irs_cr1[domain] = data;
|
|
return true;
|
|
case A_IRS_SYNCR:
|
|
/* Sync is a no-op for QEMU: ignore write */
|
|
return true;
|
|
case A_IRS_PE_SELR:
|
|
cs->irs_pe_selr[domain] = data;
|
|
return true;
|
|
case A_IRS_PE_CR0:
|
|
/* We don't implement 1ofN, so this is RAZ/WI for us */
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool config_readll(GICv5 *s, GICv5Domain domain, hwaddr offset,
|
|
uint64_t *data, MemTxAttrs attrs)
|
|
{
|
|
GICv5Common *cs = ARM_GICV5_COMMON(s);
|
|
|
|
switch (offset) {
|
|
case A_IRS_IST_BASER:
|
|
*data = cs->irs_ist_baser[domain];
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool config_writell(GICv5 *s, GICv5Domain domain, hwaddr offset,
|
|
uint64_t data, MemTxAttrs attrs)
|
|
{
|
|
switch (offset) {
|
|
case A_IRS_IST_BASER:
|
|
irs_ist_baser_write(s, domain, data);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static MemTxResult config_read(void *opaque, GICv5Domain domain, hwaddr offset,
|
|
uint64_t *data, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
GICv5 *s = ARM_GICV5(opaque);
|
|
bool result;
|
|
|
|
switch (size) {
|
|
case 4:
|
|
result = config_readl(s, domain, offset, data, attrs);
|
|
break;
|
|
case 8:
|
|
result = config_readll(s, domain, offset, data, attrs);
|
|
break;
|
|
default:
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
if (!result) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: invalid guest read for IRS %s config frame "
|
|
"at offset " HWADDR_FMT_plx
|
|
" size %u\n", __func__, domain_name[domain],
|
|
offset, size);
|
|
trace_gicv5_badread(domain_name[domain], offset, size);
|
|
/*
|
|
* The spec requires that reserved registers are RAZ/WI; so we
|
|
* log the error but return MEMTX_OK so we don't cause a
|
|
* spurious data abort.
|
|
*/
|
|
*data = 0;
|
|
} else {
|
|
trace_gicv5_read(domain_name[domain], offset, *data, size);
|
|
}
|
|
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
static MemTxResult config_write(void *opaque, GICv5Domain domain,
|
|
hwaddr offset, uint64_t data, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
GICv5 *s = ARM_GICV5(opaque);
|
|
bool result;
|
|
|
|
switch (size) {
|
|
case 4:
|
|
result = config_writel(s, domain, offset, data, attrs);
|
|
break;
|
|
case 8:
|
|
result = config_writell(s, domain, offset, data, attrs);
|
|
break;
|
|
default:
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
if (!result) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: invalid guest write for IRS %s config frame "
|
|
"at offset " HWADDR_FMT_plx
|
|
" size %u\n", __func__, domain_name[domain],
|
|
offset, size);
|
|
trace_gicv5_badwrite(domain_name[domain], offset, data, size);
|
|
/*
|
|
* The spec requires that reserved registers are RAZ/WI; so we
|
|
* log the error but return MEMTX_OK so we don't cause a
|
|
* spurious data abort.
|
|
*/
|
|
} else {
|
|
trace_gicv5_write(domain_name[domain], offset, data, size);
|
|
}
|
|
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
#define DEFINE_READ_WRITE_WRAPPERS(NAME, DOMAIN) \
|
|
static MemTxResult config_##NAME##_read(void *opaque, hwaddr offset, \
|
|
uint64_t *data, unsigned size, \
|
|
MemTxAttrs attrs) \
|
|
{ \
|
|
return config_read(opaque, DOMAIN, offset, data, size, attrs); \
|
|
} \
|
|
static MemTxResult config_##NAME##_write(void *opaque, hwaddr offset, \
|
|
uint64_t data, unsigned size, \
|
|
MemTxAttrs attrs) \
|
|
{ \
|
|
return config_write(opaque, DOMAIN, offset, data, size, attrs); \
|
|
}
|
|
|
|
DEFINE_READ_WRITE_WRAPPERS(ns, GICV5_ID_NS)
|
|
DEFINE_READ_WRITE_WRAPPERS(realm, GICV5_ID_REALM)
|
|
DEFINE_READ_WRITE_WRAPPERS(secure, GICV5_ID_S)
|
|
DEFINE_READ_WRITE_WRAPPERS(el3, GICV5_ID_EL3)
|
|
|
|
#define FRAME_OP_ENTRY(NAME, DOMAIN) \
|
|
[DOMAIN] = { \
|
|
.read_with_attrs = config_##NAME##_read, \
|
|
.write_with_attrs = config_##NAME##_write, \
|
|
.endianness = DEVICE_LITTLE_ENDIAN, \
|
|
.valid.min_access_size = 4, \
|
|
.valid.max_access_size = 8, \
|
|
.impl.min_access_size = 4, \
|
|
.impl.max_access_size = 8, \
|
|
}
|
|
|
|
static const MemoryRegionOps config_frame_ops[NUM_GICV5_DOMAINS] = {
|
|
FRAME_OP_ENTRY(ns, GICV5_ID_NS),
|
|
FRAME_OP_ENTRY(realm, GICV5_ID_REALM),
|
|
FRAME_OP_ENTRY(secure, GICV5_ID_S),
|
|
FRAME_OP_ENTRY(el3, GICV5_ID_EL3),
|
|
};
|
|
|
|
static void gicv5_set_spi(void *opaque, int irq, int level)
|
|
{
|
|
/* These irqs are all SPIs; the INTID is irq + s->spi_base */
|
|
GICv5Common *cs = ARM_GICV5_COMMON(opaque);
|
|
GICv5 *s = ARM_GICV5(cs);
|
|
uint32_t spi_id = irq + cs->spi_base;
|
|
GICv5SPIState *spi = gicv5_raw_spi_state(cs, spi_id);
|
|
|
|
if (!spi || spi->level == level) {
|
|
return;
|
|
}
|
|
|
|
trace_gicv5_spi(spi_id, level);
|
|
|
|
spi->level = level;
|
|
spi_sample(spi);
|
|
trace_gicv5_spi_state(spi_id, spi->level, spi->pending, spi->active);
|
|
|
|
irs_recalc_hppi(s, spi->domain, spi->iaffid);
|
|
}
|
|
|
|
static void gicv5_reset_hold(Object *obj, ResetType type)
|
|
{
|
|
GICv5 *s = ARM_GICV5(obj);
|
|
GICv5Class *c = ARM_GICV5_GET_CLASS(s);
|
|
|
|
if (c->parent_phases.hold) {
|
|
c->parent_phases.hold(obj, type);
|
|
}
|
|
|
|
/* IRS_IST_BASER and IRS_IST_CFGR reset to 0, clear cached info */
|
|
for (int i = 0; i < NUM_GICV5_DOMAINS; i++) {
|
|
s->phys_lpi_config[i].valid = false;
|
|
/*
|
|
* If we got reset (power-cycled) with data in the cache, don't
|
|
* write it out to guest memory; just return to "empty cache".
|
|
*/
|
|
if (s->phys_lpi_config[i].lpi_cache) {
|
|
g_hash_table_remove_all(s->phys_lpi_config[i].lpi_cache);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gicv5_set_idregs(GICv5Common *cs)
|
|
{
|
|
/* Set the ID register value fields */
|
|
uint32_t v;
|
|
|
|
/*
|
|
* Fields in IDR0 for optional parts of the spec that we don't
|
|
* implement are 0.
|
|
*/
|
|
v = 0;
|
|
/*
|
|
* We can handle physical addresses of any size, so report support
|
|
* for 56 bits of physical address space.
|
|
*/
|
|
v = FIELD_DP32(v, IRS_IDR0, PA_RANGE, 7);
|
|
v = FIELD_DP32(v, IRS_IDR0, IRSID, cs->irsid);
|
|
cs->irs_idr0 = v;
|
|
|
|
v = 0;
|
|
v = FIELD_DP32(v, IRS_IDR1, PE_CNT, cs->num_cpus);
|
|
v = FIELD_DP32(v, IRS_IDR1, IAFFID_BITS, QEMU_GICV5_IAFFID_BITS - 1);
|
|
v = FIELD_DP32(v, IRS_IDR1, PRI_BITS, QEMU_GICV5_PRI_BITS - 1);
|
|
cs->irs_idr1 = v;
|
|
|
|
v = 0;
|
|
/* We always support physical LPIs with 2-level ISTs of all sizes */
|
|
v = FIELD_DP32(v, IRS_IDR2, ID_BITS, QEMU_GICV5_ID_BITS);
|
|
v = FIELD_DP32(v, IRS_IDR2, LPI, 1);
|
|
v = FIELD_DP32(v, IRS_IDR2, MIN_LPI_ID_BITS, QEMU_GICV5_MIN_LPI_ID_BITS);
|
|
v = FIELD_DP32(v, IRS_IDR2, IST_LEVELS, 1);
|
|
v = FIELD_DP32(v, IRS_IDR2, IST_L2SZ, 7);
|
|
/* Our impl does not need IST metadata, so ISTMD and ISTMD_SZ are 0 */
|
|
cs->irs_idr2 = v;
|
|
|
|
/* We don't implement virtualization yet, so these are zero */
|
|
cs->irs_idr3 = 0;
|
|
cs->irs_idr4 = 0;
|
|
|
|
/* These three have just one field each */
|
|
cs->irs_idr5 = FIELD_DP32(0, IRS_IDR5, SPI_RANGE, cs->spi_range);
|
|
cs->irs_idr6 = FIELD_DP32(0, IRS_IDR6, SPI_IRS_RANGE, cs->spi_irs_range);
|
|
cs->irs_idr7 = FIELD_DP32(0, IRS_IDR7, SPI_BASE, cs->spi_base);
|
|
|
|
v = 0;
|
|
v = FIELD_DP32(v, IRS_IIDR, IMPLEMENTER, QEMU_GICV5_IMPLEMENTER);
|
|
v = FIELD_DP32(v, IRS_IIDR, REVISION, QEMU_GICV5_REVISION);
|
|
v = FIELD_DP32(v, IRS_IIDR, VARIANT, QEMU_GICV5_VARIANT);
|
|
v = FIELD_DP32(v, IRS_IIDR, PRODUCTID, QEMU_GICV5_PRODUCTID);
|
|
cs->irs_iidr = v;
|
|
|
|
/* This is a GICv5.0 IRS, so all fields are zero */
|
|
cs->irs_aidr = 0;
|
|
}
|
|
|
|
static void gicv5_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
GICv5 *s = ARM_GICV5(dev);
|
|
GICv5Common *cs = ARM_GICV5_COMMON(dev);
|
|
GICv5Class *gc = ARM_GICV5_GET_CLASS(dev);
|
|
Error *migration_blocker = NULL;
|
|
|
|
ERRP_GUARD();
|
|
|
|
gc->parent_realize(dev, errp);
|
|
if (*errp) {
|
|
return;
|
|
}
|
|
|
|
error_setg(&migration_blocker,
|
|
"Live migration disabled: not yet supported by GICv5");
|
|
if (migrate_add_blocker(&migration_blocker, errp)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* When we implement support for more than one interrupt domain,
|
|
* we will provide some QOM properties so the board can configure
|
|
* which domains are implemented. For now, we only implement the
|
|
* NS domain.
|
|
*/
|
|
cs->implemented_domains = (1 << GICV5_ID_NS);
|
|
|
|
gicv5_set_idregs(cs);
|
|
gicv5_common_init_irqs_and_mmio(cs, gicv5_set_spi, config_frame_ops);
|
|
|
|
for (int i = 0; i < NUM_GICV5_DOMAINS; i++) {
|
|
if (gicv5_domain_implemented(cs, i)) {
|
|
s->hppi[i] = g_new0(GICv5PendingIrq, cs->num_cpus);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gicv5_init(Object *obj)
|
|
{
|
|
}
|
|
|
|
static void gicv5_finalize(Object *obj)
|
|
{
|
|
}
|
|
|
|
static void gicv5_class_init(ObjectClass *oc, const void *data)
|
|
{
|
|
ResettableClass *rc = RESETTABLE_CLASS(oc);
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|
|
GICv5Class *gc = ARM_GICV5_CLASS(oc);
|
|
|
|
device_class_set_parent_realize(dc, gicv5_realize, &gc->parent_realize);
|
|
resettable_class_set_parent_phases(rc, NULL, gicv5_reset_hold, NULL,
|
|
&gc->parent_phases);
|
|
}
|