#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <libgen.h>
#include <sys/stat.h>
#include <errno.h>
#include <err.h>
#include <assert.h>
#include <sys/sysmacros.h>
#include <stdbool.h>
#include <sys/vmm.h>
#include <sys/vmm_dev.h>
#include <sys/vmm_data.h>
#include <vmmapi.h>
#include "common.h"
static void
should_eq_u32(const char *field_name, uint32_t a, uint32_t b)
{
if (a != b) {
errx(EXIT_FAILURE, "unexpected %s %u != %u",
field_name, a, b);
}
}
static void
test_size_boundaries(int vmfd)
{
uint8_t buf[sizeof (struct vdi_atpic_v1) + sizeof (int)];
struct vm_data_xfer vdx = {
.vdx_class = VDC_ATPIC,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_atpic_v1),
.vdx_data = buf,
};
if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
err(EXIT_FAILURE, "valid VM_DATA_READ failed");
}
should_eq_u32("vdx_result_len", vdx.vdx_result_len,
sizeof (struct vdi_atpic_v1));
vdx.vdx_result_len = 0;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
err(EXIT_FAILURE, "valid VM_DATA_WRITE failed");
}
vdx.vdx_len = sizeof (struct vdi_atpic_v1) - sizeof (int);
vdx.vdx_result_len = 0;
if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
errx(EXIT_FAILURE, "invalid VM_DATA_READ should have failed");
}
int error = errno;
if (error != ENOSPC) {
errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error);
}
should_eq_u32("vdx_result_len", vdx.vdx_result_len,
sizeof (struct vdi_atpic_v1));
vdx.vdx_result_len = 0;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
errx(EXIT_FAILURE, "invalid VM_DATA_WRITE should have failed");
}
error = errno;
if (error != ENOSPC) {
errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error);
}
should_eq_u32("vdx_result_len", vdx.vdx_result_len,
sizeof (struct vdi_atpic_v1));
vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int);
vdx.vdx_result_len = 0;
if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
err(EXIT_FAILURE, "too-large (but valid) VM_DATA_READ failed");
}
should_eq_u32("vdx_result_len", vdx.vdx_result_len,
sizeof (struct vdi_atpic_v1));
vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int);
vdx.vdx_result_len = 0;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
err(EXIT_FAILURE,
"too-large (but valid) VM_DATA_WRITE failed");
}
should_eq_u32("vdx_result_len", vdx.vdx_result_len,
sizeof (struct vdi_atpic_v1));
}
struct class_test_case {
uint16_t ctc_class;
uint16_t ctc_version;
};
static void
test_vm_classes(int vmfd)
{
const struct class_test_case cases[] = {
{ VDC_VERSION, 1 },
{ VDC_VMM_ARCH, 1 },
{ VDC_IOAPIC, 1 },
{ VDC_ATPIT, 1 },
{ VDC_ATPIC, 1 },
{ VDC_HPET, 1 },
{ VDC_PM_TIMER, 1 },
{ VDC_RTC, 2 },
{ VDC_VMM_TIME, 1 },
};
const size_t bufsz = PAGESIZE;
uint8_t *buf = malloc(bufsz);
for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) {
struct vm_data_xfer vdx = {
.vdx_class = cases[i].ctc_class,
.vdx_version = cases[i].ctc_version,
.vdx_len = bufsz,
.vdx_data = buf,
.vdx_vcpuid = -1,
};
if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
err(EXIT_FAILURE,
"VM_DATA_READ failed class:%u version:%u",
vdx.vdx_class, vdx.vdx_version);
}
if (vdx.vdx_class == VDC_VERSION ||
vdx.vdx_class == VDC_VMM_ARCH) {
continue;
}
vdx.vdx_len = vdx.vdx_result_len;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
err(EXIT_FAILURE,
"VM_DATA_WRITE failed class:%u version:%u",
vdx.vdx_class, vdx.vdx_version);
}
}
free(buf);
}
static void
test_vcpu_classes(int vmfd)
{
const struct class_test_case cases[] = {
{ VDC_MSR, 1 },
{ VDC_LAPIC, 1 },
{ VDC_VMM_ARCH, 1 },
};
const size_t bufsz = PAGESIZE;
uint8_t *buf = malloc(bufsz);
for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) {
struct vm_data_xfer vdx = {
.vdx_class = cases[i].ctc_class,
.vdx_version = cases[i].ctc_version,
.vdx_len = bufsz,
.vdx_data = buf,
.vdx_vcpuid = 0,
};
if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
err(EXIT_FAILURE,
"VM_DATA_READ failed class:%u version:%u",
vdx.vdx_class, vdx.vdx_version);
}
if (vdx.vdx_class == VDC_VMM_ARCH) {
continue;
}
vdx.vdx_len = vdx.vdx_result_len;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
err(EXIT_FAILURE,
"VM_DATA_WRITE failed class:%u version:%u",
vdx.vdx_class, vdx.vdx_version);
}
}
free(buf);
}
static void
test_bogus_class(int vmfd)
{
const size_t bufsz = PAGESIZE;
uint8_t *buf = malloc(bufsz);
struct vm_data_xfer vdx = {
.vdx_class = 10000,
.vdx_version = 1,
.vdx_len = bufsz,
.vdx_data = buf,
};
if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
errx(EXIT_FAILURE,
"VM_DATA_READ should fail for absurd vdx_class");
}
vdx.vdx_class = VDC_VERSION;
vdx.vdx_version = 10000;
if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
errx(EXIT_FAILURE,
"VM_DATA_READ should fail for absurd vdx_version");
}
free(buf);
}
static void
test_vcpuid_combos(int vmfd)
{
const size_t bufsz = PAGESIZE;
uint8_t *buf = malloc(bufsz);
struct vm_data_xfer vdx = {
.vdx_class = VDC_LAPIC,
.vdx_version = 1,
.vdx_len = bufsz,
.vdx_data = buf,
};
const int bad_per_vcpu[] = { -1, -5, 1000 };
for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) {
vdx.vdx_vcpuid = bad_per_vcpu[i];
if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
errx(EXIT_FAILURE,
"VM_DATA_READ should fail for bad vcpuid %d",
vdx.vdx_vcpuid);
}
}
vdx.vdx_vcpuid = 0;
if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
err(EXIT_FAILURE, "failed VM_DATA_READ with valid vcpuid");
}
for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) {
vdx.vdx_vcpuid = bad_per_vcpu[i];
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
errx(EXIT_FAILURE,
"VM_DATA_WRITE should fail for bad vcpuid %d",
vdx.vdx_vcpuid);
}
}
vdx.vdx_vcpuid = 0;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
err(EXIT_FAILURE, "failed VM_DATA_WRITE with valid vcpuid");
}
vdx.vdx_class = VDC_VERSION;
vdx.vdx_version = 1;
const int good_vm_wide[] = { -1, 0, 1 };
for (uint_t i = 0; i < ARRAY_SIZE(good_vm_wide); i++) {
vdx.vdx_vcpuid = good_vm_wide[i];
if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
err(EXIT_FAILURE,
"failed VM-wide VM_DATA_READ with vcpuid %d",
vdx.vdx_vcpuid);
}
}
const int bad_vm_wide[] = { -5, 1000 };
for (uint_t i = 0; i < ARRAY_SIZE(bad_vm_wide); i++) {
vdx.vdx_vcpuid = bad_vm_wide[i];
if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
errx(EXIT_FAILURE,
"VM_DATA_READ should fail for bad vcpuid %d",
vdx.vdx_vcpuid);
}
}
free(buf);
}
static void
test_vcpuid_time(int vmfd)
{
struct vdi_time_info_v1 data;
struct vm_data_xfer vdx = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (data),
.vdx_data = &data,
};
vdx.vdx_vcpuid = -1;
if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
err(EXIT_FAILURE, "VM_DATA_READ failed for valid vcpuid");
}
vdx.vdx_vcpuid = 0;
if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
err(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d",
vdx.vdx_vcpuid);
}
vdx.vdx_vcpuid = -1;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
err(EXIT_FAILURE, "VM_DATA_WRITE failed for valid vcpuid");
}
vdx.vdx_vcpuid = 0;
if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
errx(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d",
vdx.vdx_vcpuid);
}
}
int
main(int argc, char *argv[])
{
const char *suite_name = basename(argv[0]);
struct vmctx *ctx;
ctx = create_test_vm(suite_name);
if (ctx == NULL) {
errx(EXIT_FAILURE, "could not open test VM");
}
const int vmfd = vm_get_device_fd(ctx);
test_size_boundaries(vmfd);
test_vm_classes(vmfd);
test_vcpu_classes(vmfd);
test_bogus_class(vmfd);
test_vcpuid_combos(vmfd);
test_vcpuid_time(vmfd);
vm_destroy(ctx);
(void) printf("%s\tPASS\n", suite_name);
return (EXIT_SUCCESS);
}