From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([208.118.235.92]:35075) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1US6w1-000101-72 for qemu-devel@nongnu.org; Tue, 16 Apr 2013 10:32:42 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1US6Uo-000715-JY for qemu-devel@nongnu.org; Tue, 16 Apr 2013 10:04:34 -0400 Received: from mail-bk0-x231.google.com ([2a00:1450:4008:c01::231]:64754) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1US5wV-0003hF-Ut for qemu-devel@nongnu.org; Tue, 16 Apr 2013 09:29:04 -0400 Received: by mail-bk0-f49.google.com with SMTP id w12so258517bku.22 for ; Tue, 16 Apr 2013 06:29:03 -0700 (PDT) MIME-Version: 1.0 Sender: peter.crosthwaite@petalogix.com In-Reply-To: <3e0e268a897603d5ae067f6e01fad68e@thom.fr.eu.org> References: <3e0e268a897603d5ae067f6e01fad68e@thom.fr.eu.org> Date: Tue, 16 Apr 2013 23:29:02 +1000 Message-ID: From: Peter Crosthwaite Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Subject: Re: [Qemu-devel] [PATCH] ARM Cortex A9 Global Timer List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: =?ISO-8859-1?Q?Fran=E7ois_Legal?= Cc: qemu-devel@nongnu.org Hi Francois, On Tue, Apr 16, 2013 at 1:41 AM, Fran=E7ois Legal wrote: > Hello, > > > > I made up this patch to implement the Cortex A9 global timer in Qemu. > > My patch is based on the Qemu branch maintained by Xilinx for the Zynq. > Patches against this tree can be submitted to git@xilinx.com. But this work is generic enough to be appropriate for this forum. There is nothing in our tree that should affect this patch. So I suggest rebasing against the qemu.org tree - it should be trivial. Regards, Peter > > > diff -urN qemu-master/hw/cpu/a9mpcore.c qemu-master.new/hw/cpu/a9mpcore.c > --- qemu-master/hw/cpu/a9mpcore.c 2013-04-08 20:12:33.000000000 +0200 > +++ qemu-master.new/hw/cpu/a9mpcore.c 2013-04-15 12:54:06.000000000 +0= 200 > @@ -15,6 +15,7 @@ > uint32_t num_cpu; > MemoryRegion container; > DeviceState *mptimer; > + DeviceState *mpgtimer; > DeviceState *wdt; > DeviceState *gic; > DeviceState *scu; > @@ -31,6 +32,7 @@ > { > A9MPPrivState *s =3D FROM_SYSBUS(A9MPPrivState, dev); > SysBusDevice *timerbusdev, *wdtbusdev, *gicbusdev, *scubusdev; > + SysBusDevice *gtimerbusdev; > int i; > > s->gic =3D qdev_create(NULL, "arm_gic"); > @@ -50,6 +52,11 @@ > qdev_init_nofail(s->scu); > scubusdev =3D SYS_BUS_DEVICE(s->scu); > > + s->mpgtimer =3D qdev_create(NULL, "arm_mp_globaltimer"); > + qdev_prop_set_uint32(s->mpgtimer, "num-cpu", s->num_cpu); > + qdev_init_nofail(s->mpgtimer); > + gtimerbusdev =3D SYS_BUS_DEVICE(s->mpgtimer); > + > s->mptimer =3D qdev_create(NULL, "arm_mptimer"); > qdev_prop_set_uint32(s->mptimer, "num-cpu", s->num_cpu); > qdev_init_nofail(s->mptimer); > @@ -68,8 +75,6 @@ > * 0x0600-0x06ff -- private timers and watchdogs > * 0x0700-0x0fff -- nothing > * 0x1000-0x1fff -- GIC Distributor > - * > - * We should implement the global timer but don't currently do so. > */ > memory_region_init(&s->container, "a9mp-priv-container", 0x2000); > memory_region_add_subregion(&s->container, 0, > @@ -80,6 +85,8 @@ > /* Note that the A9 exposes only the "timer/watchdog for this core" > * memory region, not the "timer/watchdog for core X" ones 11MPcore > has. > */ > + memory_region_add_subregion(&s->container, 0x200, > + sysbus_mmio_get_region(gtimerbusdev, 0))= ; > memory_region_add_subregion(&s->container, 0x600, > sysbus_mmio_get_region(timerbusdev, 0)); > memory_region_add_subregion(&s->container, 0x620, > @@ -90,10 +97,13 @@ > sysbus_init_mmio(dev, &s->container); > > /* Wire up the interrupt from each watchdog and timer. > - * For each core the timer is PPI 29 and the watchdog PPI 30. > + * For each core the global timer is PPI 27, the private > + * timer is PPI 29 and the watchdog PPI 30. > */ > for (i =3D 0; i < s->num_cpu; i++) { > int ppibase =3D (s->num_irq - 32) + i * 32; > + sysbus_connect_irq(gtimerbusdev, i, > + qdev_get_gpio_in(s->gic, ppibase + 27)); > sysbus_connect_irq(timerbusdev, i, > qdev_get_gpio_in(s->gic, ppibase + 29)); > sysbus_connect_irq(wdtbusdev, i, > diff -urN qemu-master/hw/timer/arm_mpgtimer.c > qemu-master.new/hw/timer/arm_mpgtimer.c > --- qemu-master/hw/timer/arm_mpgtimer.c 1970-01-01 01:00:00.000000000 > +0100 > +++ qemu-master.new/hw/timer/arm_mpgtimer.c 2013-04-15 13:56:23.000000= 000 > +0200 > @@ -0,0 +1,359 @@ > +/* > + * Global peripheral timer block for ARM 11MPCore and A9MP > + * > + * Written by Fran=E7ois LEGAL > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * as published by the Free Software Foundation; either version > + * 2 of the License, or (at your option) any later version. > + * > + * This program 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 General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License alo= ng > + * with this program; if not, see . > + */ > + > +#include "hw/sysbus.h" > +#include "qemu/timer.h" > + > +/* This device implements the per-cpu private timer and watchdog block > + * which is used in both the ARM11MPCore and Cortex-A9MP. > + */ > + > +#define MAX_CPUS 4 > + > +/* State of a single gtimer or block */ > +typedef struct { > + uint32_t control; > + uint64_t compare; > + uint32_t inc; > + uint32_t status; > + int64_t tick; > + > + int64_t delta; > + uint64_t *gtimer_counter; > + uint32_t *gtimer_control; > + > + QEMUTimer *timer; > + MemoryRegion iomem; > + qemu_irq irq; > +} gTimerBlock; > + > +typedef struct { > + SysBusDevice busdev; > + uint32_t num_cpu; > + uint64_t gtimer_counter; > + uint32_t gtimer_control; > + gTimerBlock gtimer[MAX_CPUS]; > + MemoryRegion iomem; > +} ARMMPGTimerState; > + > +static inline int get_current_cpu(ARMMPGTimerState *s) > +{ > + CPUState *cpu_single_cpu; > + > + if (cpu_single_env !=3D NULL) { > + cpu_single_cpu =3D ENV_GET_CPU(cpu_single_env); > + > + if (cpu_single_cpu->cpu_index >=3D s->num_cpu) { > + hw_error("arm_mptimer: num-cpu %d but this cpu is %d!\n", > + s->num_cpu, cpu_single_cpu->cpu_index); > + } > + return cpu_single_cpu->cpu_index; > + } else { > + return 0; > + } > +} > + > +static inline void gtimerblock_update_irq(gTimerBlock *gtb) > +{ > + qemu_set_irq(gtb->irq, gtb->status); > +} > + > +/* Return conversion factor from mpcore timer ticks to qemu timer ticks. > */ > +static inline uint32_t gtimerblock_scale(gTimerBlock *gtb) > +{ > + return ((((*gtb->gtimer_control) >> 8) & 0xff) + 1) * 10; > +} > + > +static void gtimerblock_reload(gTimerBlock *gtb, int restart) > +{ > + if (restart) { > + gtb->tick =3D qemu_get_clock_ns(vm_clock); > + gtb->tick +=3D (int64_t)(((gtb->compare - *gtb->gtimer_counter) = + > + gtb->delta) * gtimerblock_scale(gtb)); > + } else { > + gtb->tick +=3D (int64_t)(gtb->inc * gtimerblock_scale(gtb)); > + } > + qemu_mod_timer(gtb->timer, gtb->tick); > +} > + > +static void gtimerblock_tick(void *opaque) > +{ > + gTimerBlock *gtb =3D (gTimerBlock *)opaque; > + *gtb->gtimer_counter =3D gtb->compare; > + if ((gtb->control | *gtb->gtimer_control) & 0x9) { > + gtb->compare +=3D gtb->inc; > + gtimerblock_reload(gtb, 0); > + } > + if (((gtb->control | *gtb->gtimer_control) & 0x7) && > + ((gtb->status & 1) =3D=3D 0)) { > + gtb->status =3D 1; > + gtimerblock_update_irq(gtb); > + } > +} > + > +static uint64_t gtimer_read(void *opaque, hwaddr addr, > + unsigned size) > +{ > + gTimerBlock *gtb =3D (gTimerBlock *)opaque; > + int64_t val =3D 0; > + switch (addr) { > + case 0: /* Counter LSB */ > + if (*gtb->gtimer_control & 1) { > + val =3D gtb->tick - qemu_get_clock_ns(vm_clock); > + val /=3D gtimerblock_scale(gtb); > + } > + return val < 0 ? val : 0 & 0xFFFFFFFF; > + case 4: /* Counter MSB. */ > + if (*gtb->gtimer_control & 1) { > + val =3D gtb->tick - qemu_get_clock_ns(vm_clock); > + val /=3D gtimerblock_scale(gtb); > + } > + return val < 0 ? (val >> 32) : 0; > + case 8: /* Control. */ > + return (gtb->control & 0x0000000E) | > + ((*gtb->gtimer_control) & 0x0000FF01); > + case 12: /* Interrupt status. */ > + return gtb->status; > + case 16: /* Compare LSB */ > + return gtb->compare & 0xFFFFFFFF; > + case 20: /* Counter MSB */ > + return gtb->compare >> 32; > + case 24: /* Autoincrement */ > + return gtb->inc; > + default: > + return 0; > + } > +} > + > +static void gtimer_write(void *opaque, hwaddr addr, > + uint64_t value, unsigned size) > +{ > + gTimerBlock *gtb =3D (gTimerBlock *)opaque; > + int64_t old; > + switch (addr) { > + case 0: /* Counter LSB */ > + old =3D (*gtb->gtimer_counter); > + old &=3D 0xFFFFFFFF; > + gtb->delta =3D old - (value & 0xFFFFFFFF); > + old |=3D value & 0xFFFFFFFF; > + *gtb->gtimer_counter =3D old; > + /* Cancel the previous timer. */ > + if (((gtb->control | *gtb->gtimer_control) & 7) =3D=3D 7) { > + gtimerblock_reload(gtb, 1); > + } > + break; > + case 4: /* Counter MSB. */ > + old =3D (*gtb->gtimer_counter); > + old &=3D 0xFFFFFFFF00000000; > + gtb->delta =3D old - (value << 32); > + old |=3D value << 32; > + *gtb->gtimer_counter =3D old; > + if (((gtb->control | *gtb->gtimer_control) & 7) =3D=3D 7) { > + gtimerblock_reload(gtb, 1); > + } > + break; > + case 8: /* Control. */ > + old =3D *gtb->gtimer_control; > + gtb->control =3D value & 0x0000000E; > + *gtb->gtimer_control =3D value & 0x0000FF01; > + if (((old & 1) =3D=3D 0) && ((value & 7) =3D=3D 7)) { > + gtb->delta =3D 0; > + gtimerblock_reload(gtb, 1); > + } > + break; > + case 12: /* Interrupt status. */ > + gtb->status ^=3D value & 1; > + gtimerblock_update_irq(gtb); > + break; > + case 16: /* Compare LSB */ > + old =3D gtb->compare; > + old &=3D 0xFFFFFFFF; > + gtb->delta =3D (value & 0xFFFFFFFF) - old; > + old |=3D value & 0xFFFFFFFF; > + gtb->compare =3D old; > + if (((gtb->control | *gtb->gtimer_control) & 7) =3D=3D 7) { > + gtimerblock_reload(gtb, 1); > + } > + break; > + case 20: /* Compare MSB. */ > + old =3D gtb->compare; > + old &=3D 0xFFFFFFFF00000000; > + gtb->delta =3D (value << 32) - old; > + old |=3D value << 32; > + gtb->compare =3D old; > + if (((gtb->control | *gtb->gtimer_control) & 7) =3D=3D 7) { > + gtimerblock_reload(gtb, 1); > + } > + break; > + case 24: /* Autoincrement */ > + gtb->inc =3D value; > + break; > + default: > + break; > + } > +} > + > +/* Wrapper functions to implement the "read timer/watchdog for > + * the current CPU" memory regions. > + */ > +static uint64_t arm_thisgtimer_read(void *opaque, hwaddr addr, > + unsigned size) > +{ > + ARMMPGTimerState *s =3D (ARMMPGTimerState *)opaque; > + int id =3D get_current_cpu(s); > + return gtimer_read(&s->gtimer[id], addr, size); > +} > + > +static void arm_thisgtimer_write(void *opaque, hwaddr addr, > + uint64_t value, unsigned size) > +{ > + ARMMPGTimerState *s =3D (ARMMPGTimerState *)opaque; > + int id =3D get_current_cpu(s); > + gtimer_write(&s->gtimer[id], addr, value, size); > +} > + > +static const MemoryRegionOps arm_thisgtimer_ops =3D { > + .read =3D arm_thisgtimer_read, > + .write =3D arm_thisgtimer_write, > + .valid =3D { > + .min_access_size =3D 4, > + .max_access_size =3D 4, > + }, > + .endianness =3D DEVICE_NATIVE_ENDIAN, > +}; > + > +static const MemoryRegionOps gtimerblock_ops =3D { > + .read =3D gtimer_read, > + .write =3D gtimer_write, > + .valid =3D { > + .min_access_size =3D 4, > + .max_access_size =3D 4, > + }, > + .endianness =3D DEVICE_NATIVE_ENDIAN, > +}; > + > +static void gtimer_reset(gTimerBlock *gtb) > +{ > + gtb->control =3D 0; > + gtb->status =3D 0; > + gtb->compare =3D 0; > + gtb->inc =3D 0; > + gtb->tick =3D 0; > +} > + > +static void arm_mpgtimer_reset(DeviceState *dev) > +{ > + ARMMPGTimerState *s =3D > + FROM_SYSBUS(ARMMPGTimerState, SYS_BUS_DEVICE(dev)); > + int i; > + for (i =3D 0; i < ARRAY_SIZE(s->gtimer); i++) { > + gtimer_reset(&s->gtimer[i]); > + } > +} > + > +static int arm_mpgtimer_init(SysBusDevice *dev) > +{ > + ARMMPGTimerState *s =3D FROM_SYSBUS(ARMMPGTimerState, dev); > + int i; > + if (s->num_cpu < 1 || s->num_cpu > MAX_CPUS) { > + hw_error("%s: num-cpu must be between 1 and %d\n", __func__, > MAX_CPUS); > + } > + > + /* We implement one timer block per CPU, and expose multiple MMIO > regions: > + * * region 0 is "timer for this core" > + * * region 1 is "timer for core 0" > + * * region 2 is "timer for core 1" > + * and so on. > + * The outgoing interrupt lines are > + * * timer for core 0 > + * * timer for core 1 > + * and so on. > + */ > + memory_region_init_io(&s->iomem, &arm_thisgtimer_ops, s, > + "arm_mptimer_gtimer", 0x20); > + sysbus_init_mmio(dev, &s->iomem); > + for (i =3D 0; i < s->num_cpu; i++) { > + gTimerBlock *gtb =3D &s->gtimer[i]; > + gtb->gtimer_counter =3D &s->gtimer_counter; > + gtb->gtimer_control =3D &s->gtimer_control; > + gtb->timer =3D qemu_new_timer_ns(vm_clock, gtimerblock_tick, gtb= ); > + sysbus_init_irq(dev, >b->irq); > + memory_region_init_io(>b->iomem, >imerblock_ops, gtb, > + "arm_mptimer_gtimerblock", 0x20); > + sysbus_init_mmio(dev, >b->iomem); > + } > + > + return 0; > +} > + > +static const VMStateDescription vmstate_gtimerblock =3D { > + .name =3D "arm_mptimer_gtimerblock", > + .version_id =3D 2, > + .minimum_version_id =3D 2, > + .fields =3D (VMStateField[]) { > + VMSTATE_UINT32(control, gTimerBlock), > + VMSTATE_UINT32(status, gTimerBlock), > + VMSTATE_UINT64(compare, gTimerBlock), > + VMSTATE_INT64(tick, gTimerBlock), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static const VMStateDescription vmstate_arm_mpgtimer =3D { > + .name =3D "arm_mp_globaltimer", > + .version_id =3D 2, > + .minimum_version_id =3D 2, > + .fields =3D (VMStateField[]) { > + VMSTATE_STRUCT_VARRAY_UINT32(gtimer, ARMMPGTimerState, num_cpu, > + 1, vmstate_gtimerblock, gTimerBlock), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static Property arm_mpgtimer_properties[] =3D { > + DEFINE_PROP_UINT32("num-cpu", ARMMPGTimerState, num_cpu, 0), > + DEFINE_PROP_END_OF_LIST() > +}; > + > +static void arm_mpgtimer_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc =3D DEVICE_CLASS(klass); > + SysBusDeviceClass *sbc =3D SYS_BUS_DEVICE_CLASS(klass); > + > + sbc->init =3D arm_mpgtimer_init; > + dc->vmsd =3D &vmstate_arm_mpgtimer; > + dc->reset =3D arm_mpgtimer_reset; > + dc->no_user =3D 1; > + dc->props =3D arm_mpgtimer_properties; > +} > + > +static const TypeInfo arm_mpgtimer_info =3D { > + .name =3D "arm_mp_globaltimer", > + .parent =3D TYPE_SYS_BUS_DEVICE, > + .instance_size =3D sizeof(ARMMPGTimerState), > + .class_init =3D arm_mpgtimer_class_init, > +}; > + > +static void arm_mpgtimer_register_types(void) > +{ > + type_register_static(&arm_mpgtimer_info); > +} > + > +type_init(arm_mpgtimer_register_types) > + > diff -urN qemu-master/hw/timer/arm_mptimer.c > qemu-master.new/hw/timer/arm_mptimer.c > --- qemu-master/hw/timer/arm_mptimer.c 2013-04-08 20:12:33.000000000 > +0200 > +++ qemu-master.new/hw/timer/arm_mptimer.c 2013-04-15 13:44:33.0000000= 00 > +0200 > @@ -49,13 +49,19 @@ > > static inline int get_current_cpu(ARMMPTimerState *s) > { > - CPUState *cpu_single_cpu =3D ENV_GET_CPU(cpu_single_env); > + CPUState *cpu_single_cpu; > > - if (cpu_single_cpu->cpu_index >=3D s->num_cpu) { > - hw_error("arm_mptimer: num-cpu %d but this cpu is %d!\n", > - s->num_cpu, cpu_single_cpu->cpu_index); > + if (cpu_single_env !=3D NULL) { > + cpu_single_cpu =3D ENV_GET_CPU(cpu_single_env); > + > + if (cpu_single_cpu->cpu_index >=3D s->num_cpu) { > + hw_error("arm_mptimer: num-cpu %d but this cpu is %d!\n", > + s->num_cpu, cpu_single_cpu->cpu_index); > + } > + return cpu_single_cpu->cpu_index; > + } else { > + return 0; > } > - return cpu_single_cpu->cpu_index; > } > > static inline void timerblock_update_irq(TimerBlock *tb) > diff -urN qemu-master/hw/timer/Makefile.objs > qemu-master.new/hw/timer/Makefile.objs > --- qemu-master/hw/timer/Makefile.objs 2013-04-08 20:12:33.000000000 > +0200 > +++ qemu-master.new/hw/timer/Makefile.objs 2013-04-15 12:54:06.0000000= 00 > +0200 > @@ -24,5 +24,5 @@ > obj-$(CONFIG_SH4) +=3D sh_timer.o > obj-$(CONFIG_TUSB6010) +=3D tusb6010.o > > -obj-$(CONFIG_ARM_MPTIMER) +=3D arm_mptimer.o > +obj-$(CONFIG_ARM_MPTIMER) +=3D arm_mptimer.o arm_mpgtimer.o > obj-$(CONFIG_MC146818RTC) +=3D mc146818rtc.o > > > Signed-off-by: Fran=E7ois LEGAL > > > >