qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [RFC][PATCH] Add HPET emulation to qemu (v2)


From: Beth Kon
Subject: [Qemu-devel] [RFC][PATCH] Add HPET emulation to qemu (v2)
Date: Sat, 2 Aug 2008 06:05:14 -0500

Major changes:

- Rebased to register-based operations for ease of save/restore.
- Looked through Xen's hpet implementation and picked up a bunch of 
  things, though not quite everything yet. Thanks!
- PIT and RTC are entirely disabled in legacy mode, not just their 
  interrupts.
 
There is still a bunch to do but I'm re-posting primarily because 
of the switch to register-based. I have still only tested with a 
linux guest. Windows guest is next on my list... as soon as I return 
from my week vacation.

I've been playing with CONFIG_NO_HZ and been surprised by the 
results.  I was trying to reproduce the wakeup every 10ms that 
Samuel Thibault mentioned, thinking the HPET would improve it. 
But for an idle guest in both cases (with and without HPET), the 
number of wakeups per second was relatively low (28). Ultimately 
this depends on exactly what the guest is doing
when idle, so maybe the HPET won't provide any improvement here. 
But in any case, I didn't see the 10ms wakeup cycle with CONFIG_NO_HZ. 
If anyone can shed any light on this, I could look into it more if need
be.
 
Signed-off-by: Beth Kon <address@hidden>
***

Makefile.target  |    2 
hw/hpet.c        |  441 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/i8254.c       |   11 +
hw/mc146818rtc.c |   30 +++
hw/pc.c          |    2 
5 files changed, 483 insertions(+), 3 deletions(-)

***

diff --git a/Makefile.target b/Makefile.target
index 42162c3..946bdef 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -536,7 +536,7 @@ ifeq ($(TARGET_BASE_ARCH), i386)
 OBJS+= ide.o pckbd.o ps2.o vga.o $(SOUND_HW) dma.o
 OBJS+= fdc.o mc146818rtc.o serial.o i8259.o i8254.o pcspk.o pc.o
 OBJS+= cirrus_vga.o apic.o parallel.o acpi.o piix_pci.o
-OBJS+= usb-uhci.o vmmouse.o vmport.o vmware_vga.o
+OBJS+= usb-uhci.o vmmouse.o vmport.o vmware_vga.o hpet.o
 CPPFLAGS += -DHAS_AUDIO -DHAS_AUDIO_CHOICE
 endif
 ifeq ($(TARGET_BASE_ARCH), ppc)
diff --git a/hw/hpet.c b/hw/hpet.c
new file mode 100644
index 0000000..adfecf0
--- /dev/null
+++ b/hw/hpet.c
@@ -0,0 +1,441 @@
+/*
+ *  High Precisition Event Timer emulation
+ *
+ *  Copyright (c) 2007 Alexander Graf
+ *  Copyright (c) 2008 IBM Corporation
+ *
+ *  Authors: Beth Kon <address@hidden>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * *****************************************************************
+ *
+ * This driver attempts to emulate an HPET device in software. It is by no
+ * means complete and is prone to break on certain conditions.
+ *
+ */
+#include "hw.h"
+#include "console.h"
+#include "qemu-timer.h"
+
+//#define HPET_DEBUG
+
+#define HPET_BASE              0xfed00000
+#define HPET_CLK_PERIOD         10000000ULL /* 10000000 femtoseconds == 10ns*/
+
+#define FS_PER_NS 1000000
+#define HPET_NUM_TIMERS 3
+#define HPET_TIMER_TYPE_LEVEL 1
+#define HPET_TIMER_TYPE_EDGE 0
+#define HPET_TIMER_DELIVERY_APIC 0
+#define HPET_TIMER_DELIVERY_FSB 1
+#define HPET_TIMER_CAP_FSB_INT_DEL (1 << 15)
+#define HPET_TIMER_CAP_PER_INT (1 << 4)
+
+#define HPET_CFG_ENABLE 0x001
+#define HPET_CFG_LEGACY 0x002
+
+#define HPET_ID         0x000
+#define HPET_PERIOD     0x004
+#define HPET_CFG        0x010
+#define HPET_STATUS     0x020
+#define HPET_COUNTER    0x0f0
+#define HPET_TN_REGS    0x100 ... 0x3ff /*address range of all TN regs*/
+#define HPET_TN_CFG     0x000
+#define HPET_TN_CMP     0x008
+#define HPET_TN_ROUTE   0x010
+
+
+#define HPET_TN_INT_TYPE_LEVEL   0x002
+#define HPET_TN_ENABLE           0x004
+#define HPET_TN_PERIODIC         0x008
+#define HPET_TN_PERIODIC_CAP     0x010
+#define HPET_TN_SIZE_CAP         0x020
+#define HPET_TN_SETVAL           0x040
+#define HPET_TN_32BIT            0x100
+#define HPET_TN_INT_ROUTE_MASK  0x3e00
+#define HPET_TN_INT_ROUTE_SHIFT      9
+#define HPET_TN_INT_ROUTE_CAP_SHIFT 32
+#define HPET_TN_CFG_BITS_READONLY_OR_RESERVED 0xffff80b1U
+
+#define timer_int_route(timer)   \
+    ((timer->config & HPET_TN_INT_ROUTE_MASK) >> HPET_TN_INT_ROUTE_SHIFT)
+
+#define hpet_enabled(s)          (s->config & HPET_CFG_ENABLE)
+#define timer_is_periodic(t)     (t->config & HPET_TN_PERIODIC)
+#define timer_enabled(t)         (t->config & HPET_TN_ENABLE)
+
+struct HPETState;
+typedef struct HPETTimer {  /* timers */
+    uint8_t tn;             /*timer number*/
+    QEMUTimer *qemu_timer;
+    struct HPETState *state;
+    /* Memory-mapped, software visible timer registers */
+    uint64_t config;        /* configuration/cap */
+    uint64_t cmp;           /* comparator */
+    uint64_t fsb;           /* FSB route, not supported now */
+    /* Hidden register state */
+    uint64_t period;        /* Last value written to comparator */
+} HPETTimer;
+
+
+typedef struct HPETState {
+    uint64_t hpet_offset;
+    qemu_irq *irqs;
+    HPETTimer timer[HPET_NUM_TIMERS];
+    /* Memory-mapped, software visible registers */
+    uint64_t capability;        /* capabilities */
+    uint64_t config;            /* configuration */
+    uint64_t isr;               /* interrupt status reg */
+    uint64_t hpet_counter;      /* main counter */
+} HPETState;
+
+
+
+int hpet_legacy=0;
+
+static void update_irq(struct HPETTimer *timer)
+{
+    qemu_irq irq;
+    int route;
+
+    if ( (timer->tn <= 1) && (timer->state->config & HPET_CFG_LEGACY) ) {
+
+        /* if LegacyReplacementRoute bit is set, HPET specification requires
+         * timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC,
+         * timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC. 
+         */
+        if (timer->tn == 0)
+            irq=timer->state->irqs[0];
+        else
+            irq=timer->state->irqs[8];
+    }else{
+        route=timer_int_route(timer);
+        irq=timer->state->irqs[route];
+    }
+
+    if(timer_enabled(timer) && hpet_enabled(timer->state)) {
+        qemu_irq_pulse(irq);
+    }
+}
+
+static inline uint64_t ticks_to_ns(uint64_t value) 
+{
+    return (value * HPET_CLK_PERIOD / FS_PER_NS);
+}
+
+static inline uint64_t ns_to_ticks(uint64_t value) 
+{
+    return (value * FS_PER_NS / HPET_CLK_PERIOD);
+}
+
+static inline uint64_t hpet_fixup_reg(
+    uint64_t new, uint64_t old, uint64_t mask)
+{
+    new &= mask;
+    new |= old & ~mask;
+    return new;
+}
+
+static void hpet_timer(void *opaque)
+{
+    HPETTimer *t = (HPETTimer*)opaque;
+    if(t->config & HPET_TN_PERIODIC) {
+       t->cmp += t->period;
+       qemu_mod_timer(t->qemu_timer, qemu_get_clock(vm_clock) 
+           + ticks_to_ns(t->period));
+    }
+    update_irq(t);
+}
+
+static void hpet_set_timer(HPETTimer *s)
+{
+    qemu_mod_timer(s->qemu_timer, qemu_get_clock(vm_clock) 
+        + ticks_to_ns(s->period));
+}
+
+static void hpet_del_timer(HPETTimer *t)
+{
+    qemu_del_timer(t->qemu_timer);
+}
+static uint32_t hpet_ram_readb(void *opaque, target_phys_addr_t addr)
+{
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: hpet_read b at %" PRIx64 "\n", addr);
+#endif
+    return 10;
+}
+
+static uint32_t hpet_ram_readw(void *opaque, target_phys_addr_t addr)
+{
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: hpet_read w at %" PRIx64 "\n", addr);
+#endif
+    return 10;
+}
+
+static uint32_t hpet_ram_readl(void *opaque, target_phys_addr_t addr)
+{
+    HPETState *s = (HPETState *)opaque;
+#ifdef HPET_DEBUG
+  fprintf(stderr, "qemu: hpet_read l at %" PRIx64 "\n", addr);
+#endif
+    switch(addr - HPET_BASE) {
+        case HPET_ID:
+            return s->capability;
+        case HPET_PERIOD:
+            return s->capability >> 32; 
+        case HPET_CFG:
+            return s->config;
+        case HPET_CFG + 4:
+#ifdef HPET_DEBUG
+            fprintf(stderr, "qemu: invalid hpet_read l at %" PRIx64 "\n", 
addr);
+#endif
+            return 0;
+        case HPET_COUNTER: 
+            s->hpet_counter = ns_to_ticks(qemu_get_clock(vm_clock) 
+                                          - s->hpet_offset) ;
+#ifdef HPET_DEBUG
+            fprintf(stderr, "qemu: reading counter %" PRIx64 "\n", 
s->hpet_counter);
+#endif
+            return s->hpet_counter;
+        case HPET_COUNTER + 4:
+            return 0;
+        case HPET_STATUS:
+            return s->isr;
+        case HPET_TN_REGS:
+            {
+                uint8_t timer_id = (addr - HPET_BASE - 0x100) / 0x20;
+                if (timer_id > HPET_NUM_TIMERS - 1) {
+                    fprintf(stderr, "qemu: timer id out of range\n");
+                    return 0;
+                }
+                HPETTimer *timer = &s->timer[timer_id];
+
+                switch((addr - HPET_BASE - 0x100) % 0x20) {
+                    case HPET_TN_CFG:
+                        return timer->config;
+                    case HPET_TN_CFG + 4: // Interrupt capabilities
+                        return timer->config >> 32;
+                    case HPET_TN_CMP: // comparator register
+                        return timer->cmp;
+                    case HPET_TN_CMP + 4:
+                        return timer->cmp >> 32;
+                    case HPET_TN_ROUTE:
+                        return timer->fsb >> 32;
+                }
+            }
+            break;
+    }
+
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: invalid hpet_read l at %" PRIx64 "\n", addr);
+#endif
+    return 10;
+}
+
+static void hpet_ram_writeb(void *opaque, target_phys_addr_t addr, 
+                            uint32_t value)
+{
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: invalid hpet_write b at %" PRIx64 " = %#x\n", addr, 
value);
+#endif
+}
+
+static void hpet_ram_writew(void *opaque, target_phys_addr_t addr,
+                            uint32_t value)
+{
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: invalid hpet_write w at %" PRIx64 " = %#x\n", addr, 
value);
+#endif
+}
+
+static void hpet_ram_writel(void *opaque, target_phys_addr_t addr,
+                            uint32_t value)
+{
+    int i;
+    HPETState *s = (HPETState *)opaque;
+    uint64_t old_val, new_val;
+
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: hpet_write l at %" PRIx64 " = %#x\n", addr, value);
+#endif
+    old_val = hpet_ram_readl(opaque, addr);
+    new_val = value;
+
+
+    switch(addr - HPET_BASE) {
+        case HPET_ID:
+            return;
+        case HPET_CFG:
+            s->config = hpet_fixup_reg(new_val, old_val, 0x3);
+            if (!(old_val & HPET_CFG_ENABLE) && (new_val & HPET_CFG_ENABLE)) {
+                /* Enable main counter and interrupt generation. */
+                s->hpet_offset = qemu_get_clock(vm_clock) 
+                                        - ticks_to_ns(s->hpet_counter);
+                for (i = 0; i < HPET_NUM_TIMERS; i++)
+                    hpet_set_timer(&s->timer[i]);
+            }
+            else if ( (old_val & HPET_CFG_ENABLE) && 
+                                           !(new_val & HPET_CFG_ENABLE)) {
+                /* Halt main counter and disable interrupt generation. */
+                s->hpet_counter = ns_to_ticks(qemu_get_clock(vm_clock) 
+                                          - s->hpet_offset) ;
+                for (i = 0; i < HPET_NUM_TIMERS; i++)
+                    hpet_del_timer(&s->timer[i]);
+            }
+            hpet_legacy = s->config & HPET_CFG_LEGACY;
+            break;
+        case HPET_CFG + 4: 
+#ifdef HPET_DEBUG
+            fprintf(stderr, "qemu: invalid hpet_write l at %" PRIx64 " = 
%#x\n", addr,
+                    value);
+#endif
+            break;
+       case HPET_STATUS:
+            /* FIXME: need to handle level-triggered interrupts */
+            break;
+        case HPET_COUNTER:
+          
+           if ((s->config & HPET_CFG_ENABLE)) 
+               fprintf(stderr, "qemu: Writing counter while HPET enabled!\n"); 
+           s->hpet_counter =  value;
+           break;
+        case HPET_COUNTER + 4:
+           s->hpet_counter = (s->hpet_counter & 0xffffffffULL) 
+                             | (((uint64_t)value) << 32);
+#ifdef HPET_DEBUG
+           fprintf(stderr, "qemu: HPET counter 0xf4 set to %#x -> %" PRIx64 
"\n", 
+                   value, s->hpet_counter);
+#endif
+           break;
+        case HPET_TN_REGS:
+            {
+                uint8_t timer_id = (addr - HPET_BASE - 0x100) / 0x20;
+#ifdef HPET_DEBUG
+                fprintf(stderr, 
+                         "qemu: hpet_write l timer_id = %#x \n", 
+                         timer_id);
+#endif
+                HPETTimer *timer = &s->timer[timer_id];
+                
+                switch((addr - HPET_BASE - 0x100) % 0x20) {
+                    case HPET_TN_CFG:
+#ifdef HPET_DEBUG
+                        fprintf(stderr, 
+                                "qemu: hpet_write l comparator value %#x\n", 
+                                value);
+#endif
+                        timer->config = 
+                                    hpet_fixup_reg(new_val, old_val, 0x3f4e);
+#ifdef HPET_DEBUG
+                        fprintf(stderr, "qemu: hpet_write l at %" PRIx64 " = 
%#x\n", 
+                                addr, value);
+#endif
+                        if ( new_val & HPET_TN_32BIT ) {
+                            timer->cmp = (uint32_t)timer->cmp;
+                            timer->period = (uint32_t)timer->period;
+                        }
+
+                        break;
+#ifdef HPET_DEBUG
+                    case HPET_TN_CFG + 4: // Interrupt capabilities
+                        fprintf(stderr, 
+                                "qemu: invalid hpet_write l at %" PRIx64 " = 
%#x\n", 
+                                 addr, value);
+                        break;
+#endif
+                    case HPET_TN_CMP: // comparator register
+#ifdef HPET_DEBUG
+                        fprintf(stderr, 
+                                "qemu: hpet_write l comparator value %#x\n", 
+                                value);
+#endif
+                        if ( timer->config & HPET_TN_32BIT)
+                            new_val = (uint32_t)new_val;
+                        if ( !timer_is_periodic(timer) ||
+                                   (timer->config & HPET_TN_SETVAL) )
+                            timer->cmp = new_val;
+                        else {
+                            /*
+                             * FIXME: Clamp period to reasonable min/max 
values:
+                             */
+                             timer->period = new_val;
+                        }
+                        timer->config &= ~HPET_TN_SETVAL;
+                        if ( hpet_enabled(s) )
+                            hpet_set_timer(timer);
+                        break;
+
+                    case HPET_TN_ROUTE + 4:
+#ifdef HPET_DEBUG
+                        fprintf(stderr, 
+                                "qemu: invalid hpet_write l at %" PRIx64 " = 
%#x\n", 
+                                addr, value);
+#endif
+                        break;
+                }
+            }
+            break;
+       default:
+            fprintf(stderr, "qemu: invalid hpet_write l at %" PRIx64 " = 
%#x\n",
+                    addr, value);
+    }
+
+}
+
+static CPUReadMemoryFunc *hpet_ram_read[] = {
+    hpet_ram_readb,
+    hpet_ram_readw,
+    hpet_ram_readl,
+};
+
+static CPUWriteMemoryFunc *hpet_ram_write[] = {
+    hpet_ram_writeb,
+    hpet_ram_writew,
+    hpet_ram_writel,
+};
+
+
+void hpet_init(qemu_irq *irq) {
+    int iomemtype, i;
+    HPETState *s;
+
+    /* XXX this is a dirty hack for HPET support w/o LPC
+           Actually this is a config descriptor for the RCBA */
+    fprintf (stderr, "hpet_init\n");
+    s = qemu_mallocz(sizeof(HPETState));
+    s->irqs = irq;
+    /* 64-bit main counter; 3 timers supported; LegacyReplacementRoute. */
+    s->capability = 0x8086A201ULL;
+    s->capability |= ((HPET_CLK_PERIOD) << 32);
+
+    for(i=0; i<HPET_NUM_TIMERS; i++) {
+        HPETTimer *timer = &s->timer[i];
+        timer->tn = i;
+        timer->cmp = ~0ULL;
+        timer->config =  HPET_TN_PERIODIC_CAP;
+        timer->config |=  0x00ffULL << 32;
+        timer->state = s;
+        timer->qemu_timer = qemu_new_timer(vm_clock, hpet_timer, timer);
+    }
+
+    /* HPET Area */
+
+    iomemtype = cpu_register_io_memory(0, hpet_ram_read,
+                    hpet_ram_write, s);
+
+    cpu_register_physical_memory(HPET_BASE, 0x400, iomemtype);
+}
diff --git a/hw/i8254.c b/hw/i8254.c
index 4813b03..7ffdf61 100644
--- a/hw/i8254.c
+++ b/hw/i8254.c
@@ -26,6 +26,9 @@
 #include "isa.h"
 #include "qemu-timer.h"
 
+#if defined TARGET_I386 || defined TARGET_X86_64
+extern int hpet_legacy;
+#endif
 //#define DEBUG_PIT
 
 #define RW_STATE_LSB 1
@@ -367,6 +370,14 @@ static void pit_irq_timer_update(PITChannelState *s, 
int64_t current_time)
 
     if (!s->irq_timer)
         return;
+
+#if defined TARGET_I386 || defined TARGET_X86_64
+    if (hpet_legacy) {
+        qemu_del_timer(s->irq_timer);
+        return;
+    }
+#endif
+
     expire_time = pit_get_next_transition_time(s, current_time);
     irq_level = pit_get_out1(s, current_time);
     qemu_set_irq(s->irq, irq_level);
diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c
index 30bb044..ffb7a48 100644
--- a/hw/mc146818rtc.c
+++ b/hw/mc146818rtc.c
@@ -27,6 +27,10 @@
 #include "pc.h"
 #include "isa.h"
 
+#if defined TARGET_I386 || defined TARGET_X86_64
+extern int hpet_legacy;
+#endif
+
 //#define DEBUG_CMOS
 
 #define RTC_SECONDS             0
@@ -80,7 +84,11 @@ static void rtc_timer_update(RTCState *s, int64_t 
current_time)
 
     period_code = s->cmos_data[RTC_REG_A] & 0x0f;
     if (period_code != 0 &&
+#if defined TARGET_I386 || defined TARGET_X86_64
+        (s->cmos_data[RTC_REG_B] & REG_B_PIE) && !hpet_legacy) {
+#else
         (s->cmos_data[RTC_REG_B] & REG_B_PIE)) {
+#endif
         if (period_code <= 2)
             period_code += 7;
         /* period in 32 Khz cycles */
@@ -101,7 +109,10 @@ static void rtc_periodic_timer(void *opaque)
 
     rtc_timer_update(s, s->next_periodic_time);
     s->cmos_data[RTC_REG_C] |= 0xc0;
-    qemu_irq_raise(s->irq);
+#if defined TARGET_I386 || defined TARGET_X86_64
+    if (!hpet_legacy)
+#endif
+        qemu_irq_raise(s->irq);
 }
 
 static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
@@ -281,6 +292,12 @@ static void rtc_update_second(void *opaque)
     RTCState *s = opaque;
     int64_t delay;
 
+#if defined TARGET_I386 || defined TARGET_X86_64
+    if (hpet_legacy) {
+        qemu_del_timer(s->second_timer2);
+        return;
+    }
+#endif
     /* if the oscillator is not in normal operation, we do not update */
     if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) {
         s->next_second_time += ticks_per_sec;
@@ -306,6 +323,12 @@ static void rtc_update_second2(void *opaque)
 {
     RTCState *s = opaque;
 
+#if defined TARGET_I386 || defined TARGET_X86_64
+    if (hpet_legacy) {
+        qemu_del_timer(s->second_timer);
+        return;
+    }
+#endif
     if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
         rtc_copy_date(s);
     }
@@ -359,7 +382,10 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t 
addr)
             break;
         case RTC_REG_C:
             ret = s->cmos_data[s->cmos_index];
-            qemu_irq_lower(s->irq);
+#if defined TARGET_I386 || defined TARGET_X86_64
+            if (!hpet_legacy)
+#endif
+                qemu_irq_lower(s->irq);
             s->cmos_data[RTC_REG_C] = 0x00;
             break;
         default:
diff --git a/hw/pc.c b/hw/pc.c
index fb3e0c9..80d5a19 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -46,6 +46,7 @@
 #define ACPI_DATA_SIZE       0x10000
 
 #define MAX_IDE_BUS 2
+void hpet_init(qemu_irq irq);
 
 static fdctrl_t *floppy_controller;
 static RTCState *rtc_state;
@@ -925,6 +926,7 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
     }
     pit = pit_init(0x40, i8259[0]);
     pcspk_init(pit);
+    hpet_init(i8259);
     if (pci_enabled) {
         pic_set_alt_irq_func(isa_pic, ioapic_set_irq, ioapic);
     }




reply via email to

[Prev in Thread] Current Thread [Next in Thread]