summaryrefslogtreecommitdiffstats
path: root/hw/intc/arm_gicv3_its.c
diff options
context:
space:
mode:
authorPeter Maydell2022-01-28 17:59:56 +0100
committerPeter Maydell2022-01-28 17:59:57 +0100
commit95a6af2a006e7160c958215c20e513ed29a0a76c (patch)
treea9a59028cf7a68b2cea32629c2666dbdf0af9717 /hw/intc/arm_gicv3_its.c
parentMerge remote-tracking branch 'remotes/bonzini-gitlab/tags/for-upstream' into ... (diff)
parenttarget/arm: Use correct entrypoint for SVC taken from Hyp to Hyp (diff)
downloadqemu-95a6af2a006e7160c958215c20e513ed29a0a76c.tar.gz
qemu-95a6af2a006e7160c958215c20e513ed29a0a76c.tar.xz
qemu-95a6af2a006e7160c958215c20e513ed29a0a76c.zip
Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20220128' into staging
target-arm queue: * Update copyright dates to 2022 * hw/armv7m: Fix broken VMStateDescription * hw/char/exynos4210_uart: Fix crash on trying to load VM state * rtc: Move RTC function prototypes to their own header * xlnx-versal-virt: Support PMC SLCR * xlnx-versal-virt: Support OSPI flash memory controller * scripts: Explain the difference between linux-headers and standard-headers * target/arm: Log CPU index in 'Taking exception' log * arm_gicv3_its: Various bugfixes and cleanups * arm_gicv3_its: Implement the missing MOVI and MOVALL commands * ast2600: Fix address mapping of second SPI controller * target/arm: Use correct entrypoint for SVC taken from Hyp to Hyp # gpg: Signature made Fri 28 Jan 2022 15:29:36 GMT # gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE # gpg: issuer "peter.maydell@linaro.org" # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate] # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * remotes/pmaydell/tags/pull-target-arm-20220128: (32 commits) target/arm: Use correct entrypoint for SVC taken from Hyp to Hyp hw/arm: ast2600: Fix address mapping of second SPI controller hw/intc/arm_gicv3_its: Implement MOVI hw/intc/arm_gicv3_its: Implement MOVALL hw/intc/arm_gicv3_its: Check table bounds against correct limit hw/intc/arm_gicv3_its: Make GITS_BASER<n> RAZ/WI for unimplemented registers hw/intc/arm_gicv3_its: Provide read accessor for translation_ops hw/intc/arm_gicv3: Set GICR_CTLR.CES if LPIs are supported hw/intc/arm_gicv3_redist: Remove unnecessary zero checks hw/intc/arm_gicv3_its: Sort ITS command list into numeric order hw/intc/arm_gicv3: Honour GICD_CTLR.EnableGrp1NS for LPIs hw/intc/arm_gicv3_its: Don't clear GITS_CWRITER on writes to GITS_CBASER hw/intc/arm_gicv3_its: Don't clear GITS_CREADR when GITS_CTLR.ENABLED is set hw/intc/arm_gicv3: Initialise dma_as in GIC, not ITS hw/intc/arm_gicv3_its: Add tracepoints target/arm: Log CPU index in 'Taking exception' log scripts: Explain the difference between linux-headers and standard-headers MAINTAINERS: Remove myself (for raspi). MAINTAINERS: Add an entry for Xilinx Versal OSPI hw/arm/xlnx-versal-virt: Connect mt35xu01g flashes to the OSPI ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw/intc/arm_gicv3_its.c')
-rw-r--r--hw/intc/arm_gicv3_its.c258
1 files changed, 242 insertions, 16 deletions
diff --git a/hw/intc/arm_gicv3_its.c b/hw/intc/arm_gicv3_its.c
index b2f6a8c7f0..51d9be4ae6 100644
--- a/hw/intc/arm_gicv3_its.c
+++ b/hw/intc/arm_gicv3_its.c
@@ -13,6 +13,7 @@
#include "qemu/osdep.h"
#include "qemu/log.h"
+#include "trace.h"
#include "hw/qdev-properties.h"
#include "hw/intc/arm_gicv3_its_common.h"
#include "gicv3_internal.h"
@@ -255,10 +256,10 @@ static ItsCmdResult process_its_cmd(GICv3ITSState *s, uint64_t value,
eventid = (value & EVENTID_MASK);
- if (devid >= s->dt.num_ids) {
+ if (devid >= s->dt.num_entries) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid command attributes: devid %d>=%d",
- __func__, devid, s->dt.num_ids);
+ __func__, devid, s->dt.num_entries);
return CMD_CONTINUE;
}
@@ -299,7 +300,7 @@ static ItsCmdResult process_its_cmd(GICv3ITSState *s, uint64_t value,
return CMD_CONTINUE;
}
- if (icid >= s->ct.num_ids) {
+ if (icid >= s->ct.num_entries) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid ICID 0x%x in ITE (table corrupted?)\n",
__func__, icid);
@@ -383,10 +384,10 @@ static ItsCmdResult process_mapti(GICv3ITSState *s, uint64_t value,
icid = value & ICID_MASK;
- if (devid >= s->dt.num_ids) {
+ if (devid >= s->dt.num_entries) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid command attributes: devid %d>=%d",
- __func__, devid, s->dt.num_ids);
+ __func__, devid, s->dt.num_entries);
return CMD_CONTINUE;
}
@@ -399,7 +400,7 @@ static ItsCmdResult process_mapti(GICv3ITSState *s, uint64_t value,
num_eventids = 1ULL << (FIELD_EX64(dte, DTE, SIZE) + 1);
num_intids = 1ULL << (GICD_TYPER_IDBITS + 1);
- if ((icid >= s->ct.num_ids)
+ if ((icid >= s->ct.num_entries)
|| !dte_valid || (eventid >= num_eventids) ||
(((pIntid < GICV3_LPI_INTID_START) || (pIntid >= num_intids)) &&
(pIntid != INTID_SPURIOUS))) {
@@ -484,7 +485,7 @@ static ItsCmdResult process_mapc(GICv3ITSState *s, uint32_t offset)
valid = (value & CMD_FIELD_VALID_MASK);
- if ((icid >= s->ct.num_ids) || (rdbase >= s->gicv3->num_cpu)) {
+ if ((icid >= s->ct.num_entries) || (rdbase >= s->gicv3->num_cpu)) {
qemu_log_mask(LOG_GUEST_ERROR,
"ITS MAPC: invalid collection table attributes "
"icid %d rdbase %" PRIu64 "\n", icid, rdbase);
@@ -565,7 +566,7 @@ static ItsCmdResult process_mapd(GICv3ITSState *s, uint64_t value,
valid = (value & CMD_FIELD_VALID_MASK);
- if ((devid >= s->dt.num_ids) ||
+ if ((devid >= s->dt.num_entries) ||
(size > FIELD_EX64(s->typer, GITS_TYPER, IDBITS))) {
qemu_log_mask(LOG_GUEST_ERROR,
"ITS MAPD: invalid device table attributes "
@@ -581,6 +582,201 @@ static ItsCmdResult process_mapd(GICv3ITSState *s, uint64_t value,
return update_dte(s, devid, valid, size, itt_addr) ? CMD_CONTINUE : CMD_STALL;
}
+static ItsCmdResult process_movall(GICv3ITSState *s, uint64_t value,
+ uint32_t offset)
+{
+ AddressSpace *as = &s->gicv3->dma_as;
+ MemTxResult res = MEMTX_OK;
+ uint64_t rd1, rd2;
+
+ /* No fields in dwords 0 or 1 */
+ offset += NUM_BYTES_IN_DW;
+ offset += NUM_BYTES_IN_DW;
+ value = address_space_ldq_le(as, s->cq.base_addr + offset,
+ MEMTXATTRS_UNSPECIFIED, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+
+ rd1 = FIELD_EX64(value, MOVALL_2, RDBASE1);
+ if (rd1 >= s->gicv3->num_cpu) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: RDBASE1 %" PRId64
+ " out of range (must be less than %d)\n",
+ __func__, rd1, s->gicv3->num_cpu);
+ return CMD_CONTINUE;
+ }
+
+ offset += NUM_BYTES_IN_DW;
+ value = address_space_ldq_le(as, s->cq.base_addr + offset,
+ MEMTXATTRS_UNSPECIFIED, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+
+ rd2 = FIELD_EX64(value, MOVALL_3, RDBASE2);
+ if (rd2 >= s->gicv3->num_cpu) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: RDBASE2 %" PRId64
+ " out of range (must be less than %d)\n",
+ __func__, rd2, s->gicv3->num_cpu);
+ return CMD_CONTINUE;
+ }
+
+ if (rd1 == rd2) {
+ /* Move to same target must succeed as a no-op */
+ return CMD_CONTINUE;
+ }
+
+ /* Move all pending LPIs from redistributor 1 to redistributor 2 */
+ gicv3_redist_movall_lpis(&s->gicv3->cpu[rd1], &s->gicv3->cpu[rd2]);
+
+ return CMD_CONTINUE;
+}
+
+static ItsCmdResult process_movi(GICv3ITSState *s, uint64_t value,
+ uint32_t offset)
+{
+ AddressSpace *as = &s->gicv3->dma_as;
+ MemTxResult res = MEMTX_OK;
+ uint32_t devid, eventid, intid;
+ uint16_t old_icid, new_icid;
+ uint64_t old_cte, new_cte;
+ uint64_t old_rdbase, new_rdbase;
+ uint64_t dte;
+ bool dte_valid, ite_valid, cte_valid;
+ uint64_t num_eventids;
+ IteEntry ite = {};
+
+ devid = FIELD_EX64(value, MOVI_0, DEVICEID);
+
+ offset += NUM_BYTES_IN_DW;
+ value = address_space_ldq_le(as, s->cq.base_addr + offset,
+ MEMTXATTRS_UNSPECIFIED, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+ eventid = FIELD_EX64(value, MOVI_1, EVENTID);
+
+ offset += NUM_BYTES_IN_DW;
+ value = address_space_ldq_le(as, s->cq.base_addr + offset,
+ MEMTXATTRS_UNSPECIFIED, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+ new_icid = FIELD_EX64(value, MOVI_2, ICID);
+
+ if (devid >= s->dt.num_entries) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid command attributes: devid %d>=%d",
+ __func__, devid, s->dt.num_entries);
+ return CMD_CONTINUE;
+ }
+ dte = get_dte(s, devid, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+
+ dte_valid = FIELD_EX64(dte, DTE, VALID);
+ if (!dte_valid) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid command attributes: "
+ "invalid dte: %"PRIx64" for %d\n",
+ __func__, dte, devid);
+ return CMD_CONTINUE;
+ }
+
+ num_eventids = 1ULL << (FIELD_EX64(dte, DTE, SIZE) + 1);
+ if (eventid >= num_eventids) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid command attributes: eventid %d >= %"
+ PRId64 "\n",
+ __func__, eventid, num_eventids);
+ return CMD_CONTINUE;
+ }
+
+ ite_valid = get_ite(s, eventid, dte, &old_icid, &intid, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+
+ if (!ite_valid) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid command attributes: invalid ITE\n",
+ __func__);
+ return CMD_CONTINUE;
+ }
+
+ if (old_icid >= s->ct.num_entries) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid ICID 0x%x in ITE (table corrupted?)\n",
+ __func__, old_icid);
+ return CMD_CONTINUE;
+ }
+
+ if (new_icid >= s->ct.num_entries) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid command attributes: ICID 0x%x\n",
+ __func__, new_icid);
+ return CMD_CONTINUE;
+ }
+
+ cte_valid = get_cte(s, old_icid, &old_cte, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+ if (!cte_valid) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid command attributes: "
+ "invalid cte: %"PRIx64"\n",
+ __func__, old_cte);
+ return CMD_CONTINUE;
+ }
+
+ cte_valid = get_cte(s, new_icid, &new_cte, &res);
+ if (res != MEMTX_OK) {
+ return CMD_STALL;
+ }
+ if (!cte_valid) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid command attributes: "
+ "invalid cte: %"PRIx64"\n",
+ __func__, new_cte);
+ return CMD_CONTINUE;
+ }
+
+ old_rdbase = FIELD_EX64(old_cte, CTE, RDBASE);
+ if (old_rdbase >= s->gicv3->num_cpu) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: CTE has invalid rdbase 0x%"PRIx64"\n",
+ __func__, old_rdbase);
+ return CMD_CONTINUE;
+ }
+
+ new_rdbase = FIELD_EX64(new_cte, CTE, RDBASE);
+ if (new_rdbase >= s->gicv3->num_cpu) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: CTE has invalid rdbase 0x%"PRIx64"\n",
+ __func__, new_rdbase);
+ return CMD_CONTINUE;
+ }
+
+ if (old_rdbase != new_rdbase) {
+ /* Move the LPI from the old redistributor to the new one */
+ gicv3_redist_mov_lpi(&s->gicv3->cpu[old_rdbase],
+ &s->gicv3->cpu[new_rdbase],
+ intid);
+ }
+
+ /* Update the ICID field in the interrupt translation table entry */
+ ite.itel = FIELD_DP64(ite.itel, ITE_L, VALID, 1);
+ ite.itel = FIELD_DP64(ite.itel, ITE_L, INTTYPE, ITE_INTTYPE_PHYSICAL);
+ ite.itel = FIELD_DP64(ite.itel, ITE_L, INTID, intid);
+ ite.itel = FIELD_DP64(ite.itel, ITE_L, DOORBELL, INTID_SPURIOUS);
+ ite.iteh = FIELD_DP32(ite.iteh, ITE_H, ICID, new_icid);
+ return update_ite(s, eventid, dte, ite) ? CMD_CONTINUE : CMD_STALL;
+}
+
/*
* Current implementation blocks until all
* commands are processed
@@ -634,6 +830,8 @@ static void process_cmdq(GICv3ITSState *s)
cmd = (data & CMD_MASK);
+ trace_gicv3_its_process_command(rd_offset, cmd);
+
switch (cmd) {
case GITS_CMD_INT:
result = process_its_cmd(s, data, cq_offset, INTERRUPT);
@@ -676,6 +874,12 @@ static void process_cmdq(GICv3ITSState *s)
gicv3_redist_update_lpi(&s->gicv3->cpu[i]);
}
break;
+ case GITS_CMD_MOVI:
+ result = process_movi(s, data, cq_offset);
+ break;
+ case GITS_CMD_MOVALL:
+ result = process_movall(s, data, cq_offset);
+ break;
default:
break;
}
@@ -788,7 +992,7 @@ static void extract_table_params(GICv3ITSState *s)
L1TABLE_ENTRY_SIZE) *
(page_sz / td->entry_sz));
}
- td->num_ids = 1ULL << idbits;
+ td->num_entries = MIN(td->num_entries, 1ULL << idbits);
}
}
@@ -810,6 +1014,18 @@ static void extract_cmdq_params(GICv3ITSState *s)
}
}
+static MemTxResult gicv3_its_translation_read(void *opaque, hwaddr offset,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ /*
+ * GITS_TRANSLATER is write-only, and all other addresses
+ * in the interrupt translation space frame are RES0.
+ */
+ *data = 0;
+ return MEMTX_OK;
+}
+
static MemTxResult gicv3_its_translation_write(void *opaque, hwaddr offset,
uint64_t data, unsigned size,
MemTxAttrs attrs)
@@ -818,6 +1034,8 @@ static MemTxResult gicv3_its_translation_write(void *opaque, hwaddr offset,
bool result = true;
uint32_t devid = 0;
+ trace_gicv3_its_translation_write(offset, data, size, attrs.requester_id);
+
switch (offset) {
case GITS_TRANSLATER:
if (s->ctlr & R_GITS_CTLR_ENABLED_MASK) {
@@ -848,7 +1066,6 @@ static bool its_writel(GICv3ITSState *s, hwaddr offset,
s->ctlr |= R_GITS_CTLR_ENABLED_MASK;
extract_table_params(s);
extract_cmdq_params(s);
- s->creadr = 0;
process_cmdq(s);
} else {
s->ctlr &= ~R_GITS_CTLR_ENABLED_MASK;
@@ -862,7 +1079,6 @@ static bool its_writel(GICv3ITSState *s, hwaddr offset,
if (!(s->ctlr & R_GITS_CTLR_ENABLED_MASK)) {
s->cbaser = deposit64(s->cbaser, 0, 32, value);
s->creadr = 0;
- s->cwriter = s->creadr;
}
break;
case GITS_CBASER + 4:
@@ -873,7 +1089,6 @@ static bool its_writel(GICv3ITSState *s, hwaddr offset,
if (!(s->ctlr & R_GITS_CTLR_ENABLED_MASK)) {
s->cbaser = deposit64(s->cbaser, 32, 32, value);
s->creadr = 0;
- s->cwriter = s->creadr;
}
break;
case GITS_CWRITER:
@@ -915,6 +1130,10 @@ static bool its_writel(GICv3ITSState *s, hwaddr offset,
if (!(s->ctlr & R_GITS_CTLR_ENABLED_MASK)) {
index = (offset - GITS_BASER) / 8;
+ if (s->baser[index] == 0) {
+ /* Unimplemented GITS_BASERn: RAZ/WI */
+ break;
+ }
if (offset & 7) {
value <<= 32;
value &= ~GITS_BASER_RO_MASK;
@@ -1011,6 +1230,10 @@ static bool its_writell(GICv3ITSState *s, hwaddr offset,
*/
if (!(s->ctlr & R_GITS_CTLR_ENABLED_MASK)) {
index = (offset - GITS_BASER) / 8;
+ if (s->baser[index] == 0) {
+ /* Unimplemented GITS_BASERn: RAZ/WI */
+ break;
+ }
s->baser[index] &= GITS_BASER_RO_MASK;
s->baser[index] |= (value & ~GITS_BASER_RO_MASK);
}
@@ -1023,7 +1246,6 @@ static bool its_writell(GICv3ITSState *s, hwaddr offset,
if (!(s->ctlr & R_GITS_CTLR_ENABLED_MASK)) {
s->cbaser = value;
s->creadr = 0;
- s->cwriter = s->creadr;
}
break;
case GITS_CWRITER:
@@ -1107,6 +1329,7 @@ static MemTxResult gicv3_its_read(void *opaque, hwaddr offset, uint64_t *data,
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid guest read at offset " TARGET_FMT_plx
"size %u\n", __func__, offset, size);
+ trace_gicv3_its_badread(offset, size);
/*
* The spec requires that reserved registers are RAZ/WI;
* so use false returns from leaf functions as a way to
@@ -1114,6 +1337,8 @@ static MemTxResult gicv3_its_read(void *opaque, hwaddr offset, uint64_t *data,
* the caller, or we'll cause a spurious guest data abort.
*/
*data = 0;
+ } else {
+ trace_gicv3_its_read(offset, *data, size);
}
return MEMTX_OK;
}
@@ -1140,12 +1365,15 @@ static MemTxResult gicv3_its_write(void *opaque, hwaddr offset, uint64_t data,
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid guest write at offset " TARGET_FMT_plx
"size %u\n", __func__, offset, size);
+ trace_gicv3_its_badwrite(offset, data, size);
/*
* The spec requires that reserved registers are RAZ/WI;
* so use false returns from leaf functions as a way to
* trigger the guest-error logging but don't return it to
* the caller, or we'll cause a spurious guest data abort.
*/
+ } else {
+ trace_gicv3_its_write(offset, data, size);
}
return MEMTX_OK;
}
@@ -1161,6 +1389,7 @@ static const MemoryRegionOps gicv3_its_control_ops = {
};
static const MemoryRegionOps gicv3_its_translation_ops = {
+ .read_with_attrs = gicv3_its_translation_read,
.write_with_attrs = gicv3_its_translation_write,
.valid.min_access_size = 2,
.valid.max_access_size = 4,
@@ -1183,9 +1412,6 @@ static void gicv3_arm_its_realize(DeviceState *dev, Error **errp)
gicv3_its_init_mmio(s, &gicv3_its_control_ops, &gicv3_its_translation_ops);
- address_space_init(&s->gicv3->dma_as, s->gicv3->dma,
- "gicv3-its-sysmem");
-
/* set the ITS default features supported */
s->typer = FIELD_DP64(s->typer, GITS_TYPER, PHYSICAL, 1);
s->typer = FIELD_DP64(s->typer, GITS_TYPER, ITT_ENTRY_SIZE,