#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <libgen.h>
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/debug.h>
#include <sys/mman.h>
#include <sys/vmm.h>
#include <sys/vmm_dev.h>
#include <vmmapi.h>
#include "common.h"
#include "in_guest.h"
#define PAGE_SZ 4096
#define DIRTY_BITMAP_SZ (MEM_TOTAL_SZ / (PAGE_SZ * 8))
static void
read_dirty_bitmap(struct vmctx *ctx, uint8_t *bitmap)
{
struct vmm_dirty_tracker track = {
.vdt_start_gpa = 0,
.vdt_len = MEM_TOTAL_SZ,
.vdt_pfns = (void *)bitmap,
};
int err = ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &track);
if (err != 0) {
test_fail_errno(errno, "Could not get dirty page bitmap");
}
}
static uint8_t
popc8(uint8_t val)
{
uint8_t cnt;
for (cnt = 0; val != 0; val &= (val - 1)) {
cnt++;
}
return (cnt);
}
static uint_t
count_dirty_pages(const uint8_t *bitmap)
{
uint_t count = 0;
for (uint_t i = 0; i < DIRTY_BITMAP_SZ; i++) {
count += popc8(bitmap[i]);
}
return (count);
}
void
check_supported(const char *test_suite_name)
{
char name[VM_MAX_NAMELEN];
int err;
name_test_vm(test_suite_name, name);
err = vm_create(name, VCF_TRACK_DIRTY);
if (err == 0) {
err = destroy_instance(test_suite_name);
if (err != 0) {
(void) fprintf(stderr,
"Could not destroy VM: %s\n", strerror(errno));
(void) printf("FAIL %s\n", test_suite_name);
exit(EXIT_FAILURE);
}
} else if (errno == ENOTSUP) {
(void) printf(
"Skipping test: dirty page tracking not supported\n");
(void) printf("PASS %s\n", test_suite_name);
exit(EXIT_SUCCESS);
} else {
}
}
void
test_dirty_tracking_disabled(const char *test_suite_name)
{
struct vmctx *ctx = NULL;
struct vcpu *vcpu;
int err;
uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 };
struct vmm_dirty_tracker track = {
.vdt_start_gpa = 0,
.vdt_len = MEM_TOTAL_SZ,
.vdt_pfns = (void *)dirty_bitmap,
};
ctx = test_initialize_flags(test_suite_name, 0);
if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
test_fail_errno(errno, "Could not open vcpu0");
}
err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
if (err != 0) {
test_fail_errno(err, "Could not initialize vcpu0");
}
err = ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &track);
if (err == 0) {
test_fail_msg("VM_TRACK_DIRTY_PAGES succeeded unexpectedly\n");
} else if (errno != EPERM) {
test_fail_errno(errno,
"VM_TRACK_DIRTY_PAGES failed with unexpected error");
}
test_cleanup(false);
}
int
main(int argc, char *argv[])
{
const char *test_suite_name = basename(argv[0]);
struct vmctx *ctx = NULL;
struct vcpu *vcpu;
int err;
check_supported(test_suite_name);
test_dirty_tracking_disabled(test_suite_name);
ctx = test_initialize_flags(test_suite_name, VCF_TRACK_DIRTY);
if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
test_fail_errno(errno, "Could not open vcpu0");
}
err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
if (err != 0) {
test_fail_errno(err, "Could not initialize vcpu0");
}
uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 };
read_dirty_bitmap(ctx, dirty_bitmap);
if (count_dirty_pages(dirty_bitmap) == 0) {
test_fail_msg("no pages dirtied during setup\n");
}
read_dirty_bitmap(ctx, dirty_bitmap);
if (count_dirty_pages(dirty_bitmap) != 0) {
test_fail_msg("pages still dirty after clear\n");
}
uint8_t *dptr = vm_map_gpa(ctx, MEM_LOC_STACK, 1);
*dptr = 1;
read_dirty_bitmap(ctx, dirty_bitmap);
if (count_dirty_pages(dirty_bitmap) != 1) {
test_fail_msg("direct access did not dirty page\n");
}
if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) {
test_fail_msg("unexpected page dirtied\n");
}
*dptr = 2;
if (count_dirty_pages(dirty_bitmap) != 1) {
test_fail_msg("subsequent direct access did not dirty page\n");
}
struct vm_entry ventry = { 0 };
struct vm_exit vexit = { 0 };
do {
const enum vm_exit_kind kind =
test_run_vcpu(vcpu, &ventry, &vexit);
switch (kind) {
case VEK_REENTR:
break;
case VEK_TEST_PASS:
read_dirty_bitmap(ctx, dirty_bitmap);
if (count_dirty_pages(dirty_bitmap) <= 1) {
test_fail_msg(
"in-guest access did not dirty page\n");
}
if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) {
test_fail_msg("expected page not dirtied\n");
}
test_pass();
break;
default:
test_fail_vmexit(&vexit);
break;
}
} while (true);
}