kvm: i8254: Fix conversion of in-kernel to userspace state
Due to a offset between the clock used to generate the in-kernel count_load_time (CLOCK_MONOTONIC) and the clock used for processing this in userspace (vm_clock), reading back the output of PIT channel 2 via port 0x61 was broken. One use cases that suffered from it was the CPU frequency calibration of SeaBIOS, which also affected IDE/AHCI timeouts. This fixes it by calibrating the offset between both clocks on kvm_pit_get and adjusting the kernel value before saving it in the userspace state. As the calibration only works while the vm_clock is running, we cache the in-kernel state across stopped phases. Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com> Signed-off-by: Avi Kivity <avi@redhat.com>
This commit is contained in:
parent
0614cb82ca
commit
0cdd3d1444
@ -23,31 +23,63 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu-timer.h"
|
||||
#include "sysemu.h"
|
||||
#include "hw/i8254.h"
|
||||
#include "hw/i8254_internal.h"
|
||||
#include "kvm.h"
|
||||
|
||||
#define KVM_PIT_REINJECT_BIT 0
|
||||
|
||||
#define CALIBRATION_ROUNDS 3
|
||||
|
||||
typedef struct KVMPITState {
|
||||
PITCommonState pit;
|
||||
LostTickPolicy lost_tick_policy;
|
||||
bool state_valid;
|
||||
} KVMPITState;
|
||||
|
||||
static void kvm_pit_get(PITCommonState *s)
|
||||
static int64_t abs64(int64_t v)
|
||||
{
|
||||
return v < 0 ? -v : v;
|
||||
}
|
||||
|
||||
static void kvm_pit_get(PITCommonState *pit)
|
||||
{
|
||||
KVMPITState *s = DO_UPCAST(KVMPITState, pit, pit);
|
||||
struct kvm_pit_state2 kpit;
|
||||
struct kvm_pit_channel_state *kchan;
|
||||
struct PITChannelState *sc;
|
||||
int64_t offset, clock_offset;
|
||||
struct timespec ts;
|
||||
int i, ret;
|
||||
|
||||
if (s->state_valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Measure the delta between CLOCK_MONOTONIC, the base used for
|
||||
* kvm_pit_channel_state::count_load_time, and vm_clock. Take the
|
||||
* minimum of several samples to filter out scheduling noise.
|
||||
*/
|
||||
clock_offset = INT64_MAX;
|
||||
for (i = 0; i < CALIBRATION_ROUNDS; i++) {
|
||||
offset = qemu_get_clock_ns(vm_clock);
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
offset -= ts.tv_nsec;
|
||||
offset -= (int64_t)ts.tv_sec * 1000000000;
|
||||
if (abs64(offset) < abs64(clock_offset)) {
|
||||
clock_offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (kvm_has_pit_state2()) {
|
||||
ret = kvm_vm_ioctl(kvm_state, KVM_GET_PIT2, &kpit);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "KVM_GET_PIT2 failed: %s\n", strerror(ret));
|
||||
abort();
|
||||
}
|
||||
s->channels[0].irq_disabled = kpit.flags & KVM_PIT_FLAGS_HPET_LEGACY;
|
||||
pit->channels[0].irq_disabled = kpit.flags & KVM_PIT_FLAGS_HPET_LEGACY;
|
||||
} else {
|
||||
/*
|
||||
* kvm_pit_state2 is superset of kvm_pit_state struct,
|
||||
@ -61,7 +93,7 @@ static void kvm_pit_get(PITCommonState *s)
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
kchan = &kpit.channels[i];
|
||||
sc = &s->channels[i];
|
||||
sc = &pit->channels[i];
|
||||
sc->count = kchan->count;
|
||||
sc->latched_count = kchan->latched_count;
|
||||
sc->count_latched = kchan->count_latched;
|
||||
@ -74,10 +106,10 @@ static void kvm_pit_get(PITCommonState *s)
|
||||
sc->mode = kchan->mode;
|
||||
sc->bcd = kchan->bcd;
|
||||
sc->gate = kchan->gate;
|
||||
sc->count_load_time = kchan->count_load_time;
|
||||
sc->count_load_time = kchan->count_load_time + clock_offset;
|
||||
}
|
||||
|
||||
sc = &s->channels[0];
|
||||
sc = &pit->channels[0];
|
||||
sc->next_transition_time =
|
||||
pit_get_next_transition_time(sc, sc->count_load_time);
|
||||
}
|
||||
@ -173,6 +205,19 @@ static void kvm_pit_irq_control(void *opaque, int n, int enable)
|
||||
kvm_pit_put(pit);
|
||||
}
|
||||
|
||||
static void kvm_pit_vm_state_change(void *opaque, int running,
|
||||
RunState state)
|
||||
{
|
||||
KVMPITState *s = opaque;
|
||||
|
||||
if (running) {
|
||||
s->state_valid = false;
|
||||
} else {
|
||||
kvm_pit_get(&s->pit);
|
||||
s->state_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
static int kvm_pit_initfn(PITCommonState *pit)
|
||||
{
|
||||
KVMPITState *s = DO_UPCAST(KVMPITState, pit, pit);
|
||||
@ -215,6 +260,8 @@ static int kvm_pit_initfn(PITCommonState *pit)
|
||||
|
||||
qdev_init_gpio_in(&pit->dev.qdev, kvm_pit_irq_control, 1);
|
||||
|
||||
qemu_add_vm_change_state_handler(kvm_pit_vm_state_change, s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user