#include #include #include #include #include #include #include #include #include #include #include #include static unsigned int nr_failures; #define fail(fmt, ...) \ ({ \ nr_failures++; \ (void)printf(fmt, ##__VA_ARGS__); \ }) #define memdup(ptr) \ ({ \ typeof(*(ptr)) *p_ = (ptr); \ void *n_ = malloc(sizeof(*p_)); \ \ if ( !n_ ) \ err(1, "%s malloc failure", __func__); \ \ memcpy(n_, p_, sizeof(*p_)); \ }) static void test_vendor_identification(void) { static const struct test { union { char ident[12]; struct { uint32_t b, d, c; }; }; unsigned int vendor; } tests[] = { /* The 1st entry should remain here to work around gcc bug 91667. */ { { "" }, X86_VENDOR_UNKNOWN }, { { " " }, X86_VENDOR_UNKNOWN }, { { "xxxxxxxxxxxx" }, X86_VENDOR_UNKNOWN }, { { "GenuineIntel" }, X86_VENDOR_INTEL }, { { "AuthenticAMD" }, X86_VENDOR_AMD }, { { "CentaurHauls" }, X86_VENDOR_CENTAUR }, { { " Shanghai " }, X86_VENDOR_SHANGHAI }, { { "HygonGenuine" }, X86_VENDOR_HYGON }, }; printf("Testing CPU vendor identification:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { const struct test *t = &tests[i]; unsigned int vendor = x86_cpuid_lookup_vendor(t->b, t->c, t->d); if ( vendor != t->vendor ) fail(" Test '%.12s', expected vendor %u, got %u\n", t->ident, t->vendor, vendor); } } static bool leaves_are_sorted(const xen_cpuid_leaf_t *leaves, unsigned int nr) { for ( unsigned int i = 1; i < nr; ++i ) { /* leaf index went backwards => not sorted. */ if ( leaves[i - 1].leaf > leaves[i].leaf ) return false; /* leaf index went forwards => ok */ if ( leaves[i - 1].leaf < leaves[i].leaf ) continue; /* leave index the same, subleaf didn't increase => not sorted. */ if ( leaves[i - 1].subleaf >= leaves[i].subleaf ) return false; } return true; } static void test_cpuid_current(void) { struct cpuid_policy p; xen_cpuid_leaf_t leaves[CPUID_MAX_SERIALISED_LEAVES]; unsigned int nr = ARRAY_SIZE(leaves); int rc; printf("Testing CPUID on current CPU\n"); x86_cpuid_policy_fill_native(&p); rc = x86_cpuid_copy_to_buffer(&p, leaves, &nr); if ( rc != 0 ) return fail(" Serialise, expected rc 0, got %d\n", rc); if ( !leaves_are_sorted(leaves, nr) ) return fail(" Leaves not sorted\n"); } static void test_cpuid_serialise_success(void) { static const struct test { struct cpuid_policy p; const char *name; unsigned int nr_leaves; } tests[] = { { .name = "empty policy", .nr_leaves = 4, }, /* Leaf 4 serialisation stops at the first subleaf with type 0. */ { .name = "empty leaf 4", .p = { .basic.max_leaf = 4, }, .nr_leaves = 4 + 4, }, { .name = "partial leaf 4", .p = { .basic.max_leaf = 4, .cache.subleaf[0].type = 1, }, .nr_leaves = 4 + 4 + 1, }, /* Leaf 7 serialisation stops at max_subleaf. */ { .name = "empty leaf 7", .p = { .basic.max_leaf = 7, }, .nr_leaves = 4 + 7, }, { .name = "partial leaf 7", .p = { .basic.max_leaf = 7, .feat.max_subleaf = 1, }, .nr_leaves = 4 + 7 + 1, }, /* Leaf 0xb serialisation stops at the first subleaf with type 0. */ { .name = "empty leaf 0xb", .p = { .basic.max_leaf = 0xb, }, .nr_leaves = 4 + 0xb, }, { .name = "partial leaf 0xb", .p = { .basic.max_leaf = 0xb, .topo.subleaf[0].type = 1, }, .nr_leaves = 4 + 0xb + 1, }, /* * Leaf 0xd serialisation automatically has two leaves, and stops the * highest bit set in {xcr0,xss}_{high,low}. */ { .name = "empty leaf 0xd", .p = { .basic.max_leaf = 0xd, }, .nr_leaves = 4 + 0xd + 1, }, { .name = "partial 0xd", .p = { .basic.max_leaf = 0xd, .xstate.xcr0_low = 7, }, .nr_leaves = 4 + 0xd + 1 + 1, }, }; printf("Testing CPUID serialise success:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { const struct test *t = &tests[i]; unsigned int nr = t->nr_leaves; xen_cpuid_leaf_t *leaves = malloc(nr * sizeof(*leaves)); int rc; if ( !leaves ) err(1, "%s() malloc failure", __func__); rc = x86_cpuid_copy_to_buffer(&t->p, leaves, &nr); if ( rc != 0 ) { fail(" Test %s, expected rc 0, got %d\n", t->name, rc); goto test_done; } if ( nr != t->nr_leaves ) { fail(" Test %s, expected %u leaves, got %u\n", t->name, t->nr_leaves, nr); goto test_done; } if ( !leaves_are_sorted(leaves, nr) ) { fail(" Test %s, leaves not sorted\n", t->name); goto test_done; } test_done: free(leaves); } } static void test_msr_serialise_success(void) { static const struct test { struct msr_policy p; const char *name; unsigned int nr_msrs; } tests[] = { { .name = "empty policy", .nr_msrs = MSR_MAX_SERIALISED_ENTRIES, }, }; printf("Testing MSR serialise success:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { const struct test *t = &tests[i]; unsigned int nr = t->nr_msrs; xen_msr_entry_t *msrs = malloc(nr * sizeof(*msrs)); int rc; if ( !msrs ) err(1, "%s() malloc failure", __func__); rc = x86_msr_copy_to_buffer(&t->p, msrs, &nr); if ( rc != 0 ) { fail(" Test %s, expected rc 0, got %d\n", t->name, rc); goto test_done; } if ( nr != t->nr_msrs ) { fail(" Test %s, expected %u msrs, got %u\n", t->name, t->nr_msrs, nr); goto test_done; } test_done: free(msrs); } } static void test_cpuid_deserialise_failure(void) { static const struct test { const char *name; xen_cpuid_leaf_t leaf; } tests[] = { { .name = "incorrect basic subleaf", .leaf = { .leaf = 0, .subleaf = 0 }, }, { .name = "incorrect hv1 subleaf", .leaf = { .leaf = 0x40000000, .subleaf = 0 }, }, { .name = "incorrect hv2 subleaf", .leaf = { .leaf = 0x40000100, .subleaf = 0 }, }, { .name = "incorrect extd subleaf", .leaf = { .leaf = 0x80000000, .subleaf = 0 }, }, { .name = "OoB basic leaf", .leaf = { .leaf = CPUID_GUEST_NR_BASIC }, }, { .name = "OoB cache leaf", .leaf = { .leaf = 0x4, .subleaf = CPUID_GUEST_NR_CACHE }, }, { .name = "OoB feat leaf", .leaf = { .leaf = 0x7, .subleaf = CPUID_GUEST_NR_FEAT }, }, { .name = "OoB topo leaf", .leaf = { .leaf = 0xb, .subleaf = CPUID_GUEST_NR_TOPO }, }, { .name = "OoB xstate leaf", .leaf = { .leaf = 0xd, .subleaf = CPUID_GUEST_NR_XSTATE }, }, { .name = "OoB extd leaf", .leaf = { .leaf = 0x80000000 | CPUID_GUEST_NR_EXTD }, }, }; printf("Testing CPUID deserialise failure:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { const struct test *t = &tests[i]; uint32_t err_leaf, err_subleaf; int rc; /* No writes should occur. Use NULL to catch errors. */ rc = x86_cpuid_copy_from_buffer(NULL, &t->leaf, 1, &err_leaf, &err_subleaf); if ( rc != -ERANGE ) { fail(" Test %s, expected rc %d, got %d\n", t->name, -ERANGE, rc); continue; } if ( err_leaf != t->leaf.leaf || err_subleaf != t->leaf.subleaf ) { fail(" Test %s, expected err %08x:%08x, got %08x:%08x\n", t->name, t->leaf.leaf, t->leaf.subleaf, err_leaf, err_subleaf); continue; } } } static void test_msr_deserialise_failure(void) { static const struct test { const char *name; xen_msr_entry_t msr; int rc; } tests[] = { { .name = "bad msr index", .msr = { .idx = 0xdeadc0de }, .rc = -ERANGE, }, { .name = "nonzero flags", .msr = { .idx = 0xce, .flags = 1 }, .rc = -EINVAL, }, { .name = "truncated val", .msr = { .idx = 0xce, .val = ~0ull }, .rc = -EOVERFLOW, }, { .name = "truncated val", .msr = { .idx = 0x10a, .val = ~0ull }, .rc = -EOVERFLOW, }, }; printf("Testing MSR deserialise failure:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { const struct test *t = &tests[i]; uint32_t err_msr; int rc; /* No writes should occur. Use NULL to catch errors. */ rc = x86_msr_copy_from_buffer(NULL, &t->msr, 1, &err_msr); if ( rc != t->rc ) { fail(" Test %s, expected rc %d, got %d\n", t->name, t->rc, rc); continue; } if ( err_msr != t->msr.idx ) { fail(" Test %s, expected err_msr %#x, got %#x\n", t->name, t->msr.idx, err_msr); continue; } } } static void test_cpuid_out_of_range_clearing(void) { static const struct test { const char *name; unsigned int nr_markers; struct cpuid_policy p; } tests[] = { { .name = "basic", .nr_markers = 1, .p = { /* Retains marker in leaf 0. Clears others. */ .basic.max_leaf = 0, .basic.vendor_ebx = 0xc2, .basic.raw_fms = 0xc2, .cache.raw[0].a = 0xc2, .feat.raw[0].a = 0xc2, .topo.raw[0].a = 0xc2, .xstate.raw[0].a = 0xc2, .xstate.raw[1].a = 0xc2, }, }, { .name = "cache", .nr_markers = 1, .p = { /* Retains marker in subleaf 0. Clears others. */ .basic.max_leaf = 4, .cache.raw[0] = { .a = 1, .b = 0xc2 }, .cache.raw[1].b = 0xc2, .feat.raw[0].a = 0xc2, .topo.raw[0].a = 0xc2, .xstate.raw[0].a = 0xc2, .xstate.raw[1].a = 0xc2, }, }, { .name = "feat", .nr_markers = 1, .p = { /* Retains marker in subleaf 0. Clears others. */ .basic.max_leaf = 7, .feat.raw[0].b = 0xc2, .feat.raw[1].b = 0xc2, .topo.raw[0].a = 0xc2, .xstate.raw[0].a = 0xc2, .xstate.raw[1].a = 0xc2, }, }, { .name = "topo", .nr_markers = 1, .p = { /* Retains marker in subleaf 0. Clears others. */ .basic.max_leaf = 0xb, .topo.raw[0] = { .b = 0xc2, .c = 0x0100 }, .topo.raw[1].b = 0xc2, .xstate.raw[0].a = 0xc2, .xstate.raw[1].a = 0xc2, }, }, { .name = "xstate x87", .nr_markers = 2, .p = { /* First two subleaves always valid. Others cleared. */ .basic.max_leaf = 0xd, .xstate.raw[0].a = 1, .xstate.raw[0].b = 0xc2, .xstate.raw[1].b = 0xc2, .xstate.raw[2].b = 0xc2, .xstate.raw[3].b = 0xc2, }, }, { .name = "xstate sse", .nr_markers = 2, .p = { /* First two subleaves always valid. Others cleared. */ .basic.max_leaf = 0xd, .xstate.raw[0].a = 2, .xstate.raw[0].b = 0xc2, .xstate.raw[1].b = 0xc2, .xstate.raw[2].b = 0xc2, .xstate.raw[3].b = 0xc2, }, }, { .name = "xstate avx", .nr_markers = 3, .p = { /* Third subleaf also valid. Others cleared. */ .basic.max_leaf = 0xd, .xstate.raw[0].a = 7, .xstate.raw[0].b = 0xc2, .xstate.raw[1].b = 0xc2, .xstate.raw[2].b = 0xc2, .xstate.raw[3].b = 0xc2, }, }, { .name = "extd", .nr_markers = 1, .p = { /* Retains marker in leaf 0. Clears others. */ .extd.max_leaf = 0, .extd.vendor_ebx = 0xc2, .extd.raw_fms = 0xc2, }, }, }; printf("Testing CPUID out-of-range clearing:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { const struct test *t = &tests[i]; struct cpuid_policy *p = memdup(&t->p); void *ptr; unsigned int nr_markers; x86_cpuid_policy_clear_out_of_range_leaves(p); /* Count the number of 0xc2's still remaining. */ for ( ptr = p, nr_markers = 0; (ptr = memchr(ptr, 0xc2, (void *)p + sizeof(*p) - ptr)); ptr++, nr_markers++ ) ; if ( nr_markers != t->nr_markers ) fail(" Test %s fail - expected %u markers, got %u\n", t->name, t->nr_markers, nr_markers); free(p); } } static void test_is_compatible_success(void) { static struct test { const char *name; struct cpuid_policy host_cpuid; struct cpuid_policy guest_cpuid; struct msr_policy host_msr; struct msr_policy guest_msr; } tests[] = { { .name = "Host CPUID faulting, Guest not", .host_msr = { .platform_info.cpuid_faulting = true, }, }, { .name = "Host CPUID faulting, Guest wanted", .host_msr = { .platform_info.cpuid_faulting = true, }, .guest_msr = { .platform_info.cpuid_faulting = true, }, }, }; struct cpu_policy_errors no_errors = INIT_CPU_POLICY_ERRORS; printf("Testing policy compatibility success:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { struct test *t = &tests[i]; struct cpu_policy sys = { &t->host_cpuid, &t->host_msr, }, new = { &t->guest_cpuid, &t->guest_msr, }; struct cpu_policy_errors e; int res = x86_cpu_policies_are_compatible(&sys, &new, &e); /* Check the expected error output. */ if ( res != 0 || memcmp(&no_errors, &e, sizeof(no_errors)) ) fail(" Test '%s' expected no errors\n" " got res %d { leaf %08x, subleaf %08x, msr %08x }\n", t->name, res, e.leaf, e.subleaf, e.msr); } } static void test_is_compatible_failure(void) { static struct test { const char *name; struct cpuid_policy host_cpuid; struct cpuid_policy guest_cpuid; struct msr_policy host_msr; struct msr_policy guest_msr; struct cpu_policy_errors e; } tests[] = { { .name = "Host basic.max_leaf out of range", .guest_cpuid.basic.max_leaf = 1, .e = { 0, -1, -1 }, }, { .name = "Host extd.max_leaf out of range", .guest_cpuid.extd.max_leaf = 1, .e = { 0x80000000, -1, -1 }, }, { .name = "Host no CPUID faulting, Guest wanted", .guest_msr = { .platform_info.cpuid_faulting = true, }, .e = { -1, -1, 0xce }, }, }; printf("Testing policy compatibility failure:\n"); for ( size_t i = 0; i < ARRAY_SIZE(tests); ++i ) { struct test *t = &tests[i]; struct cpu_policy sys = { &t->host_cpuid, &t->host_msr, }, new = { &t->guest_cpuid, &t->guest_msr, }; struct cpu_policy_errors e; int res = x86_cpu_policies_are_compatible(&sys, &new, &e); /* Check the expected error output. */ if ( res == 0 || memcmp(&t->e, &e, sizeof(t->e)) ) fail(" Test '%s' res %d\n" " expected { leaf %08x, subleaf %08x, msr %08x }\n" " got { leaf %08x, subleaf %08x, msr %08x }\n", t->name, res, t->e.leaf, t->e.subleaf, t->e.msr, e.leaf, e.subleaf, e.msr); } } int main(int argc, char **argv) { printf("CPU Policy unit tests\n"); test_vendor_identification(); test_cpuid_current(); test_cpuid_serialise_success(); test_cpuid_deserialise_failure(); test_cpuid_out_of_range_clearing(); test_msr_serialise_success(); test_msr_deserialise_failure(); test_is_compatible_success(); test_is_compatible_failure(); if ( nr_failures ) printf("Done: %u failures\n", nr_failures); else printf("Done: all ok\n"); return !!nr_failures; }