From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1KGnBC-00035v-5V for qemu-devel@nongnu.org; Wed, 09 Jul 2008 23:50:50 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1KGnBB-00035j-FY for qemu-devel@nongnu.org; Wed, 09 Jul 2008 23:50:49 -0400 Received: from [199.232.76.173] (port=39334 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KGnBB-00035g-7W for qemu-devel@nongnu.org; Wed, 09 Jul 2008 23:50:49 -0400 Received: from e6.ny.us.ibm.com ([32.97.182.146]:47802) by monty-python.gnu.org with esmtps (TLS-1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1KGnBA-0002zo-Pn for qemu-devel@nongnu.org; Wed, 09 Jul 2008 23:50:49 -0400 Received: from d01relay04.pok.ibm.com (d01relay04.pok.ibm.com [9.56.227.236]) by e6.ny.us.ibm.com (8.13.8/8.13.8) with ESMTP id m6A3pTE0007622 for ; Wed, 9 Jul 2008 23:51:29 -0400 Received: from d01av04.pok.ibm.com (d01av04.pok.ibm.com [9.56.224.64]) by d01relay04.pok.ibm.com (8.13.8/8.13.8/NCO v9.0) with ESMTP id m6A3mtgT162948 for ; Wed, 9 Jul 2008 23:48:55 -0400 Received: from d01av04.pok.ibm.com (loopback [127.0.0.1]) by d01av04.pok.ibm.com (8.12.11.20060308/8.13.3) with ESMTP id m6A3mttd001449 for ; Wed, 9 Jul 2008 23:48:55 -0400 From: Beth Kon Content-Type: text/plain Date: Wed, 09 Jul 2008 23:48:46 -0400 Message-Id: <1215661726.9862.33.camel@beth-ubuntu> Mime-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Qemu-devel] [RFC][PATCH] Add HPET emulation to qemu Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: agraf@suse.de, kvm@vger.kernel.org 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. 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 - non-legacy routing - 64-bit operation - ... 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. Signed-off-by: Beth Kon 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 + * + * 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_PERIOD 0x00989680 /* 10000000 femptoseconds, 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) + +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; itimer[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; +} + +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) ; +#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; itimer[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) { + 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; + 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; itimer[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; itimer[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; + break; + case 1: + timer->apic_port = 8; + break; + default: + timer->apic_port = 0; + break; + } + s->timer[i].irq = irq[timer->apic_port]; + } + + /* 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..29dd599 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 @@ -369,7 +372,10 @@ static void pit_irq_timer_update(PITChannelState *s, int64_t current_time) return; 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); +#if defined TARGET_I386 || defined TARGET_X86_64 + if (!hpet_legacy) +#endif + qemu_set_irq(s->irq, irq_level); #ifdef DEBUG_PIT printf("irq_level=%d next_delay=%f\n", irq_level, diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index 30bb044..bb8c6a8 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 @@ -101,7 +105,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) @@ -320,14 +327,20 @@ static void rtc_update_second2(void *opaque) s->cmos_data[RTC_HOURS_ALARM] == s->current_tm.tm_hour)) { s->cmos_data[RTC_REG_C] |= 0xa0; - qemu_irq_raise(s->irq); +#if defined TARGET_I386 || defined TARGET_X86_64 + if (!hpet_legacy) +#endif + qemu_irq_raise(s->irq); } } /* update ended interrupt */ if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { s->cmos_data[RTC_REG_C] |= 0x90; - qemu_irq_raise(s->irq); +#if defined TARGET_I386 || defined TARGET_X86_64 + if (!hpet_legacy) +#endif + qemu_irq_raise(s->irq); } /* clear update in progress bit */ @@ -359,7 +372,11 @@ 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 99df09d..deefd2c 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -47,6 +47,8 @@ #define MAX_IDE_BUS 2 +void hpet_init(qemu_irq irq); + static fdctrl_t *floppy_controller; static RTCState *rtc_state; static PITState *pit; @@ -923,6 +925,9 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size, ioapic = ioapic_init(); } pit = pit_init(0x40, i8259[0]); + + hpet_init(i8259); + pcspk_init(pit); if (pci_enabled) { pic_set_alt_irq_func(isa_pic, ioapic_set_irq, ioapic); -- Elizabeth Kon (Beth) IBM Linux Technology Center Open Hypervisor Team email: eak@us.ibm.com