Patchwork [4/4] kvm: x86: Add user space part for in-kernel i8254

login
register
mail settings
Submitter Jan Kiszka
Date Feb. 5, 2012, 10:46 a.m.
Message ID <b77da8f93a69c10198eb450315a16c3f89db9c9b.1328438750.git.jan.kiszka@web.de>
Download mbox | patch
Permalink /patch/139621/
State New
Headers show

Comments

Jan Kiszka - Feb. 5, 2012, 10:46 a.m.
From: Jan Kiszka <jan.kiszka@siemens.com>

This provides the required user space stubs to enable the in-kernel
i8254 emulation of KVM.

The in-kernel model supports lost tick compensation according to the
"delay" policy. This is enabled by default and can be switched off via a
device property.

Depending on the feature set of the host kernel (before 2.6.32), we may
have to disable the HPET or lack sound output from the PC speaker.

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
---
 Makefile.target |    2 +-
 hw/i8254.h      |   11 +++
 hw/kvm/i8254.c  |  241 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/pc.c         |   14 +++-
 4 files changed, 265 insertions(+), 3 deletions(-)
 create mode 100644 hw/kvm/i8254.c

Patch

diff --git a/Makefile.target b/Makefile.target
index 68481a3..2af5511 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -235,7 +235,7 @@  obj-i386-y += vmport.o
 obj-i386-y += pci-hotplug.o smbios.o wdt_ib700.o
 obj-i386-y += debugcon.o multiboot.o
 obj-i386-y += pc_piix.o
-obj-i386-$(CONFIG_KVM) += kvm/clock.o kvm/apic.o kvm/i8259.o kvm/ioapic.o
+obj-i386-$(CONFIG_KVM) += kvm/clock.o kvm/apic.o kvm/i8259.o kvm/ioapic.o kvm/i8254.o
 obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
 
 # shared objects
diff --git a/hw/i8254.h b/hw/i8254.h
index a1d2e98..ba6b598 100644
--- a/hw/i8254.h
+++ b/hw/i8254.h
@@ -51,6 +51,17 @@  static inline ISADevice *pit_init(ISABus *bus, int base, int isa_irq,
     return dev;
 }
 
+static inline ISADevice *kvm_pit_init(ISABus *bus, int base)
+{
+    ISADevice *dev;
+
+    dev = isa_create(bus, "kvm-pit");
+    qdev_prop_set_uint32(&dev->qdev, "iobase", base);
+    qdev_init_nofail(&dev->qdev);
+
+    return dev;
+}
+
 void pit_set_gate(ISADevice *dev, int channel, int val);
 void pit_get_channel_info(ISADevice *dev, int channel, PITChannelInfo *info);
 
diff --git a/hw/kvm/i8254.c b/hw/kvm/i8254.c
new file mode 100644
index 0000000..a1e06d5
--- /dev/null
+++ b/hw/kvm/i8254.c
@@ -0,0 +1,241 @@ 
+/*
+ * KVM in-kernel PIT (i8254) support
+ *
+ * Copyright (c) 2012 Siemens AG
+ *
+ * Authors:
+ *  Jan Kiszka          <jan.kiszka@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu-timer.h"
+#include "hw/i8254.h"
+#include "hw/i8254_internal.h"
+#include "kvm.h"
+
+#define KVM_PIT_REINJECT_BIT 0
+
+typedef struct KVMPITState {
+    PITCommonState pit;
+    LostTickPolicy lost_tick_policy;
+} KVMPITState;
+
+static void kvm_pit_get(PITCommonState *s)
+{
+    struct kvm_pit_state2 kpit;
+    struct kvm_pit_channel_state *kchan;
+    struct PITChannelState *sc;
+    int i, ret;
+
+    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;
+    } else {
+        /*
+         * kvm_pit_state2 is superset of kvm_pit_state struct,
+         * so we can use it for KVM_GET_PIT as well.
+         */
+        ret = kvm_vm_ioctl(kvm_state, KVM_GET_PIT, &kpit);
+        if (ret < 0) {
+            fprintf(stderr, "KVM_GET_PIT failed: %s\n", strerror(ret));
+            abort();
+        }
+    }
+    for (i = 0; i < 3; i++) {
+        kchan = &kpit.channels[i];
+        sc = &s->channels[i];
+        sc->count = kchan->count;
+        sc->latched_count = kchan->latched_count;
+        sc->count_latched = kchan->count_latched;
+        sc->status_latched = kchan->status_latched;
+        sc->status = kchan->status;
+        sc->read_state = kchan->read_state;
+        sc->write_state = kchan->write_state;
+        sc->write_latch = kchan->write_latch;
+        sc->rw_mode = kchan->rw_mode;
+        sc->mode = kchan->mode;
+        sc->bcd = kchan->bcd;
+        sc->gate = kchan->gate;
+        sc->count_load_time = kchan->count_load_time;
+    }
+
+    sc = &s->channels[0];
+    sc->next_transition_time =
+        pit_get_next_transition_time(sc, sc->count_load_time);
+}
+
+static void kvm_pit_put(PITCommonState *s)
+{
+    struct kvm_pit_state2 kpit;
+    struct kvm_pit_channel_state *kchan;
+    struct PITChannelState *sc;
+    int i, ret;
+
+    kpit.flags = s->channels[0].irq_disabled ? KVM_PIT_FLAGS_HPET_LEGACY : 0;
+    for (i = 0; i < 3; i++) {
+        kchan = &kpit.channels[i];
+        sc = &s->channels[i];
+        kchan->count = sc->count;
+        kchan->latched_count = sc->latched_count;
+        kchan->count_latched = sc->count_latched;
+        kchan->status_latched = sc->status_latched;
+        kchan->status = sc->status;
+        kchan->read_state = sc->read_state;
+        kchan->write_state = sc->write_state;
+        kchan->write_latch = sc->write_latch;
+        kchan->rw_mode = sc->rw_mode;
+        kchan->mode = sc->mode;
+        kchan->bcd = sc->bcd;
+        kchan->gate = sc->gate;
+        kchan->count_load_time = sc->count_load_time;
+    }
+
+    ret = kvm_vm_ioctl(kvm_state,
+                       kvm_has_pit_state2() ? KVM_SET_PIT2 : KVM_SET_PIT,
+                       &kpit);
+    if (ret < 0) {
+        fprintf(stderr, "%s failed: %s\n",
+                kvm_has_pit_state2() ? "KVM_SET_PIT2" : "KVM_SET_PIT",
+                strerror(ret));
+        abort();
+    }
+}
+
+static void kvm_pit_set_gate(PITCommonState *s, PITChannelState *sc, int val)
+{
+    kvm_pit_get(s);
+
+    switch (sc->mode) {
+    default:
+    case 0:
+    case 4:
+        /* XXX: just disable/enable counting */
+        break;
+    case 1:
+    case 2:
+    case 3:
+    case 5:
+        if (sc->gate < val) {
+            /* restart counting on rising edge */
+            sc->count_load_time = qemu_get_clock_ns(vm_clock);
+        }
+        break;
+    }
+    sc->gate = val;
+
+    kvm_pit_put(s);
+}
+
+static void kvm_pit_get_channel_info(PITCommonState *s, PITChannelState *sc,
+                                     PITChannelInfo *info)
+{
+    kvm_pit_get(s);
+
+    pit_get_channel_info_common(s, sc, info);
+}
+
+static void kvm_pit_reset(DeviceState *dev)
+{
+    PITCommonState *s = DO_UPCAST(PITCommonState, dev.qdev, dev);
+
+    pit_reset_common(s);
+
+    kvm_pit_put(s);
+}
+
+static void kvm_pit_irq_control(void *opaque, int n, int enable)
+{
+    PITCommonState *pit = opaque;
+    PITChannelState *s = &pit->channels[0];
+
+    kvm_pit_get(pit);
+
+    s->irq_disabled = !enable;
+
+    kvm_pit_put(pit);
+}
+
+static int kvm_pit_initfn(PITCommonState *pit)
+{
+    KVMPITState *s = DO_UPCAST(KVMPITState, pit, pit);
+    struct kvm_pit_config config = {
+        .flags = 0,
+    };
+    int ret;
+
+    if (kvm_check_extension(kvm_state, KVM_CAP_PIT2)) {
+        ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_PIT2, &config);
+    } else {
+        ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_PIT);
+    }
+    if (ret < 0) {
+        fprintf(stderr, "Create kernel PIC irqchip failed: %s\n",
+                strerror(ret));
+        return ret;
+    }
+    switch (s->lost_tick_policy) {
+    case LOST_TICK_DELAY:
+        break; /* enabled by default */
+    case LOST_TICK_DISCARD:
+        if (kvm_check_extension(kvm_state, KVM_CAP_REINJECT_CONTROL)) {
+            struct kvm_reinject_control control = { .pit_reinject = 0 };
+
+            ret = kvm_vm_ioctl(kvm_state, KVM_REINJECT_CONTROL, &control);
+            if (ret < 0) {
+                fprintf(stderr,
+                        "Can't disable in-kernel PIT reinjection: %s\n",
+                        strerror(ret));
+                return ret;
+            }
+        }
+        break;
+    default:
+        return -EINVAL;
+    }
+
+    memory_region_init_reservation(&pit->ioports, "kvm-pit", 4);
+
+    qdev_init_gpio_in(&pit->dev.qdev, kvm_pit_irq_control, 1);
+
+    return 0;
+}
+
+static Property kvm_pit_properties[] = {
+    DEFINE_PROP_HEX32("iobase", KVMPITState, pit.iobase,  -1),
+    DEFINE_PROP_LOSTTICKPOLICY("lost_tick_policy", KVMPITState,
+                               lost_tick_policy, LOST_TICK_DELAY),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void kvm_pit_class_init(ObjectClass *klass, void *data)
+{
+    PITCommonClass *k = PIT_COMMON_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init = kvm_pit_initfn;
+    k->set_channel_gate = kvm_pit_set_gate;
+    k->get_channel_info = kvm_pit_get_channel_info;
+    k->pre_save = kvm_pit_get;
+    k->post_load = kvm_pit_put;
+    dc->reset = kvm_pit_reset;
+    dc->props = kvm_pit_properties;
+}
+
+static TypeInfo kvm_pit_info = {
+    .name          = "kvm-pit",
+    .parent        = TYPE_PIT_COMMON,
+    .instance_size = sizeof(KVMPITState),
+    .class_init = kvm_pit_class_init,
+};
+
+static void kvm_pit_register(void)
+{
+    type_register_static(&kvm_pit_info);
+}
+
+device_init(kvm_pit_register)
diff --git a/hw/pc.c b/hw/pc.c
index e2b6560..94952a4 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -1151,7 +1151,13 @@  void pc_basic_device_init(ISABus *isa_bus, qemu_irq *gsi,
 
     register_ioport_write(0xf0, 1, 1, ioportF0_write, NULL);
 
-    if (!no_hpet) {
+    /*
+     * Check if an HPET shall be created.
+     *
+     * Without KVM_CAP_PIT_STATE2, we cannot switch off the in-kernel PIT
+     * when the HPET wants to take over. Thus we have to disable the latter.
+     */
+    if (!no_hpet && (!kvm_irqchip_in_kernel() || kvm_has_pit_state2())) {
         hpet = sysbus_try_create_simple("hpet", HPET_BASE, NULL);
 
         if (hpet) {
@@ -1167,7 +1173,11 @@  void pc_basic_device_init(ISABus *isa_bus, qemu_irq *gsi,
 
     qemu_register_boot_set(pc_boot_set, *rtc_state);
 
-    pit = pit_init(isa_bus, 0x40, pit_isa_irq, pit_alt_irq);
+    if (kvm_irqchip_in_kernel()) {
+        pit = kvm_pit_init(isa_bus, 0x40);
+    } else {
+        pit = pit_init(isa_bus, 0x40, pit_isa_irq, pit_alt_irq);
+    }
     if (hpet) {
         /* connect PIT to output control line of the HPET */
         qdev_connect_gpio_out(hpet, 0, qdev_get_gpio_in(&pit->qdev, 0));