diff options
Diffstat (limited to 'tools/testing')
27 files changed, 851 insertions, 347 deletions
diff --git a/tools/testing/nvdimm/pmem-dax.c b/tools/testing/nvdimm/pmem-dax.c index b53596ad601b..2e7fd8227969 100644 --- a/tools/testing/nvdimm/pmem-dax.c +++ b/tools/testing/nvdimm/pmem-dax.c @@ -31,17 +31,21 @@ long __pmem_direct_access(struct pmem_device *pmem, pgoff_t pgoff, if (get_nfit_res(pmem->phys_addr + offset)) { struct page *page; - *kaddr = pmem->virt_addr + offset; + if (kaddr) + *kaddr = pmem->virt_addr + offset; page = vmalloc_to_page(pmem->virt_addr + offset); - *pfn = page_to_pfn_t(page); + if (pfn) + *pfn = page_to_pfn_t(page); pr_debug_ratelimited("%s: pmem: %p pgoff: %#lx pfn: %#lx\n", __func__, pmem, pgoff, page_to_pfn(page)); return 1; } - *kaddr = pmem->virt_addr + offset; - *pfn = phys_to_pfn_t(pmem->phys_addr + offset, pmem->pfn_flags); + if (kaddr) + *kaddr = pmem->virt_addr + offset; + if (pfn) + *pfn = phys_to_pfn_t(pmem->phys_addr + offset, pmem->pfn_flags); /* * If badblocks are present, limit known good range to the diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c index e2926f72a821..cffc2c5a778d 100644 --- a/tools/testing/nvdimm/test/nfit.c +++ b/tools/testing/nvdimm/test/nfit.c @@ -142,6 +142,28 @@ static u32 handle[] = { static unsigned long dimm_fail_cmd_flags[NUM_DCR]; static int dimm_fail_cmd_code[NUM_DCR]; +static const struct nd_intel_smart smart_def = { + .flags = ND_INTEL_SMART_HEALTH_VALID + | ND_INTEL_SMART_SPARES_VALID + | ND_INTEL_SMART_ALARM_VALID + | ND_INTEL_SMART_USED_VALID + | ND_INTEL_SMART_SHUTDOWN_VALID + | ND_INTEL_SMART_MTEMP_VALID + | ND_INTEL_SMART_CTEMP_VALID, + .health = ND_INTEL_SMART_NON_CRITICAL_HEALTH, + .media_temperature = 23 * 16, + .ctrl_temperature = 25 * 16, + .pmic_temperature = 40 * 16, + .spares = 75, + .alarm_flags = ND_INTEL_SMART_SPARE_TRIP + | ND_INTEL_SMART_TEMP_TRIP, + .ait_status = 1, + .life_used = 5, + .shutdown_state = 0, + .vendor_size = 0, + .shutdown_count = 100, +}; + struct nfit_test_fw { enum intel_fw_update_state state; u32 context; @@ -752,15 +774,30 @@ static int nfit_test_cmd_smart_inject( if (buf_len != sizeof(*inj)) return -EINVAL; - if (inj->mtemp_enable) - smart->media_temperature = inj->media_temperature; - if (inj->spare_enable) - smart->spares = inj->spares; - if (inj->fatal_enable) - smart->health = ND_INTEL_SMART_FATAL_HEALTH; - if (inj->unsafe_shutdown_enable) { - smart->shutdown_state = 1; - smart->shutdown_count++; + if (inj->flags & ND_INTEL_SMART_INJECT_MTEMP) { + if (inj->mtemp_enable) + smart->media_temperature = inj->media_temperature; + else + smart->media_temperature = smart_def.media_temperature; + } + if (inj->flags & ND_INTEL_SMART_INJECT_SPARE) { + if (inj->spare_enable) + smart->spares = inj->spares; + else + smart->spares = smart_def.spares; + } + if (inj->flags & ND_INTEL_SMART_INJECT_FATAL) { + if (inj->fatal_enable) + smart->health = ND_INTEL_SMART_FATAL_HEALTH; + else + smart->health = ND_INTEL_SMART_NON_CRITICAL_HEALTH; + } + if (inj->flags & ND_INTEL_SMART_INJECT_SHUTDOWN) { + if (inj->unsafe_shutdown_enable) { + smart->shutdown_state = 1; + smart->shutdown_count++; + } else + smart->shutdown_state = 0; } inj->status = 0; smart_notify(bus_dev, dimm_dev, smart, thresh); @@ -884,6 +921,16 @@ static int nd_intel_test_cmd_set_lss_status(struct nfit_test *t, return 0; } +static int override_return_code(int dimm, unsigned int func, int rc) +{ + if ((1 << func) & dimm_fail_cmd_flags[dimm]) { + if (dimm_fail_cmd_code[dimm]) + return dimm_fail_cmd_code[dimm]; + return -EIO; + } + return rc; +} + static int get_dimm(struct nfit_mem *nfit_mem, unsigned int func) { int i; @@ -894,13 +941,6 @@ static int get_dimm(struct nfit_mem *nfit_mem, unsigned int func) break; if (i >= ARRAY_SIZE(handle)) return -ENXIO; - - if ((1 << func) & dimm_fail_cmd_flags[i]) { - if (dimm_fail_cmd_code[i]) - return dimm_fail_cmd_code[i]; - return -EIO; - } - return i; } @@ -939,48 +979,59 @@ static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc, switch (func) { case ND_INTEL_ENABLE_LSS_STATUS: - return nd_intel_test_cmd_set_lss_status(t, + rc = nd_intel_test_cmd_set_lss_status(t, buf, buf_len); + break; case ND_INTEL_FW_GET_INFO: - return nd_intel_test_get_fw_info(t, buf, + rc = nd_intel_test_get_fw_info(t, buf, buf_len, i - t->dcr_idx); + break; case ND_INTEL_FW_START_UPDATE: - return nd_intel_test_start_update(t, buf, + rc = nd_intel_test_start_update(t, buf, buf_len, i - t->dcr_idx); + break; case ND_INTEL_FW_SEND_DATA: - return nd_intel_test_send_data(t, buf, + rc = nd_intel_test_send_data(t, buf, buf_len, i - t->dcr_idx); + break; case ND_INTEL_FW_FINISH_UPDATE: - return nd_intel_test_finish_fw(t, buf, + rc = nd_intel_test_finish_fw(t, buf, buf_len, i - t->dcr_idx); + break; case ND_INTEL_FW_FINISH_QUERY: - return nd_intel_test_finish_query(t, buf, + rc = nd_intel_test_finish_query(t, buf, buf_len, i - t->dcr_idx); + break; case ND_INTEL_SMART: - return nfit_test_cmd_smart(buf, buf_len, + rc = nfit_test_cmd_smart(buf, buf_len, &t->smart[i - t->dcr_idx]); + break; case ND_INTEL_SMART_THRESHOLD: - return nfit_test_cmd_smart_threshold(buf, + rc = nfit_test_cmd_smart_threshold(buf, buf_len, &t->smart_threshold[i - t->dcr_idx]); + break; case ND_INTEL_SMART_SET_THRESHOLD: - return nfit_test_cmd_smart_set_threshold(buf, + rc = nfit_test_cmd_smart_set_threshold(buf, buf_len, &t->smart_threshold[i - t->dcr_idx], &t->smart[i - t->dcr_idx], &t->pdev.dev, t->dimm_dev[i]); + break; case ND_INTEL_SMART_INJECT: - return nfit_test_cmd_smart_inject(buf, + rc = nfit_test_cmd_smart_inject(buf, buf_len, &t->smart_threshold[i - t->dcr_idx], &t->smart[i - t->dcr_idx], &t->pdev.dev, t->dimm_dev[i]); + break; default: return -ENOTTY; } + return override_return_code(i, func, rc); } if (!test_bit(cmd, &cmd_mask) @@ -1006,6 +1057,7 @@ static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc, default: return -ENOTTY; } + return override_return_code(i, func, rc); } else { struct ars_state *ars_state = &t->ars_state; struct nd_cmd_pkg *call_pkg = buf; @@ -1302,29 +1354,9 @@ static void smart_init(struct nfit_test *t) .ctrl_temperature = 30 * 16, .spares = 5, }; - const struct nd_intel_smart smart_data = { - .flags = ND_INTEL_SMART_HEALTH_VALID - | ND_INTEL_SMART_SPARES_VALID - | ND_INTEL_SMART_ALARM_VALID - | ND_INTEL_SMART_USED_VALID - | ND_INTEL_SMART_SHUTDOWN_VALID - | ND_INTEL_SMART_MTEMP_VALID, - .health = ND_INTEL_SMART_NON_CRITICAL_HEALTH, - .media_temperature = 23 * 16, - .ctrl_temperature = 25 * 16, - .pmic_temperature = 40 * 16, - .spares = 75, - .alarm_flags = ND_INTEL_SMART_SPARE_TRIP - | ND_INTEL_SMART_TEMP_TRIP, - .ait_status = 1, - .life_used = 5, - .shutdown_state = 0, - .vendor_size = 0, - .shutdown_count = 100, - }; for (i = 0; i < t->num_dcr; i++) { - memcpy(&t->smart[i], &smart_data, sizeof(smart_data)); + memcpy(&t->smart[i], &smart_def, sizeof(smart_def)); memcpy(&t->smart_threshold[i], &smart_t_data, sizeof(smart_t_data)); } diff --git a/tools/testing/radix-tree/Makefile b/tools/testing/radix-tree/Makefile index db66f8a0d4be..37baecc3766f 100644 --- a/tools/testing/radix-tree/Makefile +++ b/tools/testing/radix-tree/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 -CFLAGS += -I. -I../../include -g -O2 -Wall -D_LGPL_SOURCE -fsanitize=address -LDFLAGS += -fsanitize=address +CFLAGS += -I. -I../../include -g -Og -Wall -D_LGPL_SOURCE -fsanitize=address \ + -fsanitize=undefined +LDFLAGS += -fsanitize=address -fsanitize=undefined LDLIBS+= -lpthread -lurcu TARGETS = main idr-test multiorder CORE_OFILES := radix-tree.o idr.o linux.o test.o find_bit.o @@ -21,6 +22,7 @@ targets: generated/map-shift.h $(TARGETS) main: $(OFILES) +idr-test.o: ../../../lib/test_ida.c idr-test: idr-test.o $(CORE_OFILES) multiorder: multiorder.o $(CORE_OFILES) diff --git a/tools/testing/radix-tree/idr-test.c b/tools/testing/radix-tree/idr-test.c index ee820fcc29b0..321ba92c70d2 100644 --- a/tools/testing/radix-tree/idr-test.c +++ b/tools/testing/radix-tree/idr-test.c @@ -309,141 +309,61 @@ void idr_checks(void) idr_u32_test(0); } +#define module_init(x) +#define module_exit(x) +#define MODULE_AUTHOR(x) +#define MODULE_LICENSE(x) +#define dump_stack() assert(0) +void ida_dump(struct ida *); + +#include "../../../lib/test_ida.c" + /* * Check that we get the correct error when we run out of memory doing - * allocations. To ensure we run out of memory, just "forget" to preload. + * allocations. In userspace, GFP_NOWAIT will always fail an allocation. * The first test is for not having a bitmap available, and the second test * is for not being able to allocate a level of the radix tree. */ void ida_check_nomem(void) { DEFINE_IDA(ida); - int id, err; - - err = ida_get_new_above(&ida, 256, &id); - assert(err == -EAGAIN); - err = ida_get_new_above(&ida, 1UL << 30, &id); - assert(err == -EAGAIN); -} - -/* - * Check what happens when we fill a leaf and then delete it. This may - * discover mishandling of IDR_FREE. - */ -void ida_check_leaf(void) -{ - DEFINE_IDA(ida); int id; - unsigned long i; - for (i = 0; i < IDA_BITMAP_BITS; i++) { - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new(&ida, &id)); - assert(id == i); - } - - ida_destroy(&ida); - assert(ida_is_empty(&ida)); - - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new(&ida, &id)); - assert(id == 0); - ida_destroy(&ida); - assert(ida_is_empty(&ida)); + id = ida_alloc_min(&ida, 256, GFP_NOWAIT); + IDA_BUG_ON(&ida, id != -ENOMEM); + id = ida_alloc_min(&ida, 1UL << 30, GFP_NOWAIT); + IDA_BUG_ON(&ida, id != -ENOMEM); + IDA_BUG_ON(&ida, !ida_is_empty(&ida)); } /* * Check handling of conversions between exceptional entries and full bitmaps. */ -void ida_check_conv(void) +void ida_check_conv_user(void) { DEFINE_IDA(ida); - int id; unsigned long i; - for (i = 0; i < IDA_BITMAP_BITS * 2; i += IDA_BITMAP_BITS) { - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, i + 1, &id)); - assert(id == i + 1); - assert(!ida_get_new_above(&ida, i + BITS_PER_LONG, &id)); - assert(id == i + BITS_PER_LONG); - ida_remove(&ida, i + 1); - ida_remove(&ida, i + BITS_PER_LONG); - assert(ida_is_empty(&ida)); - } - - assert(ida_pre_get(&ida, GFP_KERNEL)); - - for (i = 0; i < IDA_BITMAP_BITS * 2; i++) { - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new(&ida, &id)); - assert(id == i); - } - - for (i = IDA_BITMAP_BITS * 2; i > 0; i--) { - ida_remove(&ida, i - 1); - } - assert(ida_is_empty(&ida)); - - for (i = 0; i < IDA_BITMAP_BITS + BITS_PER_LONG - 4; i++) { - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new(&ida, &id)); - assert(id == i); - } - - for (i = IDA_BITMAP_BITS + BITS_PER_LONG - 4; i > 0; i--) { - ida_remove(&ida, i - 1); - } - assert(ida_is_empty(&ida)); - radix_tree_cpu_dead(1); for (i = 0; i < 1000000; i++) { - int err = ida_get_new(&ida, &id); - if (err == -EAGAIN) { - assert((i % IDA_BITMAP_BITS) == (BITS_PER_LONG - 2)); - assert(ida_pre_get(&ida, GFP_KERNEL)); - err = ida_get_new(&ida, &id); + int id = ida_alloc(&ida, GFP_NOWAIT); + if (id == -ENOMEM) { + IDA_BUG_ON(&ida, (i % IDA_BITMAP_BITS) != + BITS_PER_LONG - 2); + id = ida_alloc(&ida, GFP_KERNEL); } else { - assert((i % IDA_BITMAP_BITS) != (BITS_PER_LONG - 2)); + IDA_BUG_ON(&ida, (i % IDA_BITMAP_BITS) == + BITS_PER_LONG - 2); } - assert(!err); - assert(id == i); + IDA_BUG_ON(&ida, id != i); } ida_destroy(&ida); } -/* - * Check allocations up to and slightly above the maximum allowed (2^31-1) ID. - * Allocating up to 2^31-1 should succeed, and then allocating the next one - * should fail. - */ -void ida_check_max(void) -{ - DEFINE_IDA(ida); - int id, err; - unsigned long i, j; - - for (j = 1; j < 65537; j *= 2) { - unsigned long base = (1UL << 31) - j; - for (i = 0; i < j; i++) { - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, base, &id)); - assert(id == base + i); - } - assert(ida_pre_get(&ida, GFP_KERNEL)); - err = ida_get_new_above(&ida, base, &id); - assert(err == -ENOSPC); - ida_destroy(&ida); - assert(ida_is_empty(&ida)); - rcu_barrier(); - } -} - void ida_check_random(void) { DEFINE_IDA(ida); DECLARE_BITMAP(bitmap, 2048); - int id, err; unsigned int i; time_t s = time(NULL); @@ -454,15 +374,11 @@ void ida_check_random(void) int bit = i & 2047; if (test_bit(bit, bitmap)) { __clear_bit(bit, bitmap); - ida_remove(&ida, bit); + ida_free(&ida, bit); } else { __set_bit(bit, bitmap); - do { - ida_pre_get(&ida, GFP_KERNEL); - err = ida_get_new_above(&ida, bit, &id); - } while (err == -EAGAIN); - assert(!err); - assert(id == bit); + IDA_BUG_ON(&ida, ida_alloc_min(&ida, bit, GFP_KERNEL) + != bit); } } ida_destroy(&ida); @@ -488,71 +404,12 @@ void ida_simple_get_remove_test(void) ida_destroy(&ida); } -void ida_checks(void) +void user_ida_checks(void) { - DEFINE_IDA(ida); - int id; - unsigned long i; - radix_tree_cpu_dead(1); - ida_check_nomem(); - - for (i = 0; i < 10000; i++) { - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new(&ida, &id)); - assert(id == i); - } - - ida_remove(&ida, 20); - ida_remove(&ida, 21); - for (i = 0; i < 3; i++) { - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new(&ida, &id)); - if (i == 2) - assert(id == 10000); - } - - for (i = 0; i < 5000; i++) - ida_remove(&ida, i); - - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, 5000, &id)); - assert(id == 10001); - - ida_destroy(&ida); - - assert(ida_is_empty(&ida)); - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, 1, &id)); - assert(id == 1); - - ida_remove(&ida, id); - assert(ida_is_empty(&ida)); - ida_destroy(&ida); - assert(ida_is_empty(&ida)); - - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, 1, &id)); - ida_destroy(&ida); - assert(ida_is_empty(&ida)); - - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, 1, &id)); - assert(id == 1); - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, 1025, &id)); - assert(id == 1025); - assert(ida_pre_get(&ida, GFP_KERNEL)); - assert(!ida_get_new_above(&ida, 10000, &id)); - assert(id == 10000); - ida_remove(&ida, 1025); - ida_destroy(&ida); - assert(ida_is_empty(&ida)); - - ida_check_leaf(); - ida_check_max(); - ida_check_conv(); + ida_check_nomem(); + ida_check_conv_user(); ida_check_random(); ida_simple_get_remove_test(); @@ -582,12 +439,19 @@ void ida_thread_tests(void) pthread_join(threads[i], NULL); } +void ida_tests(void) +{ + user_ida_checks(); + ida_checks(); + ida_exit(); + ida_thread_tests(); +} + int __weak main(void) { radix_tree_init(); idr_checks(); - ida_checks(); - ida_thread_tests(); + ida_tests(); radix_tree_cpu_dead(1); rcu_barrier(); if (nr_allocated) diff --git a/tools/testing/radix-tree/linux/xarray.h b/tools/testing/radix-tree/linux/xarray.h new file mode 100644 index 000000000000..df3812cda376 --- /dev/null +++ b/tools/testing/radix-tree/linux/xarray.h @@ -0,0 +1,2 @@ +#include "generated/map-shift.h" +#include "../../../../include/linux/xarray.h" diff --git a/tools/testing/radix-tree/main.c b/tools/testing/radix-tree/main.c index 257f3f8aacaa..b741686e53d6 100644 --- a/tools/testing/radix-tree/main.c +++ b/tools/testing/radix-tree/main.c @@ -27,20 +27,22 @@ void __gang_check(unsigned long middle, long down, long up, int chunk, int hop) item_check_present(&tree, middle + idx); item_check_absent(&tree, middle + up); - item_gang_check_present(&tree, middle - down, - up + down, chunk, hop); - item_full_scan(&tree, middle - down, down + up, chunk); + if (chunk > 0) { + item_gang_check_present(&tree, middle - down, up + down, + chunk, hop); + item_full_scan(&tree, middle - down, down + up, chunk); + } item_kill_tree(&tree); } void gang_check(void) { - __gang_check(1 << 30, 128, 128, 35, 2); - __gang_check(1 << 31, 128, 128, 32, 32); - __gang_check(1 << 31, 128, 128, 32, 100); - __gang_check(1 << 31, 128, 128, 17, 7); - __gang_check(0xffff0000, 0, 65536, 17, 7); - __gang_check(0xfffffffe, 1, 1, 17, 7); + __gang_check(1UL << 30, 128, 128, 35, 2); + __gang_check(1UL << 31, 128, 128, 32, 32); + __gang_check(1UL << 31, 128, 128, 32, 100); + __gang_check(1UL << 31, 128, 128, 17, 7); + __gang_check(0xffff0000UL, 0, 65536, 17, 7); + __gang_check(0xfffffffeUL, 1, 1, 17, 7); } void __big_gang_check(void) @@ -322,7 +324,7 @@ static void single_thread_tests(bool long_run) printv(2, "after dynamic_height_check: %d allocated, preempt %d\n", nr_allocated, preempt_count); idr_checks(); - ida_checks(); + ida_tests(); rcu_barrier(); printv(2, "after idr_checks: %d allocated, preempt %d\n", nr_allocated, preempt_count); @@ -369,7 +371,6 @@ int main(int argc, char **argv) iteration_test(0, 10 + 90 * long_run); iteration_test(7, 10 + 90 * long_run); single_thread_tests(long_run); - ida_thread_tests(); /* Free any remaining preallocated nodes */ radix_tree_cpu_dead(0); diff --git a/tools/testing/radix-tree/test.h b/tools/testing/radix-tree/test.h index 31f1d9b6f506..92d901eacf49 100644 --- a/tools/testing/radix-tree/test.h +++ b/tools/testing/radix-tree/test.h @@ -39,8 +39,7 @@ void multiorder_checks(void); void iteration_test(unsigned order, unsigned duration); void benchmark(void); void idr_checks(void); -void ida_checks(void); -void ida_thread_tests(void); +void ida_tests(void); struct item * item_tag_set(struct radix_tree_root *root, unsigned long index, int tag); diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index dd0e5163f01f..03b0f551bedf 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -11,13 +11,16 @@ TEST_GEN_PROGS_x86_64 += sync_regs_test TEST_GEN_PROGS_x86_64 += vmx_tsc_adjust_test TEST_GEN_PROGS_x86_64 += cr4_cpuid_sync_test TEST_GEN_PROGS_x86_64 += state_test +TEST_GEN_PROGS_x86_64 += dirty_log_test TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M)) LIBKVM += $(LIBKVM_$(UNAME_M)) INSTALL_HDR_PATH = $(top_srcdir)/usr LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/ -CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_HDR_PATH) -Iinclude -I$(<D) -I.. +LINUX_TOOL_INCLUDE = $(top_srcdir)tools/include +CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_TOOL_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude -I$(<D) -I.. +LDFLAGS += -lpthread # After inclusion, $(OUTPUT) is defined and # $(TEST_GEN_PROGS) starts with $(OUTPUT)/ diff --git a/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c b/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c index 8346b33c2073..11ec358bf969 100644 --- a/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c +++ b/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c @@ -23,20 +23,6 @@ #define X86_FEATURE_OSXSAVE (1<<27) #define VCPU_ID 1 -enum { - GUEST_UPDATE_CR4 = 0x1000, - GUEST_FAILED, - GUEST_DONE, -}; - -static void exit_to_hv(uint16_t port) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port) - : "rax"); -} - static inline bool cr4_cpuid_is_sync(void) { int func, subfunc; @@ -64,17 +50,15 @@ static void guest_code(void) set_cr4(cr4); /* verify CR4.OSXSAVE == CPUID.OSXSAVE */ - if (!cr4_cpuid_is_sync()) - exit_to_hv(GUEST_FAILED); + GUEST_ASSERT(cr4_cpuid_is_sync()); /* notify hypervisor to change CR4 */ - exit_to_hv(GUEST_UPDATE_CR4); + GUEST_SYNC(0); /* check again */ - if (!cr4_cpuid_is_sync()) - exit_to_hv(GUEST_FAILED); + GUEST_ASSERT(cr4_cpuid_is_sync()); - exit_to_hv(GUEST_DONE); + GUEST_DONE(); } int main(int argc, char *argv[]) @@ -95,7 +79,7 @@ int main(int argc, char *argv[]) setbuf(stdout, NULL); /* Create VM */ - vm = vm_create_default(VCPU_ID, guest_code); + vm = vm_create_default(VCPU_ID, 0, guest_code); vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); run = vcpu_state(vm, VCPU_ID); @@ -104,16 +88,16 @@ int main(int argc, char *argv[]) if (run->exit_reason == KVM_EXIT_IO) { switch (run->io.port) { - case GUEST_UPDATE_CR4: + case GUEST_PORT_SYNC: /* emulate hypervisor clearing CR4.OSXSAVE */ vcpu_sregs_get(vm, VCPU_ID, &sregs); sregs.cr4 &= ~X86_CR4_OSXSAVE; vcpu_sregs_set(vm, VCPU_ID, &sregs); break; - case GUEST_FAILED: + case GUEST_PORT_ABORT: TEST_ASSERT(false, "Guest CR4 bit (OSXSAVE) unsynchronized with CPUID bit."); break; - case GUEST_DONE: + case GUEST_PORT_DONE: goto done; default: TEST_ASSERT(false, "Unknown port 0x%x.", diff --git a/tools/testing/selftests/kvm/dirty_log_test.c b/tools/testing/selftests/kvm/dirty_log_test.c new file mode 100644 index 000000000000..0c2cdc105f96 --- /dev/null +++ b/tools/testing/selftests/kvm/dirty_log_test.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KVM dirty page logging test + * + * Copyright (C) 2018, Red Hat, Inc. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <pthread.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> + +#include "test_util.h" +#include "kvm_util.h" + +#define DEBUG printf + +#define VCPU_ID 1 +/* The memory slot index to track dirty pages */ +#define TEST_MEM_SLOT_INDEX 1 +/* + * GPA offset of the testing memory slot. Must be bigger than the + * default vm mem slot, which is DEFAULT_GUEST_PHY_PAGES. + */ +#define TEST_MEM_OFFSET (1ULL << 30) /* 1G */ +/* Size of the testing memory slot */ +#define TEST_MEM_PAGES (1ULL << 18) /* 1G for 4K pages */ +/* How many pages to dirty for each guest loop */ +#define TEST_PAGES_PER_LOOP 1024 +/* How many host loops to run (one KVM_GET_DIRTY_LOG for each loop) */ +#define TEST_HOST_LOOP_N 32 +/* Interval for each host loop (ms) */ +#define TEST_HOST_LOOP_INTERVAL 10 + +/* + * Guest variables. We use these variables to share data between host + * and guest. There are two copies of the variables, one in host memory + * (which is unused) and one in guest memory. When the host wants to + * access these variables, it needs to call addr_gva2hva() to access the + * guest copy. + */ +uint64_t guest_random_array[TEST_PAGES_PER_LOOP]; +uint64_t guest_iteration; +uint64_t guest_page_size; + +/* + * Writes to the first byte of a random page within the testing memory + * region continuously. + */ +void guest_code(void) +{ + int i = 0; + uint64_t volatile *array = guest_random_array; + uint64_t volatile *guest_addr; + + while (true) { + for (i = 0; i < TEST_PAGES_PER_LOOP; i++) { + /* + * Write to the first 8 bytes of a random page + * on the testing memory region. + */ + guest_addr = (uint64_t *) + (TEST_MEM_OFFSET + + (array[i] % TEST_MEM_PAGES) * guest_page_size); + *guest_addr = guest_iteration; + } + /* Tell the host that we need more random numbers */ + GUEST_SYNC(1); + } +} + +/* + * Host variables. These variables should only be used by the host + * rather than the guest. + */ +bool host_quit; + +/* Points to the test VM memory region on which we track dirty logs */ +void *host_test_mem; + +/* For statistics only */ +uint64_t host_dirty_count; +uint64_t host_clear_count; +uint64_t host_track_next_count; + +/* + * We use this bitmap to track some pages that should have its dirty + * bit set in the _next_ iteration. For example, if we detected the + * page value changed to current iteration but at the same time the + * page bit is cleared in the latest bitmap, then the system must + * report that write in the next get dirty log call. + */ +unsigned long *host_bmap_track; + +void generate_random_array(uint64_t *guest_array, uint64_t size) +{ + uint64_t i; + + for (i = 0; i < size; i++) { + guest_array[i] = random(); + } +} + +void *vcpu_worker(void *data) +{ + int ret; + uint64_t loops, *guest_array, pages_count = 0; + struct kvm_vm *vm = data; + struct kvm_run *run; + struct guest_args args; + + run = vcpu_state(vm, VCPU_ID); + + /* Retrieve the guest random array pointer and cache it */ + guest_array = addr_gva2hva(vm, (vm_vaddr_t)guest_random_array); + + DEBUG("VCPU starts\n"); + + generate_random_array(guest_array, TEST_PAGES_PER_LOOP); + + while (!READ_ONCE(host_quit)) { + /* Let the guest to dirty these random pages */ + ret = _vcpu_run(vm, VCPU_ID); + guest_args_read(vm, VCPU_ID, &args); + if (run->exit_reason == KVM_EXIT_IO && + args.port == GUEST_PORT_SYNC) { + pages_count += TEST_PAGES_PER_LOOP; + generate_random_array(guest_array, TEST_PAGES_PER_LOOP); + } else { + TEST_ASSERT(false, + "Invalid guest sync status: " + "exit_reason=%s\n", + exit_reason_str(run->exit_reason)); + } + } + + DEBUG("VCPU exits, dirtied %"PRIu64" pages\n", pages_count); + + return NULL; +} + +void vm_dirty_log_verify(unsigned long *bmap, uint64_t iteration) +{ + uint64_t page; + uint64_t volatile *value_ptr; + + for (page = 0; page < TEST_MEM_PAGES; page++) { + value_ptr = host_test_mem + page * getpagesize(); + + /* If this is a special page that we were tracking... */ + if (test_and_clear_bit(page, host_bmap_track)) { + host_track_next_count++; + TEST_ASSERT(test_bit(page, bmap), + "Page %"PRIu64" should have its dirty bit " + "set in this iteration but it is missing", + page); + } + + if (test_bit(page, bmap)) { + host_dirty_count++; + /* + * If the bit is set, the value written onto + * the corresponding page should be either the + * previous iteration number or the current one. + */ + TEST_ASSERT(*value_ptr == iteration || + *value_ptr == iteration - 1, + "Set page %"PRIu64" value %"PRIu64 + " incorrect (iteration=%"PRIu64")", + page, *value_ptr, iteration); + } else { + host_clear_count++; + /* + * If cleared, the value written can be any + * value smaller or equals to the iteration + * number. Note that the value can be exactly + * (iteration-1) if that write can happen + * like this: + * + * (1) increase loop count to "iteration-1" + * (2) write to page P happens (with value + * "iteration-1") + * (3) get dirty log for "iteration-1"; we'll + * see that page P bit is set (dirtied), + * and not set the bit in host_bmap_track + * (4) increase loop count to "iteration" + * (which is current iteration) + * (5) get dirty log for current iteration, + * we'll see that page P is cleared, with + * value "iteration-1". + */ + TEST_ASSERT(*value_ptr <= iteration, + "Clear page %"PRIu64" value %"PRIu64 + " incorrect (iteration=%"PRIu64")", + page, *value_ptr, iteration); + if (*value_ptr == iteration) { + /* + * This page is _just_ modified; it + * should report its dirtyness in the + * next run + */ + set_bit(page, host_bmap_track); + } + } + } +} + +void help(char *name) +{ + puts(""); + printf("usage: %s [-i iterations] [-I interval] [-h]\n", name); + puts(""); + printf(" -i: specify iteration counts (default: %"PRIu64")\n", + TEST_HOST_LOOP_N); + printf(" -I: specify interval in ms (default: %"PRIu64" ms)\n", + TEST_HOST_LOOP_INTERVAL); + puts(""); + exit(0); +} + +int main(int argc, char *argv[]) +{ + pthread_t vcpu_thread; + struct kvm_vm *vm; + uint64_t volatile *psize, *iteration; + unsigned long *bmap, iterations = TEST_HOST_LOOP_N, + interval = TEST_HOST_LOOP_INTERVAL; + int opt; + + while ((opt = getopt(argc, argv, "hi:I:")) != -1) { + switch (opt) { + case 'i': + iterations = strtol(optarg, NULL, 10); + break; + case 'I': + interval = strtol(optarg, NULL, 10); + break; + case 'h': + default: + help(argv[0]); + break; + } + } + + TEST_ASSERT(iterations > 2, "Iteration must be bigger than zero\n"); + TEST_ASSERT(interval > 0, "Interval must be bigger than zero"); + + DEBUG("Test iterations: %"PRIu64", interval: %"PRIu64" (ms)\n", + iterations, interval); + + srandom(time(0)); + + bmap = bitmap_alloc(TEST_MEM_PAGES); + host_bmap_track = bitmap_alloc(TEST_MEM_PAGES); + + vm = vm_create_default(VCPU_ID, TEST_MEM_PAGES, guest_code); + + /* Add an extra memory slot for testing dirty logging */ + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + TEST_MEM_OFFSET, + TEST_MEM_SLOT_INDEX, + TEST_MEM_PAGES, + KVM_MEM_LOG_DIRTY_PAGES); + /* Cache the HVA pointer of the region */ + host_test_mem = addr_gpa2hva(vm, (vm_paddr_t)TEST_MEM_OFFSET); + + /* Do 1:1 mapping for the dirty track memory slot */ + virt_map(vm, TEST_MEM_OFFSET, TEST_MEM_OFFSET, + TEST_MEM_PAGES * getpagesize(), 0); + + vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); + + /* Tell the guest about the page size on the system */ + psize = addr_gva2hva(vm, (vm_vaddr_t)&guest_page_size); + *psize = getpagesize(); + + /* Start the iterations */ + iteration = addr_gva2hva(vm, (vm_vaddr_t)&guest_iteration); + *iteration = 1; + + /* Start dirtying pages */ + pthread_create(&vcpu_thread, NULL, vcpu_worker, vm); + + while (*iteration < iterations) { + /* Give the vcpu thread some time to dirty some pages */ + usleep(interval * 1000); + kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap); + vm_dirty_log_verify(bmap, *iteration); + (*iteration)++; + } + + /* Tell the vcpu thread to quit */ + host_quit = true; + pthread_join(vcpu_thread, NULL); + + DEBUG("Total bits checked: dirty (%"PRIu64"), clear (%"PRIu64"), " + "track_next (%"PRIu64")\n", host_dirty_count, host_clear_count, + host_track_next_count); + + free(bmap); + free(host_bmap_track); + kvm_vm_free(vm); + + return 0; +} diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h index d32632f71ab8..bb5a25fb82c6 100644 --- a/tools/testing/selftests/kvm/include/kvm_util.h +++ b/tools/testing/selftests/kvm/include/kvm_util.h @@ -55,6 +55,7 @@ struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm); void kvm_vm_free(struct kvm_vm *vmp); void kvm_vm_restart(struct kvm_vm *vmp, int perm); void kvm_vm_release(struct kvm_vm *vmp); +void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log); int kvm_memcmp_hva_gva(void *hva, struct kvm_vm *vm, const vm_vaddr_t gva, size_t len); @@ -80,6 +81,8 @@ void vm_mem_region_set_flags(struct kvm_vm *vm, uint32_t slot, uint32_t flags); void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid, int pgd_memslot, int gdt_memslot); vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min, uint32_t data_memslot, uint32_t pgd_memslot); +void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr, + size_t size, uint32_t pgd_memslot); void *addr_gpa2hva(struct kvm_vm *vm, vm_paddr_t gpa); void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva); vm_paddr_t addr_hva2gpa(struct kvm_vm *vm, void *hva); @@ -127,7 +130,8 @@ kvm_get_supported_cpuid_entry(uint32_t function) return kvm_get_supported_cpuid_index(function, 0); } -struct kvm_vm *vm_create_default(uint32_t vcpuid, void *guest_code); +struct kvm_vm *vm_create_default(uint32_t vcpuid, uint64_t extra_mem_size, + void *guest_code); void vm_vcpu_add_default(struct kvm_vm *vm, uint32_t vcpuid, void *guest_code); typedef void (*vmx_guest_code_t)(vm_vaddr_t vmxon_vaddr, @@ -144,4 +148,43 @@ allocate_kvm_dirty_log(struct kvm_userspace_memory_region *region); int vm_create_device(struct kvm_vm *vm, struct kvm_create_device *cd); +#define GUEST_PORT_SYNC 0x1000 +#define GUEST_PORT_ABORT 0x1001 +#define GUEST_PORT_DONE 0x1002 + +static inline void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1) +{ + __asm__ __volatile__("in %[port], %%al" + : + : [port]"d"(port), "D"(arg0), "S"(arg1) + : "rax"); +} + +/* + * Allows to pass three arguments to the host: port is 16bit wide, + * arg0 & arg1 are 64bit wide + */ +#define GUEST_SYNC_ARGS(_port, _arg0, _arg1) \ + __exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1)) + +#define GUEST_ASSERT(_condition) do { \ + if (!(_condition)) \ + GUEST_SYNC_ARGS(GUEST_PORT_ABORT, \ + "Failed guest assert: " \ + #_condition, __LINE__); \ + } while (0) + +#define GUEST_SYNC(stage) GUEST_SYNC_ARGS(GUEST_PORT_SYNC, "hello", stage) + +#define GUEST_DONE() GUEST_SYNC_ARGS(GUEST_PORT_DONE, 0, 0) + +struct guest_args { + uint64_t arg0; + uint64_t arg1; + uint16_t port; +} __attribute__ ((packed)); + +void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id, + struct guest_args *args); + #endif /* SELFTEST_KVM_UTIL_H */ diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h index ac53730b30aa..73c3933436ec 100644 --- a/tools/testing/selftests/kvm/include/test_util.h +++ b/tools/testing/selftests/kvm/include/test_util.h @@ -28,8 +28,6 @@ int test_seq_read(const char *path, char **bufp, size_t *sizep); void test_assert(bool exp, const char *exp_str, const char *file, unsigned int line, const char *fmt, ...); -#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) - #define TEST_ASSERT(e, fmt, ...) \ test_assert((e), #e, __FILE__, __LINE__, fmt, ##__VA_ARGS__) diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index 643309d6de74..e9ba389c48db 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -14,6 +14,7 @@ #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> +#include <linux/kernel.h> #define KVM_DEV_PATH "/dev/kvm" @@ -168,6 +169,16 @@ void kvm_vm_restart(struct kvm_vm *vmp, int perm) } } +void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log) +{ + struct kvm_dirty_log args = { .dirty_bitmap = log, .slot = slot }; + int ret; + + ret = ioctl(vm->fd, KVM_GET_DIRTY_LOG, &args); + TEST_ASSERT(ret == 0, "%s: KVM_GET_DIRTY_LOG failed: %s", + strerror(-ret)); +} + /* Userspace Memory Region Find * * Input Args: @@ -923,6 +934,39 @@ vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min, return vaddr_start; } +/* + * Map a range of VM virtual address to the VM's physical address + * + * Input Args: + * vm - Virtual Machine + * vaddr - Virtuall address to map + * paddr - VM Physical Address + * size - The size of the range to map + * pgd_memslot - Memory region slot for new virtual translation tables + * + * Output Args: None + * + * Return: None + * + * Within the VM given by vm, creates a virtual translation for the + * page range starting at vaddr to the page range starting at paddr. + */ +void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr, + size_t size, uint32_t pgd_memslot) +{ + size_t page_size = vm->page_size; + size_t npages = size / page_size; + + TEST_ASSERT(vaddr + size > vaddr, "Vaddr overflow"); + TEST_ASSERT(paddr + size > paddr, "Paddr overflow"); + + while (npages--) { + virt_pg_map(vm, vaddr, paddr, pgd_memslot); + vaddr += page_size; + paddr += page_size; + } +} + /* Address VM Physical to Host Virtual * * Input Args: @@ -1536,3 +1580,17 @@ void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva) { return addr_gpa2hva(vm, addr_gva2gpa(vm, gva)); } + +void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id, + struct guest_args *args) +{ + struct kvm_run *run = vcpu_state(vm, vcpu_id); + struct kvm_regs regs; + + memset(®s, 0, sizeof(regs)); + vcpu_regs_get(vm, vcpu_id, ®s); + + args->port = run->io.port; + args->arg0 = regs.rdi; + args->arg1 = regs.rsi; +} diff --git a/tools/testing/selftests/kvm/lib/x86.c b/tools/testing/selftests/kvm/lib/x86.c index e38345252df5..a3122f1949a8 100644 --- a/tools/testing/selftests/kvm/lib/x86.c +++ b/tools/testing/selftests/kvm/lib/x86.c @@ -702,6 +702,9 @@ void vcpu_set_cpuid(struct kvm_vm *vm, * * Input Args: * vcpuid - The id of the single VCPU to add to the VM. + * extra_mem_pages - The size of extra memories to add (this will + * decide how much extra space we will need to + * setup the page tables using mem slot 0) * guest_code - The vCPU's entry point * * Output Args: None @@ -709,12 +712,23 @@ void vcpu_set_cpuid(struct kvm_vm *vm, * Return: * Pointer to opaque structure that describes the created VM. */ -struct kvm_vm *vm_create_default(uint32_t vcpuid, void *guest_code) +struct kvm_vm *vm_create_default(uint32_t vcpuid, uint64_t extra_mem_pages, + void *guest_code) { struct kvm_vm *vm; + /* + * For x86 the maximum page table size for a memory region + * will be when only 4K pages are used. In that case the + * total extra size for page tables (for extra N pages) will + * be: N/512+N/512^2+N/512^3+... which is definitely smaller + * than N/512*2. + */ + uint64_t extra_pg_pages = extra_mem_pages / 512 * 2; /* Create VM */ - vm = vm_create(VM_MODE_FLAT48PG, DEFAULT_GUEST_PHY_PAGES, O_RDWR); + vm = vm_create(VM_MODE_FLAT48PG, + DEFAULT_GUEST_PHY_PAGES + extra_pg_pages, + O_RDWR); /* Setup guest code */ kvm_vm_elf_load(vm, program_invocation_name, 0, 0); diff --git a/tools/testing/selftests/kvm/set_sregs_test.c b/tools/testing/selftests/kvm/set_sregs_test.c index 090fd3f19352..881419d5746e 100644 --- a/tools/testing/selftests/kvm/set_sregs_test.c +++ b/tools/testing/selftests/kvm/set_sregs_test.c @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) setbuf(stdout, NULL); /* Create VM */ - vm = vm_create_default(VCPU_ID, NULL); + vm = vm_create_default(VCPU_ID, 0, NULL); vcpu_sregs_get(vm, VCPU_ID, &sregs); sregs.apic_base = 1 << 10; diff --git a/tools/testing/selftests/kvm/state_test.c b/tools/testing/selftests/kvm/state_test.c index ecabf25b7077..900e3e9dfb9f 100644 --- a/tools/testing/selftests/kvm/state_test.c +++ b/tools/testing/selftests/kvm/state_test.c @@ -21,28 +21,6 @@ #include "vmx.h" #define VCPU_ID 5 -#define PORT_SYNC 0x1000 -#define PORT_ABORT 0x1001 -#define PORT_DONE 0x1002 - -static inline void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port), "D"(arg0), "S"(arg1) - : "rax"); -} - -#define exit_to_l0(_port, _arg0, _arg1) \ - __exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1)) - -#define GUEST_ASSERT(_condition) do { \ - if (!(_condition)) \ - exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition, __LINE__);\ -} while (0) - -#define GUEST_SYNC(stage) \ - exit_to_l0(PORT_SYNC, "hello", stage); static bool have_nested_state; @@ -137,7 +115,7 @@ void guest_code(struct vmx_pages *vmx_pages) if (vmx_pages) l1_guest_code(vmx_pages); - exit_to_l0(PORT_DONE, 0, 0); + GUEST_DONE(); } int main(int argc, char *argv[]) @@ -154,7 +132,7 @@ int main(int argc, char *argv[]) struct kvm_cpuid_entry2 *entry = kvm_get_supported_cpuid_entry(1); /* Create VM */ - vm = vm_create_default(VCPU_ID, guest_code); + vm = vm_create_default(VCPU_ID, 0, guest_code); vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); run = vcpu_state(vm, VCPU_ID); @@ -178,13 +156,13 @@ int main(int argc, char *argv[]) memset(®s1, 0, sizeof(regs1)); vcpu_regs_get(vm, VCPU_ID, ®s1); switch (run->io.port) { - case PORT_ABORT: + case GUEST_PORT_ABORT: TEST_ASSERT(false, "%s at %s:%d", (const char *) regs1.rdi, __FILE__, regs1.rsi); /* NOT REACHED */ - case PORT_SYNC: + case GUEST_PORT_SYNC: break; - case PORT_DONE: + case GUEST_PORT_DONE: goto done; default: TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port); diff --git a/tools/testing/selftests/kvm/sync_regs_test.c b/tools/testing/selftests/kvm/sync_regs_test.c index eae1ece3c31b..213343e5dff9 100644 --- a/tools/testing/selftests/kvm/sync_regs_test.c +++ b/tools/testing/selftests/kvm/sync_regs_test.c @@ -22,28 +22,11 @@ #include "x86.h" #define VCPU_ID 5 -#define PORT_HOST_SYNC 0x1000 - -static void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port), "D"(arg0), "S"(arg1) - : "rax"); -} - -#define exit_to_l0(_port, _arg0, _arg1) \ - __exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1)) - -#define GUEST_ASSERT(_condition) do { \ - if (!(_condition)) \ - exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition, 0);\ -} while (0) void guest_code(void) { for (;;) { - exit_to_l0(PORT_HOST_SYNC, "hello", 0); + GUEST_SYNC(0); asm volatile ("inc %r11"); } } @@ -111,7 +94,7 @@ int main(int argc, char *argv[]) } /* Create VM */ - vm = vm_create_default(VCPU_ID, guest_code); + vm = vm_create_default(VCPU_ID, 0, guest_code); run = vcpu_state(vm, VCPU_ID); diff --git a/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c b/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c index fc414c284368..49bcc68b0235 100644 --- a/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c +++ b/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c @@ -62,27 +62,12 @@ struct kvm_single_msr { /* The virtual machine object. */ static struct kvm_vm *vm; -#define exit_to_l0(_port, _arg) do_exit_to_l0(_port, (unsigned long) (_arg)) -static void do_exit_to_l0(uint16_t port, unsigned long arg) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port), "D"(arg) - : "rax"); -} - - -#define GUEST_ASSERT(_condition) do { \ - if (!(_condition)) \ - exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition); \ -} while (0) - static void check_ia32_tsc_adjust(int64_t max) { int64_t adjust; adjust = rdmsr(MSR_IA32_TSC_ADJUST); - exit_to_l0(PORT_REPORT, adjust); + GUEST_SYNC(adjust); GUEST_ASSERT(adjust <= max); } @@ -132,7 +117,7 @@ static void l1_guest_code(struct vmx_pages *vmx_pages) check_ia32_tsc_adjust(-2 * TSC_ADJUST_VALUE); - exit_to_l0(PORT_DONE, 0); + GUEST_DONE(); } void report(int64_t val) @@ -152,7 +137,7 @@ int main(int argc, char *argv[]) exit(KSFT_SKIP); } - vm = vm_create_default(VCPU_ID, (void *) l1_guest_code); + vm = vm_create_default(VCPU_ID, 0, (void *) l1_guest_code); vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); /* Allocate VMX pages and shared descriptors (vmx_pages). */ @@ -161,26 +146,26 @@ int main(int argc, char *argv[]) for (;;) { volatile struct kvm_run *run = vcpu_state(vm, VCPU_ID); - struct kvm_regs regs; + struct guest_args args; vcpu_run(vm, VCPU_ID); - vcpu_regs_get(vm, VCPU_ID, ®s); + guest_args_read(vm, VCPU_ID, &args); TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, - "Got exit_reason other than KVM_EXIT_IO: %u (%s), rip=%lx\n", + "Got exit_reason other than KVM_EXIT_IO: %u (%s)\n", run->exit_reason, - exit_reason_str(run->exit_reason), regs.rip); + exit_reason_str(run->exit_reason)); - switch (run->io.port) { - case PORT_ABORT: - TEST_ASSERT(false, "%s", (const char *) regs.rdi); + switch (args.port) { + case GUEST_PORT_ABORT: + TEST_ASSERT(false, "%s", (const char *) args.arg0); /* NOT REACHED */ - case PORT_REPORT: - report(regs.rdi); + case GUEST_PORT_SYNC: + report(args.arg1); break; - case PORT_DONE: + case GUEST_PORT_DONE: goto done; default: - TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port); + TEST_ASSERT(false, "Unknown port 0x%x.", args.port); } } diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore index 74e5912e9f2e..82121a81681f 100644 --- a/tools/testing/selftests/proc/.gitignore +++ b/tools/testing/selftests/proc/.gitignore @@ -9,3 +9,5 @@ /proc-uptime-001 /proc-uptime-002 /read +/self +/thread-self diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile index db310eedc268..1c12c34cf85d 100644 --- a/tools/testing/selftests/proc/Makefile +++ b/tools/testing/selftests/proc/Makefile @@ -1,4 +1,5 @@ CFLAGS += -Wall -O2 -Wno-unused-function +CFLAGS += -D_GNU_SOURCE TEST_GEN_PROGS := TEST_GEN_PROGS += fd-001-lookup @@ -12,5 +13,7 @@ TEST_GEN_PROGS += proc-self-wchan TEST_GEN_PROGS += proc-uptime-001 TEST_GEN_PROGS += proc-uptime-002 TEST_GEN_PROGS += read +TEST_GEN_PROGS += self +TEST_GEN_PROGS += thread-self include ../lib.mk diff --git a/tools/testing/selftests/proc/proc.h b/tools/testing/selftests/proc/proc.h index 4e178166fd84..b7d57ea40237 100644 --- a/tools/testing/selftests/proc/proc.h +++ b/tools/testing/selftests/proc/proc.h @@ -6,6 +6,18 @@ #include <stdbool.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> +#include <sys/syscall.h> + +static inline pid_t sys_getpid(void) +{ + return syscall(SYS_getpid); +} + +static inline pid_t sys_gettid(void) +{ + return syscall(SYS_gettid); +} static inline bool streq(const char *s1, const char *s2) { diff --git a/tools/testing/selftests/proc/self.c b/tools/testing/selftests/proc/self.c new file mode 100644 index 000000000000..21c15a1ffefb --- /dev/null +++ b/tools/testing/selftests/proc/self.c @@ -0,0 +1,39 @@ +/* + * Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +// Test that /proc/self gives correct TGID. +#undef NDEBUG +#include <assert.h> +#include <stdio.h> +#include <unistd.h> + +#include "proc.h" + +int main(void) +{ + char buf1[64], buf2[64]; + pid_t pid; + ssize_t rv; + + pid = sys_getpid(); + snprintf(buf1, sizeof(buf1), "%u", pid); + + rv = readlink("/proc/self", buf2, sizeof(buf2)); + assert(rv == strlen(buf1)); + buf2[rv] = '\0'; + assert(streq(buf1, buf2)); + + return 0; +} diff --git a/tools/testing/selftests/proc/thread-self.c b/tools/testing/selftests/proc/thread-self.c new file mode 100644 index 000000000000..4b23b39b7ae0 --- /dev/null +++ b/tools/testing/selftests/proc/thread-self.c @@ -0,0 +1,64 @@ +/* + * Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +// Test that /proc/thread-self gives correct TGID/PID. +#undef NDEBUG +#include <assert.h> +#include <sched.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/wait.h> + +#include "proc.h" + +int f(void *arg) +{ + char buf1[64], buf2[64]; + pid_t pid, tid; + ssize_t rv; + + pid = sys_getpid(); + tid = sys_gettid(); + snprintf(buf1, sizeof(buf1), "%u/task/%u", pid, tid); + + rv = readlink("/proc/thread-self", buf2, sizeof(buf2)); + assert(rv == strlen(buf1)); + buf2[rv] = '\0'; + assert(streq(buf1, buf2)); + + if (arg) + exit(0); + return 0; +} + +int main(void) +{ + const int PAGE_SIZE = sysconf(_SC_PAGESIZE); + pid_t pid; + void *stack; + + /* main thread */ + f((void *)0); + + stack = mmap(NULL, 2 * PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + assert(stack != MAP_FAILED); + /* side thread */ + pid = clone(f, stack + PAGE_SIZE, CLONE_THREAD|CLONE_SIGHAND|CLONE_VM, (void *)1); + assert(pid > 0); + pause(); + + return 0; +} diff --git a/tools/testing/selftests/vm/.gitignore b/tools/testing/selftests/vm/.gitignore index 342c7bc9dc8c..af5ff83f6d7f 100644 --- a/tools/testing/selftests/vm/.gitignore +++ b/tools/testing/selftests/vm/.gitignore @@ -1,6 +1,7 @@ hugepage-mmap hugepage-shm map_hugetlb +map_populate thuge-gen compaction_test mlock2-tests diff --git a/tools/testing/selftests/vm/Makefile b/tools/testing/selftests/vm/Makefile index fdefa2295ddc..9881876d2aa0 100644 --- a/tools/testing/selftests/vm/Makefile +++ b/tools/testing/selftests/vm/Makefile @@ -12,6 +12,7 @@ TEST_GEN_FILES += gup_benchmark TEST_GEN_FILES += hugepage-mmap TEST_GEN_FILES += hugepage-shm TEST_GEN_FILES += map_hugetlb +TEST_GEN_FILES += map_populate TEST_GEN_FILES += mlock-random-test TEST_GEN_FILES += mlock2-tests TEST_GEN_FILES += on-fault-limit diff --git a/tools/testing/selftests/vm/map_populate.c b/tools/testing/selftests/vm/map_populate.c new file mode 100644 index 000000000000..6b8aeaa0bf7a --- /dev/null +++ b/tools/testing/selftests/vm/map_populate.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Dmitry Safonov, Arista Networks + * + * MAP_POPULATE | MAP_PRIVATE should COW VMA pages. + */ + +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifndef MMAP_SZ +#define MMAP_SZ 4096 +#endif + +#define BUG_ON(condition, description) \ + do { \ + if (condition) { \ + fprintf(stderr, "[FAIL]\t%s:%d\t%s:%s\n", __func__, \ + __LINE__, (description), strerror(errno)); \ + exit(1); \ + } \ + } while (0) + +static int parent_f(int sock, unsigned long *smap, int child) +{ + int status, ret; + + ret = read(sock, &status, sizeof(int)); + BUG_ON(ret <= 0, "read(sock)"); + + *smap = 0x22222BAD; + ret = msync(smap, MMAP_SZ, MS_SYNC); + BUG_ON(ret, "msync()"); + + ret = write(sock, &status, sizeof(int)); + BUG_ON(ret <= 0, "write(sock)"); + + waitpid(child, &status, 0); + BUG_ON(!WIFEXITED(status), "child in unexpected state"); + + return WEXITSTATUS(status); +} + +static int child_f(int sock, unsigned long *smap, int fd) +{ + int ret, buf = 0; + + smap = mmap(0, MMAP_SZ, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_POPULATE, fd, 0); + BUG_ON(smap == MAP_FAILED, "mmap()"); + + BUG_ON(*smap != 0xdeadbabe, "MAP_PRIVATE | MAP_POPULATE changed file"); + + ret = write(sock, &buf, sizeof(int)); + BUG_ON(ret <= 0, "write(sock)"); + + ret = read(sock, &buf, sizeof(int)); + BUG_ON(ret <= 0, "read(sock)"); + + BUG_ON(*smap == 0x22222BAD, "MAP_POPULATE didn't COW private page"); + BUG_ON(*smap != 0xdeadbabe, "mapping was corrupted"); + + return 0; +} + +int main(int argc, char **argv) +{ + int sock[2], child, ret; + FILE *ftmp; + unsigned long *smap; + + ftmp = tmpfile(); + BUG_ON(ftmp == 0, "tmpfile()"); + + ret = ftruncate(fileno(ftmp), MMAP_SZ); + BUG_ON(ret, "ftruncate()"); + + smap = mmap(0, MMAP_SZ, PROT_READ | PROT_WRITE, + MAP_SHARED, fileno(ftmp), 0); + BUG_ON(smap == MAP_FAILED, "mmap()"); + + *smap = 0xdeadbabe; + /* Probably unnecessary, but let it be. */ + ret = msync(smap, MMAP_SZ, MS_SYNC); + BUG_ON(ret, "msync()"); + + ret = socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sock); + BUG_ON(ret, "socketpair()"); + + child = fork(); + BUG_ON(child == -1, "fork()"); + + if (child) { + ret = close(sock[0]); + BUG_ON(ret, "close()"); + + return parent_f(sock[1], smap, child); + } + + ret = close(sock[1]); + BUG_ON(ret, "close()"); + + return child_f(sock[0], smap, fileno(ftmp)); +} diff --git a/tools/testing/selftests/vm/run_vmtests b/tools/testing/selftests/vm/run_vmtests index 88cbe5575f0c..584a91ae4a8f 100755 --- a/tools/testing/selftests/vm/run_vmtests +++ b/tools/testing/selftests/vm/run_vmtests @@ -168,6 +168,17 @@ else fi echo "--------------------" +echo "running map_populate" +echo "--------------------" +./map_populate +if [ $? -ne 0 ]; then + echo "[FAIL]" + exitcode=1 +else + echo "[PASS]" +fi + +echo "--------------------" echo "running mlock2-tests" echo "--------------------" ./mlock2-tests |