[PATCH 4/5] Userspace changes for KVM HPET (v4)

Beth Kon
2009-06-11T14:41:58Z

The big change here is handling of enabling/disabling of hpet legacy mode. When hpet enters
legacy mode, the spec says that the pit stops generating interrupts. In practice, we want to
stop the pit periodic timer from running because it is wasteful in a virtual environment.

We also have to worry about the hpet leaving legacy mode (which, at least in linux, happens
only during a shutdown or crash). At this point, according to the hpet spec, PIT interrupts
need to be reenabled. For us, it means the PIT timer needs to be restarted.

This patch handles this situation better than the previous version by coming closer to
just disabling PIT interrupts. It allows the PIT state to change if the OS modifies it,
even while PIT is disabled, but does not allow a pit timer to start. Then if HPET
legacy mode is disabled, whatever the PIT state is at that point, the PIT timer is
restarted accordingly.

Signed-off-by: Beth Kon <eak@us.ibm.com>

---
hw/hpet.c | 15 +++++++++++----
hw/i8254.c | 43 ++++++++++++++++++++++++++++++-------------
hw/i8254.h | 2 ++
hw/pc.h | 4 ++--
kvm/include/x86/asm/kvm.h | 1 +
qemu-kvm.c | 20 ++++++++++++++++++++
qemu-kvm.h | 3 ++-
vl.c | 7 ++++++-
8 files changed, 74 insertions(+), 21 deletions(-)

diff --git a/hw/hpet.c b/hw/hpet.c
index 29db325..043b92b 100644
--- a/hw/hpet.c
+++ b/hw/hpet.c
@@ -206,6 +206,9 @@ static int hpet_load(QEMUFile *f, void *opaque, int version_id)
qemu_get_timer(f, s->timer[i].qemu_timer);
}
}
+ if (hpet_in_legacy_mode()) {
+ hpet_disable_pit();
+ }
return 0;
}

@@ -475,9 +478,11 @@ static void hpet_ram_writel(void *opaque, target_phys_addr_t addr,
}
/* i8254 and RTC are disabled when HPET is in legacy mode */
if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
- hpet_pit_disable();
+ hpet_disable_pit();
+ dprintf("qemu: hpet disabled pit\n");
} else if (deactivating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
- hpet_pit_enable();
+ hpet_enable_pit();
+ dprintf("qemu: hpet enabled pit\n");
}
break;
case HPET_CFG + 4:
@@ -554,13 +559,15 @@ static void hpet_reset(void *opaque) {
/* 64-bit main counter; 3 timers supported; LegacyReplacementRoute. */
s->capability = 0x8086a201ULL;
s->capability |= ((HPET_CLK_PERIOD) << 32);
- if (count > 0)
+ if (count > 0) {
/* we don't enable pit when hpet_reset is first called (by hpet_init)
* because hpet is taking over for pit here. On subsequent invocations,
* hpet_reset is called due to system reset. At this point control must
* be returned to pit until SW reenables hpet.
*/
- hpet_pit_enable();
+ hpet_enable_pit();
+ dprintf("qemu: hpet enabled pit\n");
+ }
count = 1;
}

diff --git a/hw/i8254.c b/hw/i8254.c
index 2f229f9..8c8076f 100644
--- a/hw/i8254.c
+++ b/hw/i8254.c
@@ -25,6 +25,7 @@
#include "pc.h"
#include "isa.h"
#include "qemu-timer.h"
+#include "qemu-kvm.h"
#include "i8254.h"

//#define DEBUG_PIT
@@ -198,6 +199,9 @@ int pit_get_mode(PITState *pit, int channel)

static inline void pit_load_count(PITChannelState *s, int val)
{
+ if (s->channel == 0 && pit_state.hpet_legacy_mode) {
+ return;
+ }
if (val == 0)
val = 0x10000;
s->count_load_time = qemu_get_clock(vm_clock);
@@ -371,10 +375,11 @@ static void pit_irq_timer_update(PITChannelState *s, int64_t current_time)
(double)(expire_time - current_time) / ticks_per_sec);
#endif
s->next_transition_time = expire_time;
- if (expire_time != -1)
+ if (expire_time != -1) {
qemu_mod_timer(s->irq_timer, expire_time);
- else
+ } else {
qemu_del_timer(s->irq_timer);
+ }
}

static void pit_irq_timer(void *opaque)
@@ -451,6 +456,7 @@ void pit_reset(void *opaque)
PITChannelState *s;
int i;

+ pit->hpet_legacy_mode = 0;
for(i = 0;i < 3; i++) {
s = &pit->channels[i];
s->mode = 3;
@@ -460,32 +466,43 @@ void pit_reset(void *opaque)
}

/* When HPET is operating in legacy mode, i8254 timer0 is disabled */
-void hpet_pit_disable(void) {
- PITChannelState *s;
- s = &pit_state.channels[0];
- if (s->irq_timer)
- qemu_del_timer(s->irq_timer);
+
+void hpet_disable_pit(void)
+{
+ PITChannelState *s = &pit_state.channels[0];
+ if (qemu_kvm_pit_in_kernel()) {
+ kvm_hpet_disable_kpit();
+ } else {
+ if (s->irq_timer) {
+ qemu_del_timer(s->irq_timer);
+ }
+ }
}

/* When HPET is reset or leaving legacy mode, it must reenable i8254
* timer 0
*/

-void hpet_pit_enable(void)
+void hpet_enable_pit(void)
{
PITState *pit = &pit_state;
- PITChannelState *s;
- s = &pit->channels[0];
- s->mode = 3;
- s->gate = 1;
- pit_load_count(s, 0);
+ PITChannelState *s = &pit->channels[0];
+ if (qemu_kvm_pit_in_kernel()) {
+ kvm_hpet_enable_kpit();
+ } else {
+ pit_load_count(s, s->count);
+ }
}

PITState *pit_init(int base, qemu_irq irq)
{
PITState *pit = &pit_state;
PITChannelState *s;
+ int i;

+ for (i = 0; i <3 ; i++) {
+ pit->channels[i].channel = i;
+ }
s = &pit->channels[0];
/* the timer 0 is connected to an IRQ */
s->irq_timer = qemu_new_timer(vm_clock, pit_irq_timer, s);
diff --git a/hw/i8254.h b/hw/i8254.h
index ee68ab5..9efd4ab 100644
--- a/hw/i8254.h
+++ b/hw/i8254.h
@@ -35,6 +35,7 @@

typedef struct PITChannelState {
int count; /* can be 65536 */
+ uint8_t channel;
uint16_t latched_count;
uint8_t count_latched;
uint8_t status_latched;
@@ -55,6 +56,7 @@ typedef struct PITChannelState {

struct PITState {
PITChannelState channels[3];
+ uint8_t hpet_legacy_mode;
};

void pit_save(QEMUFile *f, void *opaque);
diff --git a/hw/pc.h b/hw/pc.h
index 3af22f2..abac8d8 100644
--- a/hw/pc.h
+++ b/hw/pc.h
@@ -74,8 +74,8 @@ int pit_get_out(PITState *pit, int channel, int64_t current_time);

PITState *kvm_pit_init(int base, qemu_irq irq);

-void hpet_pit_disable(void);
-void hpet_pit_enable(void);
+void hpet_disable_pit(void);
+void hpet_enable_pit(void);

/* vmport.c */
void vmport_init(void);
diff --git a/kvm/include/x86/asm/kvm.h b/kvm/include/x86/asm/kvm.h
index dc90c47..df8d846 100644
--- a/kvm/include/x86/asm/kvm.h
+++ b/kvm/include/x86/asm/kvm.h
@@ -274,6 +274,7 @@ struct kvm_guest_debug_arch {

struct kvm_pit_state {
struct kvm_pit_channel_state channels[3];
+ __u8 hpet_legacy_mode;
};

struct kvm_reinject_control {
diff --git a/qemu-kvm.c b/qemu-kvm.c
index 7acc0ef..98bd117 100644
--- a/qemu-kvm.c
+++ b/qemu-kvm.c
@@ -467,6 +467,26 @@ int kvm_vcpu_inited(CPUState *env)
return env->kvm_cpu_state.created;
}

+void kvm_hpet_disable_kpit(void)
+{
+ struct kvm_pit_state ps;
+ if (qemu_kvm_pit_in_kernel()) {
+ kvm_get_pit(kvm_context, &ps);
+ ps.hpet_legacy_mode = 1;
+ kvm_set_pit(kvm_context, &ps);
+ }
+}
+
+void kvm_hpet_enable_kpit(void)
+{
+ struct kvm_pit_state ps;
+ if (qemu_kvm_pit_in_kernel()) {
+ kvm_get_pit(kvm_context, &ps);
+ ps.hpet_legacy_mode = 0;
+ kvm_set_pit(kvm_context, &ps);
+ }
+}
+
int kvm_init_ap(void)
{
#ifdef TARGET_I386
diff --git a/qemu-kvm.h b/qemu-kvm.h
index 6bbafbc..c094107 100644
--- a/qemu-kvm.h
+++ b/qemu-kvm.h
@@ -34,7 +34,8 @@ int kvm_qemu_check_extension(int ext);
void kvm_apic_init(CPUState *env);
/* called from vcpu initialization */
void qemu_kvm_load_lapic(CPUState *env);
-
+void kvm_hpet_enable_kpit(void);
+void kvm_hpet_disable_kpit(void);
int kvm_set_irq(int irq, int level, int *status);

int kvm_physical_memory_set_dirty_tracking(int enable);
diff --git a/vl.c b/vl.c
index 9b1d1ab..485b091 100644
--- a/vl.c
+++ b/vl.c
@@ -6056,10 +6056,15 @@ int main(int argc, char **argv, char **envp)
module_call_init(MODULE_INIT_DEVICE);

if (kvm_enabled()) {
- kvm_init_ap();
+ kvm_init_ap();
#ifdef USE_KVM
if (kvm_irqchip && !qemu_kvm_has_gsi_routing()) {
irq0override = 0;
+ /* if kernel can't do irq routing, interrupt source
+ * override 0->2 can not be set up as required by hpet,
+ * so disable hpet.
+ */
+ no_hpet=1;
}
#endif
}