The IRS_SAVE_VMR register allows software to force the IRS to write
out any cached virtual interrupt state and configuration to the guest
memory data structures.  (A hypervisor will use this as part of
saving the configuration of a VM for migration.)

When software writes the S bit to 1, we need to write out cached
information in our hash tables for the vLPI and vSPI for the VM.  As
usual for QEMU, this operation is instantaneous from the guest's
point of view.

IRS_SAVE_VMR also has functionality for software to check that the
state of the VM didn't change during the save procedure.  Because we
will have only one IRS, we take advantage of I_DSRQY's suggestion
that we make writing {0,1} to {S,Q} be a no-op.  This means handling
of the reporting of the Quiesced state can be done entirely in the
implementation of IRS_SAVE_VM_STATUSR, which will be done in the
following commit.

Signed-off-by: Peter Maydell <[email protected]>
---
 hw/intc/arm_gicv5.c                | 104 +++++++++++++++++++++++++++++
 hw/intc/arm_gicv5_common.c         |   1 +
 hw/intc/trace-events               |   1 +
 include/hw/intc/arm_gicv5_common.h |   1 +
 4 files changed, 107 insertions(+)

diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c
index 4572e5a4017..e1796ec17a6 100644
--- a/hw/intc/arm_gicv5.c
+++ b/hw/intc/arm_gicv5.c
@@ -2450,6 +2450,75 @@ static void irs_vpe_dbr_write(GICv5 *s, GICv5Domain 
domain, uint64_t value)
     }
 }
 
+/* Data we need to pass to sync_l2_iste() */
+typedef struct SyncL2ISTEUserData {
+    GICv5Common *cs;
+    GICv5ISTConfig *cfg;
+} SyncL2ISTEUserData;
+
+static void sync_l2_iste(gpointer key, gpointer value, gpointer user_data)
+{
+    uint64_t id = GPOINTER_TO_INT(key);
+    uint64_t l2_iste = *(uint32_t *)value;
+    SyncL2ISTEUserData *ud = user_data;
+    hwaddr l2_iste_addr;
+
+    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);
+    }
+}
+
+static void irs_sync_ist_to_memory(GICv5Common *cs, GICv5ISTConfig *cfg)
+{
+    /*
+     * Push any information in the IST hash table out to guest memory
+     * (but don't drop the hash table entries).  Writeback errors are
+     * ignored. Compare put_l2_iste()'s code for writing out data whon
+     * it removes L2_ISTE entries from the hash table.
+     */
+    SyncL2ISTEUserData ud;
+
+    if (!cfg->valid) {
+        return;
+    }
+
+    ud.cs = cs;
+    ud.cfg = cfg;
+
+    g_hash_table_foreach(cfg->lpi_cache, sync_l2_iste, &ud);
+}
+
+static void irs_save_vmr_write(GICv5 *s, GICv5Domain domain, uint64_t value)
+{
+    GICv5Common *cs = ARM_GICV5_COMMON(s);
+    GICv5VMTConfig *cfg = &s->vmt_config[domain];
+    uint32_t vmid;
+    VMTE *vmte;
+
+    cs->irs_save_vmr[domain] = value;
+
+    if (!FIELD_EX64(cs->irs_save_vmr[domain], IRS_SAVE_VMR, S)) {
+        return;
+    }
+
+    /* Write out vLPI and vSPI config to guest memory */
+    vmid = FIELD_EX64(cs->irs_save_vmr[domain], IRS_SAVE_VMR, VM_ID);
+
+    if (!cfg->valid || vmid >= (1 << cfg->vm_id_bits)) {
+        return;
+    }
+
+    vmte = g_hash_table_lookup(cfg->vm_cache, GINT_TO_POINTER(vmid));
+    if (!vmte) {
+        return;
+    }
+
+    trace_gicv5_irs_save_vmr_write(domain_name[domain], vmid);
+
+    irs_sync_ist_to_memory(cs, &vmte->lpi_ist);
+    irs_sync_ist_to_memory(cs, &vmte->spi_ist);
+}
+
 static void spi_sample(GICv5SPIState *spi)
 {
     /*
@@ -2717,6 +2786,22 @@ static bool config_readl(GICv5 *s, GICv5Domain domain, 
hwaddr offset,
             *data = 0;
         }
         return true;
+    case A_IRS_SAVE_VMR:
+        if (gicv5_domain_has_virt(cs, domain)) {
+            *data = extract64(cs->irs_save_vmr[domain], 0, 32);
+        } else {
+            /* RAZ if no virt */
+            *data = 0;
+        }
+        return true;
+    case A_IRS_SAVE_VMR + 4:
+        if (gicv5_domain_has_virt(cs, domain)) {
+            *data = extract64(cs->irs_save_vmr[domain], 32, 32);
+        } else {
+            /* RAZ if no virt */
+            *data = 0;
+        }
+        return true;
     case A_IRS_VPE_CR0:
         /* RES0 when VIRT_ONE_N is not implemented */
         *data = 0;
@@ -2913,6 +2998,14 @@ static bool config_writel(GICv5 *s, GICv5Domain domain, 
hwaddr offset,
         irs_vpe_dbr_write(s, domain,
                           deposit64(cs->irs_vpe_dbr[domain], 32, 32, data));
         return true;
+    case A_IRS_SAVE_VMR:
+        irs_save_vmr_write(s, domain,
+                           deposit64(cs->irs_save_vmr[domain], 0, 32, data));
+        return true;
+    case A_IRS_SAVE_VMR + 4:
+        irs_save_vmr_write(s, domain,
+                           deposit64(cs->irs_save_vmr[domain], 32, 32, data));
+        return true;
     case A_IRS_VPE_CR0:
         /* RES0 when VIRT_ONE_N is not implemented */
         return true;
@@ -3072,6 +3165,14 @@ static bool config_readll(GICv5 *s, GICv5Domain domain, 
hwaddr offset,
             *data = 0;
         }
         return true;
+    case A_IRS_SAVE_VMR:
+        if (gicv5_domain_has_virt(cs, domain)) {
+            *data = cs->irs_save_vmr[domain];
+        } else {
+            /* RAZ/WI if no virt */
+            *data = 0;
+        }
+        return true;
     }
 
     return false;
@@ -3108,6 +3209,9 @@ static bool config_writell(GICv5 *s, GICv5Domain domain, 
hwaddr offset,
     case A_IRS_VPE_DBR:
         irs_vpe_dbr_write(s, domain, data);
         return true;
+    case A_IRS_SAVE_VMR:
+        irs_save_vmr_write(s, domain, data);
+        return true;
     }
 
     return false;
diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c
index 50365c4dfe6..667f4501ef1 100644
--- a/hw/intc/arm_gicv5_common.c
+++ b/hw/intc/arm_gicv5_common.c
@@ -116,6 +116,7 @@ static void gicv5_common_reset_hold(Object *obj, ResetType 
type)
         memset(cs->irs_vmap_vper, 0, sizeof(cs->irs_vmap_vper));
         memset(cs->irs_vpe_selr, 0, sizeof(cs->irs_vpe_selr));
         memset(cs->irs_vpe_dbr, 0, sizeof(cs->irs_vpe_dbr));
+        memset(cs->irs_save_vmr, 0, sizeof(cs->irs_vpe_dbr));
 
         for (int i = 0; i < NUM_GICV5_DOMAINS; i++) {
             cs->irs_vpe_statusr[i] = R_IRS_VPE_STATUSR_IDLE_MASK;
diff --git a/hw/intc/trace-events b/hw/intc/trace-events
index ddb7ab06207..9902cb97b8f 100644
--- a/hw/intc/trace-events
+++ b/hw/intc/trace-events
@@ -261,6 +261,7 @@ gicv5_irs_vpe_dbr_write_nv(const char *domain, uint32_t 
vmid, uint32_t vpeid) "G
 gicv5_set_resident(const char *domain, uint32_t vmid, uint32_t vpeid, uint32_t 
cpuidx) "GICv5 IRS %s VM ID %u VPE %u set_resident phys CPU idx %u: now 
resident"
 gicv5_set_non_resident(const char *domain, uint32_t cpuidx, bool db, uint8_t 
dbpm) "GICv5 IRS %s set_resident phys CPU idx %u: now no resident VPE; db_req 
%d dbpm %u"
 gicv5_irs_ring_doorbell(const char *domain, uint32_t vmid, uint32_t db_intid) 
"GICv5 IRS %s VM ID %u: ringing doorbell LPI %u"
+gicv5_irs_save_vmr_write(const char *domain, uint32_t vmid) "GICv5 IRS %s VM 
ID %u saving IST data to memory"
 
 # arm_gicv5_common.c
 gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, 
uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u 
CPUs, SPI base %u, SPI IRS range %u, SPI range %u"
diff --git a/include/hw/intc/arm_gicv5_common.h 
b/include/hw/intc/arm_gicv5_common.h
index d82cc23d10c..7bd6f557058 100644
--- a/include/hw/intc/arm_gicv5_common.h
+++ b/include/hw/intc/arm_gicv5_common.h
@@ -99,6 +99,7 @@ struct GICv5Common {
     uint64_t irs_vpe_selr[NUM_GICV5_DOMAINS];
     uint32_t irs_vpe_statusr[NUM_GICV5_DOMAINS];
     uint64_t irs_vpe_dbr[NUM_GICV5_DOMAINS];
+    uint64_t irs_save_vmr[NUM_GICV5_DOMAINS];
 
     /*
      * Pointer to an array of state information for the SPIs.  Array
-- 
2.43.0


Reply via email to