qemu-devel
[Top][All Lists]
Advanced

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

[Qemu-devel] Re: [RFC][PATCH] Add HPET emulation to qemu


From: Alexander Graf
Subject: [Qemu-devel] Re: [RFC][PATCH] Add HPET emulation to qemu
Date: Sat, 12 Jul 2008 17:42:40 +0200

Hi Beth,

On Jul 10, 2008, at 5:48 AM, Beth Kon wrote:

This patch, based on an earlier patch by Alexander Graf, adds HPET
emulation to qemu. I am sending out a separate patch to kvm with the
required bios changes.

This work is incomplete.

Wow it's good to see that someone's working on it. I am pretty sure that you're basing on an older version of my HPET emulation, so you might also want to take a look at the current patch file residing in http://alex.csgraf.de/qemu/osxpatches.tar.bz2

Currently working (at least generally):
- linux 2.6.25.9 guest


Todo:
- other guest support (i.e. adding whatever may be missing for
  support of other modes of operation used by other OS's).
- level-triggered interrupts

Look at my current version for that. I only have Level support in there for now though.


- non-legacy routing
- 64-bit operation
- ...

While reading through the code I realized how badly commented it is. At least the functions should have some comments on them what their purpose is. Furthermore there still are a lot of magic numbers in the code. While that is "normal qemu code style" and I wrote it this way, I'm not too fond of it. So it might be a good idea to at least make the switch numbers defines.



Basically what I've done so far is make it work for linux.

The one area that feels ugly/wrong at the moment is handling the
disabling of 8254 and RTC timer interrupts when operating in legacy
mode. The HPET spec says "in this case the 8254/RTC timer will not cause
any interrupts". I'm not sure if I should disable the RTC/8254 in some
more general way, or just disable interrupts. Comments appreciated.

IIRC the spec defines that the HPET _can_ replace the 8254, but does not have to. So you should be mostly fine on that part.



Signed-off-by: Beth Kon <address@hidden>

diffstat output:
Makefile.target  |    2
hw/hpet.c        |  393
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/i8254.c       |    8 -
hw/mc146818rtc.c |   25 ++-
hw/pc.c          |    5
5 files changed, 427 insertions(+), 6 deletions(-)


diff --git a/Makefile.target b/Makefile.target
index 73adbb1..05829ea 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -530,7 +530,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..e74de08
--- /dev/null
+++ b/hw/hpet.c
@@ -0,0 +1,393 @@
+/*
+ *  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

This is a dirty hack that I did to make Mac OS X happy. Actually the HPET base address gets specified in the RCBA on the LPC and is configured by the BIOS to point to a valid address, with 0xfed00000 being the default (IIRC if you write 0 to the fields you end up with that address).


+#define HPET_PERIOD             0x00989680 /* 10000000 femptoseconds,
10ns*/

Any reason why this is a hex value? I find 10000000 a lot easier to read :-)


+
+#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)
+
+struct HPETState;
+typedef struct HPETTimer {
+    QEMUTimer *timer;
+    struct HPETState *state;
+    uint8_t type;
+    uint8_t active;
+    uint8_t delivery;
+    uint8_t apic_port;
+    uint8_t periodic;
+    uint8_t enabled;
+    uint32_t comparator; // if(hpet_counter == comparator) IRQ();
+    uint32_t delta;
+    qemu_irq irq;
+} HPETTimer;
+
+typedef struct HPETState {
+    uint64_t hpet_counter;
+    uint64_t hpet_offset;
+    int64_t next_periodic_time;
+    uint8_t legacy_route;
+    uint8_t enabled;
+    uint8_t updated;
+    qemu_irq *irqs;
+    HPETTimer timer[HPET_NUM_TIMERS];
+} HPETState;
+
+
+int hpet_legacy;
+
+static void update_irq(struct HPETTimer *timer)
+{
+    if(timer->enabled && timer->state->enabled) {
+        qemu_irq_pulse(timer->irq);
+    }
+}
+
+static void update_irq_all(struct HPETState *s)
+{
+    int i;
+    for(i=0; i<HPET_NUM_TIMERS; i++)
+        update_irq(&s->timer[i]);
+}
+
+static inline int64_t ticks_to_ns(int64_t value)
+{
+    return (value * HPET_PERIOD / FS_PER_NS);
+}
+
+static inline int64_t ns_to_ticks(int64_t value)
+{
+    return (value * FS_PER_NS / HPET_PERIOD);
+}
+
+static void hpet_timer(void *opaque)
+{
+    HPETTimer *s = (HPETTimer*)opaque;
+    if(s->periodic) {
+       s->comparator += s->delta;
+       qemu_mod_timer(s->timer, qemu_get_clock(vm_clock)
+           + ticks_to_ns(s->delta));
+    }
+    update_irq(s);
+}
+
+static void hpet_check(HPETTimer *s)
+{
+    qemu_mod_timer(s->timer, qemu_get_clock(vm_clock)
+        + ticks_to_ns(s->delta));
+}
+
+static uint32_t hpet_ram_readb(void *opaque, target_phys_addr_t addr)
+{
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: hpet_read b at %#lx\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 %#lx\n", addr);
+#endif
+    return 10;
+}

If I'm not completely mistaken, all reads and writes need to be in 32- or 64-bit mode. So it's pretty safe to remove these. I only added them to see if Mac OS X actually would access them. To still enable other people to do the same you might as well ifdef them out.


+
+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 %#lx\n", addr);
+#endif
+    switch(addr - HPET_BASE) {
+        case 0x00:
+            return 0x8086a201;
+        case 0x04:
+            return HPET_PERIOD;
+        case 0x10:
+            return ((s->legacy_route << 1) | s->enabled);
+        case 0x14:
+#ifdef HPET_DEBUG
+            fprintf(stderr, "qemu: invalid hpet_read l at %#lx\n",
addr);
+#endif
+            return 0;
+        case 0xf0:
+            s->hpet_counter = ns_to_ticks(qemu_get_clock(vm_clock)
+                                          - s->hpet_offset) ;

I'm having trouble understanding this part. The hpet_counter is actually the ticks of the internal main clock of the HPET. This value is actually supposed to constantly change wrt to the current time. The "timers" in the HPET can now compare themselves to the "current value" of the hpet_counter at all times, rising an interrupt if something matches.

So far for the theory. I thought that it'd be a lot more convenient to simply write down an internal offset (hpet_counter) to the actual clock and base all calculations on that, so we can actually trigger a timer based on event_time - offset. I don't see any reason to set hpet_counter again, but maybe it's been too long that I've looked at that code :).

Hum ... having looked further, is that what hpet_offset is supposed to be and the reason you're setting s->updated?


+#ifdef HPET_DEBUG
+            fprintf(stderr, "qemu: reading counter %#lx\n",
s->hpet_counter);
+#endif
+            return s->hpet_counter;
+        case 0xf4:
+            return 0;
+        case 0x20:
+            {
+                uint32_t retval = 0;
+                int i;
+                for(i=0; i<HPET_NUM_TIMERS; i++) {
+                    if(s->timer[i].type == HPET_TIMER_TYPE_LEVEL)
+                        retval |= s->timer[i].active << i;
+                }
+                return retval;
+            }
+        case 0x100 ... 0x3ff:
+            {
+                uint8_t timer_id = (addr - HPET_BASE - 0x100) / 0x20;
+                HPETTimer *timer = &s->timer[timer_id];
+
+                switch((addr - HPET_BASE - 0x100) % 0x20) {
+                    case 0x0:
+                        return ((timer->delivery ==
HPET_TIMER_DELIVERY_FSB) << 14)
+                             | (timer->apic_port << 9)
+                             | HPET_TIMER_CAP_PER_INT
+                             | (timer->periodic << 3)
+                             | (timer->enabled << 2)
+                             | (timer->type << 1);
+                    case 0x4: // Interrupt capabilities
+                        return 0x00ff;
+                    case 0x8: // comparator register
+                        return timer->comparator;
+                    case 0xc:
+                        return 0x0;
+                }
+            }
+            break;
+    }
+
+#ifdef HPET_DEBUG
+    fprintf(stderr, "qemu: invalid hpet_read l at %#lx\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 %#lx = %#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 %#lx = %#x\n", addr,
value);
+#endif
+}
+
+static void hpet_ram_writel(void *opaque, target_phys_addr_t addr,
+                            uint32_t value)
+{
+    HPETState *s = (HPETState *)opaque;
+#ifdef HPET_DEBUG
+ fprintf(stderr, "qemu: hpet_write l at %#lx = %#x\n", addr, value);
+#endif
+    switch(addr - HPET_BASE) {
+        case 0x00:
+            return;
+        case 0x10:
+            if(value <= 3) {

If I may cite Avi:Algorithmic and Bit-operations don't mix well. I don't really see why we shouldn't interpret the first two bits when any bit > 1 is set. If I read the spec correctly, the values are reserved and should not be used, don't really make anything fail for now though. This check would make the operation a noop though! A warning would be nice though.

Looking at my code, it seems I did the same - so blame me if anyone asks :-).


+                s->enabled = value & 1;
+                if (s->enabled && s->updated) {
+                    /* subtract hpet_counter in case it was set to a
non-zero
+                       value */
+                    s->hpet_offset = qemu_get_clock(vm_clock)
+                                     - ticks_to_ns(s->hpet_counter);
+                    s->updated = 0;
+                }
+                s->legacy_route = (value >> 1) & 1;
+                hpet_legacy = s->legacy_route;

Is this what real hardware does? As soon as the HPET is there and someone sets a random bit on it suddenly other completely unrelated devices cease to work? I kinda doubt that.


+                update_irq_all(s);
+            } else {
+#ifdef HPET_DEBUG
+ fprintf(stderr, "qemu: invalid hpet_write l at %#lx = %
#x\n",
+                        addr, value);
+#endif
+            }
+            break;
+        case 0x14:
+#ifdef HPET_DEBUG
+            fprintf(stderr, "qemu: invalid hpet_write l at %#lx = %#x
\n", addr,
+                    value);
+#endif
+            break;
+       case 0x20:
+            {
+                int i;
+                for(i=0; i<HPET_NUM_TIMERS; i++) {
+                    if(s->timer[i].type == HPET_TIMER_TYPE_LEVEL) {
+                        if(value & (1 << i)) {
+                            s->timer[i].active = 0;
+                            update_irq(&s->timer[i]);
+                        }
+                    }
+                }
+            }
+            break;
+        case 0xf0:
+           if (!s->enabled) {
+               s->hpet_counter = (s->hpet_counter & (0xffffffffULL <<
32))
+                                  | value;
+               s->updated = 1;
+#ifdef HPET_DEBUG
+ fprintf(stderr, "qemu: HPET counter 0xf0 set to %#x -> %#lx
\n",
+                   value, s->hpet_counter);
+#endif
+           } else {
+ fprintf(stderr, "qemu: Invalid write while HPET enabled
\n");
+           }
+           break;
+        case 0xf4:
+           s->hpet_counter = (s->hpet_counter & 0xffffffffULL)
+                             | (((uint64_t)value) << 32);
+#ifdef HPET_DEBUG
+ fprintf(stderr, "qemu: HPET counter 0xf4 set to %#x -> %#lx
\n",
+                   value, s->hpet_counter);
+#endif
+           break;
+        case 0x100 ... 0x3ff:
+            {
+                uint8_t timer_id = (addr - HPET_BASE - 0x100) / 0x20;
+#ifdef HPET_DEBUG
+                fprintf(stderr, "qemu: hpet_write l timer_id = %#x
+                        addr = %#lx\n", timer_id);
+#endif
+                HPETTimer *timer = &s->timer[timer_id];
+
+                switch((addr - HPET_BASE - 0x100) % 0x20) {
+                    case 0x0:
+#ifdef HPET_DEBUG
+ fprintf(stderr, "qemu: hpet_write l timer reg =
%#x\n",
+                                (addr - HPET_BASE - 0x100) % 0x20 );
+#endif
+                        if(value & 1) break; // reserved
+                        timer->delivery = (value >> 14) & 1;
+                        timer->apic_port = (value >> 9) & 16;
+                        timer->irq = s->irqs[timer->apic_port];
+                        timer->periodic = (value >> 3) & 1;
+                        timer->enabled = (value >> 2) & 1;
+                        timer->type = (value >> 1) & 1;
+#ifdef HPET_DEBUG
+ fprintf(stderr, "qemu: hpet_write l at %#lx = %
#x\n",
+                                addr, value);
+#endif
+                        hpet_check(timer);
+                        break;
+#ifdef HPET_DEBUG
+                    case 0x4: // Interrupt capabilities
+                        fprintf(stderr, "qemu: qemu case 0x4 invalid
+                                hpet_write l at %#lx = %#x\n", addr,
value);
+                        break;
+#endif
+                    case 0x8: // comparator register
+#ifdef HPET_DEBUG
+ fprintf(stderr, "qemu: hpet_write l comparator
+                                value %#x\n", value);
+#endif
+                        timer->comparator = value;
+                        timer->delta = value;
+                        hpet_check(timer);
+                        break;
+                    case 0xc:
+#ifdef HPET_DEBUG
+                        fprintf(stderr, "qemu: case 0xc invalid
hpet_write l
+                                at %#lx = %#x\n", addr, value);
+#endif
+                        break;
+                }
+            }
+            break;
+       default:
+            fprintf(stderr, "qemu: invalid hpet_write l at %#lx = %#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;
+
+    for(i=0; i<HPET_NUM_TIMERS; i++) {
+        HPETTimer *timer = &s->timer[i];
+        timer->comparator = 0xffffffff;
+        timer->state = s;
+        timer->timer = qemu_new_timer(vm_clock, hpet_timer, s->timer
+i);
+        switch(i) {
+            case 0:
+                timer->apic_port = 0;

Isn't this IRQ2?

[snip]

Alex





reply via email to

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