From d11e859e4afe9b8b12f4478ab4ae3204d2d665ce Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Fri, 2 Mar 2012 20:28:46 +0100 Subject: i8254: Factor out base class for KVM reuse Applying the concept used for the *PICs once again: establish a base class for the i8254 that can be used both by the current user space emulation and the upcoming KVM in-kernel version. We share most of the public interface of the i8254, specifically to the pcspk, vmstate, reset and certain init parts. Signed-off-by: Jan Kiszka Signed-off-by: Avi Kivity --- Makefile.objs | 2 +- hw/i8254.c | 269 ++++++--------------------------------------- hw/i8254_common.c | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/i8254_internal.h | 85 +++++++++++++++ 4 files changed, 424 insertions(+), 239 deletions(-) create mode 100644 hw/i8254_common.c create mode 100644 hw/i8254_internal.h diff --git a/Makefile.objs b/Makefile.objs index 808de6a250..b39d76cbb6 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -210,7 +210,7 @@ hw-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o hw-obj-$(CONFIG_SERIAL) += serial.o hw-obj-$(CONFIG_PARALLEL) += parallel.o -hw-obj-$(CONFIG_I8254) += i8254.o +hw-obj-$(CONFIG_I8254) += i8254_common.o i8254.o hw-obj-$(CONFIG_PCSPK) += pcspk.o hw-obj-$(CONFIG_PCKBD) += pckbd.o hw-obj-$(CONFIG_USB_UHCI) += usb-uhci.o diff --git a/hw/i8254.c b/hw/i8254.c index f30396af88..9fde3449b2 100644 --- a/hw/i8254.c +++ b/hw/i8254.c @@ -26,6 +26,7 @@ #include "isa.h" #include "qemu-timer.h" #include "i8254.h" +#include "i8254_internal.h" //#define DEBUG_PIT @@ -34,34 +35,6 @@ #define RW_STATE_WORD0 3 #define RW_STATE_WORD1 4 -typedef struct PITChannelState { - int count; /* can be 65536 */ - uint16_t latched_count; - uint8_t count_latched; - uint8_t status_latched; - uint8_t status; - uint8_t read_state; - uint8_t write_state; - uint8_t write_latch; - uint8_t rw_mode; - uint8_t mode; - uint8_t bcd; /* not supported */ - uint8_t gate; /* timer start */ - int64_t count_load_time; - /* irq handling */ - int64_t next_transition_time; - QEMUTimer *irq_timer; - qemu_irq irq; - uint32_t irq_disabled; -} PITChannelState; - -typedef struct PITState { - ISADevice dev; - MemoryRegion ioports; - uint32_t iobase; - PITChannelState channels[3]; -} PITState; - static void pit_irq_timer_update(PITChannelState *s, int64_t current_time); static int pit_get_count(PITChannelState *s) @@ -89,99 +62,11 @@ static int pit_get_count(PITChannelState *s) return counter; } -/* get pit output bit */ -static int pit_get_out(PITChannelState *s, int64_t current_time) -{ - uint64_t d; - int out; - - d = muldiv64(current_time - s->count_load_time, PIT_FREQ, - get_ticks_per_sec()); - switch(s->mode) { - default: - case 0: - out = (d >= s->count); - break; - case 1: - out = (d < s->count); - break; - case 2: - if ((d % s->count) == 0 && d != 0) - out = 1; - else - out = 0; - break; - case 3: - out = (d % s->count) < ((s->count + 1) >> 1); - break; - case 4: - case 5: - out = (d == s->count); - break; - } - return out; -} - -/* return -1 if no transition will occur. */ -static int64_t pit_get_next_transition_time(PITChannelState *s, - int64_t current_time) -{ - uint64_t d, next_time, base; - int period2; - - d = muldiv64(current_time - s->count_load_time, PIT_FREQ, - get_ticks_per_sec()); - switch(s->mode) { - default: - case 0: - case 1: - if (d < s->count) - next_time = s->count; - else - return -1; - break; - case 2: - base = (d / s->count) * s->count; - if ((d - base) == 0 && d != 0) - next_time = base + s->count; - else - next_time = base + s->count + 1; - break; - case 3: - base = (d / s->count) * s->count; - period2 = ((s->count + 1) >> 1); - if ((d - base) < period2) - next_time = base + period2; - else - next_time = base + s->count; - break; - case 4: - case 5: - if (d < s->count) - next_time = s->count; - else if (d == s->count) - next_time = s->count + 1; - else - return -1; - break; - } - /* convert to timer units */ - next_time = s->count_load_time + muldiv64(next_time, get_ticks_per_sec(), - PIT_FREQ); - /* fix potential rounding problems */ - /* XXX: better solution: use a clock at PIT_FREQ Hz */ - if (next_time <= current_time) - next_time = current_time + 1; - return next_time; -} - /* val must be 0 or 1 */ -void pit_set_gate(ISADevice *dev, int channel, int val) +static void pit_set_channel_gate(PITCommonState *s, PITChannelState *sc, + int val) { - PITState *pit = DO_UPCAST(PITState, dev, dev); - PITChannelState *s = &pit->channels[channel]; - - switch(s->mode) { + switch (sc->mode) { default: case 0: case 4: @@ -189,34 +74,23 @@ void pit_set_gate(ISADevice *dev, int channel, int val) break; case 1: case 5: - if (s->gate < val) { + if (sc->gate < val) { /* restart counting on rising edge */ - s->count_load_time = qemu_get_clock_ns(vm_clock); - pit_irq_timer_update(s, s->count_load_time); + sc->count_load_time = qemu_get_clock_ns(vm_clock); + pit_irq_timer_update(sc, sc->count_load_time); } break; case 2: case 3: - if (s->gate < val) { + if (sc->gate < val) { /* restart counting on rising edge */ - s->count_load_time = qemu_get_clock_ns(vm_clock); - pit_irq_timer_update(s, s->count_load_time); + sc->count_load_time = qemu_get_clock_ns(vm_clock); + pit_irq_timer_update(sc, sc->count_load_time); } /* XXX: disable/enable counting */ break; } - s->gate = val; -} - -void pit_get_channel_info(ISADevice *dev, int channel, PITChannelInfo *info) -{ - PITState *pit = DO_UPCAST(PITState, dev, dev); - PITChannelState *s = &pit->channels[channel]; - - info->gate = s->gate; - info->mode = s->mode; - info->initial_count = s->count; - info->out = pit_get_out(s, qemu_get_clock_ns(vm_clock)); + sc->gate = val; } static inline void pit_load_count(PITChannelState *s, int val) @@ -239,7 +113,7 @@ static void pit_latch_count(PITChannelState *s) static void pit_ioport_write(void *opaque, uint32_t addr, uint32_t val) { - PITState *pit = opaque; + PITCommonState *pit = opaque; int channel, access; PITChannelState *s; @@ -306,7 +180,7 @@ static void pit_ioport_write(void *opaque, uint32_t addr, uint32_t val) static uint32_t pit_ioport_read(void *opaque, uint32_t addr) { - PITState *pit = opaque; + PITCommonState *pit = opaque; int ret, count; PITChannelState *s; @@ -387,94 +261,16 @@ static void pit_irq_timer(void *opaque) pit_irq_timer_update(s, s->next_transition_time); } -static const VMStateDescription vmstate_pit_channel = { - .name = "pit channel", - .version_id = 2, - .minimum_version_id = 2, - .minimum_version_id_old = 2, - .fields = (VMStateField []) { - VMSTATE_INT32(count, PITChannelState), - VMSTATE_UINT16(latched_count, PITChannelState), - VMSTATE_UINT8(count_latched, PITChannelState), - VMSTATE_UINT8(status_latched, PITChannelState), - VMSTATE_UINT8(status, PITChannelState), - VMSTATE_UINT8(read_state, PITChannelState), - VMSTATE_UINT8(write_state, PITChannelState), - VMSTATE_UINT8(write_latch, PITChannelState), - VMSTATE_UINT8(rw_mode, PITChannelState), - VMSTATE_UINT8(mode, PITChannelState), - VMSTATE_UINT8(bcd, PITChannelState), - VMSTATE_UINT8(gate, PITChannelState), - VMSTATE_INT64(count_load_time, PITChannelState), - VMSTATE_INT64(next_transition_time, PITChannelState), - VMSTATE_END_OF_LIST() - } -}; - -static int pit_load_old(QEMUFile *f, void *opaque, int version_id) +static void pit_reset(DeviceState *dev) { - PITState *pit = opaque; + PITCommonState *pit = DO_UPCAST(PITCommonState, dev.qdev, dev); PITChannelState *s; - int i; - - if (version_id != 1) - return -EINVAL; - - for(i = 0; i < 3; i++) { - s = &pit->channels[i]; - s->count=qemu_get_be32(f); - qemu_get_be16s(f, &s->latched_count); - qemu_get_8s(f, &s->count_latched); - qemu_get_8s(f, &s->status_latched); - qemu_get_8s(f, &s->status); - qemu_get_8s(f, &s->read_state); - qemu_get_8s(f, &s->write_state); - qemu_get_8s(f, &s->write_latch); - qemu_get_8s(f, &s->rw_mode); - qemu_get_8s(f, &s->mode); - qemu_get_8s(f, &s->bcd); - qemu_get_8s(f, &s->gate); - s->count_load_time=qemu_get_be64(f); - s->irq_disabled = 0; - if (s->irq_timer) { - s->next_transition_time=qemu_get_be64(f); - qemu_get_timer(f, s->irq_timer); - } - } - return 0; -} -static const VMStateDescription vmstate_pit = { - .name = "i8254", - .version_id = 3, - .minimum_version_id = 2, - .minimum_version_id_old = 1, - .load_state_old = pit_load_old, - .fields = (VMStateField []) { - VMSTATE_UINT32_V(channels[0].irq_disabled, PITState, 3), - VMSTATE_STRUCT_ARRAY(channels, PITState, 3, 2, vmstate_pit_channel, PITChannelState), - VMSTATE_TIMER(channels[0].irq_timer, PITState), - VMSTATE_END_OF_LIST() - } -}; + pit_reset_common(pit); -static void pit_reset(DeviceState *dev) -{ - PITState *pit = container_of(dev, PITState, dev.qdev); - PITChannelState *s; - int i; - - for(i = 0;i < 3; i++) { - s = &pit->channels[i]; - s->mode = 3; - s->gate = (i != 2); - s->count_load_time = qemu_get_clock_ns(vm_clock); - s->count = 0x10000; - if (i == 0 && !s->irq_disabled) { - s->next_transition_time = - pit_get_next_transition_time(s, s->count_load_time); - qemu_mod_timer(s->irq_timer, s->next_transition_time); - } + s = &pit->channels[0]; + if (!s->irq_disabled) { + qemu_mod_timer(s->irq_timer, s->next_transition_time); } } @@ -482,7 +278,7 @@ static void pit_reset(DeviceState *dev) * reenable it when legacy mode is left again. */ static void pit_irq_control(void *opaque, int n, int enable) { - PITState *pit = opaque; + PITCommonState *pit = opaque; PITChannelState *s = &pit->channels[0]; if (enable) { @@ -504,46 +300,43 @@ static const MemoryRegionOps pit_ioport_ops = { .old_portio = pit_portio }; -static int pit_initfn(ISADevice *dev) +static int pit_initfn(PITCommonState *pit) { - PITState *pit = DO_UPCAST(PITState, dev, dev); PITChannelState *s; s = &pit->channels[0]; /* the timer 0 is connected to an IRQ */ s->irq_timer = qemu_new_timer_ns(vm_clock, pit_irq_timer, s); - qdev_init_gpio_out(&dev->qdev, &s->irq, 1); + qdev_init_gpio_out(&pit->dev.qdev, &s->irq, 1); memory_region_init_io(&pit->ioports, &pit_ioport_ops, pit, "pit", 4); - isa_register_ioport(dev, &pit->ioports, pit->iobase); - - qdev_init_gpio_in(&dev->qdev, pit_irq_control, 1); - qdev_set_legacy_instance_id(&dev->qdev, pit->iobase, 2); + qdev_init_gpio_in(&pit->dev.qdev, pit_irq_control, 1); return 0; } static Property pit_properties[] = { - DEFINE_PROP_HEX32("iobase", PITState, iobase, -1), + DEFINE_PROP_HEX32("iobase", PITCommonState, iobase, -1), DEFINE_PROP_END_OF_LIST(), }; static void pit_class_initfn(ObjectClass *klass, void *data) { + PITCommonClass *k = PIT_COMMON_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); - ic->init = pit_initfn; - dc->no_user = 1; + + k->init = pit_initfn; + k->set_channel_gate = pit_set_channel_gate; + k->get_channel_info = pit_get_channel_info_common; dc->reset = pit_reset; - dc->vmsd = &vmstate_pit; dc->props = pit_properties; } static TypeInfo pit_info = { .name = "isa-pit", - .parent = TYPE_ISA_DEVICE, - .instance_size = sizeof(PITState), + .parent = TYPE_PIT_COMMON, + .instance_size = sizeof(PITCommonState), .class_init = pit_class_initfn, }; diff --git a/hw/i8254_common.c b/hw/i8254_common.c new file mode 100644 index 0000000000..6373e878fe --- /dev/null +++ b/hw/i8254_common.c @@ -0,0 +1,307 @@ +/* + * QEMU 8253/8254 - common bits of emulated and KVM kernel model + * + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2012 Jan Kiszka, Siemens AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw.h" +#include "pc.h" +#include "isa.h" +#include "qemu-timer.h" +#include "i8254.h" +#include "i8254_internal.h" + +/* val must be 0 or 1 */ +void pit_set_gate(ISADevice *dev, int channel, int val) +{ + PITCommonState *pit = PIT_COMMON(dev); + PITChannelState *s = &pit->channels[channel]; + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); + + c->set_channel_gate(pit, s, val); +} + +/* get pit output bit */ +int pit_get_out(PITChannelState *s, int64_t current_time) +{ + uint64_t d; + int out; + + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, + get_ticks_per_sec()); + switch (s->mode) { + default: + case 0: + out = (d >= s->count); + break; + case 1: + out = (d < s->count); + break; + case 2: + if ((d % s->count) == 0 && d != 0) { + out = 1; + } else { + out = 0; + } + break; + case 3: + out = (d % s->count) < ((s->count + 1) >> 1); + break; + case 4: + case 5: + out = (d == s->count); + break; + } + return out; +} + +/* return -1 if no transition will occur. */ +int64_t pit_get_next_transition_time(PITChannelState *s, int64_t current_time) +{ + uint64_t d, next_time, base; + int period2; + + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, + get_ticks_per_sec()); + switch (s->mode) { + default: + case 0: + case 1: + if (d < s->count) { + next_time = s->count; + } else { + return -1; + } + break; + case 2: + base = (d / s->count) * s->count; + if ((d - base) == 0 && d != 0) { + next_time = base + s->count; + } else { + next_time = base + s->count + 1; + } + break; + case 3: + base = (d / s->count) * s->count; + period2 = ((s->count + 1) >> 1); + if ((d - base) < period2) { + next_time = base + period2; + } else { + next_time = base + s->count; + } + break; + case 4: + case 5: + if (d < s->count) { + next_time = s->count; + } else if (d == s->count) { + next_time = s->count + 1; + } else { + return -1; + } + break; + } + /* convert to timer units */ + next_time = s->count_load_time + muldiv64(next_time, get_ticks_per_sec(), + PIT_FREQ); + /* fix potential rounding problems */ + /* XXX: better solution: use a clock at PIT_FREQ Hz */ + if (next_time <= current_time) { + next_time = current_time + 1; + } + return next_time; +} + +void pit_get_channel_info_common(PITCommonState *s, PITChannelState *sc, + PITChannelInfo *info) +{ + info->gate = sc->gate; + info->mode = sc->mode; + info->initial_count = sc->count; + info->out = pit_get_out(sc, qemu_get_clock_ns(vm_clock)); +} + +void pit_get_channel_info(ISADevice *dev, int channel, PITChannelInfo *info) +{ + PITCommonState *pit = PIT_COMMON(dev); + PITChannelState *s = &pit->channels[channel]; + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); + + c->get_channel_info(pit, s, info); +} + +void pit_reset_common(PITCommonState *pit) +{ + PITChannelState *s; + int i; + + for (i = 0; i < 3; i++) { + s = &pit->channels[i]; + s->mode = 3; + s->gate = (i != 2); + s->count_load_time = qemu_get_clock_ns(vm_clock); + s->count = 0x10000; + if (i == 0 && !s->irq_disabled) { + s->next_transition_time = + pit_get_next_transition_time(s, s->count_load_time); + } + } +} + +static int pit_init_common(ISADevice *dev) +{ + PITCommonState *pit = PIT_COMMON(dev); + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); + int ret; + + ret = c->init(pit); + if (ret < 0) { + return ret; + } + + isa_register_ioport(dev, &pit->ioports, pit->iobase); + + qdev_set_legacy_instance_id(&dev->qdev, pit->iobase, 2); + + return 0; +} + +static const VMStateDescription vmstate_pit_channel = { + .name = "pit channel", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField[]) { + VMSTATE_INT32(count, PITChannelState), + VMSTATE_UINT16(latched_count, PITChannelState), + VMSTATE_UINT8(count_latched, PITChannelState), + VMSTATE_UINT8(status_latched, PITChannelState), + VMSTATE_UINT8(status, PITChannelState), + VMSTATE_UINT8(read_state, PITChannelState), + VMSTATE_UINT8(write_state, PITChannelState), + VMSTATE_UINT8(write_latch, PITChannelState), + VMSTATE_UINT8(rw_mode, PITChannelState), + VMSTATE_UINT8(mode, PITChannelState), + VMSTATE_UINT8(bcd, PITChannelState), + VMSTATE_UINT8(gate, PITChannelState), + VMSTATE_INT64(count_load_time, PITChannelState), + VMSTATE_INT64(next_transition_time, PITChannelState), + VMSTATE_END_OF_LIST() + } +}; + +static int pit_load_old(QEMUFile *f, void *opaque, int version_id) +{ + PITCommonState *pit = opaque; + PITChannelState *s; + int i; + + if (version_id != 1) { + return -EINVAL; + } + + for (i = 0; i < 3; i++) { + s = &pit->channels[i]; + s->count = qemu_get_be32(f); + qemu_get_be16s(f, &s->latched_count); + qemu_get_8s(f, &s->count_latched); + qemu_get_8s(f, &s->status_latched); + qemu_get_8s(f, &s->status); + qemu_get_8s(f, &s->read_state); + qemu_get_8s(f, &s->write_state); + qemu_get_8s(f, &s->write_latch); + qemu_get_8s(f, &s->rw_mode); + qemu_get_8s(f, &s->mode); + qemu_get_8s(f, &s->bcd); + qemu_get_8s(f, &s->gate); + s->count_load_time = qemu_get_be64(f); + s->irq_disabled = 0; + if (s->irq_timer) { + s->next_transition_time = qemu_get_be64(f); + qemu_get_timer(f, s->irq_timer); + } + } + return 0; +} + +static void pit_dispatch_pre_save(void *opaque) +{ + PITCommonState *s = opaque; + PITCommonClass *c = PIT_COMMON_GET_CLASS(s); + + if (c->pre_save) { + c->pre_save(s); + } +} + +static int pit_dispatch_post_load(void *opaque, int version_id) +{ + PITCommonState *s = opaque; + PITCommonClass *c = PIT_COMMON_GET_CLASS(s); + + if (c->post_load) { + c->post_load(s); + } + return 0; +} + +static const VMStateDescription vmstate_pit_common = { + .name = "i8254", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 1, + .load_state_old = pit_load_old, + .pre_save = pit_dispatch_pre_save, + .post_load = pit_dispatch_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_V(channels[0].irq_disabled, PITCommonState, 3), + VMSTATE_STRUCT_ARRAY(channels, PITCommonState, 3, 2, + vmstate_pit_channel, PITChannelState), + VMSTATE_TIMER(channels[0].irq_timer, PITCommonState), + VMSTATE_END_OF_LIST() + } +}; + +static void pit_common_class_init(ObjectClass *klass, void *data) +{ + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + ic->init = pit_init_common; + dc->vmsd = &vmstate_pit_common; + dc->no_user = 1; +} + +static TypeInfo pit_common_type = { + .name = TYPE_PIT_COMMON, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(PITCommonState), + .class_size = sizeof(PITCommonClass), + .class_init = pit_common_class_init, + .abstract = true, +}; + +static void register_devices(void) +{ + type_register_static(&pit_common_type); +} + +type_init(register_devices); diff --git a/hw/i8254_internal.h b/hw/i8254_internal.h new file mode 100644 index 0000000000..686f0c2ba9 --- /dev/null +++ b/hw/i8254_internal.h @@ -0,0 +1,85 @@ +/* + * QEMU 8253/8254 - internal interfaces + * + * Copyright (c) 2011 Jan Kiszka, Siemens AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef QEMU_I8254_INTERNAL_H +#define QEMU_I8254_INTERNAL_H + +#include "hw.h" +#include "pc.h" +#include "isa.h" + +typedef struct PITChannelState { + int count; /* can be 65536 */ + uint16_t latched_count; + uint8_t count_latched; + uint8_t status_latched; + uint8_t status; + uint8_t read_state; + uint8_t write_state; + uint8_t write_latch; + uint8_t rw_mode; + uint8_t mode; + uint8_t bcd; /* not supported */ + uint8_t gate; /* timer start */ + int64_t count_load_time; + /* irq handling */ + int64_t next_transition_time; + QEMUTimer *irq_timer; + qemu_irq irq; + uint32_t irq_disabled; +} PITChannelState; + +typedef struct PITCommonState { + ISADevice dev; + MemoryRegion ioports; + uint32_t iobase; + PITChannelState channels[3]; +} PITCommonState; + +#define TYPE_PIT_COMMON "pit-common" +#define PIT_COMMON(obj) \ + OBJECT_CHECK(PITCommonState, (obj), TYPE_PIT_COMMON) +#define PIT_COMMON_CLASS(klass) \ + OBJECT_CLASS_CHECK(PITCommonClass, (klass), TYPE_PIT_COMMON) +#define PIT_COMMON_GET_CLASS(obj) \ + OBJECT_GET_CLASS(PITCommonClass, (obj), TYPE_PIT_COMMON) + +typedef struct PITCommonClass { + ISADeviceClass parent_class; + + int (*init)(PITCommonState *s); + void (*set_channel_gate)(PITCommonState *s, PITChannelState *sc, int val); + void (*get_channel_info)(PITCommonState *s, PITChannelState *sc, + PITChannelInfo *info); + void (*pre_save)(PITCommonState *s); + void (*post_load)(PITCommonState *s); +} PITCommonClass; + +int pit_get_out(PITChannelState *s, int64_t current_time); +int64_t pit_get_next_transition_time(PITChannelState *s, int64_t current_time); +void pit_get_channel_info_common(PITCommonState *s, PITChannelState *sc, + PITChannelInfo *info); +void pit_reset_common(PITCommonState *s); + +#endif /* !QEMU_I8254_INTERNAL_H */ -- cgit v1.2.3 From 3fbc1c0c1309a6cc2a0699fbf5e286d32250a0be Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Fri, 2 Mar 2012 20:28:47 +0100 Subject: i8254: Open-code timer restore Same as for the APIC: To enable migration between accelerated and non-accelerated models, we need to arm the channel 0 timer only inside the emulated PIT model. The common code just saves/restores that timer to the the next_transition_time field. Signed-off-by: Jan Kiszka Signed-off-by: Avi Kivity --- hw/i8254.c | 12 ++++++++++++ hw/i8254_common.c | 10 +++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/hw/i8254.c b/hw/i8254.c index 9fde3449b2..77bd5e8222 100644 --- a/hw/i8254.c +++ b/hw/i8254.c @@ -300,6 +300,17 @@ static const MemoryRegionOps pit_ioport_ops = { .old_portio = pit_portio }; +static void pit_post_load(PITCommonState *s) +{ + PITChannelState *sc = &s->channels[0]; + + if (sc->next_transition_time != -1) { + qemu_mod_timer(sc->irq_timer, sc->next_transition_time); + } else { + qemu_del_timer(sc->irq_timer); + } +} + static int pit_initfn(PITCommonState *pit) { PITChannelState *s; @@ -329,6 +340,7 @@ static void pit_class_initfn(ObjectClass *klass, void *data) k->init = pit_initfn; k->set_channel_gate = pit_set_channel_gate; k->get_channel_info = pit_get_channel_info_common; + k->post_load = pit_post_load; dc->reset = pit_reset; dc->props = pit_properties; } diff --git a/hw/i8254_common.c b/hw/i8254_common.c index 6373e878fe..a03d7cd458 100644 --- a/hw/i8254_common.c +++ b/hw/i8254_common.c @@ -211,6 +211,7 @@ static const VMStateDescription vmstate_pit_channel = { static int pit_load_old(QEMUFile *f, void *opaque, int version_id) { PITCommonState *pit = opaque; + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); PITChannelState *s; int i; @@ -234,11 +235,13 @@ static int pit_load_old(QEMUFile *f, void *opaque, int version_id) qemu_get_8s(f, &s->gate); s->count_load_time = qemu_get_be64(f); s->irq_disabled = 0; - if (s->irq_timer) { + if (i == 0) { s->next_transition_time = qemu_get_be64(f); - qemu_get_timer(f, s->irq_timer); } } + if (c->post_load) { + c->post_load(pit); + } return 0; } @@ -275,7 +278,8 @@ static const VMStateDescription vmstate_pit_common = { VMSTATE_UINT32_V(channels[0].irq_disabled, PITCommonState, 3), VMSTATE_STRUCT_ARRAY(channels, PITCommonState, 3, 2, vmstate_pit_channel, PITChannelState), - VMSTATE_TIMER(channels[0].irq_timer, PITCommonState), + VMSTATE_INT64(channels[0].next_transition_time, + PITCommonState), /* formerly irq_timer */ VMSTATE_END_OF_LIST() } }; -- cgit v1.2.3 From 8a7c73932e03f13f29dea8622e15f1ded478d407 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Fri, 2 Mar 2012 20:28:48 +0100 Subject: kvm: Add kvm_has_pit_state2 helper To be used for in-kernel PIT emulation. Signed-off-by: Jan Kiszka Signed-off-by: Avi Kivity --- kvm-all.c | 10 ++++++++++ kvm-stub.c | 5 +++++ kvm.h | 1 + 3 files changed, 16 insertions(+) diff --git a/kvm-all.c b/kvm-all.c index 77eadf608f..278085fc2d 100644 --- a/kvm-all.c +++ b/kvm-all.c @@ -76,6 +76,7 @@ struct KVMState struct kvm_sw_breakpoint_head kvm_sw_breakpoints; #endif int pit_in_kernel; + int pit_state2; int xsave, xcrs; int many_ioeventfds; int irqchip_inject_ioctl; @@ -1058,6 +1059,10 @@ int kvm_init(void) s->xcrs = kvm_check_extension(s, KVM_CAP_XCRS); #endif +#ifdef KVM_CAP_PIT_STATE2 + s->pit_state2 = kvm_check_extension(s, KVM_CAP_PIT_STATE2); +#endif + ret = kvm_arch_init(s); if (ret < 0) { goto err; @@ -1390,6 +1395,11 @@ int kvm_has_xcrs(void) return kvm_state->xcrs; } +int kvm_has_pit_state2(void) +{ + return kvm_state->pit_state2; +} + int kvm_has_many_ioeventfds(void) { if (!kvm_enabled()) { diff --git a/kvm-stub.c b/kvm-stub.c index f63a0d2c81..1f1c6861ed 100644 --- a/kvm-stub.c +++ b/kvm-stub.c @@ -78,6 +78,11 @@ int kvm_allows_irq0_override(void) return 1; } +int kvm_has_pit_state2(void) +{ + return 0; +} + void kvm_setup_guest_memory(void *start, size_t size) { } diff --git a/kvm.h b/kvm.h index f9f1dc86b3..8ef44767af 100644 --- a/kvm.h +++ b/kvm.h @@ -54,6 +54,7 @@ int kvm_has_robust_singlestep(void); int kvm_has_debugregs(void); int kvm_has_xsave(void); int kvm_has_xcrs(void); +int kvm_has_pit_state2(void); int kvm_has_many_ioeventfds(void); int kvm_has_gsi_routing(void); -- cgit v1.2.3 From 5d17c0d2df4998598e6002b27b8e47e792899a0f Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Fri, 2 Mar 2012 20:28:49 +0100 Subject: kvm: x86: Add user space part for in-kernel i8254 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 Signed-off-by: Avi Kivity --- Makefile.target | 2 +- hw/i8254.h | 11 +++ hw/kvm/i8254.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/pc.c | 14 +++- 4 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 hw/kvm/i8254.c diff --git a/Makefile.target b/Makefile.target index 5f3fc40fe5..1bd25a89ad 100644 --- a/Makefile.target +++ b/Makefile.target @@ -244,7 +244,7 @@ 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-y += pc_sysfw.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 a1d2e9835b..ba6b598a99 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 0000000000..bb5fe07d1e --- /dev/null +++ b/hw/kvm/i8254.c @@ -0,0 +1,254 @@ +/* + * KVM in-kernel PIT (i8254) support + * + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2012 Jan Kiszka, Siemens AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#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); +} + +type_init(kvm_pit_register) diff --git a/hw/pc.c b/hw/pc.c index 12c02f2044..bb9867b070 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -1096,7 +1096,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) { @@ -1112,7 +1118,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)); -- cgit v1.2.3 From 7e680753cfa2986e0a8b3b222b6bf0b003c5eb69 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 29 Feb 2012 17:54:29 +0200 Subject: kvm: fill in padding to help valgrind valgrind warns about padding fields which are passed to vcpu ioctls uninitialized. This is not an error in practice because kvm ignored padding. Since the ioctls in question are off data path and the cost is zero anyway, initialize padding to 0 to suppress these errors. Signed-off-by: Michael S. Tsirkin Signed-off-by: Avi Kivity --- kvm-all.c | 2 ++ target-i386/kvm.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/kvm-all.c b/kvm-all.c index 278085fc2d..21c7dd28db 100644 --- a/kvm-all.c +++ b/kvm-all.c @@ -448,6 +448,7 @@ int kvm_coalesce_mmio_region(target_phys_addr_t start, ram_addr_t size) zone.addr = start; zone.size = size; + zone.pad = 0; ret = kvm_vm_ioctl(s, KVM_REGISTER_COALESCED_MMIO, &zone); } @@ -465,6 +466,7 @@ int kvm_uncoalesce_mmio_region(target_phys_addr_t start, ram_addr_t size) zone.addr = start; zone.size = size; + zone.pad = 0; ret = kvm_vm_ioctl(s, KVM_UNREGISTER_COALESCED_MMIO, &zone); } diff --git a/target-i386/kvm.c b/target-i386/kvm.c index 9a732078f3..619d773114 100644 --- a/target-i386/kvm.c +++ b/target-i386/kvm.c @@ -555,6 +555,7 @@ int kvm_arch_init_vcpu(CPUState *env) qemu_add_vm_change_state_handler(cpu_update_state, env); + cpuid_data.cpuid.padding = 0; r = kvm_vcpu_ioctl(env, KVM_SET_CPUID2, &cpuid_data); if (r) { return r; @@ -740,6 +741,7 @@ static void set_seg(struct kvm_segment *lhs, const SegmentCache *rhs) lhs->g = (flags & DESC_G_MASK) != 0; lhs->avl = (flags & DESC_AVL_MASK) != 0; lhs->unusable = 0; + lhs->padding = 0; } static void get_seg(SegmentCache *lhs, const struct kvm_segment *rhs) @@ -919,8 +921,10 @@ static int kvm_put_sregs(CPUState *env) sregs.idt.limit = env->idt.limit; sregs.idt.base = env->idt.base; + memset(sregs.idt.padding, 0, sizeof sregs.idt.padding); sregs.gdt.limit = env->gdt.limit; sregs.gdt.base = env->gdt.base; + memset(sregs.gdt.padding, 0, sizeof sregs.gdt.padding); sregs.cr0 = env->cr[0]; sregs.cr2 = env->cr[2]; @@ -1392,6 +1396,7 @@ static int kvm_put_vcpu_events(CPUState *env, int level) events.exception.nr = env->exception_injected; events.exception.has_error_code = env->has_error_code; events.exception.error_code = env->error_code; + events.exception.pad = 0; events.interrupt.injected = (env->interrupt_injected >= 0); events.interrupt.nr = env->interrupt_injected; @@ -1400,6 +1405,7 @@ static int kvm_put_vcpu_events(CPUState *env, int level) events.nmi.injected = env->nmi_injected; events.nmi.pending = env->nmi_pending; events.nmi.masked = !!(env->hflags2 & HF2_NMI_MASK); + events.nmi.pad = 0; events.sipi_vector = env->sipi_vector; -- cgit v1.2.3