@@ -48,7 +48,8 @@
#define DMAR_IQT_REG 0x88 /* invalidation queue tail */
#define DMAR_IQA_REG 0x90 /* invalidation queue addr */
#define DMAR_IECTL_REG 0xa0 /* invalidation event control register */
-#define DMAR_IRTA_REG 0xb8 /* intr remap */
+#define DMAR_IRTA_REG 0xb8 /* base address of intr remap table */
+#define DMAR_IRTUA_REG 0xbc /* upper address of intr remap table */
#define OFFSET_STRIDE (9)
#define dmar_readl(dmar, reg) readl((dmar) + (reg))
@@ -150,6 +151,9 @@
#define DMA_GCMD_SIRTP (((u64)1) << 24)
#define DMA_GCMD_CFI (((u64)1) << 23)
+/* mask of one-shot bits */
+#define DMA_GCMD_ONE_SHOT_MASK 0x96ffffff
+
/* GSTS_REG */
#define DMA_GSTS_TES (((u64)1) << 31)
#define DMA_GSTS_RTPS (((u64)1) << 30)
@@ -157,10 +161,18 @@
#define DMA_GSTS_AFLS (((u64)1) << 28)
#define DMA_GSTS_WBFS (((u64)1) << 27)
#define DMA_GSTS_QIES (((u64)1) <<26)
+#define DMA_GSTS_SIRTPS_SHIFT 24
+#define DMA_GSTS_SIRTPS (((u64)1) << DMA_GSTS_SIRTPS_SHIFT)
#define DMA_GSTS_IRES (((u64)1) <<25)
-#define DMA_GSTS_SIRTPS (((u64)1) << 24)
#define DMA_GSTS_CFIS (((u64)1) <<23)
+/* IRTA_REG */
+/* The base of 4KB aligned interrupt remapping table */
+#define DMA_IRTA_ADDR(val) ((val) & ~0xfffULL)
+/* The size of remapping table is 2^(x+1), where x is the size field in IRTA */
+#define DMA_IRTA_S(val) (val & 0xf)
+#define DMA_IRTA_SIZE(val) (1UL << (DMA_IRTA_S(val) + 1))
+
/* PMEN_REG */
#define DMA_PMEN_EPM (((u32)1) << 31)
#define DMA_PMEN_PRS (((u32)1) << 0)
@@ -36,6 +36,12 @@
#define VVTD_MAX_OFFSET VVTD_FRCD_END
struct hvm_hw_vvtd {
+ bool eim_enabled;
+
+ /* Interrupt remapping table base gfn and the max of entries */
+ uint16_t irt_max_entry;
+ gfn_t irt;
+
uint32_t regs[VVTD_MAX_OFFSET/sizeof(uint32_t)];
};
@@ -73,6 +79,16 @@ boolean_runtime_param("viommu_verbose", viommu_verbose);
#define VVTD_REG_POS(vvtd, offset) &(vvtd->hw.regs[offset/sizeof(uint32_t)])
+static inline void vvtd_set_bit(struct vvtd *vvtd, uint32_t reg, int nr)
+{
+ __set_bit(nr, VVTD_REG_POS(vvtd, reg));
+}
+
+static inline void vvtd_clear_bit(struct vvtd *vvtd, uint32_t reg, int nr)
+{
+ __clear_bit(nr, VVTD_REG_POS(vvtd, reg));
+}
+
static inline void vvtd_set_reg(struct vvtd *vvtd, uint32_t reg, uint32_t value)
{
*VVTD_REG_POS(vvtd, reg) = value;
@@ -102,6 +118,52 @@ static void *domain_vvtd(const struct domain *d)
return NULL;
}
+static void write_gcmd_sirtp(struct vvtd *vvtd, uint32_t val)
+{
+ uint64_t irta = vvtd_get_reg_quad(vvtd, DMAR_IRTA_REG);
+
+ if ( !(val & DMA_GCMD_SIRTP) )
+ return;
+
+ /*
+ * Hardware clears this bit when software sets the SIRTPS field in
+ * the Global Command register and sets it when hardware completes
+ * the 'Set Interrupt Remap Table Pointer' operation.
+ */
+ vvtd_clear_bit(vvtd, DMAR_GSTS_REG, DMA_GSTS_SIRTPS_SHIFT);
+
+ if ( gfn_x(vvtd->hw.irt) != PFN_DOWN(DMA_IRTA_ADDR(irta)) ||
+ vvtd->hw.irt_max_entry != DMA_IRTA_SIZE(irta) )
+ {
+ vvtd->hw.irt = _gfn(PFN_DOWN(DMA_IRTA_ADDR(irta)));
+ vvtd->hw.irt_max_entry = DMA_IRTA_SIZE(irta);
+ vvtd->hw.eim_enabled = !!(irta & IRTA_EIME);
+ vvtd_info("Update IR info (addr=%lx eim=%d size=%d)\n",
+ gfn_x(vvtd->hw.irt), vvtd->hw.eim_enabled,
+ vvtd->hw.irt_max_entry);
+ }
+ vvtd_set_bit(vvtd, DMAR_GSTS_REG, DMA_GSTS_SIRTPS_SHIFT);
+}
+
+static void vvtd_write_gcmd(struct vvtd *vvtd, uint32_t val)
+{
+ uint32_t orig = vvtd_get_reg(vvtd, DMAR_GSTS_REG);
+ uint32_t changed;
+
+ orig = orig & DMA_GCMD_ONE_SHOT_MASK; /* reset the one-shot bits */
+ changed = orig ^ val;
+
+ if ( !changed )
+ return;
+
+ if ( changed & (changed - 1) )
+ vvtd_info("Write %x to GCMD (current %x), updating multiple fields",
+ val, orig);
+
+ if ( changed & DMA_GCMD_SIRTP )
+ write_gcmd_sirtp(vvtd, val);
+}
+
static int vvtd_in_range(struct vcpu *v, unsigned long addr)
{
struct vvtd *vvtd = domain_vvtd(v->domain);
@@ -139,6 +201,30 @@ static int vvtd_write(struct vcpu *v, unsigned long addr,
vvtd_info("Write offset %x len %d val %lx\n", offset, len, val);
+ if ( (len != 4 && len != 8) || (offset & (len - 1)) )
+ return X86EMUL_OKAY;
+
+ switch ( offset )
+ {
+ case DMAR_GCMD_REG:
+ vvtd_write_gcmd(vvtd, val);
+ break;
+
+ case DMAR_IRTA_REG:
+ vvtd_set_reg(vvtd, offset, val);
+ if ( len == 4 )
+ break;
+ val = val >> 32;
+ offset += 4;
+ /* Fall through */
+ case DMAR_IRTUA_REG:
+ vvtd_set_reg(vvtd, offset, val);
+ break;
+
+ default:
+ break;
+ }
+
return X86EMUL_OKAY;
}