#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"
#define APIC_ADDR_TIMER_ICR 0xfee00380
#define APIC_ADDR_TIMER_CCR 0xfee00390
#define TIMER_TEST_VAL 0x10000
static void
test_ccr_clamp(int vmfd, struct vcpu *vcpu)
{
if (ioctl(vmfd, VM_PAUSE, 0) != 0) {
err(EXIT_FAILURE, "VM_PAUSE failed");
}
struct vdi_lapic_v1 lapic_data;
struct vm_data_xfer xfer = {
.vdx_vcpuid = 0,
.vdx_class = VDC_LAPIC,
.vdx_version = 1,
.vdx_len = sizeof (lapic_data),
.vdx_data = &lapic_data,
};
if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
err(EXIT_FAILURE, "VM_DATA_READ of lapic failed");
}
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
err(EXIT_FAILURE, "VM_DATA_WRITE of lapic failed");
}
lapic_data.vl_lapic.vlp_icr_timer = TIMER_TEST_VAL;
lapic_data.vl_timer_target = 1000000000UL * 100;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
err(EXIT_FAILURE, "VM_DATA_WRITE of lapic failed");
}
if (ioctl(vmfd, VM_RESUME, 0) != 0) {
err(EXIT_FAILURE, "VM_RESUME failed");
}
uint64_t ccr_value = 0;
int error = vm_readwrite_kernemu_device(vcpu, APIC_ADDR_TIMER_CCR,
false, 4, &ccr_value);
if (error != 0) {
err(EXIT_FAILURE, "could not emulate MMIO of LAPIC CCR");
}
if (ccr_value != TIMER_TEST_VAL) {
errx(EXIT_FAILURE, "CCR not clamped: %lx != %x",
ccr_value, TIMER_TEST_VAL);
}
}
static void
test_timer_icr_constraints(int vmfd, struct vcpu *vcpu)
{
if (ioctl(vmfd, VM_PAUSE, 0) != 0) {
err(EXIT_FAILURE, "VM_PAUSE failed");
}
uint64_t icr_value = 1 << 30;
int error = vm_readwrite_kernemu_device(vcpu, APIC_ADDR_TIMER_CCR,
true, 4, &icr_value);
if (error != 0) {
err(EXIT_FAILURE, "failed to load timer ICR value");
}
struct vdi_lapic_v1 lapic_data;
struct vm_data_xfer xfer = {
.vdx_vcpuid = 0,
.vdx_class = VDC_LAPIC,
.vdx_version = 1,
.vdx_len = sizeof (lapic_data),
.vdx_data = &lapic_data,
};
if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
err(EXIT_FAILURE, "VM_DATA_READ of lapic failed");
}
if (lapic_data.vl_lapic.vlp_icr_timer == 0) {
errx(EXIT_FAILURE, "ICR_TIMER is 0");
}
if (lapic_data.vl_timer_target == 0) {
errx(EXIT_FAILURE, "vlapic timer not scheduled");
}
if (vcpu_reset(vcpu) != 0) {
err(EXIT_FAILURE, "vcpu_reset() failed");
}
if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
err(EXIT_FAILURE, "VM_DATA_READ of lapic failed");
}
if (lapic_data.vl_lapic.vlp_icr_timer != 0) {
errx(EXIT_FAILURE, "ICR_TIMER is not 0");
}
if (lapic_data.vl_timer_target != 0) {
errx(EXIT_FAILURE, "vlapic timer should not be scheduled");
}
lapic_data.vl_timer_target = 1 << 20;
lapic_data.vl_lapic.vlp_icr_timer = 0;
if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
errx(EXIT_FAILURE,
"VM_DATA_WRITE of invalid lapic data should fail");
}
}
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);
test_ccr_clamp(vmfd, vcpu);
test_timer_icr_constraints(vmfd, vcpu);
vm_destroy(ctx);
(void) printf("%s\tPASS\n", suite_name);
return (EXIT_SUCCESS);
}