This patch series must be applied on top of the hpet branch. 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 earlier versions 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. Changes from v6: - added ioctl interface for setting hpet legacy mode in kernel pit - moved check for hpet_legacy_mode in pit_load_count to allow state info to be copied before returning if legacy mode is enabled. - sprinkled in some #ifdef TARGET_I386 Signed-off-by: Beth Kon <e...@us.ibm.com> diff --git a/hw/hpet.c b/hw/hpet.c index 29db325..2f5255f 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,16 @@ 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) + s->config = 0ULL; + 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-kvm.c b/hw/i8254-kvm.c index 8390d75..76ce6f2 100644 --- a/hw/i8254-kvm.c +++ b/hw/i8254-kvm.c @@ -36,6 +36,7 @@ static void kvm_pit_save(QEMUFile *f, void *opaque) struct kvm_pit_state pit; struct kvm_pit_channel_state *c; struct PITChannelState *sc; + __u8 hpet_legacy_mode; int i; kvm_get_pit(kvm_context, &pit); @@ -59,6 +60,10 @@ static void kvm_pit_save(QEMUFile *f, void *opaque) } pit_save(f, s); + if (kvm_has_hpet_legacy_mode(kvm_context)) { + kvm_get_hpet_legacy_mode(kvm_context, &hpet_legacy_mode); + qemu_put_8s(f, &hpet_legacy_mode); + } } static int kvm_pit_load(QEMUFile *f, void *opaque, int version_id) @@ -67,6 +72,7 @@ static int kvm_pit_load(QEMUFile *f, void *opaque, int version_id) struct kvm_pit_state pit; struct kvm_pit_channel_state *c; struct PITChannelState *sc; + __u8 hpet_legacy_mode; int i; pit_load(f, s, version_id); @@ -89,8 +95,13 @@ static int kvm_pit_load(QEMUFile *f, void *opaque, int version_id) c->count_load_time = sc->count_load_time; } - kvm_set_pit(kvm_context, &pit); + if (kvm_has_hpet_legacy_mode(kvm_context)) { + qemu_get_8s(f, &hpet_legacy_mode); + kvm_get_hpet_legacy_mode(kvm_context, &hpet_legacy_mode); + } + kvm_set_hpet_legacy_mode(kvm_context, &hpet_legacy_mode); + kvm_set_pit(kvm_context, &pit); return 0; } diff --git a/hw/i8254.c b/hw/i8254.c index 2f229f9..0136c64 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 @@ -202,6 +203,11 @@ static inline void pit_load_count(PITChannelState *s, int val) val = 0x10000; s->count_load_time = qemu_get_clock(vm_clock); s->count = val; +#ifdef TARGET_I386 + if (s->channel == 0 && pit_state.hpet_legacy_mode) { + return; + } +#endif pit_irq_timer_update(s, s->count_load_time); } @@ -371,10 +377,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 +458,9 @@ void pit_reset(void *opaque) PITChannelState *s; int i; +#ifdef TARGET_I386 + pit->hpet_legacy_mode = 0; +#endif for(i = 0;i < 3; i++) { s = &pit->channels[i]; s->mode = 3; @@ -459,33 +469,50 @@ void pit_reset(void *opaque) } } +#ifdef TARGET_I386 /* 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 { + pit_state.hpet_legacy_mode = 1; + 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_state.hpet_legacy_mode = 0; + pit_load_count(s, s->count); + } } +#endif 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/linux/kvm.h b/kvm/include/linux/kvm.h index ca93871..3559628 100644 --- a/kvm/include/linux/kvm.h +++ b/kvm/include/linux/kvm.h @@ -464,6 +464,7 @@ struct kvm_trace_rec { /* Another bug in KVM_SET_USER_MEMORY_REGION fixed: */ #define KVM_CAP_JOIN_MEMORY_REGIONS_WORKS 30 #define KVM_CAP_PIT2 33 +#define KVM_CAP_HPET_LEGACY_MODE 35 #ifdef KVM_CAP_IRQ_ROUTING @@ -613,6 +614,9 @@ struct kvm_debug_guest { #define KVM_IA64_VCPU_GET_STACK _IOR(KVMIO, 0x9a, void *) #define KVM_IA64_VCPU_SET_STACK _IOW(KVMIO, 0x9b, void *) +#define KVM_GET_HPET_LEGACY_MODE _IOR(KVMIO, 0x9c, __u8) +#define KVM_SET_HPET_LEGACY_MODE _IOWR(KVMIO, 0x9d, __u8) + #define KVM_TRC_INJ_VIRQ (KVM_TRC_HANDLER + 0x02) #define KVM_TRC_REDELIVER_EVT (KVM_TRC_HANDLER + 0x03) #define KVM_TRC_PEND_INTR (KVM_TRC_HANDLER + 0x04) diff --git a/libkvm-all.h b/libkvm-all.h index 4f7b9a3..6e76704 100644 --- a/libkvm-all.h +++ b/libkvm-all.h @@ -294,6 +294,36 @@ int kvm_get_interrupt_flag(kvm_vcpu_context_t vcpu); uint64_t kvm_get_apic_base(kvm_vcpu_context_t vcpu); /*! + * \brief Check for existence of kvm hpet legacy mode + * + * \param kvm Pointer to the current kvm_context + * \return 0 on success + */ +int kvm_has_hpet_legacy_mode(kvm_context_t kvm); + +/*! + * \brief Set hpet legacy mode + * + * mode = 1: start legacy mode, disable kpit + * mode = 0: end legacy mode, enable kpit + * + * \param kvm Pointer to the current kvm_context + * \param mode Pointer to the mode + * \return 0 on success + */ +int kvm_set_hpet_legacy_mode(kvm_context_t kvm, __u8 *mode); + +/*! + * \brief Get kernel hpet legacy mode + * + * + * \param kvm Pointer to the current kvm_context + * \param mode Pointer to the mode + * \return 0 on success + */ +int kvm_get_hpet_legacy_mode(kvm_context_t kvm, __u8 *mode); + +/*! * \brief Check if a vcpu is ready for interrupt injection * * This checks if vcpu interrupts are not masked by mov ss or sti. diff --git a/qemu-kvm.c b/qemu-kvm.c index 0f7adcc..6c57b36 100644 --- a/qemu-kvm.c +++ b/qemu-kvm.c @@ -1975,6 +1975,32 @@ int kvm_vcpu_inited(CPUState *env) return env->kvm_cpu_state.created; } +#ifdef TARGET_I386 +void kvm_hpet_disable_kpit(void) +{ + int rc; + uint8_t mode = 1; + + rc = kvm_set_hpet_legacy_mode(kvm_context, &mode); + if (rc < 0) { + fprintf(stderr,"HPET kernel legacy support unavailable! rc = %d", rc); + exit(1); + } +} + +void kvm_hpet_enable_kpit(void) +{ + int rc; + uint8_t mode = 0; + + rc = kvm_set_hpet_legacy_mode(kvm_context, &mode); + if (rc < 0) { + fprintf(stderr,"HPET kernel legacy support unavailable! rc = %d", rc); + exit(1); + } +} +#endif + int kvm_init_ap(void) { #ifdef TARGET_I386 diff --git a/qemu-kvm.h b/qemu-kvm.h index 2c0afb5..fcda865 100644 --- a/qemu-kvm.h +++ b/qemu-kvm.h @@ -35,6 +35,8 @@ 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); @@ -170,6 +172,9 @@ int kvm_has_sync_mmu(void); #define qemu_kvm_irqchip_in_kernel() kvm_irqchip_in_kernel(kvm_context) #define qemu_kvm_pit_in_kernel() kvm_pit_in_kernel(kvm_context) #define qemu_kvm_has_gsi_routing() kvm_has_gsi_routing(kvm_context) +#ifdef TARGET_I386 +#define qemu_kvm_has_hpet_legacy_mode() kvm_has_hpet_legacy_mode(kvm_context) +#endif void kvm_init_vcpu(CPUState *env); void kvm_load_tsc(CPUState *env); #else @@ -178,6 +183,9 @@ void kvm_load_tsc(CPUState *env); #define qemu_kvm_irqchip_in_kernel() (0) #define qemu_kvm_pit_in_kernel() (0) #define qemu_kvm_has_gsi_routing() (0) +#ifdef TARGET_I386 +#define qemu_kvm_has_hpet_legacy_mode() (0) +#endif #define kvm_load_registers(env) do {} while(0) #define kvm_save_registers(env) do {} while(0) #define qemu_kvm_cpu_stop(env) do {} while(0) diff --git a/target-i386/libkvm.c b/target-i386/libkvm.c index 0f4e009..c96bc69 100644 --- a/target-i386/libkvm.c +++ b/target-i386/libkvm.c @@ -562,6 +562,47 @@ int kvm_disable_tpr_access_reporting(kvm_vcpu_context_t vcpu) #endif +int kvm_has_hpet_legacy_mode(kvm_context_t kvm) +{ + int r = 0; + +#ifdef KVM_CAP_HPET_LEGACY_MODE + r = kvm_check_extension(kvm, KVM_CAP_HPET_LEGACY_MODE); +#endif + return r; +} + +int kvm_get_hpet_legacy_mode(kvm_context_t kvm, uint8_t *mode) +{ + int r; + + if (!kvm_has_hpet_legacy_mode(kvm)) { + return -1; + } + r = ioctl(kvm->vm_fd, KVM_GET_HPET_LEGACY_MODE, mode); + if (r == -1) { + r = -errno; + perror("kvm_get_hpet_legacy_mode"); + return r; + } + return 0; +} + +int kvm_set_hpet_legacy_mode(kvm_context_t kvm, uint8_t *mode) +{ + int r; + + if (!kvm_has_hpet_legacy_mode(kvm)) { + return -1; + } + r = ioctl(kvm->vm_fd, KVM_SET_HPET_LEGACY_MODE, mode); + if (r == -1) { + r = -errno; + perror("kvm_set_hpet_legacy_mode"); + return r; + } + return 0; +} #ifdef KVM_CAP_EXT_CPUID static struct kvm_cpuid2 *try_get_cpuid(kvm_context_t kvm, int max) diff --git a/vl.c b/vl.c index 80daa52..7c248a9 100644 --- a/vl.c +++ b/vl.c @@ -248,7 +248,9 @@ int assigned_devices_index; int smp_cpus = 1; const char *vnc_display; int acpi_enabled = 1; +#ifdef TARGET_I386 int no_hpet = 0; +#endif int fd_bootchk = 1; int no_reboot = 0; int no_shutdown = 0; @@ -6195,10 +6197,21 @@ 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 (kvm_irqchip) { + if (!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. + */ +#ifdef TARGET_I386 + no_hpet=1; + } else if (!qemu_kvm_has_hpet_legacy_mode()) { + no_hpet=1; + } +#endif } #endif } -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html