@@ -166,3 +166,39 @@ void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
hpm_incr_ctr(s, ctr_idx);
}
}
+
+/* Timer callback for cycle counter overflow. */
+void riscv_iommu_hpm_timer_cb(void *priv)
+{
+ RISCVIOMMUState *s = priv;
+ const uint32_t inhibit = riscv_iommu_reg_get32(
+ s, RISCV_IOMMU_REG_IOCOUNTINH);
+ uint32_t ovf;
+
+ if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) {
+ return;
+ }
+
+ if (s->irq_overflow_left > 0) {
+ uint64_t irq_trigger_at =
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->irq_overflow_left;
+ timer_mod_anticipate_ns(s->hpm_timer, irq_trigger_at);
+ s->irq_overflow_left = 0;
+ return;
+ }
+
+ ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF);
+ if (!get_field(ovf, RISCV_IOMMU_IOCOUNTOVF_CY)) {
+ /*
+ * We don't need to set hpmcycle_val to zero and update hpmcycle_prev to
+ * current clock value. The way we calculate iohpmcycs will overflow
+ * and return the correct value. This avoids the need to synchronize
+ * timer callback and write callback.
+ */
+ riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IOCOUNTOVF,
+ RISCV_IOMMU_IOCOUNTOVF_CY, 0);
+ riscv_iommu_reg_mod64(s, RISCV_IOMMU_REG_IOHPMCYCLES,
+ RISCV_IOMMU_IOHPMCYCLES_OVF, 0);
+ riscv_iommu_notify(s, RISCV_IOMMU_INTR_PM);
+ }
+}
@@ -25,5 +25,6 @@
uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s);
void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
unsigned event_id);
+void riscv_iommu_hpm_timer_cb(void *priv);
#endif
@@ -2281,6 +2281,8 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp)
address_space_init(&s->trap_as, &s->trap_mr, "riscv-iommu-trap-as");
if (s->cap & RISCV_IOMMU_CAP_HPM) {
+ s->hpm_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, riscv_iommu_hpm_timer_cb, s);
s->hpm_event_ctr_map = g_hash_table_new(g_direct_hash, g_direct_equal);
}
}
@@ -2294,6 +2296,7 @@ static void riscv_iommu_unrealize(DeviceState *dev)
if (s->cap & RISCV_IOMMU_CAP_HPM) {
g_hash_table_unref(s->hpm_event_ctr_map);
+ timer_free(s->hpm_timer);
}
}
@@ -88,8 +88,10 @@ struct RISCVIOMMUState {
QLIST_HEAD(, RISCVIOMMUSpace) spaces;
/* HPM cycle counter */
+ QEMUTimer *hpm_timer;
uint64_t hpmcycle_val; /* Current value of cycle register */
uint64_t hpmcycle_prev; /* Saved value of QEMU_CLOCK_VIRTUAL clock */
+ uint64_t irq_overflow_left; /* Value beyond INT64_MAX after overflow */
/* HPM event counters */
GHashTable *hpm_event_ctr_map; /* Mapping of events to counters */