i386/xen: handle PV timer hypercalls

Introduce support for one shot and periodic mode of Xen PV timers,
whereby timer interrupts come through a special virq event channel
with deadlines being set through:

1) set_timer_op hypercall (only oneshot)
2) vcpu_op hypercall for {set,stop}_{singleshot,periodic}_timer
hypercalls

Signed-off-by: Joao Martins <joao.m.martins@oracle.com>
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
master
Joao Martins 2018-09-17 07:04:54 -04:00 committed by David Woodhouse
parent b46f9745b1
commit b746a77926
5 changed files with 308 additions and 2 deletions

View File

@ -1222,6 +1222,37 @@ int xen_evtchn_send_op(struct evtchn_send *send)
return ret;
}
int xen_evtchn_set_port(uint16_t port)
{
XenEvtchnState *s = xen_evtchn_singleton;
XenEvtchnPort *p;
int ret = -EINVAL;
if (!s) {
return -ENOTSUP;
}
if (!valid_port(port)) {
return -EINVAL;
}
qemu_mutex_lock(&s->port_lock);
p = &s->port_table[port];
/* QEMU has no business sending to anything but these */
if (p->type == EVTCHNSTAT_virq ||
(p->type == EVTCHNSTAT_interdomain &&
(p->type_val & PORT_INFO_TYPEVAL_REMOTE_QEMU))) {
set_port_pending(s, port);
ret = 0;
}
qemu_mutex_unlock(&s->port_lock);
return ret;
}
EvtchnInfoList *qmp_xen_event_list(Error **errp)
{
XenEvtchnState *s = xen_evtchn_singleton;

View File

@ -20,6 +20,8 @@ int xen_evtchn_set_callback_param(uint64_t param);
void xen_evtchn_connect_gsis(qemu_irq *system_gsis);
void xen_evtchn_set_callback_level(int level);
int xen_evtchn_set_port(uint16_t port);
struct evtchn_status;
struct evtchn_close;
struct evtchn_unmask;

View File

@ -26,6 +26,7 @@
#include "exec/cpu-defs.h"
#include "qapi/qapi-types-common.h"
#include "qemu/cpu-float.h"
#include "qemu/timer.h"
#define XEN_NR_VIRQS 24
@ -1811,6 +1812,10 @@ typedef struct CPUArchState {
bool xen_callback_asserted;
uint16_t xen_virq[XEN_NR_VIRQS];
uint64_t xen_singleshot_timer_ns;
QEMUTimer *xen_singleshot_timer;
uint64_t xen_periodic_timer_period;
QEMUTimer *xen_periodic_timer;
QemuMutex xen_timers_lock;
#endif
#if defined(CONFIG_HVF)
HVFX86LazyFlags hvf_lflags;

View File

@ -38,6 +38,9 @@
#include "xen-compat.h"
static void xen_vcpu_singleshot_timer_event(void *opaque);
static void xen_vcpu_periodic_timer_event(void *opaque);
#ifdef TARGET_X86_64
#define hypercall_compat32(longmode) (!(longmode))
#else
@ -201,6 +204,23 @@ int kvm_xen_init_vcpu(CPUState *cs)
env->xen_vcpu_time_info_gpa = INVALID_GPA;
env->xen_vcpu_runstate_gpa = INVALID_GPA;
qemu_mutex_init(&env->xen_timers_lock);
env->xen_singleshot_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
xen_vcpu_singleshot_timer_event,
cpu);
if (!env->xen_singleshot_timer) {
return -ENOMEM;
}
env->xen_singleshot_timer->opaque = cs;
env->xen_periodic_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
xen_vcpu_periodic_timer_event,
cpu);
if (!env->xen_periodic_timer) {
return -ENOMEM;
}
env->xen_periodic_timer->opaque = cs;
return 0;
}
@ -232,7 +252,8 @@ static bool kvm_xen_hcall_xen_version(struct kvm_xen_exit *exit, X86CPU *cpu,
1 << XENFEAT_writable_descriptor_tables |
1 << XENFEAT_auto_translated_physmap |
1 << XENFEAT_supervisor_mode_kernel |
1 << XENFEAT_hvm_callback_vector;
1 << XENFEAT_hvm_callback_vector |
1 << XENFEAT_hvm_safe_pvclock;
}
err = kvm_copy_to_gva(CPU(cpu), arg, &fi, sizeof(fi));
@ -878,13 +899,208 @@ static int vcpuop_register_runstate_info(CPUState *cs, CPUState *target,
return 0;
}
static uint64_t kvm_get_current_ns(void)
{
struct kvm_clock_data data;
int ret;
ret = kvm_vm_ioctl(kvm_state, KVM_GET_CLOCK, &data);
if (ret < 0) {
fprintf(stderr, "KVM_GET_CLOCK failed: %s\n", strerror(ret));
abort();
}
return data.clock;
}
static void xen_vcpu_singleshot_timer_event(void *opaque)
{
CPUState *cpu = opaque;
CPUX86State *env = &X86_CPU(cpu)->env;
uint16_t port = env->xen_virq[VIRQ_TIMER];
if (likely(port)) {
xen_evtchn_set_port(port);
}
qemu_mutex_lock(&env->xen_timers_lock);
env->xen_singleshot_timer_ns = 0;
qemu_mutex_unlock(&env->xen_timers_lock);
}
static void xen_vcpu_periodic_timer_event(void *opaque)
{
CPUState *cpu = opaque;
CPUX86State *env = &X86_CPU(cpu)->env;
uint16_t port = env->xen_virq[VIRQ_TIMER];
int64_t qemu_now;
if (likely(port)) {
xen_evtchn_set_port(port);
}
qemu_mutex_lock(&env->xen_timers_lock);
qemu_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
timer_mod_ns(env->xen_periodic_timer,
qemu_now + env->xen_periodic_timer_period);
qemu_mutex_unlock(&env->xen_timers_lock);
}
static int do_set_periodic_timer(CPUState *target, uint64_t period_ns)
{
CPUX86State *tenv = &X86_CPU(target)->env;
int64_t qemu_now;
timer_del(tenv->xen_periodic_timer);
qemu_mutex_lock(&tenv->xen_timers_lock);
qemu_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
timer_mod_ns(tenv->xen_periodic_timer, qemu_now + period_ns);
tenv->xen_periodic_timer_period = period_ns;
qemu_mutex_unlock(&tenv->xen_timers_lock);
return 0;
}
#define MILLISECS(_ms) ((int64_t)((_ms) * 1000000ULL))
#define MICROSECS(_us) ((int64_t)((_us) * 1000ULL))
#define STIME_MAX ((time_t)((int64_t)~0ull >> 1))
/* Chosen so (NOW() + delta) wont overflow without an uptime of 200 years */
#define STIME_DELTA_MAX ((int64_t)((uint64_t)~0ull >> 2))
static int vcpuop_set_periodic_timer(CPUState *cs, CPUState *target,
uint64_t arg)
{
struct vcpu_set_periodic_timer spt;
qemu_build_assert(sizeof(spt) == 8);
if (kvm_copy_from_gva(cs, arg, &spt, sizeof(spt))) {
return -EFAULT;
}
if (spt.period_ns < MILLISECS(1) || spt.period_ns > STIME_DELTA_MAX) {
return -EINVAL;
}
return do_set_periodic_timer(target, spt.period_ns);
}
static int vcpuop_stop_periodic_timer(CPUState *target)
{
CPUX86State *tenv = &X86_CPU(target)->env;
qemu_mutex_lock(&tenv->xen_timers_lock);
timer_del(tenv->xen_periodic_timer);
tenv->xen_periodic_timer_period = 0;
qemu_mutex_unlock(&tenv->xen_timers_lock);
return 0;
}
static int do_set_singleshot_timer(CPUState *cs, uint64_t timeout_abs,
bool future, bool linux_wa)
{
CPUX86State *env = &X86_CPU(cs)->env;
int64_t now = kvm_get_current_ns();
int64_t qemu_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
int64_t delta = timeout_abs - now;
if (future && timeout_abs < now) {
return -ETIME;
}
if (linux_wa && unlikely((int64_t)timeout_abs < 0 ||
(delta > 0 && (uint32_t)(delta >> 50) != 0))) {
/*
* Xen has a 'Linux workaround' in do_set_timer_op() which checks
* for negative absolute timeout values (caused by integer
* overflow), and for values about 13 days in the future (2^50ns)
* which would be caused by jiffies overflow. For those cases, it
* sets the timeout 100ms in the future (not *too* soon, since if
* a guest really did set a long timeout on purpose we don't want
* to keep churning CPU time by waking it up).
*/
delta = (100 * SCALE_MS);
timeout_abs = now + delta;
}
qemu_mutex_lock(&env->xen_timers_lock);
timer_mod_ns(env->xen_singleshot_timer, qemu_now + delta);
env->xen_singleshot_timer_ns = now + delta;
qemu_mutex_unlock(&env->xen_timers_lock);
return 0;
}
static int vcpuop_set_singleshot_timer(CPUState *cs, uint64_t arg)
{
struct vcpu_set_singleshot_timer sst = { 0 };
/*
* The struct is a uint64_t followed by a uint32_t. On 32-bit that
* makes it 12 bytes. On 64-bit it gets padded to 16. The parts
* that get used are identical, and there's four bytes of padding
* unused at the end. For true Xen compatibility we should attempt
* to copy the full 16 bytes from 64-bit guests, and return -EFAULT
* if we can't get the padding too. But that's daft. Just copy what
* we need.
*/
qemu_build_assert(offsetof(struct vcpu_set_singleshot_timer, flags) == 8);
qemu_build_assert(sizeof(sst) >= 12);
if (kvm_copy_from_gva(cs, arg, &sst, 12)) {
return -EFAULT;
}
return do_set_singleshot_timer(cs, sst.timeout_abs_ns,
!!(sst.flags & VCPU_SSHOTTMR_future),
false);
}
static int vcpuop_stop_singleshot_timer(CPUState *cs)
{
CPUX86State *env = &X86_CPU(cs)->env;
qemu_mutex_lock(&env->xen_timers_lock);
timer_del(env->xen_singleshot_timer);
env->xen_singleshot_timer_ns = 0;
qemu_mutex_unlock(&env->xen_timers_lock);
return 0;
}
static bool kvm_xen_hcall_set_timer_op(struct kvm_xen_exit *exit, X86CPU *cpu,
uint64_t timeout)
{
int err;
if (unlikely(timeout == 0)) {
err = vcpuop_stop_singleshot_timer(CPU(cpu));
} else {
err = do_set_singleshot_timer(CPU(cpu), timeout, false, true);
}
exit->u.hcall.result = err;
return true;
}
static bool kvm_xen_hcall_vcpu_op(struct kvm_xen_exit *exit, X86CPU *cpu,
int cmd, int vcpu_id, uint64_t arg)
{
CPUState *dest = qemu_get_cpu(vcpu_id);
CPUState *cs = CPU(cpu);
CPUState *dest = cs->cpu_index == vcpu_id ? cs : qemu_get_cpu(vcpu_id);
int err;
if (!dest) {
err = -ENOENT;
goto out;
}
switch (cmd) {
case VCPUOP_register_runstate_memory_area:
err = vcpuop_register_runstate_info(cs, dest, arg);
@ -895,11 +1111,34 @@ static bool kvm_xen_hcall_vcpu_op(struct kvm_xen_exit *exit, X86CPU *cpu,
case VCPUOP_register_vcpu_info:
err = vcpuop_register_vcpu_info(cs, dest, arg);
break;
case VCPUOP_set_singleshot_timer: {
if (cs->cpu_index == vcpu_id) {
err = vcpuop_set_singleshot_timer(dest, arg);
} else {
err = -EINVAL;
}
break;
}
case VCPUOP_stop_singleshot_timer:
if (cs->cpu_index == vcpu_id) {
err = vcpuop_stop_singleshot_timer(dest);
} else {
err = -EINVAL;
}
break;
case VCPUOP_set_periodic_timer: {
err = vcpuop_set_periodic_timer(cs, dest, arg);
break;
}
case VCPUOP_stop_periodic_timer:
err = vcpuop_stop_periodic_timer(dest);
break;
default:
return false;
}
out:
exit->u.hcall.result = err;
return true;
}
@ -1249,6 +1488,16 @@ static bool do_kvm_xen_handle_exit(X86CPU *cpu, struct kvm_xen_exit *exit)
}
switch (code) {
case __HYPERVISOR_set_timer_op:
if (exit->u.hcall.longmode) {
return kvm_xen_hcall_set_timer_op(exit, cpu,
exit->u.hcall.params[0]);
} else {
/* In 32-bit mode, the 64-bit timer value is in two args. */
uint64_t val = ((uint64_t)exit->u.hcall.params[1]) << 32 |
(uint32_t)exit->u.hcall.params[0];
return kvm_xen_hcall_set_timer_op(exit, cpu, val);
}
case __HYPERVISOR_grant_table_op:
return kvm_xen_hcall_gnttab_op(exit, cpu, exit->u.hcall.params[0],
exit->u.hcall.params[1],
@ -1358,7 +1607,25 @@ int kvm_put_xen_state(CPUState *cs)
}
}
if (env->xen_periodic_timer_period) {
ret = do_set_periodic_timer(cs, env->xen_periodic_timer_period);
if (ret < 0) {
return ret;
}
}
if (!kvm_xen_has_cap(EVTCHN_SEND)) {
/*
* If the kernel has EVTCHN_SEND support then it handles timers too,
* so the timer will be restored by kvm_xen_set_vcpu_timer() below.
*/
if (env->xen_singleshot_timer_ns) {
ret = do_set_singleshot_timer(cs, env->xen_singleshot_timer_ns,
false, false);
if (ret < 0) {
return ret;
}
}
return 0;
}

View File

@ -1277,6 +1277,7 @@ static const VMStateDescription vmstate_xen_vcpu = {
VMSTATE_UINT8(env.xen_vcpu_callback_vector, X86CPU),
VMSTATE_UINT16_ARRAY(env.xen_virq, X86CPU, XEN_NR_VIRQS),
VMSTATE_UINT64(env.xen_singleshot_timer_ns, X86CPU),
VMSTATE_UINT64(env.xen_periodic_timer_period, X86CPU),
VMSTATE_END_OF_LIST()
}
};