diff --git a/tests/vpmu/Makefile b/tests/vpmu/Makefile
new file mode 100644
index 0000000..1eaf436
--- /dev/null
+++ b/tests/vpmu/Makefile
@@ -0,0 +1,9 @@
+include $(ROOT)/build/common.mk
+
+NAME      := vpmu
+CATEGORY  := utility
+TEST-ENVS := $(ALL_ENVIRONMENTS)
+
+obj-perenv += main.o
+
+include $(ROOT)/build/gen.mk
diff --git a/tests/vpmu/main.c b/tests/vpmu/main.c
new file mode 100644
index 0000000..ed00953
--- /dev/null
+++ b/tests/vpmu/main.c
@@ -0,0 +1,504 @@
+/**
+ * @file tests/vpmu/main.c
+ * @ref test-vpmu
+ *
+ * @page test-vpmu vpmu
+ *
+ * Test MSRs exposed by VPMU for various versions of Architectural Performance
+ * Monitoring Unit
+ *
+ * @see tests/vpmu/main.c
+ */
+
+#include <xtf.h>
+#include <arch/msr-index.h>
+#include <arch/mm.h>
+
+#define EVENT_UOPS_RETIRED                   0x004101c2
+#define EVENT_UOPS_RETIRED_ANYTHREAD         0x006101c2
+#define FIXED_CTR_CTL_BITS                   4
+#define FIXED_CTR_ENABLE                     0x0000000A
+#define FIXED_CTR_ENABLE_ANYTHREAD           0x0000000E
+#define IA32_PERFEVENTSELx_ENABLE_ANYTHREAD  (1ull << 21)
+#define IA32_PERFEVENTSELx_ENABLE_PCB        (1ull << 19)
+#define IA32_DEBUGCTL_Freeze_LBR_ON_PMI      (1ull << 11)
+#define IA32_DEBUGCTL_Freeze_PerfMon_On_PMI  (1ull << 12)
+
+const char test_title[] = "Test vpmu";
+
+static void get_intel_pmu_version(uint8_t *v, uint8_t *ng, uint8_t *nf,
+                                  uint8_t *ngb)
+{
+    /* Inter Software Development Manual Vol 2A
+     * Table 3-8  Information Returned by CPUID Instruction */
+
+    uint32_t leaf = 0x0a, subleaf = 0;
+
+    uint32_t eax, ebx, ecx, edx;
+
+    cpuid_count(leaf, subleaf, &eax, &ebx, &ecx, &edx);
+
+    /* Extract the version ID - EAX 7:0 */
+    *v =  (eax & 0xff);
+
+    /* Extract number of general purpose counter regs - EAX 15:8 */
+    *ng = (eax >> 8) & 0xff;
+
+    /* Extract number of fixed function counter regs - EDX 4:0 */
+    *nf = edx & 0x1f;
+
+    /* Extract number of bits in general purpose counter registers bits */
+    *ngb = (eax >> 16)  & 0xff;
+}
+
+static bool test_valid_msr_write(uint32_t idx, uint64_t wval)
+{
+    const char *pfx = "Error: test_valid_msr_write:";
+
+    uint64_t rval = 0;
+
+    printk("Writing 0x%" PRIx64 " to MSR 0x%x\n", wval, idx);
+
+    if( wrmsr_safe(idx, wval) )
+    {
+        xtf_error("%s wrmsr for MSR 0x%x resulted in a fault!\n", pfx, idx);
+        return false;
+    }
+
+    /* check to see if the values were written correctly */
+    if ( rdmsr_safe(idx, &rval) )
+    {
+        xtf_error("%s rdmsr for MSR 0x%x resulted in a fault!\n", pfx, idx);
+        return false;
+    }
+    if ( rval != wval )
+    {
+        xtf_error("%s rdmsr value mismatch for MSR 0x%x Expected: %"PRIx64
+                ", Found %"PRIx64"\n", pfx, idx, wval, rval);
+        return false;
+    }
+
+    return true;
+}
+
+static bool test_invalid_msr_write(uint32_t idx, uint64_t wval)
+{
+    printk("Write invalid value %"PRIx64" to MSR 0x%x\n", wval, idx);
+
+    /* wrmsr_safe must return false after faulting */
+    if( !wrmsr_safe(idx, wval) )
+    {
+        xtf_error("Error: test_invalid_msr_write wrmsr for MSR 0x%x did not "
+                  "fault!\n", idx);
+        return false;
+    }
+
+    printk("wrmsr to MSR 0x%x faulted as expected.\n", idx);
+
+    return true;
+}
+
+static bool test_transparent_msr_write(uint32_t idx, uint64_t wval)
+{
+    const char *pfx = "Error: test_transparent_msr_write:";
+
+    uint64_t rval1 = 0;
+
+    uint64_t rval2 = 0;
+
+    printk("Attempting to write a value with no effect to MSR 0x%x\n", idx);
+
+    /* read current value */
+    if ( rdmsr_safe(idx, &rval1) )
+    {
+        xtf_error("%s rdmsr for MSR 0x%x resulted in a fault\n", pfx, idx);
+        return false;
+    }
+
+    /* wrmsr should not fault but should not write anything either */
+    if  ( wrmsr_safe(idx, wval) )
+    {
+        xtf_error("%s wrmsr for MSR 0x%x resulted in a fault\n", pfx, idx);
+        return false;
+    }
+
+    /* read new value */
+    if ( rdmsr_safe(idx, &rval2) )
+    {
+        xtf_error("%s rdmsr for MSR 0x%x resulted in a fault\n", pfx, idx);
+        return false;
+    }
+
+    /* Check if the new value is the same as the one before wrmsr */
+
+    if ( rval1 != rval2 )
+    {
+        xtf_error("%s rdmsr value mismatch for MSR 0x%x Expected %"PRIx64
+                ". Found %"PRIx64"\n", pfx, idx, rval1, rval2);
+        return false;
+    }
+
+    return true;
+}
+
+static bool test_intel_pmu_ver1(uint8_t ng)
+{
+    /* Intel Sofwtare Development Manual Vol. 3B,
+     * Section 18.2.1 - Architectural Performance Monitoring Version 1
+     *
+     * MSRs made available by the VPMU -
+     *
+     * IA32_PMCx (start at address 0xc1)
+     * IA32_PERFEVENTSELx (start at address 0x186)
+     */
+
+    uint32_t idx;
+
+    uint64_t wval = 0;
+
+    uint8_t i;
+
+    printk("-----------------\n");
+    printk("Testing version 1\n");
+    printk("-----------------\n");
+
+    /* for all general purpose counters */
+    for ( i = 0; i < ng; i++ )
+    {
+        /* test writing to IA32_PMCx */
+        idx = MSR_IA32_PMC(i);
+
+        /* test we can write to all valid bits in the counters */
+        /* don't set bit 31 since that gets sign-extended */
+        wval = ((1ull << 31) - 1) ;
+
+        if ( !test_valid_msr_write(idx, wval) )
+            return false;
+
+        /* set all valid bits in MSR_IA32_EVENTSELx */
+        idx = MSR_IA32_PERFEVTSEL(i);
+        wval = ((1ull << 32) - 1) ^ (IA32_PERFEVENTSELx_ENABLE_ANYTHREAD |
+                IA32_PERFEVENTSELx_ENABLE_PCB);
+
+        if ( !test_valid_msr_write(idx, wval) )
+            return false;
+
+        /* test writing an invalid value and assert that it faults */
+        wval = ~((1ull << 32) - 1);
+
+        if ( !test_invalid_msr_write(idx, wval) )
+            return false;
+    }
+
+    return true;
+}
+
+static bool test_intel_pmu_ver2(uint8_t ng, uint8_t nf)
+{
+    /* Intel Sofwtare Development Manual Vol. 3B,
+     * Section 18.2.2 - Architectural Performance Monitoring Version 2
+     *
+     * MSRs made available by the VPMU -
+     *
+     * IA32_FIXED_CTRx (start at address 0x309)
+     * IA32_FIXED_CTR_CTRL
+     * IA32_PERF_GLOBAL_CTRL
+     * IA32_PERF_GLOBAL_STATUS (read-only)
+     * IA32_PERF_GLOBAL_OVF_CTRL
+     * IA32_DEBUGCTL_Freeze_LBR_ON_PMI
+     * IA32_DEBUGCTL_Freeze_PerfMon_On_PMI
+     */
+
+    uint32_t idx;
+
+    uint64_t wval, rval;
+
+    uint8_t i;
+
+    printk("-----------------\n");
+    printk("Testing version 2\n");
+    printk("-----------------\n");
+
+    for ( i = 0; i < nf; i++ )
+    {
+        /* test writing to IA32_FIXED_CTRx */
+        idx = MSR_IA32_FIXED_CTR(i);
+
+        wval = (1ull << 32) - 1;
+
+        if ( !test_valid_msr_write(idx, wval) )
+             return false;
+
+        /* test invalid write to IA32_FIXED_CTRx */
+        wval = ~wval;
+
+        if ( !test_invalid_msr_write(idx, wval) )
+             return false;
+    }
+
+    /* test IA32_FIXED_CTR_CTRL */
+    idx = MSR_IA32_FIXED_CTR_CTRL;
+
+    /* enable all fixed counters */
+    wval = 0;
+
+    for ( i = 0; i < nf; i++ )
+        wval |= (FIXED_CTR_ENABLE << (FIXED_CTR_CTL_BITS * i));
+
+    if ( !test_valid_msr_write(idx, wval) )
+        return false;
+
+    /* invert wval to test writing an invalid value */
+    wval = ~wval;
+
+    if ( !test_invalid_msr_write(idx, wval) )
+      return false;
+
+    /* test IA32_PERF_GLOBAL_CTRL */
+    idx = MSR_IA32_PERF_GLOBAL_CTRL;
+
+    wval = 0;
+
+    /* set all fixed function counters enable bits */
+    for ( i=0; i < nf; i ++ )
+        wval |= ((uint64_t)1 << (32 + i));
+
+    /* set all general purpose counters enable bits*/
+    for ( i = 0; i < ng; i++ )
+        wval |= (1 << i);
+
+    if ( !test_valid_msr_write(idx, wval) )
+         return false;
+
+    /* invert wval to test invalid write to MSR_IA32_PERF_GLOBAL_CTRL*/
+    wval = ~wval;
+
+    if ( !test_invalid_msr_write(idx, wval) )
+        return false;
+
+    /* test IA32_PERF_GLOBAL_OVF_CTRL */
+    idx = MSR_IA32_PERF_GLOBAL_OVF_CTRL;
+
+    /* set all valid bits in MSR_IA32_PERF_GLOBAL_OVF_CTRL */
+    wval = 0xC000000000000000 | (((1ULL << nf) - 1) << 32) | ((1ULL << ng) - 1);
+
+    /* Writing to MSR_IA32_PERF_GLOBAL_OVF_CTRL clears
+     * MSR_IA32_PERF_GLOBAL_STATUS but but always returns 0 when read so
+     * it is tested as a transparent write */
+
+    if ( !test_transparent_msr_write(idx, wval) )
+        return false;
+
+    /* invert wval to test invalid write to MSR_IA32_PERF_GLOBAL_OVF_CTRL*/
+    wval = ~wval;
+
+    if ( !test_invalid_msr_write(idx, wval) )
+        return false;
+
+    /* test IA32_PERF_GLOBAL_STATUS (read-only) */
+    idx = MSR_IA32_PERF_GLOBAL_STATUS;
+
+    if ( rdmsr_safe(idx, &rval) )
+    {
+        xtf_error("Error: test_intel_pmu_ver2: "
+                  "rdmsr_safe for MSR 0x%x resulted in a fault!\n", idx);
+        return false;
+    }
+
+    /* try to write the IA32_PERF_GLOBAL_STATUS register and expect it to fail*/
+    if ( !test_invalid_msr_write(idx, rval) )
+        return false;
+
+
+    /* test IA32_DEBUGCTL */
+    idx = MSR_IA32_DEBUGCTL;
+
+    /* Test IA32_DEBUGCTL facilities enabled by v2 */
+    wval = IA32_DEBUGCTL_Freeze_LBR_ON_PMI | IA32_DEBUGCTL_Freeze_PerfMon_On_PMI;
+
+    /* FIXME: This should really be a valid write but it isnt supported by the
+     * VPMU yet */
+    if ( !test_transparent_msr_write(idx, wval) )
+        return false;
+
+    return true;
+}
+
+static bool test_intel_pmu_ver3(uint8_t ng, uint8_t nf)
+{
+    /* Intel Sofwtare Development Manual Vol. 3B
+     * Section 18.2.3 Architectural Performance Monitoring Version 3
+     *
+     * MSRs made available by the VPMU
+     *
+     * IA32_PERFEVENTSELx.ANYTHREAD (Bit 21)
+     * IA32_FIXED_CTR_CTRL.ANYTHREADx (Bit 2, 6, 11)
+     *
+     * Version 3 introduces ANYTHREAD bit but VPMU does not support it to
+     * ensure that a VCPU isn't able to read values from physical resources that
+     * are not allocated to it. This test case validates that we are unable to
+     * write to .ANYTHREAD bit in IA32_PERFEVENTSELx and IA32_FIXED_CTR_CTRL
+     */
+
+    uint64_t wval;
+
+    uint32_t idx;
+
+    uint8_t i;
+
+    printk("-----------------\n");
+    printk("Testing version 3\n");
+    printk("-----------------\n");
+
+    /* test IA32_PERFEVENTSELx.ANYTHREAD is disabled */
+    for ( i = 0; i < ng; i++ )
+    {
+        idx = MSR_IA32_PERFEVTSEL(i);
+
+        wval = EVENT_UOPS_RETIRED_ANYTHREAD;
+
+        if ( !test_invalid_msr_write(idx, wval) )
+            return false;
+    }
+
+
+    /* test IA32_FIXED_CTR_CTL.ANYTHREAD is disabled */
+    idx = MSR_IA32_FIXED_CTR_CTRL;
+
+    wval = 0;
+
+    for ( i = 0; i < nf; i++ )
+        wval |= (FIXED_CTR_ENABLE_ANYTHREAD << (4 * i)) ;
+
+    if ( !test_invalid_msr_write(idx, wval) )
+        return false;
+
+    return true;
+}
+
+static bool test_full_width_cnts(uint8_t ng, uint8_t ngb)
+{
+    uint64_t caps;
+
+    uint32_t idx;
+
+    uint64_t wval;
+
+    uint8_t i;
+
+    /* Get perf capabilties */
+    if ( rdmsr_safe(MSR_IA32_PERF_CAPABILITIES, &caps) )
+    {
+        printk("Fault while reading MSR_IA32_PERF_CAPABILITIES\n");
+        return false;
+    }
+
+    if ( !(caps >> 13) & 1 )
+    {
+        printk("Full width counters not supported\n");
+        return true;
+    }
+
+    /* test writes to full width counters */
+    for ( i = 0; i < ng; i++)
+    {
+        idx = MSR_IA32_A_PMC(i);
+
+        wval = ((1ull << ngb) - 1) ;
+
+        if ( !test_valid_msr_write(idx, wval) )
+                return false;
+
+        /* invert wval to test invalid write to MSR_IA32_PERF_GLOBAL_OVF_CTRL*/
+        wval = ~wval;
+
+        if ( !test_invalid_msr_write(idx, wval) )
+            return false;
+    }
+
+    return true;
+
+}
+
+void test_main(void)
+{
+    /* Architectural Performance Monitoring Version */
+    uint8_t ver;
+
+    /* Number of general purpose counter registers */
+    uint8_t ngregs;
+
+    /* Number of fixed function counter registers */
+    uint8_t nfregs;
+
+    /* Bit width of general-purpose, performance monitoring counter */
+    uint8_t ngbits;
+
+    if ( IS_DEFINED(CONFIG_PV) )
+    {
+        printk("VPMU testing for PV guests currently unsupported\n");
+        goto skip;
+    }
+
+    if ( vendor_is_amd )
+    {
+        printk("VPMU testing for AMD currently unsupported\n");
+        goto skip;
+    }
+
+    get_intel_pmu_version(&ver, &ngregs, &nfregs, &ngbits);
+
+    printk("Ver=%u: GPR=%u: FFR=%x GPB=%u\n", ver, ngregs, nfregs, ngbits);
+
+    switch (ver)
+    {
+
+        case 4:
+                /* version 4 facilities are not supported
+                 * VPMU  emulates version 3 */
+                /* fall through */
+
+        case 3:
+                /* test version 3 facilities */
+                if ( !test_intel_pmu_ver3( ngregs, nfregs) )
+                    return xtf_failure("Fail: Failed VPMU version 3\n");
+                /* fall through */
+
+        case 2:
+                /* test version 2 facilities */
+                if ( !test_intel_pmu_ver2(ngregs, nfregs) )
+                    return xtf_failure("Fail: Failed VPMU version 2\n");
+
+                /* test version 1 facilities */
+                if ( !test_intel_pmu_ver1(ngregs) )
+                    return xtf_failure("Fail: Failed VPMU version 1\n");
+
+                /* test full width counters */
+                if ( !test_full_width_cnts(ngregs, ngbits) )
+                    return xtf_failure("Fail: Failed full width test\n");
+
+                break;
+
+        case 1:
+                /* version 1 unsupported */
+
+        default:
+                printk("VMPU not supported!\n");
+                goto skip;
+    }
+
+    return xtf_success(NULL);
+
+skip:
+    return xtf_skip(NULL);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
