2018-08-22 10:20:00 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* KVM dirty page logging test
*
* Copyright ( C ) 2018 , Red Hat , Inc .
*/
2018-09-18 20:54:34 +03:00
# define _GNU_SOURCE /* for program_invocation_name */
2018-08-22 10:20:00 +03:00
# 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"
2018-09-18 20:54:28 +03:00
# include "processor.h"
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:32 +03:00
# define DEBUG printf
# define VCPU_ID 1
2018-08-22 10:20:00 +03:00
/* The memory slot index to track dirty pages */
2018-09-18 20:54:32 +03:00
# define TEST_MEM_SLOT_INDEX 1
2018-08-22 10:20:00 +03:00
/*
* GPA offset of the testing memory slot . Must be bigger than the
* default vm mem slot , which is DEFAULT_GUEST_PHY_PAGES .
*/
2018-09-18 20:54:32 +03:00
# define TEST_MEM_OFFSET (1ul << 30) /* 1G */
2018-08-22 10:20:00 +03:00
/* How many pages to dirty for each guest loop */
2018-09-18 20:54:32 +03:00
# define TEST_PAGES_PER_LOOP 1024
2018-08-22 10:20:00 +03:00
/* How many host loops to run (one KVM_GET_DIRTY_LOG for each loop) */
2018-09-18 20:54:32 +03:00
# define TEST_HOST_LOOP_N 32
2018-08-22 10:20:00 +03:00
/* Interval for each host loop (ms) */
2018-09-18 20:54:32 +03:00
# define TEST_HOST_LOOP_INTERVAL 10
2018-08-22 10:20:00 +03:00
/*
2018-09-18 20:54:32 +03:00
* Guest / Host shared variables . Ensure addr_gva2hva ( ) and / or
* sync_global_to / from_guest ( ) are used when accessing from
* the host . READ / WRITE_ONCE ( ) should also be used with anything
* that may change .
2018-08-22 10:20:00 +03:00
*/
2018-09-18 20:54:32 +03:00
static uint64_t host_page_size ;
static uint64_t guest_page_size ;
2018-09-18 20:54:34 +03:00
static uint64_t guest_num_pages ;
2018-09-18 20:54:32 +03:00
static uint64_t random_array [ TEST_PAGES_PER_LOOP ] ;
static uint64_t iteration ;
2018-08-22 10:20:00 +03:00
/*
2018-09-18 20:54:32 +03:00
* Continuously write to the first 8 bytes of a random pages within
* the testing memory region .
2018-08-22 10:20:00 +03:00
*/
2018-09-18 20:54:32 +03:00
static void guest_code ( void )
2018-08-22 10:20:00 +03:00
{
2018-09-18 20:54:32 +03:00
int i ;
2018-08-22 10:20:00 +03:00
while ( true ) {
for ( i = 0 ; i < TEST_PAGES_PER_LOOP ; i + + ) {
2018-09-18 20:54:32 +03:00
uint64_t addr = TEST_MEM_OFFSET ;
2018-09-18 20:54:34 +03:00
addr + = ( READ_ONCE ( random_array [ i ] ) % guest_num_pages )
2018-09-18 20:54:32 +03:00
* guest_page_size ;
addr & = ~ ( host_page_size - 1 ) ;
* ( uint64_t * ) addr = READ_ONCE ( iteration ) ;
2018-08-22 10:20:00 +03:00
}
2018-09-18 20:54:32 +03:00
2018-08-22 10:20:00 +03:00
/* Tell the host that we need more random numbers */
GUEST_SYNC ( 1 ) ;
}
}
2018-09-18 20:54:32 +03:00
/* Host variables */
static bool host_quit ;
2018-08-22 10:20:00 +03:00
/* Points to the test VM memory region on which we track dirty logs */
2018-09-18 20:54:32 +03:00
static void * host_test_mem ;
static uint64_t host_num_pages ;
2018-08-22 10:20:00 +03:00
/* For statistics only */
2018-09-18 20:54:32 +03:00
static uint64_t host_dirty_count ;
static uint64_t host_clear_count ;
static uint64_t host_track_next_count ;
2018-08-22 10:20:00 +03:00
/*
* 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 .
*/
2018-09-18 20:54:32 +03:00
static unsigned long * host_bmap_track ;
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:32 +03:00
static void generate_random_array ( uint64_t * guest_array , uint64_t size )
2018-08-22 10:20:00 +03:00
{
uint64_t i ;
2018-09-18 20:54:32 +03:00
for ( i = 0 ; i < size ; i + + )
2018-08-22 10:20:00 +03:00
guest_array [ i ] = random ( ) ;
}
2018-09-18 20:54:32 +03:00
static void * vcpu_worker ( void * data )
2018-08-22 10:20:00 +03:00
{
int ret ;
struct kvm_vm * vm = data ;
2018-09-18 20:54:32 +03:00
uint64_t * guest_array ;
uint64_t pages_count = 0 ;
2018-08-22 10:20:00 +03:00
struct kvm_run * run ;
2018-09-18 20:54:25 +03:00
struct ucall uc ;
2018-08-22 10:20:00 +03:00
run = vcpu_state ( vm , VCPU_ID ) ;
2018-09-18 20:54:32 +03:00
guest_array = addr_gva2hva ( vm , ( vm_vaddr_t ) random_array ) ;
2018-08-22 10:20:00 +03:00
generate_random_array ( guest_array , TEST_PAGES_PER_LOOP ) ;
while ( ! READ_ONCE ( host_quit ) ) {
2018-09-18 20:54:32 +03:00
/* Let the guest dirty the random pages */
2018-08-22 10:20:00 +03:00
ret = _vcpu_run ( vm , VCPU_ID ) ;
2018-09-18 20:54:32 +03:00
if ( get_ucall ( vm , VCPU_ID , & uc ) = = UCALL_SYNC ) {
2018-08-22 10:20:00 +03:00
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 ) ) ;
}
}
2018-09-18 20:54:32 +03:00
DEBUG ( " Dirtied % " PRIu64 " pages \n " , pages_count ) ;
2018-08-22 10:20:00 +03:00
return NULL ;
}
2018-09-18 20:54:32 +03:00
static void vm_dirty_log_verify ( unsigned long * bmap )
2018-08-22 10:20:00 +03:00
{
uint64_t page ;
2018-09-18 20:54:32 +03:00
uint64_t * value_ptr ;
2018-09-18 20:54:34 +03:00
uint64_t step = host_page_size > = guest_page_size ? 1 :
guest_page_size / host_page_size ;
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:34 +03:00
for ( page = 0 ; page < host_num_pages ; page + = step ) {
2018-09-18 20:54:32 +03:00
value_ptr = host_test_mem + page * host_page_size ;
2018-08-22 10:20:00 +03:00
/* 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 ) ;
}
}
}
}
2018-09-18 20:54:34 +03:00
static struct kvm_vm * create_vm ( enum vm_guest_mode mode , uint32_t vcpuid ,
uint64_t extra_mem_pages , void * guest_code )
2018-08-22 10:20:00 +03:00
{
2018-09-18 20:54:34 +03:00
struct kvm_vm * vm ;
uint64_t extra_pg_pages = extra_mem_pages / 512 * 2 ;
vm = vm_create ( mode , DEFAULT_GUEST_PHY_PAGES + extra_pg_pages , O_RDWR ) ;
kvm_vm_elf_load ( vm , program_invocation_name , 0 , 0 ) ;
# ifdef __x86_64__
vm_create_irqchip ( vm ) ;
# endif
vm_vcpu_add_default ( vm , vcpuid , guest_code ) ;
return vm ;
2018-08-22 10:20:00 +03:00
}
2018-09-18 20:54:34 +03:00
static void run_test ( enum vm_guest_mode mode , unsigned long iterations ,
unsigned long interval )
2018-08-22 10:20:00 +03:00
{
2018-09-18 20:54:34 +03:00
unsigned int guest_page_shift ;
2018-08-22 10:20:00 +03:00
pthread_t vcpu_thread ;
struct kvm_vm * vm ;
2018-09-18 20:54:32 +03:00
unsigned long * bmap ;
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:34 +03:00
switch ( mode ) {
case VM_MODE_P52V48_4K :
2018-09-18 20:54:35 +03:00
case VM_MODE_P40V48_4K :
2018-09-18 20:54:34 +03:00
guest_page_shift = 12 ;
break ;
case VM_MODE_P52V48_64K :
2018-09-18 20:54:35 +03:00
case VM_MODE_P40V48_64K :
2018-09-18 20:54:34 +03:00
guest_page_shift = 16 ;
break ;
default :
TEST_ASSERT ( false , " Unknown guest mode, mode: 0x%x " , mode ) ;
2018-08-22 10:20:00 +03:00
}
2018-09-18 20:54:34 +03:00
DEBUG ( " Testing guest mode: %s \n " , vm_guest_mode_string ( mode ) ) ;
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:34 +03:00
guest_page_size = ( 1ul < < guest_page_shift ) ;
/* 1G of guest page sized pages */
guest_num_pages = ( 1ul < < ( 30 - guest_page_shift ) ) ;
2018-09-18 20:54:32 +03:00
host_page_size = getpagesize ( ) ;
2018-09-18 20:54:34 +03:00
host_num_pages = ( guest_num_pages * guest_page_size ) / host_page_size +
! ! ( ( guest_num_pages * guest_page_size ) % host_page_size ) ;
2018-09-18 20:54:32 +03:00
bmap = bitmap_alloc ( host_num_pages ) ;
host_bmap_track = bitmap_alloc ( host_num_pages ) ;
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:34 +03:00
vm = create_vm ( mode , VCPU_ID , guest_num_pages , guest_code ) ;
2018-08-22 10:20:00 +03:00
/* 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 ,
2018-09-18 20:54:34 +03:00
guest_num_pages ,
2018-08-22 10:20:00 +03:00
KVM_MEM_LOG_DIRTY_PAGES ) ;
/* Do 1:1 mapping for the dirty track memory slot */
virt_map ( vm , TEST_MEM_OFFSET , TEST_MEM_OFFSET ,
2018-09-18 20:54:34 +03:00
guest_num_pages * guest_page_size , 0 ) ;
2018-09-18 20:54:32 +03:00
/* Cache the HVA pointer of the region */
host_test_mem = addr_gpa2hva ( vm , ( vm_paddr_t ) TEST_MEM_OFFSET ) ;
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:32 +03:00
# ifdef __x86_64__
2018-08-22 10:20:00 +03:00
vcpu_set_cpuid ( vm , VCPU_ID , kvm_get_supported_cpuid ( ) ) ;
2018-09-18 20:54:32 +03:00
# endif
# ifdef __aarch64__
ucall_init ( vm , UCALL_MMIO , NULL ) ;
# endif
2018-08-22 10:20:00 +03:00
2018-09-18 20:54:34 +03:00
/* Export the shared variables to the guest */
2018-09-18 20:54:32 +03:00
sync_global_to_guest ( vm , host_page_size ) ;
sync_global_to_guest ( vm , guest_page_size ) ;
2018-09-18 20:54:34 +03:00
sync_global_to_guest ( vm , guest_num_pages ) ;
2018-08-22 10:20:00 +03:00
/* Start the iterations */
2018-09-18 20:54:32 +03:00
iteration = 1 ;
sync_global_to_guest ( vm , iteration ) ;
2018-09-18 20:54:34 +03:00
host_quit = false ;
host_dirty_count = 0 ;
host_clear_count = 0 ;
host_track_next_count = 0 ;
2018-08-22 10:20:00 +03:00
pthread_create ( & vcpu_thread , NULL , vcpu_worker , vm ) ;
2018-09-18 20:54:32 +03:00
while ( iteration < iterations ) {
2018-08-22 10:20:00 +03:00
/* Give the vcpu thread some time to dirty some pages */
usleep ( interval * 1000 ) ;
kvm_vm_get_dirty_log ( vm , TEST_MEM_SLOT_INDEX , bmap ) ;
2018-09-18 20:54:32 +03:00
vm_dirty_log_verify ( bmap ) ;
iteration + + ;
sync_global_to_guest ( vm , iteration ) ;
2018-08-22 10:20:00 +03:00
}
/* 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 ) ;
2018-09-18 20:54:32 +03:00
ucall_uninit ( vm ) ;
2018-08-22 10:20:00 +03:00
kvm_vm_free ( vm ) ;
2018-09-18 20:54:34 +03:00
}
static struct vm_guest_modes {
enum vm_guest_mode mode ;
bool supported ;
bool enabled ;
} vm_guest_modes [ NUM_VM_MODES ] = {
2018-09-18 20:54:35 +03:00
# if defined(__x86_64__)
2018-09-18 20:54:34 +03:00
{ VM_MODE_P52V48_4K , 1 , 1 , } ,
{ VM_MODE_P52V48_64K , 0 , 0 , } ,
2018-09-18 20:54:35 +03:00
{ VM_MODE_P40V48_4K , 0 , 0 , } ,
{ VM_MODE_P40V48_64K , 0 , 0 , } ,
# elif defined(__aarch64__)
{ VM_MODE_P52V48_4K , 0 , 0 , } ,
{ VM_MODE_P52V48_64K , 0 , 0 , } ,
{ VM_MODE_P40V48_4K , 1 , 1 , } ,
{ VM_MODE_P40V48_64K , 1 , 1 , } ,
2018-09-18 20:54:34 +03:00
# endif
} ;
static void help ( char * name )
{
int i ;
puts ( " " ) ;
printf ( " usage: %s [-h] [-i iterations] [-I interval] [-m mode] \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 ) ;
printf ( " -m: specify the guest mode ID to test "
" (default: test all supported modes) \n "
" This option may be used multiple times. \n "
" Guest mode IDs: \n " ) ;
for ( i = 0 ; i < NUM_VM_MODES ; + + i ) {
printf ( " %d: %s%s \n " ,
vm_guest_modes [ i ] . mode ,
vm_guest_mode_string ( vm_guest_modes [ i ] . mode ) ,
vm_guest_modes [ i ] . supported ? " (supported) " : " " ) ;
}
puts ( " " ) ;
exit ( 0 ) ;
}
int main ( int argc , char * argv [ ] )
{
unsigned long iterations = TEST_HOST_LOOP_N ;
unsigned long interval = TEST_HOST_LOOP_INTERVAL ;
bool mode_selected = false ;
unsigned int mode ;
int opt , i ;
while ( ( opt = getopt ( argc , argv , " hi:I:m: " ) ) ! = - 1 ) {
switch ( opt ) {
case ' i ' :
iterations = strtol ( optarg , NULL , 10 ) ;
break ;
case ' I ' :
interval = strtol ( optarg , NULL , 10 ) ;
break ;
case ' m ' :
if ( ! mode_selected ) {
for ( i = 0 ; i < NUM_VM_MODES ; + + i )
vm_guest_modes [ i ] . enabled = 0 ;
mode_selected = true ;
}
mode = strtoul ( optarg , NULL , 10 ) ;
TEST_ASSERT ( mode < NUM_VM_MODES ,
" Guest mode ID %d too big " , mode ) ;
vm_guest_modes [ mode ] . enabled = 1 ;
break ;
case ' h ' :
default :
help ( argv [ 0 ] ) ;
break ;
}
}
TEST_ASSERT ( iterations > 2 , " Iterations must be greater than two " ) ;
TEST_ASSERT ( interval > 0 , " Interval must be greater than zero " ) ;
DEBUG ( " Test iterations: % " PRIu64 " , interval: % " PRIu64 " (ms) \n " ,
iterations , interval ) ;
srandom ( time ( 0 ) ) ;
for ( i = 0 ; i < NUM_VM_MODES ; + + i ) {
if ( ! vm_guest_modes [ i ] . enabled )
continue ;
TEST_ASSERT ( vm_guest_modes [ i ] . supported ,
" Guest mode ID %d (%s) not supported. " ,
vm_guest_modes [ i ] . mode ,
vm_guest_mode_string ( vm_guest_modes [ i ] . mode ) ) ;
run_test ( vm_guest_modes [ i ] . mode , iterations , interval ) ;
}
2018-08-22 10:20:00 +03:00
return 0 ;
}