The ICH_CONTEXTR_EL2 register is how the hypervisor tells the GICv5
to make a VCPU for a VM resident on a CPU, so that the IRS will
forward virtual interrupts for that VCPU to this CPU. We implement
this similarly to the GICv5 stream protocol, which uses a
SetResident command to tell the IRS to make the VCPU resident.
This will always fail until we have implemented the IRS's
end of virtualization support.

Signed-off-by: Peter Maydell <[email protected]>
---
 hw/intc/arm_gicv5.c                |   8 ++
 include/hw/intc/arm_gicv5_stream.h |  30 +++++++
 target/arm/cpu.h                   |   1 +
 target/arm/tcg/gicv5-cpuif.c       | 121 +++++++++++++++++++++++++++++
 target/arm/tcg/trace-events        |   1 +
 5 files changed, 161 insertions(+)

diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c
index f714add1406..5ac50acc786 100644
--- a/hw/intc/arm_gicv5.c
+++ b/hw/intc/arm_gicv5.c
@@ -1263,6 +1263,14 @@ txfail:
                   "physical address 0x" HWADDR_FMT_plx "\n", intid, l1_addr);
 }
 
+bool gicv5_set_resident(GICv5Common *cs, GICv5Domain domain, bool valid,
+                        uint32_t vpeid, bool db, uint8_t dbpm, uint32_t vmid,
+                        uint32_t cpuidx)
+{
+    /* Until we implement virtualization, SetResident always fails */
+    return true;
+}
+
 /* Data we need to pass through to irs_clean_lpi_cache_entry() */
 typedef struct CleanLPICacheUserData {
     GICv5Common *cs;
diff --git a/include/hw/intc/arm_gicv5_stream.h 
b/include/hw/intc/arm_gicv5_stream.h
index 21be859a233..162d55a8e6b 100644
--- a/include/hw/intc/arm_gicv5_stream.h
+++ b/include/hw/intc/arm_gicv5_stream.h
@@ -229,4 +229,34 @@ GICv5PendingIrq gicv5_get_hppi(GICv5Common *cs, 
GICv5Domain domain,
 void gicv5_deactivate(GICv5Common *cs, uint32_t id, GICv5Domain domain,
                       GICv5IntType type, bool virtual);
 
+/**
+ * gicv5_set_resident
+ * @cs: GIC IRS to send command to
+ * @domain: interrupt domain to act on
+ * @valid: true if setting resident VPE, false to clear it
+ * @vpeid: identifier of the VPE to make resident
+ * @db: true to request a doorbell
+ * @dbpm: doorbell priority mask
+ * @vmid: Virtual Machine identifier
+ * @cpuidx: IRS CPU index of this CPU interface
+ *
+ * Tell the IRS to make the specified VPE resident for this CPU. This
+ * corresponds approximately to the stream protocol SetResident command.
+ *
+ * Returns the Fault state: false if the SetResident operation
+ * succeeded, true if the VPE could not be made resident. Clearing the
+ * VPE always succeeds and returns false.
+ *
+ * For QEMU this is a synchronous operation: the function does not return
+ * until the IRS has either succeeded or failed in making the VPE resident
+ * (including identifying whether there is a pending virtual interrupt for
+ * the VPE and forwarding it by calling gicv5_forward_interrupt() if so).
+ *
+ * Unlike the stream protocol (where the IRS knows which CPU it is talking
+ * to), we have to pass the cpuidx to tell the IRS which CPU we are.
+ */
+bool gicv5_set_resident(GICv5Common *cs, GICv5Domain domain, bool valid,
+                        uint32_t vpeid, bool db, uint8_t dbpm, uint32_t vmid,
+                        uint32_t cpuidx);
+
 #endif
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index abaee5fc3a0..0d8c2176a43 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -599,6 +599,7 @@ typedef struct CPUArchState {
         /* And for the virtual interrupt domain */
         GICv5PendingIrq virt_ppi_hppi;
 
+        uint64_t ich_contextr_el2;
         uint64_t ich_apr_el2;
         uint64_t ich_vctlr_el2;
         uint64_t ich_vmcr_el2;
diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c
index a938428b20a..2eb8184e329 100644
--- a/target/arm/tcg/gicv5-cpuif.c
+++ b/target/arm/tcg/gicv5-cpuif.c
@@ -73,6 +73,14 @@ FIELD(ICH_VMCR_EL2, VMPR, 27, 5)
 FIELD(ICH_VCTLR_EL2, EN, 0, 1)
 FIELD(ICH_VCTLR_EL2, V3, 1, 1)
 
+FIELD(ICH_CONTEXTR_EL2, VM, 0, 16)
+FIELD(ICH_CONTEXTR_EL2, VPE, 32, 16)
+FIELD(ICH_CONTEXTR_EL2, DBPM, 55, 5)
+FIELD(ICH_CONTEXTR_EL2, DB, 60, 1)
+FIELD(ICH_CONTEXTR_EL2, IRICHPPIDIS, 61, 1)
+FIELD(ICH_CONTEXTR_EL2, F, 62, 1)
+FIELD(ICH_CONTEXTR_EL2, V, 63, 1)
+
 /*
  * We implement 24 bits of interrupt ID, the mandated 5 bits of priority,
  * and no legacy GICv3.3 vcpu interface (yet)
@@ -821,6 +829,110 @@ static void gic_ich_vmcr_write(CPUARMState *env, const 
ARMCPRegInfo *ri,
     gicv5_update_virq_vfiq(env);
 }
 
+static void gic_ich_contextr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                                   uint64_t value)
+{
+    GICv5Common *gic = gicv5_get_gic(env);
+    GICv5Domain domain = gicv5_logical_domain(env);
+    bool valid = FIELD_EX64(value, ICH_CONTEXTR_EL2, V);
+    uint32_t vpe = FIELD_EX64(value, ICH_CONTEXTR_EL2, VPE);
+    bool db = FIELD_EX64(value, ICH_CONTEXTR_EL2, DB);
+    uint8_t dbpm = FIELD_EX64(value, ICH_CONTEXTR_EL2, DBPM);
+    uint32_t vm = FIELD_EX64(value, ICH_CONTEXTR_EL2, VM);
+    uint32_t cpuidx = env->gicv5.cpuidx;
+    bool fault;
+    uint64_t oldval = env->gicv5_cpuif.ich_contextr_el2;
+    bool vm_vpe_changed = (oldval ^ value) & (R_ICH_CONTEXTR_EL2_VM_MASK |
+                                              R_ICH_CONTEXTR_EL2_VPE_MASK);
+    bool valid_changed = (oldval ^ value) & R_ICH_CONTEXTR_EL2_V_MASK;
+
+    /*
+     * DB and DBPM are RAZ: they are write-only fields that have
+     * an effect when V is changed from 1 to 0.
+     */
+    value &= ~(R_ICH_CONTEXTR_EL2_DB_MASK | R_ICH_CONTEXTR_EL2_DBPM_MASK);
+
+    raw_write(env, ri, value);
+
+    trace_gicv5_ich_contextr_write(cpuidx, value);
+
+    /*
+     * Changing V definitely causes us to make a new VPE resident, or
+     * remove the old one.
+     *
+     * Changing VPE or VM while V=1 is CONSTRAINED UNPREDICTABLE
+     * (R_VFKHN): we choose to make the new VPE resident so that we
+     * can trust that the information we have in the register matches
+     * what the IRS thinks the resident VPE is.
+     *
+     * For writes that don't trigger a change of resident VPE, we only
+     * need to update our copy of the register, and recalculate
+     * interrupts if IIRICHPPIDIS changed.
+     *
+     * If we do change the resident VPE, the interrupt recalculation
+     * will happen when the IRS calls gicv5_forward_interrupt().
+     */
+    if (!(valid_changed || (valid && vm_vpe_changed))) {
+        if ((oldval & value) & R_ICH_CONTEXTR_EL2_IRICHPPIDIS_MASK) {
+            gicv5_update_virq_vfiq(env);
+        }
+        return;
+    }
+
+    if (valid) {
+        /* Doorbell info is only relevant when clearing the VPE */
+        dbpm = 0;
+        db = false;
+    } else {
+        /* The IRS knows what the VPE is so we don't need to tell it */
+        vm = 0;
+        vpe = 0;
+    }
+
+    fault = gicv5_set_resident(gic, domain, valid, vpe, db, dbpm, vm, cpuidx);
+
+    if (valid) {
+        /*
+         * We only update F for a VPE set, not a clear.
+         *
+         * On a failure, it is CONSTRAINED UNPREDICTABLE whether V
+         * reads as 0 or 1 (R_NHCGW); we choose to set it to 0 so we
+         * can reliably use it to track whether a VPE is resident.  We
+         * use this for instance in GIC VDPEND: we trust the V and VM
+         * fields to be accurate.
+         */
+        value = env->gicv5_cpuif.ich_contextr_el2;
+        value = FIELD_DP64(value, ICH_CONTEXTR_EL2, F, fault);
+        if (fault) {
+            value = FIELD_DP64(value, ICH_CONTEXTR_EL2, V, 0);
+        }
+        env->gicv5_cpuif.ich_contextr_el2 = value;
+    }
+}
+
+static void gic_ich_contextr_reset(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+    /*
+     * This register resets to zero, which includes telling the IRS
+     * that there is no longer a resident VPE for it.
+     *
+     * It doesn't matter if the domain we pass here doesn't match the
+     * domain of the VM/VPE: as with the GIC stream interface (see
+     * rule R_PYWKM) our IRS ignores the mismatch for a SetResident
+     * request with valid = false.
+     *
+     * As this is a reset fn we may get here before the GIC pointer
+     * has been set up yet, so unusually we need to check for NULL>
+     */
+    raw_write(env, ri, 0);
+    GICv5Common *gic = gicv5_get_gic(env);
+
+    if (gic) {
+        gicv5_set_resident(gic, gicv5_logical_domain(env),
+                           false, 0, false, 0, 0, env->gicv5.cpuidx);
+    }
+}
+
 /*
  * ICC_APR_EL1 is banked and reads/writes as the version for the
  * current logical interrupt domain.
@@ -1500,6 +1612,15 @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = {
         .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ich_vctlr_el2),
         .writefn = gic_ich_vctlr_write,
     },
+    {   .name = "ICH_CONTEXTR_EL2", .state = ARM_CP_STATE_AA64,
+        .opc0 = 3, .opc1 = 4, .crn = 12, .crm = 11, .opc2 = 6,
+        .access = PL2_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW,
+        .nv2_redirect_offset = 0xb08,
+        .accessfn = access_gicv5_ich_reg,
+        .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ich_contextr_el2),
+        .writefn = gic_ich_contextr_write,
+        .resetfn = gic_ich_contextr_reset,
+    },
     {   .name = "ICH_VMCR_EL2", .state = ARM_CP_STATE_AA64,
         .opc0 = 3, .opc1 = 4, .crn = 12, .crm = 11, .opc2 = 7,
         .access = PL2_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW,
diff --git a/target/arm/tcg/trace-events b/target/arm/tcg/trace-events
index 71f1369fcda..75f40c44f26 100644
--- a/target/arm/tcg/trace-events
+++ b/target/arm/tcg/trace-events
@@ -11,3 +11,4 @@ gicv5_cddi(int domain, uint32_t id) "domain %d CDDI 
deactivating interrupt ID 0x
 gicv5_update_irq_fiq(bool irq, bool fiq, bool nmi) "now IRQ %d FIQ %d NMI %d"
 gicv5_update_virq_vfiq(bool virq, bool vinmi) "now VIRQ %d VINMI %d"
 gicv5_update_ppi_state(int ppi, bool level) "PPI %d source level now %d"
+gicv5_ich_contextr_write(uint32_t cpuidx, uint64_t value) "cpuidx %d 
ICH_CONTEXTR_WRITE 0x%" PRIx64
-- 
2.43.0


Reply via email to