| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Test for s390x CMMA migration |
| * |
| * Copyright IBM Corp. 2023 |
| * |
| * Authors: |
| * Nico Boehr <nrb@linux.ibm.com> |
| */ |
| |
| #define _GNU_SOURCE /* for program_invocation_short_name */ |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| |
| #include "test_util.h" |
| #include "kvm_util.h" |
| #include "kselftest.h" |
| |
| #define MAIN_PAGE_COUNT 512 |
| |
| #define TEST_DATA_PAGE_COUNT 512 |
| #define TEST_DATA_MEMSLOT 1 |
| #define TEST_DATA_START_GFN 4096 |
| |
| #define TEST_DATA_TWO_PAGE_COUNT 256 |
| #define TEST_DATA_TWO_MEMSLOT 2 |
| #define TEST_DATA_TWO_START_GFN 8192 |
| |
| static char cmma_value_buf[MAIN_PAGE_COUNT + TEST_DATA_PAGE_COUNT]; |
| |
| /** |
| * Dirty CMMA attributes of exactly one page in the TEST_DATA memslot, |
| * so use_cmma goes on and the CMMA related ioctls do something. |
| */ |
| static void guest_do_one_essa(void) |
| { |
| asm volatile( |
| /* load TEST_DATA_START_GFN into r1 */ |
| " llilf 1,%[start_gfn]\n" |
| /* calculate the address from the gfn */ |
| " sllg 1,1,12(0)\n" |
| /* set the first page in TEST_DATA memslot to STABLE */ |
| " .insn rrf,0xb9ab0000,2,1,1,0\n" |
| /* hypercall */ |
| " diag 0,0,0x501\n" |
| "0: j 0b" |
| : |
| : [start_gfn] "L"(TEST_DATA_START_GFN) |
| : "r1", "r2", "memory", "cc" |
| ); |
| } |
| |
| /** |
| * Touch CMMA attributes of all pages in TEST_DATA memslot. Set them to stable |
| * state. |
| */ |
| static void guest_dirty_test_data(void) |
| { |
| asm volatile( |
| /* r1 = TEST_DATA_START_GFN */ |
| " xgr 1,1\n" |
| " llilf 1,%[start_gfn]\n" |
| /* r5 = TEST_DATA_PAGE_COUNT */ |
| " lghi 5,%[page_count]\n" |
| /* r5 += r1 */ |
| "2: agfr 5,1\n" |
| /* r2 = r1 << 12 */ |
| "1: sllg 2,1,12(0)\n" |
| /* essa(r4, r2, SET_STABLE) */ |
| " .insn rrf,0xb9ab0000,4,2,1,0\n" |
| /* i++ */ |
| " agfi 1,1\n" |
| /* if r1 < r5 goto 1 */ |
| " cgrjl 1,5,1b\n" |
| /* hypercall */ |
| " diag 0,0,0x501\n" |
| "0: j 0b" |
| : |
| : [start_gfn] "L"(TEST_DATA_START_GFN), |
| [page_count] "L"(TEST_DATA_PAGE_COUNT) |
| : |
| /* the counter in our loop over the pages */ |
| "r1", |
| /* the calculated page physical address */ |
| "r2", |
| /* ESSA output register */ |
| "r4", |
| /* last page */ |
| "r5", |
| "cc", "memory" |
| ); |
| } |
| |
| static void create_main_memslot(struct kvm_vm *vm) |
| { |
| int i; |
| |
| vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, MAIN_PAGE_COUNT, 0); |
| /* set the array of memslots to zero like __vm_create does */ |
| for (i = 0; i < NR_MEM_REGIONS; i++) |
| vm->memslots[i] = 0; |
| } |
| |
| static void create_test_memslot(struct kvm_vm *vm) |
| { |
| vm_userspace_mem_region_add(vm, |
| VM_MEM_SRC_ANONYMOUS, |
| TEST_DATA_START_GFN << vm->page_shift, |
| TEST_DATA_MEMSLOT, |
| TEST_DATA_PAGE_COUNT, |
| 0 |
| ); |
| vm->memslots[MEM_REGION_TEST_DATA] = TEST_DATA_MEMSLOT; |
| } |
| |
| static void create_memslots(struct kvm_vm *vm) |
| { |
| /* |
| * Our VM has the following memory layout: |
| * +------+---------------------------+ |
| * | GFN | Memslot | |
| * +------+---------------------------+ |
| * | 0 | | |
| * | ... | MAIN (Code, Stack, ...) | |
| * | 511 | | |
| * +------+---------------------------+ |
| * | 4096 | | |
| * | ... | TEST_DATA | |
| * | 4607 | | |
| * +------+---------------------------+ |
| */ |
| create_main_memslot(vm); |
| create_test_memslot(vm); |
| } |
| |
| static void finish_vm_setup(struct kvm_vm *vm) |
| { |
| struct userspace_mem_region *slot0; |
| |
| kvm_vm_elf_load(vm, program_invocation_name); |
| |
| slot0 = memslot2region(vm, 0); |
| ucall_init(vm, slot0->region.guest_phys_addr + slot0->region.memory_size); |
| |
| kvm_arch_vm_post_create(vm); |
| } |
| |
| static struct kvm_vm *create_vm_two_memslots(void) |
| { |
| struct kvm_vm *vm; |
| |
| vm = vm_create_barebones(); |
| |
| create_memslots(vm); |
| |
| finish_vm_setup(vm); |
| |
| return vm; |
| } |
| |
| static void enable_cmma(struct kvm_vm *vm) |
| { |
| int r; |
| |
| r = __kvm_device_attr_set(vm->fd, KVM_S390_VM_MEM_CTRL, KVM_S390_VM_MEM_ENABLE_CMMA, NULL); |
| TEST_ASSERT(!r, "enabling cmma failed r=%d errno=%d", r, errno); |
| } |
| |
| static void enable_dirty_tracking(struct kvm_vm *vm) |
| { |
| vm_mem_region_set_flags(vm, 0, KVM_MEM_LOG_DIRTY_PAGES); |
| vm_mem_region_set_flags(vm, TEST_DATA_MEMSLOT, KVM_MEM_LOG_DIRTY_PAGES); |
| } |
| |
| static int __enable_migration_mode(struct kvm_vm *vm) |
| { |
| return __kvm_device_attr_set(vm->fd, |
| KVM_S390_VM_MIGRATION, |
| KVM_S390_VM_MIGRATION_START, |
| NULL |
| ); |
| } |
| |
| static void enable_migration_mode(struct kvm_vm *vm) |
| { |
| int r = __enable_migration_mode(vm); |
| |
| TEST_ASSERT(!r, "enabling migration mode failed r=%d errno=%d", r, errno); |
| } |
| |
| static bool is_migration_mode_on(struct kvm_vm *vm) |
| { |
| u64 out; |
| int r; |
| |
| r = __kvm_device_attr_get(vm->fd, |
| KVM_S390_VM_MIGRATION, |
| KVM_S390_VM_MIGRATION_STATUS, |
| &out |
| ); |
| TEST_ASSERT(!r, "getting migration mode status failed r=%d errno=%d", r, errno); |
| return out; |
| } |
| |
| static int vm_get_cmma_bits(struct kvm_vm *vm, u64 flags, int *errno_out) |
| { |
| struct kvm_s390_cmma_log args; |
| int rc; |
| |
| errno = 0; |
| |
| args = (struct kvm_s390_cmma_log){ |
| .start_gfn = 0, |
| .count = sizeof(cmma_value_buf), |
| .flags = flags, |
| .values = (__u64)&cmma_value_buf[0] |
| }; |
| rc = __vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); |
| |
| *errno_out = errno; |
| return rc; |
| } |
| |
| static void test_get_cmma_basic(void) |
| { |
| struct kvm_vm *vm = create_vm_two_memslots(); |
| struct kvm_vcpu *vcpu; |
| int rc, errno_out; |
| |
| /* GET_CMMA_BITS without CMMA enabled should fail */ |
| rc = vm_get_cmma_bits(vm, 0, &errno_out); |
| TEST_ASSERT_EQ(rc, -1); |
| TEST_ASSERT_EQ(errno_out, ENXIO); |
| |
| enable_cmma(vm); |
| vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa); |
| |
| vcpu_run(vcpu); |
| |
| /* GET_CMMA_BITS without migration mode and without peeking should fail */ |
| rc = vm_get_cmma_bits(vm, 0, &errno_out); |
| TEST_ASSERT_EQ(rc, -1); |
| TEST_ASSERT_EQ(errno_out, EINVAL); |
| |
| /* GET_CMMA_BITS without migration mode and with peeking should work */ |
| rc = vm_get_cmma_bits(vm, KVM_S390_CMMA_PEEK, &errno_out); |
| TEST_ASSERT_EQ(rc, 0); |
| TEST_ASSERT_EQ(errno_out, 0); |
| |
| enable_dirty_tracking(vm); |
| enable_migration_mode(vm); |
| |
| /* GET_CMMA_BITS with invalid flags */ |
| rc = vm_get_cmma_bits(vm, 0xfeedc0fe, &errno_out); |
| TEST_ASSERT_EQ(rc, -1); |
| TEST_ASSERT_EQ(errno_out, EINVAL); |
| |
| kvm_vm_free(vm); |
| } |
| |
| static void assert_exit_was_hypercall(struct kvm_vcpu *vcpu) |
| { |
| TEST_ASSERT_EQ(vcpu->run->exit_reason, 13); |
| TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, 4); |
| TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x8300); |
| TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipb, 0x5010000); |
| } |
| |
| static void test_migration_mode(void) |
| { |
| struct kvm_vm *vm = vm_create_barebones(); |
| struct kvm_vcpu *vcpu; |
| u64 orig_psw; |
| int rc; |
| |
| /* enabling migration mode on a VM without memory should fail */ |
| rc = __enable_migration_mode(vm); |
| TEST_ASSERT_EQ(rc, -1); |
| TEST_ASSERT_EQ(errno, EINVAL); |
| TEST_ASSERT(!is_migration_mode_on(vm), "migration mode should still be off"); |
| errno = 0; |
| |
| create_memslots(vm); |
| finish_vm_setup(vm); |
| |
| enable_cmma(vm); |
| vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa); |
| orig_psw = vcpu->run->psw_addr; |
| |
| /* |
| * Execute one essa instruction in the guest. Otherwise the guest will |
| * not have use_cmm enabled and GET_CMMA_BITS will return no pages. |
| */ |
| vcpu_run(vcpu); |
| assert_exit_was_hypercall(vcpu); |
| |
| /* migration mode when memslots have dirty tracking off should fail */ |
| rc = __enable_migration_mode(vm); |
| TEST_ASSERT_EQ(rc, -1); |
| TEST_ASSERT_EQ(errno, EINVAL); |
| TEST_ASSERT(!is_migration_mode_on(vm), "migration mode should still be off"); |
| errno = 0; |
| |
| /* enable dirty tracking */ |
| enable_dirty_tracking(vm); |
| |
| /* enabling migration mode should work now */ |
| rc = __enable_migration_mode(vm); |
| TEST_ASSERT_EQ(rc, 0); |
| TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); |
| errno = 0; |
| |
| /* execute another ESSA instruction to see this goes fine */ |
| vcpu->run->psw_addr = orig_psw; |
| vcpu_run(vcpu); |
| assert_exit_was_hypercall(vcpu); |
| |
| /* |
| * With migration mode on, create a new memslot with dirty tracking off. |
| * This should turn off migration mode. |
| */ |
| TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); |
| vm_userspace_mem_region_add(vm, |
| VM_MEM_SRC_ANONYMOUS, |
| TEST_DATA_TWO_START_GFN << vm->page_shift, |
| TEST_DATA_TWO_MEMSLOT, |
| TEST_DATA_TWO_PAGE_COUNT, |
| 0 |
| ); |
| TEST_ASSERT(!is_migration_mode_on(vm), |
| "creating memslot without dirty tracking turns off migration mode" |
| ); |
| |
| /* ESSA instructions should still execute fine */ |
| vcpu->run->psw_addr = orig_psw; |
| vcpu_run(vcpu); |
| assert_exit_was_hypercall(vcpu); |
| |
| /* |
| * Turn on dirty tracking on the new memslot. |
| * It should be possible to turn migration mode back on again. |
| */ |
| vm_mem_region_set_flags(vm, TEST_DATA_TWO_MEMSLOT, KVM_MEM_LOG_DIRTY_PAGES); |
| rc = __enable_migration_mode(vm); |
| TEST_ASSERT_EQ(rc, 0); |
| TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); |
| errno = 0; |
| |
| /* |
| * Turn off dirty tracking again, this time with just a flag change. |
| * Again, migration mode should turn off. |
| */ |
| TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); |
| vm_mem_region_set_flags(vm, TEST_DATA_TWO_MEMSLOT, 0); |
| TEST_ASSERT(!is_migration_mode_on(vm), |
| "disabling dirty tracking should turn off migration mode" |
| ); |
| |
| /* ESSA instructions should still execute fine */ |
| vcpu->run->psw_addr = orig_psw; |
| vcpu_run(vcpu); |
| assert_exit_was_hypercall(vcpu); |
| |
| kvm_vm_free(vm); |
| } |
| |
| /** |
| * Given a VM with the MAIN and TEST_DATA memslot, assert that both slots have |
| * CMMA attributes of all pages in both memslots and nothing more dirty. |
| * This has the useful side effect of ensuring nothing is CMMA dirty after this |
| * function. |
| */ |
| static void assert_all_slots_cmma_dirty(struct kvm_vm *vm) |
| { |
| struct kvm_s390_cmma_log args; |
| |
| /* |
| * First iteration - everything should be dirty. |
| * Start at the main memslot... |
| */ |
| args = (struct kvm_s390_cmma_log){ |
| .start_gfn = 0, |
| .count = sizeof(cmma_value_buf), |
| .flags = 0, |
| .values = (__u64)&cmma_value_buf[0] |
| }; |
| memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); |
| vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); |
| TEST_ASSERT_EQ(args.count, MAIN_PAGE_COUNT); |
| TEST_ASSERT_EQ(args.remaining, TEST_DATA_PAGE_COUNT); |
| TEST_ASSERT_EQ(args.start_gfn, 0); |
| |
| /* ...and then - after a hole - the TEST_DATA memslot should follow */ |
| args = (struct kvm_s390_cmma_log){ |
| .start_gfn = MAIN_PAGE_COUNT, |
| .count = sizeof(cmma_value_buf), |
| .flags = 0, |
| .values = (__u64)&cmma_value_buf[0] |
| }; |
| memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); |
| vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); |
| TEST_ASSERT_EQ(args.count, TEST_DATA_PAGE_COUNT); |
| TEST_ASSERT_EQ(args.start_gfn, TEST_DATA_START_GFN); |
| TEST_ASSERT_EQ(args.remaining, 0); |
| |
| /* ...and nothing else should be there */ |
| args = (struct kvm_s390_cmma_log){ |
| .start_gfn = TEST_DATA_START_GFN + TEST_DATA_PAGE_COUNT, |
| .count = sizeof(cmma_value_buf), |
| .flags = 0, |
| .values = (__u64)&cmma_value_buf[0] |
| }; |
| memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); |
| vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); |
| TEST_ASSERT_EQ(args.count, 0); |
| TEST_ASSERT_EQ(args.start_gfn, 0); |
| TEST_ASSERT_EQ(args.remaining, 0); |
| } |
| |
| /** |
| * Given a VM, assert no pages are CMMA dirty. |
| */ |
| static void assert_no_pages_cmma_dirty(struct kvm_vm *vm) |
| { |
| struct kvm_s390_cmma_log args; |
| |
| /* If we start from GFN 0 again, nothing should be dirty. */ |
| args = (struct kvm_s390_cmma_log){ |
| .start_gfn = 0, |
| .count = sizeof(cmma_value_buf), |
| .flags = 0, |
| .values = (__u64)&cmma_value_buf[0] |
| }; |
| memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); |
| vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); |
| if (args.count || args.remaining || args.start_gfn) |
| TEST_FAIL("pages are still dirty start_gfn=0x%llx count=%u remaining=%llu", |
| args.start_gfn, |
| args.count, |
| args.remaining |
| ); |
| } |
| |
| static void test_get_inital_dirty(void) |
| { |
| struct kvm_vm *vm = create_vm_two_memslots(); |
| struct kvm_vcpu *vcpu; |
| |
| enable_cmma(vm); |
| vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa); |
| |
| /* |
| * Execute one essa instruction in the guest. Otherwise the guest will |
| * not have use_cmm enabled and GET_CMMA_BITS will return no pages. |
| */ |
| vcpu_run(vcpu); |
| assert_exit_was_hypercall(vcpu); |
| |
| enable_dirty_tracking(vm); |
| enable_migration_mode(vm); |
| |
| assert_all_slots_cmma_dirty(vm); |
| |
| /* Start from the beginning again and make sure nothing else is dirty */ |
| assert_no_pages_cmma_dirty(vm); |
| |
| kvm_vm_free(vm); |
| } |
| |
| static void query_cmma_range(struct kvm_vm *vm, |
| u64 start_gfn, u64 gfn_count, |
| struct kvm_s390_cmma_log *res_out) |
| { |
| *res_out = (struct kvm_s390_cmma_log){ |
| .start_gfn = start_gfn, |
| .count = gfn_count, |
| .flags = 0, |
| .values = (__u64)&cmma_value_buf[0] |
| }; |
| memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); |
| vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, res_out); |
| } |
| |
| /** |
| * Assert the given cmma_log struct that was executed by query_cmma_range() |
| * indicates the first dirty gfn is at first_dirty_gfn and contains exactly |
| * dirty_gfn_count CMMA values. |
| */ |
| static void assert_cmma_dirty(u64 first_dirty_gfn, |
| u64 dirty_gfn_count, |
| const struct kvm_s390_cmma_log *res) |
| { |
| TEST_ASSERT_EQ(res->start_gfn, first_dirty_gfn); |
| TEST_ASSERT_EQ(res->count, dirty_gfn_count); |
| for (size_t i = 0; i < dirty_gfn_count; i++) |
| TEST_ASSERT_EQ(cmma_value_buf[0], 0x0); /* stable state */ |
| TEST_ASSERT_EQ(cmma_value_buf[dirty_gfn_count], 0xff); /* not touched */ |
| } |
| |
| static void test_get_skip_holes(void) |
| { |
| size_t gfn_offset; |
| struct kvm_vm *vm = create_vm_two_memslots(); |
| struct kvm_s390_cmma_log log; |
| struct kvm_vcpu *vcpu; |
| u64 orig_psw; |
| |
| enable_cmma(vm); |
| vcpu = vm_vcpu_add(vm, 1, guest_dirty_test_data); |
| |
| orig_psw = vcpu->run->psw_addr; |
| |
| /* |
| * Execute some essa instructions in the guest. Otherwise the guest will |
| * not have use_cmm enabled and GET_CMMA_BITS will return no pages. |
| */ |
| vcpu_run(vcpu); |
| assert_exit_was_hypercall(vcpu); |
| |
| enable_dirty_tracking(vm); |
| enable_migration_mode(vm); |
| |
| /* un-dirty all pages */ |
| assert_all_slots_cmma_dirty(vm); |
| |
| /* Then, dirty just the TEST_DATA memslot */ |
| vcpu->run->psw_addr = orig_psw; |
| vcpu_run(vcpu); |
| |
| gfn_offset = TEST_DATA_START_GFN; |
| /** |
| * Query CMMA attributes of one page, starting at page 0. Since the |
| * main memslot was not touched by the VM, this should yield the first |
| * page of the TEST_DATA memslot. |
| * The dirty bitmap should now look like this: |
| * 0: not dirty |
| * [0x1, 0x200): dirty |
| */ |
| query_cmma_range(vm, 0, 1, &log); |
| assert_cmma_dirty(gfn_offset, 1, &log); |
| gfn_offset++; |
| |
| /** |
| * Query CMMA attributes of 32 (0x20) pages past the end of the TEST_DATA |
| * memslot. This should wrap back to the beginning of the TEST_DATA |
| * memslot, page 1. |
| * The dirty bitmap should now look like this: |
| * [0, 0x21): not dirty |
| * [0x21, 0x200): dirty |
| */ |
| query_cmma_range(vm, TEST_DATA_START_GFN + TEST_DATA_PAGE_COUNT, 0x20, &log); |
| assert_cmma_dirty(gfn_offset, 0x20, &log); |
| gfn_offset += 0x20; |
| |
| /* Skip 32 pages */ |
| gfn_offset += 0x20; |
| |
| /** |
| * After skipping 32 pages, query the next 32 (0x20) pages. |
| * The dirty bitmap should now look like this: |
| * [0, 0x21): not dirty |
| * [0x21, 0x41): dirty |
| * [0x41, 0x61): not dirty |
| * [0x61, 0x200): dirty |
| */ |
| query_cmma_range(vm, gfn_offset, 0x20, &log); |
| assert_cmma_dirty(gfn_offset, 0x20, &log); |
| gfn_offset += 0x20; |
| |
| /** |
| * Query 1 page from the beginning of the TEST_DATA memslot. This should |
| * yield page 0x21. |
| * The dirty bitmap should now look like this: |
| * [0, 0x22): not dirty |
| * [0x22, 0x41): dirty |
| * [0x41, 0x61): not dirty |
| * [0x61, 0x200): dirty |
| */ |
| query_cmma_range(vm, TEST_DATA_START_GFN, 1, &log); |
| assert_cmma_dirty(TEST_DATA_START_GFN + 0x21, 1, &log); |
| gfn_offset++; |
| |
| /** |
| * Query 15 (0xF) pages from page 0x23 in TEST_DATA memslot. |
| * This should yield pages [0x23, 0x33). |
| * The dirty bitmap should now look like this: |
| * [0, 0x22): not dirty |
| * 0x22: dirty |
| * [0x23, 0x33): not dirty |
| * [0x33, 0x41): dirty |
| * [0x41, 0x61): not dirty |
| * [0x61, 0x200): dirty |
| */ |
| gfn_offset = TEST_DATA_START_GFN + 0x23; |
| query_cmma_range(vm, gfn_offset, 15, &log); |
| assert_cmma_dirty(gfn_offset, 15, &log); |
| |
| /** |
| * Query 17 (0x11) pages from page 0x22 in TEST_DATA memslot. |
| * This should yield page [0x22, 0x33) |
| * The dirty bitmap should now look like this: |
| * [0, 0x33): not dirty |
| * [0x33, 0x41): dirty |
| * [0x41, 0x61): not dirty |
| * [0x61, 0x200): dirty |
| */ |
| gfn_offset = TEST_DATA_START_GFN + 0x22; |
| query_cmma_range(vm, gfn_offset, 17, &log); |
| assert_cmma_dirty(gfn_offset, 17, &log); |
| |
| /** |
| * Query 25 (0x19) pages from page 0x40 in TEST_DATA memslot. |
| * This should yield page 0x40 and nothing more, since there are more |
| * than 16 non-dirty pages after page 0x40. |
| * The dirty bitmap should now look like this: |
| * [0, 0x33): not dirty |
| * [0x33, 0x40): dirty |
| * [0x40, 0x61): not dirty |
| * [0x61, 0x200): dirty |
| */ |
| gfn_offset = TEST_DATA_START_GFN + 0x40; |
| query_cmma_range(vm, gfn_offset, 25, &log); |
| assert_cmma_dirty(gfn_offset, 1, &log); |
| |
| /** |
| * Query pages [0x33, 0x40). |
| * The dirty bitmap should now look like this: |
| * [0, 0x61): not dirty |
| * [0x61, 0x200): dirty |
| */ |
| gfn_offset = TEST_DATA_START_GFN + 0x33; |
| query_cmma_range(vm, gfn_offset, 0x40 - 0x33, &log); |
| assert_cmma_dirty(gfn_offset, 0x40 - 0x33, &log); |
| |
| /** |
| * Query the remaining pages [0x61, 0x200). |
| */ |
| gfn_offset = TEST_DATA_START_GFN; |
| query_cmma_range(vm, gfn_offset, TEST_DATA_PAGE_COUNT - 0x61, &log); |
| assert_cmma_dirty(TEST_DATA_START_GFN + 0x61, TEST_DATA_PAGE_COUNT - 0x61, &log); |
| |
| assert_no_pages_cmma_dirty(vm); |
| } |
| |
| struct testdef { |
| const char *name; |
| void (*test)(void); |
| } testlist[] = { |
| { "migration mode and dirty tracking", test_migration_mode }, |
| { "GET_CMMA_BITS: basic calls", test_get_cmma_basic }, |
| { "GET_CMMA_BITS: all pages are dirty initally", test_get_inital_dirty }, |
| { "GET_CMMA_BITS: holes are skipped", test_get_skip_holes }, |
| }; |
| |
| /** |
| * The kernel may support CMMA, but the machine may not (i.e. if running as |
| * guest-3). |
| * |
| * In this case, the CMMA capabilities are all there, but the CMMA-related |
| * ioctls fail. To find out whether the machine supports CMMA, create a |
| * temporary VM and then query the CMMA feature of the VM. |
| */ |
| static int machine_has_cmma(void) |
| { |
| struct kvm_vm *vm = vm_create_barebones(); |
| int r; |
| |
| r = !__kvm_has_device_attr(vm->fd, KVM_S390_VM_MEM_CTRL, KVM_S390_VM_MEM_ENABLE_CMMA); |
| kvm_vm_free(vm); |
| |
| return r; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int idx; |
| |
| TEST_REQUIRE(kvm_has_cap(KVM_CAP_SYNC_REGS)); |
| TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_CMMA_MIGRATION)); |
| TEST_REQUIRE(machine_has_cmma()); |
| |
| ksft_print_header(); |
| |
| ksft_set_plan(ARRAY_SIZE(testlist)); |
| |
| for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) { |
| testlist[idx].test(); |
| ksft_test_result_pass("%s\n", testlist[idx].name); |
| } |
| |
| ksft_finished(); /* Print results and exit() accordingly */ |
| } |