Skip to content

Commit

Permalink
hw/intc/arm_gicv3_its: Handle virtual interrupts in process_its_cmd()
Browse files Browse the repository at this point in the history
For GICv4, interrupt table entries read by process_its_cmd() may
indicate virtual LPIs which are to be directly injected into a VM.
Implement the ITS side of the code for handling this.  This is
similar to the existing handling of physical LPIs, but instead of
looking up a collection ID in a collection table, we look up a vPEID
in a vPE table.  As with the physical LPIs, we leave the rest of the
work to code in the redistributor device.

The redistributor half will be implemented in a later commit;
for now we just provide a stub function which does nothing.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20220408141550.1271295-15-peter.maydell@linaro.org
  • Loading branch information
pm215 committed Apr 22, 2022
1 parent 2d692e2 commit 469cf23
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 2 deletions.
99 changes: 97 additions & 2 deletions hw/intc/arm_gicv3_its.c
Expand Up @@ -314,6 +314,42 @@ static MemTxResult get_dte(GICv3ITSState *s, uint32_t devid, DTEntry *dte)
return res;
}

/*
* Read the vPE Table entry at index @vpeid. On success (including
* successfully determining that there is no valid entry for this index),
* we return MEMTX_OK and populate the VTEntry struct accordingly.
* If there is an error reading memory then we return the error code.
*/
static MemTxResult get_vte(GICv3ITSState *s, uint32_t vpeid, VTEntry *vte)
{
MemTxResult res = MEMTX_OK;
AddressSpace *as = &s->gicv3->dma_as;
uint64_t entry_addr = table_entry_addr(s, &s->vpet, vpeid, &res);
uint64_t vteval;

if (entry_addr == -1) {
/* No L2 table entry, i.e. no valid VTE, or a memory error */
vte->valid = false;
goto out;
}
vteval = address_space_ldq_le(as, entry_addr, MEMTXATTRS_UNSPECIFIED, &res);
if (res != MEMTX_OK) {
goto out;
}
vte->valid = FIELD_EX64(vteval, VTE, VALID);
vte->vptsize = FIELD_EX64(vteval, VTE, VPTSIZE);
vte->vptaddr = FIELD_EX64(vteval, VTE, VPTADDR);
vte->rdbase = FIELD_EX64(vteval, VTE, RDBASE);
out:
if (res != MEMTX_OK) {
trace_gicv3_its_vte_read_fault(vpeid);
} else {
trace_gicv3_its_vte_read(vpeid, vte->valid, vte->vptsize,
vte->vptaddr, vte->rdbase);
}
return res;
}

/*
* Given a (DeviceID, EventID), look up the corresponding ITE, including
* checking for the various invalid-value cases. If we find a valid ITE,
Expand Down Expand Up @@ -397,6 +433,38 @@ static ItsCmdResult lookup_cte(GICv3ITSState *s, const char *who,
return CMD_CONTINUE_OK;
}

/*
* Given a VPEID, look up the corresponding VTE, including checking
* for various invalid-value cases. if we find a valid VTE, fill in @vte
* and return CMD_CONTINUE_OK; otherwise return CMD_STALL or CMD_CONTINUE
* (and the contents of @vte should not be relied on).
*
* The string @who is purely for the LOG_GUEST_ERROR messages,
* and should indicate the name of the calling function or similar.
*/
static ItsCmdResult lookup_vte(GICv3ITSState *s, const char *who,
uint32_t vpeid, VTEntry *vte)
{
if (vpeid >= s->vpet.num_entries) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid VPEID 0x%x\n", who, vpeid);
return CMD_CONTINUE;
}

if (get_vte(s, vpeid, vte) != MEMTX_OK) {
return CMD_STALL;
}
if (!vte->valid) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid VTE for VPEID 0x%x\n", who, vpeid);
return CMD_CONTINUE;
}

if (vte->rdbase >= s->gicv3->num_cpu) {
return CMD_CONTINUE;
}
return CMD_CONTINUE_OK;
}

static ItsCmdResult process_its_cmd_phys(GICv3ITSState *s, const ITEntry *ite,
int irqlevel)
{
Expand All @@ -411,6 +479,33 @@ static ItsCmdResult process_its_cmd_phys(GICv3ITSState *s, const ITEntry *ite,
return CMD_CONTINUE_OK;
}

static ItsCmdResult process_its_cmd_virt(GICv3ITSState *s, const ITEntry *ite,
int irqlevel)
{
VTEntry vte;
ItsCmdResult cmdres;

cmdres = lookup_vte(s, __func__, ite->vpeid, &vte);
if (cmdres != CMD_CONTINUE_OK) {
return cmdres;
}

if (!intid_in_lpi_range(ite->intid) ||
ite->intid >= (1ULL << (vte.vptsize + 1))) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: intid 0x%x out of range\n",
__func__, ite->intid);
return CMD_CONTINUE;
}

/*
* For QEMU the actual pending of the vLPI is handled in the
* redistributor code
*/
gicv3_redist_process_vlpi(&s->gicv3->cpu[vte.rdbase], ite->intid,
vte.vptaddr << 16, ite->doorbell, irqlevel);
return CMD_CONTINUE_OK;
}

/*
* This function handles the processing of following commands based on
* the ItsCmdType parameter passed:-
Expand Down Expand Up @@ -446,8 +541,8 @@ static ItsCmdResult do_process_its_cmd(GICv3ITSState *s, uint32_t devid,
__func__, ite.inttype);
return CMD_CONTINUE;
}
/* The GICv4 virtual interrupt handling will go here */
g_assert_not_reached();
cmdres = process_its_cmd_virt(s, &ite, irqlevel);
break;
default:
g_assert_not_reached();
}
Expand Down
9 changes: 9 additions & 0 deletions hw/intc/arm_gicv3_redist.c
Expand Up @@ -788,6 +788,15 @@ void gicv3_redist_movall_lpis(GICv3CPUState *src, GICv3CPUState *dest)
gicv3_redist_update_lpi(dest);
}

void gicv3_redist_process_vlpi(GICv3CPUState *cs, int irq, uint64_t vptaddr,
int doorbell, int level)
{
/*
* The redistributor handling for being handed a VLPI by the ITS
* will be added in a subsequent commit.
*/
}

void gicv3_redist_set_irq(GICv3CPUState *cs, int irq, int level)
{
/* Update redistributor state for a change in an external PPI input line */
Expand Down
17 changes: 17 additions & 0 deletions hw/intc/gicv3_internal.h
Expand Up @@ -527,6 +527,23 @@ MemTxResult gicv3_redist_write(void *opaque, hwaddr offset, uint64_t data,
void gicv3_dist_set_irq(GICv3State *s, int irq, int level);
void gicv3_redist_set_irq(GICv3CPUState *cs, int irq, int level);
void gicv3_redist_process_lpi(GICv3CPUState *cs, int irq, int level);
/**
* gicv3_redist_process_vlpi:
* @cs: GICv3CPUState
* @irq: (virtual) interrupt number
* @vptaddr: (guest) address of VLPI table
* @doorbell: doorbell (physical) interrupt number (1023 for "no doorbell")
* @level: level to set @irq to
*
* Process a virtual LPI being directly injected by the ITS. This function
* will update the VLPI table specified by @vptaddr and @vptsize. If the
* vCPU corresponding to that VLPI table is currently running on
* the CPU associated with this redistributor, directly inject the VLPI
* @irq. If the vCPU is not running on this CPU, raise the doorbell
* interrupt instead.
*/
void gicv3_redist_process_vlpi(GICv3CPUState *cs, int irq, uint64_t vptaddr,
int doorbell, int level);
void gicv3_redist_lpi_pending(GICv3CPUState *cs, int irq, int level);
/**
* gicv3_redist_update_lpi:
Expand Down
2 changes: 2 additions & 0 deletions hw/intc/trace-events
Expand Up @@ -200,6 +200,8 @@ gicv3_its_ite_write(uint64_t ittaddr, uint32_t eventid, int valid, int inttype,
gicv3_its_dte_read(uint32_t devid, int valid, uint32_t size, uint64_t ittaddr) "GICv3 ITS: Device Table read for DeviceID 0x%x: valid %d size 0x%x ITTaddr 0x%" PRIx64
gicv3_its_dte_write(uint32_t devid, int valid, uint32_t size, uint64_t ittaddr) "GICv3 ITS: Device Table write for DeviceID 0x%x: valid %d size 0x%x ITTaddr 0x%" PRIx64
gicv3_its_dte_read_fault(uint32_t devid) "GICv3 ITS: Device Table read for DeviceID 0x%x: faulted"
gicv3_its_vte_read(uint32_t vpeid, int valid, uint32_t vptsize, uint64_t vptaddr, uint32_t rdbase) "GICv3 ITS: vPE Table read for vPEID 0x%x: valid %d VPTsize 0x%x VPTaddr 0x%" PRIx64 " RDbase 0x%x"
gicv3_its_vte_read_fault(uint32_t vpeid) "GICv3 ITS: vPE Table read for vPEID 0x%x: faulted"
gicv3_its_vte_write(uint32_t vpeid, int valid, uint32_t vptsize, uint64_t vptaddr, uint32_t rdbase) "GICv3 ITS: vPE Table write for vPEID 0x%x: valid %d VPTsize 0x%x VPTaddr 0x%" PRIx64 " RDbase 0x%x"

# armv7m_nvic.c
Expand Down

0 comments on commit 469cf23

Please sign in to comment.