#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <errno.h>
#include <err.h>
#include <sys/vmm_dev.h>
#include <sys/vmm_data.h>
#include <vmmapi.h>
#include "common.h"
#define AMD_TSC_MIN_FREQ 500000000
#define AMD_TSC_MAX_FREQ_RATIO 15
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
should_eq_u64(const char *field_name, uint64_t a, uint64_t b)
{
if (a != b) {
errx(EXIT_FAILURE, "unexpected %s %lu != %lu",
field_name, a, b);
}
}
static void
should_geq_u64(const char *field_name, uint64_t a, uint64_t b)
{
if (a < b) {
errx(EXIT_FAILURE, "unexpected %s %lu < %lu",
field_name, a, b);
}
}
static void
should_geq_i64(const char *field_name, int64_t a, int64_t b)
{
if (a < b) {
errx(EXIT_FAILURE, "unexpected %s %ld < %ld",
field_name, a, b);
}
}
static void
test_valid_read_time_data(int vmfd, struct vdi_time_info_v1 *time_info)
{
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = time_info,
};
if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed");
}
}
static void
test_valid_write_time_data(int vmfd, struct vdi_time_info_v1 *time_info)
{
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = time_info,
};
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
int error;
error = errno;
if (error == EPERM) {
warn("VMM_DATA_WRITE got EPERM: is "
"vmm_allow_state_writes set?");
}
errx(EXIT_FAILURE, "VMM_DATA_WRITE of time info failed");
}
}
static void
test_invalid_read_time_data(int vmfd)
{
struct vdi_time_info_v1 res;
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = 0,
.vdx_data = &res,
};
int error;
if (ioctl(vmfd, VM_DATA_READ, &xfer) == 0) {
errx(EXIT_FAILURE,
"invalid VMM_DATA_READ of time info should fail");
}
error = errno;
if (error != ENOSPC) {
errx(EXIT_FAILURE, "test_invalid_read_time_data: "
"expected ENOSPC errno, got %d", error);
}
should_eq_u32("vdx_result_len", xfer.vdx_result_len,
sizeof (struct vdi_time_info_v1));
}
static void
test_invalid_write_time_data(int vmfd, struct vdi_time_info_v1 *src)
{
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = 0,
.vdx_data = src,
};
int error;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE,
"invalid VMM_DATA_WRITE of time info should fail");
}
error = errno;
if (error != ENOSPC) {
errx(EXIT_FAILURE, "test_invalid_write_time_data: "
"expected ENOSPC errno, got %d", error);
}
should_eq_u32("vdx_result_len", xfer.vdx_result_len,
sizeof (struct vdi_time_info_v1));
}
static void
test_invalid_freq(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 invalid = {
.vt_guest_freq = 0,
.vt_guest_tsc = src->vt_guest_tsc,
.vt_boot_hrtime = src->vt_boot_hrtime,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = &invalid,
};
int error;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE,
"invalid VMM_DATA_WRITE of time info (vt_guest_freq = 0) "
"should fail");
}
error = errno;
if (error != EINVAL) {
errx(EXIT_FAILURE, "test_invalid_freq: \
expected EINVAL errno, got %d", error);
}
}
static void
test_invalid_freq_amd(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 invalid = {
.vt_guest_freq = src->vt_guest_freq,
.vt_guest_tsc = src->vt_guest_tsc,
.vt_boot_hrtime = src->vt_boot_hrtime,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = &invalid,
};
int error;
invalid.vt_guest_freq = AMD_TSC_MIN_FREQ - 1;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
"(min AMD guest freq) should fail");
}
error = errno;
if (error != EINVAL) {
errx(EXIT_FAILURE, "test_invalid_freq_amd (< min freq) "
"expected EINVAL errno, got %d", error);
}
invalid.vt_guest_freq = src->vt_guest_freq * AMD_TSC_MAX_FREQ_RATIO;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
"(AMD guest freq ratio too large) should fail");
}
error = errno;
if (error != EINVAL) {
errx(EXIT_FAILURE, "test_invalid_freq_amd (> max freq) "
"expected EINVAL errno, got %d", error);
}
}
static void
test_valid_freq_amd(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 res;
int error;
struct vdi_time_info_v1 valid = {
.vt_guest_freq = AMD_TSC_MIN_FREQ,
.vt_guest_tsc = src->vt_guest_tsc,
.vt_boot_hrtime = src->vt_boot_hrtime,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = &valid,
};
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
error = errno;
errx(EXIT_FAILURE, "valid VMM_DATA_WRITE of time info "
"(min AMD guest frequency) should succeed, errno=%d",
error);
}
test_valid_read_time_data(vmfd, &res);
should_eq_u64("vt_guest_freq", res.vt_guest_freq, valid.vt_guest_freq);
valid.vt_guest_freq = src->vt_guest_freq * AMD_TSC_MAX_FREQ_RATIO - 1;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
error = errno;
errx(EXIT_FAILURE, "valid VMM_DATA_WRITE of time info "
"(max AMD guest frequency) should succeed, errno=%d",
error);
}
test_valid_read_time_data(vmfd, &res);
should_eq_u64("vt_guest_freq", res.vt_guest_freq, valid.vt_guest_freq);
}
static void
test_invalid_freq_intel(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 invalid = {
.vt_guest_freq = src->vt_guest_freq + 1,
.vt_guest_tsc = src->vt_guest_tsc,
.vt_boot_hrtime = src->vt_boot_hrtime,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = &invalid,
};
int error;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
"(intel scaling required) should fail");
}
error = errno;
if (error != EPERM) {
errx(EXIT_FAILURE, "test_invalid_freq_intel: "
"expected EPERM errno, got %d", error);
}
}
static void
test_invalid_host_times(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 invalid = {
.vt_guest_freq = src->vt_guest_freq,
.vt_guest_tsc = src->vt_guest_tsc,
.vt_boot_hrtime = src->vt_boot_hrtime,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = &invalid,
};
int error;
invalid.vt_hrtime += 500000000000;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
"(hrtime in the future) should fail");
}
error = errno;
if (error != EINVAL) {
errx(EXIT_FAILURE, "test_invalid_host_times: "
"expected EINVAL errno, got %d", error);
}
}
static void
test_invalid_boot_hrtime(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 invalid = {
.vt_guest_freq = src->vt_guest_freq,
.vt_guest_tsc = src->vt_guest_tsc,
.vt_boot_hrtime = src->vt_boot_hrtime,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
struct vm_data_xfer xfer = {
.vdx_class = VDC_VMM_TIME,
.vdx_version = 1,
.vdx_len = sizeof (struct vdi_time_info_v1),
.vdx_data = &invalid,
};
int error;
invalid.vt_boot_hrtime += src->vt_hrtime + 500000000000;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
"(boot_hrtime in the future) should fail");
}
error = errno;
if (error != EINVAL) {
errx(EXIT_FAILURE, "test_invalid_boot_hrtime: "
"expected EINVAL errno, got %d", error);
}
}
static void
test_valid_guest_tsc(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 valid = {
.vt_guest_freq = src->vt_guest_freq,
.vt_guest_tsc = src->vt_guest_tsc + 500000000000,
.vt_boot_hrtime = src->vt_boot_hrtime,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
test_valid_write_time_data(vmfd, &valid);
struct vdi_time_info_v1 res;
test_valid_read_time_data(vmfd, &res);
should_geq_u64("vt_guest_tsc", res.vt_guest_tsc, valid.vt_guest_tsc);
}
static void
test_valid_boot_hrtime(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 res;
struct vdi_time_info_v1 valid = {
.vt_guest_freq = src->vt_guest_freq,
.vt_guest_tsc = src->vt_guest_tsc,
.vt_boot_hrtime = -100000000000,
.vt_hrtime = src->vt_hrtime,
.vt_hres_sec = src->vt_hres_sec,
.vt_hres_ns = src->vt_hres_ns,
};
test_valid_write_time_data(vmfd, &valid);
test_valid_read_time_data(vmfd, &res);
should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime);
valid.vt_boot_hrtime = 0;
test_valid_write_time_data(vmfd, &valid);
test_valid_read_time_data(vmfd, &res);
should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime);
valid.vt_boot_hrtime = src->vt_boot_hrtime + 1;
test_valid_write_time_data(vmfd, &valid);
test_valid_read_time_data(vmfd, &res);
should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime);
}
static void
test_adjust(int vmfd, struct vdi_time_info_v1 *src)
{
struct vdi_time_info_v1 res;
test_valid_write_time_data(vmfd, src);
test_valid_read_time_data(vmfd, &res);
should_geq_i64("vt_hrtime", res.vt_hrtime, src->vt_hrtime);
if (src->vt_hres_sec == res.vt_hres_sec) {
should_geq_u64("vt_hres_ns", res.vt_hres_ns, src->vt_hres_ns);
} else if (src->vt_hres_sec > res.vt_hres_sec) {
errx(EXIT_FAILURE, "test_adjust: hrestime went backwards");
}
should_geq_u64("vt_guest_tsc", res.vt_guest_tsc, src->vt_guest_tsc);
}
int
main(int argc, char *argv[])
{
const char *suite_name = basename(argv[0]);
struct vmctx *ctx;
struct vcpu *vcpu;
ctx = create_test_vm(suite_name);
if (ctx == NULL) {
errx(EXIT_FAILURE, "could not open test VM");
}
if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
err(EXIT_FAILURE, "Could not open vcpu0");
}
if (vm_activate_cpu(vcpu) != 0) {
err(EXIT_FAILURE, "could not activate vcpu0");
}
const int vmfd = vm_get_device_fd(ctx);
const bool is_svm = cpu_vendor_amd();
struct vdi_time_info_v1 time_info;
test_valid_read_time_data(vmfd, &time_info);
test_invalid_read_time_data(vmfd);
test_valid_write_time_data(vmfd, &time_info);
test_invalid_write_time_data(vmfd, &time_info);
test_invalid_host_times(vmfd, &time_info);
test_invalid_freq(vmfd, &time_info);
if (is_svm) {
test_invalid_freq_amd(vmfd, &time_info);
} else {
test_invalid_freq_intel(vmfd, &time_info);
}
test_invalid_boot_hrtime(vmfd, &time_info);
if (is_svm) {
test_valid_freq_amd(vmfd, &time_info);
}
test_valid_guest_tsc(vmfd, &time_info);
test_valid_boot_hrtime(vmfd, &time_info);
test_adjust(vmfd, &time_info);
vm_vcpu_close(vcpu);
vm_destroy(ctx);
(void) printf("%s\tPASS\n", suite_name);
return (EXIT_SUCCESS);
}