Merge remote-tracking branch 'pmaydell/tags/pull-target-arm-20130820' into staging

target-arm queue

# gpg: Signature made Tue 20 Aug 2013 08:56:28 AM CDT using RSA key ID 14360CDE
# gpg: Can't check signature: public key not found

# By Peter Maydell (20) and Peter Chubb (1)
# Via Peter Maydell
* pmaydell/tags/pull-target-arm-20130820: (21 commits)
  hw/timer/imx_epit: Simplify and fix imx_epit implementation
  default-configs: Fix A9MP and A15MP config names
  hw/cpu/a15mpcore: Wire generic timer outputs to GIC inputs
  target-arm: Implement the generic timer
  target-arm: Support coprocessor registers which do I/O
  target-arm: Allow raw_read() and raw_write() to handle 64 bit regs
  hw/arm/pic_cpu: Remove the now-unneeded arm_pic_init_cpu()
  hw/arm/xilinx_zynq: Don't use arm_pic_init_cpu()
  hw/arm/vexpress: Don't use arm_pic_init_cpu()
  hw/arm/versatilepb: Don't use arm_pic_init_cpu()
  hw/arm/strongarm: Don't use arm_pic_init_cpu()
  hw/arm/realview: Don't use arm_pic_init_cpu()
  hw/arm/omap*: Don't use arm_pic_init_cpu()
  hw/arm/musicpal: Don't use arm_pic_init_cpu()
  hw/arm/kzm: Don't use arm_pic_init_cpu()
  hw/arm/integratorcp: Don't use arm_pic_init_cpu()
  hw/arm/highbank: Don't use arm_pic_init_cpu()
  hw/arm/exynos4210: Don't use arm_pic_init_cpu()
  hw/arm/armv7m: Don't use arm_pic_init_cpu()
  target-arm: Make IRQ and FIQ gpio lines on the CPU object
  ...

Message-id: 1377007680-4934-1-git-send-email-peter.maydell@linaro.org
Signed-off-by: Anthony Liguori <anthony@codemonkey.ws>
diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
index 27cbe3d..ac0815d 100644
--- a/default-configs/arm-softmmu.mak
+++ b/default-configs/arm-softmmu.mak
@@ -34,9 +34,9 @@
 CONFIG_MICRODRIVE=y
 CONFIG_USB_MUSB=y
 
-CONFIG_ARM9MPCORE=y
 CONFIG_ARM11MPCORE=y
-CONFIG_ARM15MPCORE=y
+CONFIG_A9MPCORE=y
+CONFIG_A15MPCORE=y
 
 CONFIG_ARM_GIC=y
 CONFIG_ARM_GIC_KVM=$(CONFIG_KVM)
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 9e3a06f..3671b42 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -1,6 +1,6 @@
 obj-y += boot.o collie.o exynos4_boards.o gumstix.o highbank.o
 obj-y += integratorcp.o kzm.o mainstone.o musicpal.o nseries.o
-obj-y += omap_sx1.o palm.o pic_cpu.o realview.o spitz.o stellaris.o
+obj-y += omap_sx1.o palm.o realview.o spitz.o stellaris.o
 obj-y += tosa.o versatilepb.o vexpress.o xilinx_zynq.o z2.o
 
 obj-y += armv7m.o exynos4210.o pxa2xx.o pxa2xx_gpio.o pxa2xx_pic.o
diff --git a/hw/arm/armv7m.c b/hw/arm/armv7m.c
index 82d36fb..89a9015 100644
--- a/hw/arm/armv7m.c
+++ b/hw/arm/armv7m.c
@@ -173,7 +173,6 @@
     DeviceState *nvic;
     /* FIXME: make this local state.  */
     static qemu_irq pic[64];
-    qemu_irq *cpu_pic;
     int image_size;
     uint64_t entry;
     uint64_t lowaddr;
@@ -221,8 +220,8 @@
     nvic = qdev_create(NULL, "armv7m_nvic");
     env->nvic = nvic;
     qdev_init_nofail(nvic);
-    cpu_pic = arm_pic_init_cpu(cpu);
-    sysbus_connect_irq(SYS_BUS_DEVICE(nvic), 0, cpu_pic[ARM_PIC_CPU_IRQ]);
+    sysbus_connect_irq(SYS_BUS_DEVICE(nvic), 0,
+                       qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ));
     for (i = 0; i < 64; i++) {
         pic[i] = qdev_get_gpio_in(nvic, i);
     }
diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c
index 216b9b7..4ebb938 100644
--- a/hw/arm/exynos4210.c
+++ b/hw/arm/exynos4210.c
@@ -137,10 +137,8 @@
 Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
         unsigned long ram_size)
 {
-    qemu_irq cpu_irq[EXYNOS4210_NCPUS];
     int i, n;
     Exynos4210State *s = g_new(Exynos4210State, 1);
-    qemu_irq *irqp;
     qemu_irq gate_irq[EXYNOS4210_NCPUS][EXYNOS4210_IRQ_GATE_NINPUTS];
     unsigned long mem_size;
     DeviceState *dev;
@@ -152,15 +150,6 @@
             fprintf(stderr, "Unable to find CPU %d definition\n", n);
             exit(1);
         }
-
-        /* Create PIC controller for each processor instance */
-        irqp = arm_pic_init_cpu(s->cpu[n]);
-
-        /*
-         * Get GICs gpio_in cpu_irq to connect a combiner to them later.
-         * Use only IRQ for a while.
-         */
-        cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
     }
 
     /*** IRQs ***/
@@ -178,8 +167,9 @@
         }
         busdev = SYS_BUS_DEVICE(dev);
 
-        /* Connect IRQ Gate output to cpu_irq */
-        sysbus_connect_irq(busdev, 0, cpu_irq[i]);
+        /* Connect IRQ Gate output to CPU's IRQ line */
+        sysbus_connect_irq(busdev, 0,
+                           qdev_get_gpio_in(DEVICE(s->cpu[i]), ARM_CPU_IRQ));
     }
 
     /* Private memory region and Internal GIC */
diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c
index 35d5511..f733a6c 100644
--- a/hw/arm/highbank.c
+++ b/hw/arm/highbank.c
@@ -209,7 +209,6 @@
     const char *initrd_filename = args->initrd_filename;
     DeviceState *dev = NULL;
     SysBusDevice *busdev;
-    qemu_irq *irqp;
     qemu_irq pic[128];
     int n;
     qemu_irq cpu_irq[4];
@@ -239,8 +238,7 @@
 
         /* This will become a QOM property eventually */
         cpu->reset_cbar = GIC_BASE_ADDR;
-        irqp = arm_pic_init_cpu(cpu);
-        cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
+        cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ);
     }
 
     sysmem = get_system_memory();
diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c
index d518188..59c3726 100644
--- a/hw/arm/integratorcp.c
+++ b/hw/arm/integratorcp.c
@@ -465,7 +465,6 @@
     MemoryRegion *ram = g_new(MemoryRegion, 1);
     MemoryRegion *ram_alias = g_new(MemoryRegion, 1);
     qemu_irq pic[32];
-    qemu_irq *cpu_pic;
     DeviceState *dev;
     int i;
 
@@ -493,10 +492,10 @@
     qdev_init_nofail(dev);
     sysbus_mmio_map((SysBusDevice *)dev, 0, 0x10000000);
 
-    cpu_pic = arm_pic_init_cpu(cpu);
     dev = sysbus_create_varargs(TYPE_INTEGRATOR_PIC, 0x14000000,
-                                cpu_pic[ARM_PIC_CPU_IRQ],
-                                cpu_pic[ARM_PIC_CPU_FIQ], NULL);
+                                qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ),
+                                qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ),
+                                NULL);
     for (i = 0; i < 32; i++) {
         pic[i] = qdev_get_gpio_in(dev, i);
     }
diff --git a/hw/arm/kzm.c b/hw/arm/kzm.c
index bd6c05c..a248bf0 100644
--- a/hw/arm/kzm.c
+++ b/hw/arm/kzm.c
@@ -82,7 +82,6 @@
     MemoryRegion *ram = g_new(MemoryRegion, 1);
     MemoryRegion *sram = g_new(MemoryRegion, 1);
     MemoryRegion *ram_alias = g_new(MemoryRegion, 1);
-    qemu_irq *cpu_pic;
     DeviceState *dev;
     DeviceState *ccm;
 
@@ -108,11 +107,10 @@
     memory_region_init_ram(sram, NULL, "kzm.sram", 0x4000);
     memory_region_add_subregion(address_space_mem, 0x1FFFC000, sram);
 
-    cpu_pic = arm_pic_init_cpu(cpu);
     dev = sysbus_create_varargs("imx_avic", 0x68000000,
-                                cpu_pic[ARM_PIC_CPU_IRQ],
-                                cpu_pic[ARM_PIC_CPU_FIQ], NULL);
-
+                                qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ),
+                                qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ),
+                                NULL);
 
     imx_serial_create(0, 0x43f90000, qdev_get_gpio_in(dev, 45));
     imx_serial_create(1, 0x43f94000, qdev_get_gpio_in(dev, 32));
diff --git a/hw/arm/musicpal.c b/hw/arm/musicpal.c
index d715143..4404b8d 100644
--- a/hw/arm/musicpal.c
+++ b/hw/arm/musicpal.c
@@ -1586,7 +1586,6 @@
     const char *kernel_cmdline = args->kernel_cmdline;
     const char *initrd_filename = args->initrd_filename;
     ARMCPU *cpu;
-    qemu_irq *cpu_pic;
     qemu_irq pic[32];
     DeviceState *dev;
     DeviceState *i2c_dev;
@@ -1610,7 +1609,6 @@
         fprintf(stderr, "Unable to find CPU definition\n");
         exit(1);
     }
-    cpu_pic = arm_pic_init_cpu(cpu);
 
     /* For now we use a fixed - the original - RAM size */
     memory_region_init_ram(ram, NULL, "musicpal.ram", MP_RAM_DEFAULT_SIZE);
@@ -1622,7 +1620,7 @@
     memory_region_add_subregion(address_space_mem, MP_SRAM_BASE, sram);
 
     dev = sysbus_create_simple(TYPE_MV88W8618_PIC, MP_PIC_BASE,
-                               cpu_pic[ARM_PIC_CPU_IRQ]);
+                               qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ));
     for (i = 0; i < 32; i++) {
         pic[i] = qdev_get_gpio_in(dev, i);
     }
diff --git a/hw/arm/omap1.c b/hw/arm/omap1.c
index 19be5fc..b6a0b27 100644
--- a/hw/arm/omap1.c
+++ b/hw/arm/omap1.c
@@ -3827,7 +3827,6 @@
     int i;
     struct omap_mpu_state_s *s = (struct omap_mpu_state_s *)
             g_malloc0(sizeof(struct omap_mpu_state_s));
-    qemu_irq *cpu_irq;
     qemu_irq dma_irqs[6];
     DriveInfo *dinfo;
     SysBusDevice *busdev;
@@ -3860,14 +3859,15 @@
 
     omap_clkm_init(system_memory, 0xfffece00, 0xe1008000, s);
 
-    cpu_irq = arm_pic_init_cpu(s->cpu);
     s->ih[0] = qdev_create(NULL, "omap-intc");
     qdev_prop_set_uint32(s->ih[0], "size", 0x100);
     qdev_prop_set_ptr(s->ih[0], "clk", omap_findclk(s, "arminth_ck"));
     qdev_init_nofail(s->ih[0]);
     busdev = SYS_BUS_DEVICE(s->ih[0]);
-    sysbus_connect_irq(busdev, 0, cpu_irq[ARM_PIC_CPU_IRQ]);
-    sysbus_connect_irq(busdev, 1, cpu_irq[ARM_PIC_CPU_FIQ]);
+    sysbus_connect_irq(busdev, 0,
+                       qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ));
+    sysbus_connect_irq(busdev, 1,
+                       qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ));
     sysbus_mmio_map(busdev, 0, 0xfffecb00);
     s->ih[1] = qdev_create(NULL, "omap-intc");
     qdev_prop_set_uint32(s->ih[1], "size", 0x800);
diff --git a/hw/arm/omap2.c b/hw/arm/omap2.c
index ec9610b..36efde0 100644
--- a/hw/arm/omap2.c
+++ b/hw/arm/omap2.c
@@ -2244,7 +2244,6 @@
 {
     struct omap_mpu_state_s *s = (struct omap_mpu_state_s *)
             g_malloc0(sizeof(struct omap_mpu_state_s));
-    qemu_irq *cpu_irq;
     qemu_irq dma_irqs[4];
     DriveInfo *dinfo;
     int i;
@@ -2277,15 +2276,16 @@
     s->l4 = omap_l4_init(sysmem, OMAP2_L4_BASE, 54);
 
     /* Actually mapped at any 2K boundary in the ARM11 private-peripheral if */
-    cpu_irq = arm_pic_init_cpu(s->cpu);
     s->ih[0] = qdev_create(NULL, "omap2-intc");
     qdev_prop_set_uint8(s->ih[0], "revision", 0x21);
     qdev_prop_set_ptr(s->ih[0], "fclk", omap_findclk(s, "mpu_intc_fclk"));
     qdev_prop_set_ptr(s->ih[0], "iclk", omap_findclk(s, "mpu_intc_iclk"));
     qdev_init_nofail(s->ih[0]);
     busdev = SYS_BUS_DEVICE(s->ih[0]);
-    sysbus_connect_irq(busdev, 0, cpu_irq[ARM_PIC_CPU_IRQ]);
-    sysbus_connect_irq(busdev, 1, cpu_irq[ARM_PIC_CPU_FIQ]);
+    sysbus_connect_irq(busdev, 0,
+                       qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ));
+    sysbus_connect_irq(busdev, 1,
+                       qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ));
     sysbus_mmio_map(busdev, 0, 0x480fe000);
     s->prcm = omap_prcm_init(omap_l4tao(s->l4, 3),
                              qdev_get_gpio_in(s->ih[0],
diff --git a/hw/arm/pic_cpu.c b/hw/arm/pic_cpu.c
deleted file mode 100644
index 875280a..0000000
--- a/hw/arm/pic_cpu.c
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Generic ARM Programmable Interrupt Controller support.
- *
- * Copyright (c) 2006 CodeSourcery.
- * Written by Paul Brook
- *
- * This code is licensed under the LGPL
- */
-
-#include "hw/hw.h"
-#include "hw/arm/arm.h"
-#include "sysemu/kvm.h"
-
-/* Input 0 is IRQ and input 1 is FIQ.  */
-static void arm_pic_cpu_handler(void *opaque, int irq, int level)
-{
-    ARMCPU *cpu = opaque;
-    CPUState *cs = CPU(cpu);
-
-    switch (irq) {
-    case ARM_PIC_CPU_IRQ:
-        if (level) {
-            cpu_interrupt(cs, CPU_INTERRUPT_HARD);
-        } else {
-            cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
-        }
-        break;
-    case ARM_PIC_CPU_FIQ:
-        if (level) {
-            cpu_interrupt(cs, CPU_INTERRUPT_FIQ);
-        } else {
-            cpu_reset_interrupt(cs, CPU_INTERRUPT_FIQ);
-        }
-        break;
-    default:
-        hw_error("arm_pic_cpu_handler: Bad interrupt line %d\n", irq);
-    }
-}
-
-static void kvm_arm_pic_cpu_handler(void *opaque, int irq, int level)
-{
-#ifdef CONFIG_KVM
-    ARMCPU *cpu = opaque;
-    CPUState *cs = CPU(cpu);
-    int kvm_irq = KVM_ARM_IRQ_TYPE_CPU << KVM_ARM_IRQ_TYPE_SHIFT;
-
-    switch (irq) {
-    case ARM_PIC_CPU_IRQ:
-        kvm_irq |= KVM_ARM_IRQ_CPU_IRQ;
-        break;
-    case ARM_PIC_CPU_FIQ:
-        kvm_irq |= KVM_ARM_IRQ_CPU_FIQ;
-        break;
-    default:
-        hw_error("kvm_arm_pic_cpu_handler: Bad interrupt line %d\n", irq);
-    }
-    kvm_irq |= cs->cpu_index << KVM_ARM_IRQ_VCPU_SHIFT;
-    kvm_set_irq(kvm_state, kvm_irq, level ? 1 : 0);
-#endif
-}
-
-qemu_irq *arm_pic_init_cpu(ARMCPU *cpu)
-{
-    if (kvm_enabled()) {
-        return qemu_allocate_irqs(kvm_arm_pic_cpu_handler, cpu, 2);
-    }
-    return qemu_allocate_irqs(arm_pic_cpu_handler, cpu, 2);
-}
diff --git a/hw/arm/realview.c b/hw/arm/realview.c
index 3060f48..82ec02d 100644
--- a/hw/arm/realview.c
+++ b/hw/arm/realview.c
@@ -56,7 +56,6 @@
     MemoryRegion *ram_hack = g_new(MemoryRegion, 1);
     DeviceState *dev, *sysctl, *gpio2, *pl041;
     SysBusDevice *busdev;
-    qemu_irq *irqp;
     qemu_irq pic[64];
     qemu_irq mmc_irq[2];
     PCIBus *pci_bus = NULL;
@@ -92,8 +91,7 @@
             fprintf(stderr, "Unable to find CPU definition\n");
             exit(1);
         }
-        irqp = arm_pic_init_cpu(cpu);
-        cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
+        cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ);
     }
     env = &cpu->env;
     if (arm_feature(env, ARM_FEATURE_V7)) {
diff --git a/hw/arm/strongarm.c b/hw/arm/strongarm.c
index 82a9492..7b8ef8c 100644
--- a/hw/arm/strongarm.c
+++ b/hw/arm/strongarm.c
@@ -1588,7 +1588,6 @@
                             unsigned int sdram_size, const char *rev)
 {
     StrongARMState *s;
-    qemu_irq *pic;
     int i;
 
     s = g_malloc0(sizeof(StrongARMState));
@@ -1613,9 +1612,10 @@
     vmstate_register_ram_global(&s->sdram);
     memory_region_add_subregion(sysmem, SA_SDCS0, &s->sdram);
 
-    pic = arm_pic_init_cpu(s->cpu);
     s->pic = sysbus_create_varargs("strongarm_pic", 0x90050000,
-                    pic[ARM_PIC_CPU_IRQ], pic[ARM_PIC_CPU_FIQ], NULL);
+                    qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ),
+                    qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ),
+                    NULL);
 
     sysbus_create_varargs("pxa25x-timer", 0x90000000,
                     qdev_get_gpio_in(s->pic, SA_PIC_OSTC0),
diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c
index b48d84c..4a6fcee 100644
--- a/hw/arm/versatilepb.c
+++ b/hw/arm/versatilepb.c
@@ -178,7 +178,6 @@
     ARMCPU *cpu;
     MemoryRegion *sysmem = get_system_memory();
     MemoryRegion *ram = g_new(MemoryRegion, 1);
-    qemu_irq *cpu_pic;
     qemu_irq pic[32];
     qemu_irq sic[32];
     DeviceState *dev, *sysctl;
@@ -211,10 +210,10 @@
     qdev_init_nofail(sysctl);
     sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, 0x10000000);
 
-    cpu_pic = arm_pic_init_cpu(cpu);
     dev = sysbus_create_varargs("pl190", 0x10140000,
-                                cpu_pic[ARM_PIC_CPU_IRQ],
-                                cpu_pic[ARM_PIC_CPU_FIQ], NULL);
+                                qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ),
+                                qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ),
+                                NULL);
     for (n = 0; n < 32; n++) {
         pic[n] = qdev_get_gpio_in(dev, n);
     }
diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c
index 9586e38..fbd71a7 100644
--- a/hw/arm/vexpress.c
+++ b/hw/arm/vexpress.c
@@ -183,7 +183,6 @@
     MemoryRegion *lowram = g_new(MemoryRegion, 1);
     DeviceState *dev;
     SysBusDevice *busdev;
-    qemu_irq *irqp;
     int n;
     qemu_irq cpu_irq[4];
     ram_addr_t low_ram_size;
@@ -198,8 +197,7 @@
             fprintf(stderr, "Unable to find CPU definition\n");
             exit(1);
         }
-        irqp = arm_pic_init_cpu(cpu);
-        cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
+        cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ);
     }
 
     if (ram_size > 0x40000000) {
@@ -312,15 +310,13 @@
 
     for (n = 0; n < smp_cpus; n++) {
         ARMCPU *cpu;
-        qemu_irq *irqp;
 
         cpu = cpu_arm_init(cpu_model);
         if (!cpu) {
             fprintf(stderr, "Unable to find CPU definition\n");
             exit(1);
         }
-        irqp = arm_pic_init_cpu(cpu);
-        cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
+        cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ);
     }
 
     {
diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c
index 3444823..0f18c85 100644
--- a/hw/arm/xilinx_zynq.c
+++ b/hw/arm/xilinx_zynq.c
@@ -108,11 +108,9 @@
     MemoryRegion *ocm_ram = g_new(MemoryRegion, 1);
     DeviceState *dev;
     SysBusDevice *busdev;
-    qemu_irq *irqp;
     qemu_irq pic[64];
     NICInfo *nd;
     int n;
-    qemu_irq cpu_irq;
 
     if (!cpu_model) {
         cpu_model = "cortex-a9";
@@ -123,8 +121,6 @@
         fprintf(stderr, "Unable to find CPU definition\n");
         exit(1);
     }
-    irqp = arm_pic_init_cpu(cpu);
-    cpu_irq = irqp[ARM_PIC_CPU_IRQ];
 
     /* max 2GB ram */
     if (ram_size > 0x80000000) {
@@ -159,7 +155,8 @@
     qdev_init_nofail(dev);
     busdev = SYS_BUS_DEVICE(dev);
     sysbus_mmio_map(busdev, 0, 0xF8F00000);
-    sysbus_connect_irq(busdev, 0, cpu_irq);
+    sysbus_connect_irq(busdev, 0,
+                       qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ));
 
     for (n = 0; n < 64; n++) {
         pic[n] = qdev_get_gpio_in(dev, n);
diff --git a/hw/cpu/Makefile.objs b/hw/cpu/Makefile.objs
index 4461ece..df287c1 100644
--- a/hw/cpu/Makefile.objs
+++ b/hw/cpu/Makefile.objs
@@ -1,5 +1,5 @@
 obj-$(CONFIG_ARM11MPCORE) += arm11mpcore.o
-obj-$(CONFIG_ARM9MPCORE) += a9mpcore.o
-obj-$(CONFIG_ARM15MPCORE) += a15mpcore.o
+obj-$(CONFIG_A9MPCORE) += a9mpcore.o
+obj-$(CONFIG_A15MPCORE) += a15mpcore.o
 obj-$(CONFIG_ICC_BUS) += icc_bus.o
 
diff --git a/hw/cpu/a15mpcore.c b/hw/cpu/a15mpcore.c
index 4f37964..af182da 100644
--- a/hw/cpu/a15mpcore.c
+++ b/hw/cpu/a15mpcore.c
@@ -49,6 +49,8 @@
     A15MPPrivState *s = A15MPCORE_PRIV(dev);
     SysBusDevice *busdev;
     const char *gictype = "arm_gic";
+    int i;
+    CPUState *cpu;
 
     if (kvm_irqchip_in_kernel()) {
         gictype = "kvm-arm-gic";
@@ -67,6 +69,22 @@
     /* Pass through inbound GPIO lines to the GIC */
     qdev_init_gpio_in(DEVICE(dev), a15mp_priv_set_irq, s->num_irq - 32);
 
+    /* Wire the outputs from each CPU's generic timer to the
+     * appropriate GIC PPI inputs
+     */
+    for (i = 0, cpu = first_cpu; i < s->num_cpu; i++, cpu = cpu->next_cpu) {
+        DeviceState *cpudev = DEVICE(cpu);
+        int ppibase = s->num_irq - 32 + i * 32;
+        /* physical timer; we wire it up to the non-secure timer's ID,
+         * since a real A15 always has TrustZone but QEMU doesn't.
+         */
+        qdev_connect_gpio_out(cpudev, 0,
+                              qdev_get_gpio_in(s->gic, ppibase + 30));
+        /* virtual timer */
+        qdev_connect_gpio_out(cpudev, 1,
+                              qdev_get_gpio_in(s->gic, ppibase + 27));
+    }
+
     /* Memory map (addresses are offsets from PERIPHBASE):
      *  0x0000-0x0fff -- reserved
      *  0x1000-0x1fff -- GIC Distributor
diff --git a/hw/timer/imx_epit.c b/hw/timer/imx_epit.c
index 117dc7b..dc73d65 100644
--- a/hw/timer/imx_epit.c
+++ b/hw/timer/imx_epit.c
@@ -43,7 +43,7 @@
 }
 
 #  define DPRINTF(fmt, args...) \
-          do { printf("%s: " fmt , __func__, ##args); } while (0)
+    do { fprintf(stderr, "%s: " fmt , __func__, ##args); } while (0)
 #else
 #  define DPRINTF(fmt, args...) do {} while (0)
 #endif
@@ -152,7 +152,7 @@
     /*
      * Soft reset doesn't touch some bits; hard reset clears them
      */
-    s->cr &= ~(CR_EN|CR_ENMOD|CR_STOPEN|CR_DOZEN|CR_WAITEN|CR_DBGEN);
+    s->cr &= (CR_EN|CR_ENMOD|CR_STOPEN|CR_DOZEN|CR_WAITEN|CR_DBGEN);
     s->sr = 0;
     s->lr = TIMER_MAX;
     s->cmp = 0;
@@ -167,7 +167,7 @@
     ptimer_set_limit(s->timer_reload, TIMER_MAX, 1);
     if (s->freq && (s->cr & CR_EN)) {
         /* if the timer is still enabled, restart it */
-        ptimer_run(s->timer_reload, 1);
+        ptimer_run(s->timer_reload, 0);
     }
 }
 
@@ -218,17 +218,17 @@
 
 static void imx_epit_reload_compare_timer(IMXEPITState *s)
 {
-    if ((s->cr & CR_OCIEN) && s->cmp) {
-        /* if the compare feature is on */
+    if ((s->cr & (CR_EN | CR_OCIEN)) == (CR_EN | CR_OCIEN))  {
+        /* if the compare feature is on and timers are running */
         uint32_t tmp = imx_epit_update_count(s);
+        uint64_t next;
         if (tmp > s->cmp) {
-            /* reinit the cmp timer if required */
-            ptimer_set_count(s->timer_cmp, tmp - s->cmp);
-            if ((s->cr & CR_EN)) {
-                /* Restart the cmp timer if required */
-                ptimer_run(s->timer_cmp, 0);
-            }
+            /* It'll fire in this round of the timer */
+            next = tmp - s->cmp;
+        } else { /* catch it next time around */
+            next = tmp - s->cmp + ((s->cr & CR_RLD) ? TIMER_MAX : s->lr);
         }
+        ptimer_set_count(s->timer_cmp, next);
     }
 }
 
@@ -237,11 +237,14 @@
 {
     IMXEPITState *s = IMX_EPIT(opaque);
     uint32_t reg = offset >> 2;
+    uint64_t oldcr;
 
     DPRINTF("(%s, value = 0x%08x)\n", imx_epit_reg_name(reg), (uint32_t)value);
 
     switch (reg) {
     case 0: /* CR */
+
+        oldcr = s->cr;
         s->cr = value & 0x03ffffff;
         if (s->cr & CR_SWR) {
             /* handle the reset */
@@ -250,22 +253,35 @@
             imx_epit_set_freq(s);
         }
 
-        if (s->freq && (s->cr & CR_EN)) {
+        if (s->freq && (s->cr & CR_EN) && !(oldcr & CR_EN)) {
             if (s->cr & CR_ENMOD) {
                 if (s->cr & CR_RLD) {
                     ptimer_set_limit(s->timer_reload, s->lr, 1);
+                    ptimer_set_limit(s->timer_cmp, s->lr, 1);
                 } else {
                     ptimer_set_limit(s->timer_reload, TIMER_MAX, 1);
+                    ptimer_set_limit(s->timer_cmp, TIMER_MAX, 1);
                 }
             }
 
             imx_epit_reload_compare_timer(s);
-
-            ptimer_run(s->timer_reload, 1);
-        } else {
+            ptimer_run(s->timer_reload, 0);
+            if (s->cr & CR_OCIEN) {
+                ptimer_run(s->timer_cmp, 0);
+            } else {
+                ptimer_stop(s->timer_cmp);
+            }
+        } else if (!(s->cr & CR_EN)) {
             /* stop both timers */
             ptimer_stop(s->timer_reload);
             ptimer_stop(s->timer_cmp);
+        } else  if (s->cr & CR_OCIEN) {
+            if (!(oldcr & CR_OCIEN)) {
+                imx_epit_reload_compare_timer(s);
+                ptimer_run(s->timer_cmp, 0);
+            }
+        } else {
+            ptimer_stop(s->timer_cmp);
         }
         break;
 
@@ -284,13 +300,13 @@
             /* Also set the limit if the LRD bit is set */
             /* If IOVW bit is set then set the timer value */
             ptimer_set_limit(s->timer_reload, s->lr, s->cr & CR_IOVW);
+            ptimer_set_limit(s->timer_cmp, s->lr, 0);
         } else if (s->cr & CR_IOVW) {
             /* If IOVW bit is set then set the timer value */
             ptimer_set_count(s->timer_reload, s->lr);
         }
 
         imx_epit_reload_compare_timer(s);
-
         break;
 
     case 3: /* CMP */
@@ -306,51 +322,14 @@
         break;
     }
 }
-
-static void imx_epit_timeout(void *opaque)
-{
-    IMXEPITState *s = IMX_EPIT(opaque);
-
-    DPRINTF("\n");
-
-    if (!(s->cr & CR_EN)) {
-        return;
-    }
-
-    if (s->cr & CR_RLD) {
-        ptimer_set_limit(s->timer_reload, s->lr, 1);
-    } else {
-        ptimer_set_limit(s->timer_reload, TIMER_MAX, 1);
-    }
-
-    if (s->cr & CR_OCIEN) {
-        /* if compare register is 0 then we handle the interrupt here */
-        if (s->cmp == 0) {
-            s->sr = 1;
-            imx_epit_update_int(s);
-        } else if (s->cmp <= s->lr) {
-            /* We should launch the compare register */
-            ptimer_set_count(s->timer_cmp, s->lr - s->cmp);
-            ptimer_run(s->timer_cmp, 0);
-        } else {
-            IPRINTF("s->lr < s->cmp\n");
-        }
-    }
-}
-
 static void imx_epit_cmp(void *opaque)
 {
     IMXEPITState *s = IMX_EPIT(opaque);
 
-    DPRINTF("\n");
+    DPRINTF("sr was %d\n", s->sr);
 
-    ptimer_stop(s->timer_cmp);
-
-    /* compare register is not 0 */
-    if (s->cmp) {
-        s->sr = 1;
-        imx_epit_update_int(s);
-    }
+    s->sr = 1;
+    imx_epit_update_int(s);
 }
 
 void imx_timerp_create(const hwaddr addr, qemu_irq irq, DeviceState *ccm)
@@ -400,8 +379,7 @@
                           0x00001000);
     sysbus_init_mmio(sbd, &s->iomem);
 
-    bh = qemu_bh_new(imx_epit_timeout, s);
-    s->timer_reload = ptimer_init(bh);
+    s->timer_reload = ptimer_init(NULL);
 
     bh = qemu_bh_new(imx_epit_cmp, s);
     s->timer_cmp = ptimer_init(bh);
diff --git a/include/hw/arm/arm.h b/include/hw/arm/arm.h
index bae87c6..ecbbba8 100644
--- a/include/hw/arm/arm.h
+++ b/include/hw/arm/arm.h
@@ -14,11 +14,6 @@
 #include "exec/memory.h"
 #include "hw/irq.h"
 
-/* The CPU is also modelled as an interrupt controller.  */
-#define ARM_PIC_CPU_IRQ 0
-#define ARM_PIC_CPU_FIQ 1
-qemu_irq *arm_pic_init_cpu(ARMCPU *cpu);
-
 /* armv7m.c */
 qemu_irq *armv7m_init(MemoryRegion *address_space_mem,
                       int flash_size, int sram_size,
diff --git a/target-arm/cpu-qom.h b/target-arm/cpu-qom.h
index cf36587..9f47bae 100644
--- a/target-arm/cpu-qom.h
+++ b/target-arm/cpu-qom.h
@@ -86,6 +86,11 @@
     uint64_t *cpreg_vmstate_values;
     int32_t cpreg_vmstate_array_len;
 
+    /* Timers used by the generic (architected) timer */
+    QEMUTimer *gt_timer[NUM_GTIMERS];
+    /* GPIO outputs for generic timer */
+    qemu_irq gt_timer_outputs[NUM_GTIMERS];
+
     /* The instance init functions for implementation-specific subclasses
      * set these fields to specify the implementation-dependent values of
      * various constant registers and reset values of non-constant
@@ -152,4 +157,8 @@
 int arm_cpu_gdb_read_register(CPUState *cpu, uint8_t *buf, int reg);
 int arm_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg);
 
+/* Callback functions for the generic timer's timers. */
+void arm_gt_ptimer_cb(void *opaque);
+void arm_gt_vtimer_cb(void *opaque);
+
 #endif
diff --git a/target-arm/cpu.c b/target-arm/cpu.c
index 5a7566b..f01ce03 100644
--- a/target-arm/cpu.c
+++ b/target-arm/cpu.c
@@ -23,7 +23,9 @@
 #if !defined(CONFIG_USER_ONLY)
 #include "hw/loader.h"
 #endif
+#include "hw/arm/arm.h"
 #include "sysemu/sysemu.h"
+#include "sysemu/kvm.h"
 
 static void arm_cpu_set_pc(CPUState *cs, vaddr value)
 {
@@ -129,6 +131,55 @@
     tb_flush(env);
 }
 
+#ifndef CONFIG_USER_ONLY
+static void arm_cpu_set_irq(void *opaque, int irq, int level)
+{
+    ARMCPU *cpu = opaque;
+    CPUState *cs = CPU(cpu);
+
+    switch (irq) {
+    case ARM_CPU_IRQ:
+        if (level) {
+            cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+        } else {
+            cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+        }
+        break;
+    case ARM_CPU_FIQ:
+        if (level) {
+            cpu_interrupt(cs, CPU_INTERRUPT_FIQ);
+        } else {
+            cpu_reset_interrupt(cs, CPU_INTERRUPT_FIQ);
+        }
+        break;
+    default:
+        hw_error("arm_cpu_set_irq: Bad interrupt line %d\n", irq);
+    }
+}
+
+static void arm_cpu_kvm_set_irq(void *opaque, int irq, int level)
+{
+#ifdef CONFIG_KVM
+    ARMCPU *cpu = opaque;
+    CPUState *cs = CPU(cpu);
+    int kvm_irq = KVM_ARM_IRQ_TYPE_CPU << KVM_ARM_IRQ_TYPE_SHIFT;
+
+    switch (irq) {
+    case ARM_CPU_IRQ:
+        kvm_irq |= KVM_ARM_IRQ_CPU_IRQ;
+        break;
+    case ARM_CPU_FIQ:
+        kvm_irq |= KVM_ARM_IRQ_CPU_FIQ;
+        break;
+    default:
+        hw_error("arm_cpu_kvm_set_irq: Bad interrupt line %d\n", irq);
+    }
+    kvm_irq |= cs->cpu_index << KVM_ARM_IRQ_VCPU_SHIFT;
+    kvm_set_irq(kvm_state, kvm_irq, level ? 1 : 0);
+#endif
+}
+#endif
+
 static inline void set_feature(CPUARMState *env, int feature)
 {
     env->features |= 1ULL << feature;
@@ -145,6 +196,22 @@
     cpu->cp_regs = g_hash_table_new_full(g_int_hash, g_int_equal,
                                          g_free, g_free);
 
+#ifndef CONFIG_USER_ONLY
+    /* Our inbound IRQ and FIQ lines */
+    if (kvm_enabled()) {
+        qdev_init_gpio_in(DEVICE(cpu), arm_cpu_kvm_set_irq, 2);
+    } else {
+        qdev_init_gpio_in(DEVICE(cpu), arm_cpu_set_irq, 2);
+    }
+
+    cpu->gt_timer[GTIMER_PHYS] = qemu_new_timer(vm_clock, GTIMER_SCALE,
+                                                arm_gt_ptimer_cb, cpu);
+    cpu->gt_timer[GTIMER_VIRT] = qemu_new_timer(vm_clock, GTIMER_SCALE,
+                                                arm_gt_vtimer_cb, cpu);
+    qdev_init_gpio_out(DEVICE(cpu), cpu->gt_timer_outputs,
+                       ARRAY_SIZE(cpu->gt_timer_outputs));
+#endif
+
     if (tcg_enabled() && !inited) {
         inited = true;
         arm_translate_init();
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index b2dc494..f2abdf3 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -58,6 +58,9 @@
 /* ARM-specific interrupt pending bits.  */
 #define CPU_INTERRUPT_FIQ   CPU_INTERRUPT_TGT_EXT_1
 
+/* Meanings of the ARMCPU object's two inbound GPIO lines */
+#define ARM_CPU_IRQ 0
+#define ARM_CPU_FIQ 1
 
 typedef void ARMWriteCPFunc(void *opaque, int cp_info,
                             int srcreg, int operand, uint32_t value);
@@ -76,6 +79,21 @@
    s<2n+1> maps to the most significant half of d<n>
  */
 
+/* CPU state for each instance of a generic timer (in cp15 c14) */
+typedef struct ARMGenericTimer {
+    uint64_t cval; /* Timer CompareValue register */
+    uint32_t ctl; /* Timer Control register */
+} ARMGenericTimer;
+
+#define GTIMER_PHYS 0
+#define GTIMER_VIRT 1
+#define NUM_GTIMERS 2
+
+/* Scale factor for generic timers, ie number of ns per tick.
+ * This gives a 62.5MHz timer.
+ */
+#define GTIMER_SCALE 16
+
 typedef struct CPUARMState {
     /* Regs for current mode.  */
     uint32_t regs[16];
@@ -143,6 +161,9 @@
         uint32_t c13_tls1; /* User RW Thread register.  */
         uint32_t c13_tls2; /* User RO Thread register.  */
         uint32_t c13_tls3; /* Privileged Thread register.  */
+        uint32_t c14_cntfrq; /* Counter Frequency register */
+        uint32_t c14_cntkctl; /* Timer Control register */
+        ARMGenericTimer c14_timer[NUM_GTIMERS];
         uint32_t c15_cpar; /* XScale Coprocessor Access Register */
         uint32_t c15_ticonfig; /* TI925T configuration byte.  */
         uint32_t c15_i_max; /* Maximum D-cache dirty line index.  */
@@ -469,6 +490,9 @@
  * old must have the OVERRIDE bit set.
  * NO_MIGRATE indicates that this register should be ignored for migration;
  * (eg because any state is accessed via some other coprocessor register).
+ * IO indicates that this register does I/O and therefore its accesses
+ * need to be surrounded by gen_io_start()/gen_io_end(). In particular,
+ * registers which implement clocks or timers require this.
  */
 #define ARM_CP_SPECIAL 1
 #define ARM_CP_CONST 2
@@ -476,13 +500,14 @@
 #define ARM_CP_SUPPRESS_TB_END 8
 #define ARM_CP_OVERRIDE 16
 #define ARM_CP_NO_MIGRATE 32
+#define ARM_CP_IO 64
 #define ARM_CP_NOP (ARM_CP_SPECIAL | (1 << 8))
 #define ARM_CP_WFI (ARM_CP_SPECIAL | (2 << 8))
 #define ARM_LAST_SPECIAL ARM_CP_WFI
 /* Used only as a terminator for ARMCPRegInfo lists */
 #define ARM_CP_SENTINEL 0xffff
 /* Mask of only the flag bits in a type field */
-#define ARM_CP_FLAG_MASK 0x3f
+#define ARM_CP_FLAG_MASK 0x7f
 
 /* Return true if cptype is a valid type field. This is used to try to
  * catch errors where the sentinel has been accidentally left off the end
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 4968391..f4e1b06 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -67,14 +67,22 @@
 static int raw_read(CPUARMState *env, const ARMCPRegInfo *ri,
                     uint64_t *value)
 {
-    *value = CPREG_FIELD32(env, ri);
+    if (ri->type & ARM_CP_64BIT) {
+        *value = CPREG_FIELD64(env, ri);
+    } else {
+        *value = CPREG_FIELD32(env, ri);
+    }
     return 0;
 }
 
 static int raw_write(CPUARMState *env, const ARMCPRegInfo *ri,
                      uint64_t value)
 {
-    CPREG_FIELD32(env, ri) = value;
+    if (ri->type & ARM_CP_64BIT) {
+        CPREG_FIELD64(env, ri) = value;
+    } else {
+        CPREG_FIELD32(env, ri) = value;
+    }
     return 0;
 }
 
@@ -687,15 +695,261 @@
     REGINFO_SENTINEL
 };
 
+#ifndef CONFIG_USER_ONLY
+
+static uint64_t gt_get_countervalue(CPUARMState *env)
+{
+    return qemu_get_clock_ns(vm_clock) / GTIMER_SCALE;
+}
+
+static void gt_recalc_timer(ARMCPU *cpu, int timeridx)
+{
+    ARMGenericTimer *gt = &cpu->env.cp15.c14_timer[timeridx];
+
+    if (gt->ctl & 1) {
+        /* Timer enabled: calculate and set current ISTATUS, irq, and
+         * reset timer to when ISTATUS next has to change
+         */
+        uint64_t count = gt_get_countervalue(&cpu->env);
+        /* Note that this must be unsigned 64 bit arithmetic: */
+        int istatus = count >= gt->cval;
+        uint64_t nexttick;
+
+        gt->ctl = deposit32(gt->ctl, 2, 1, istatus);
+        qemu_set_irq(cpu->gt_timer_outputs[timeridx],
+                     (istatus && !(gt->ctl & 2)));
+        if (istatus) {
+            /* Next transition is when count rolls back over to zero */
+            nexttick = UINT64_MAX;
+        } else {
+            /* Next transition is when we hit cval */
+            nexttick = gt->cval;
+        }
+        /* Note that the desired next expiry time might be beyond the
+         * signed-64-bit range of a QEMUTimer -- in this case we just
+         * set the timer for as far in the future as possible. When the
+         * timer expires we will reset the timer for any remaining period.
+         */
+        if (nexttick > INT64_MAX / GTIMER_SCALE) {
+            nexttick = INT64_MAX / GTIMER_SCALE;
+        }
+        qemu_mod_timer(cpu->gt_timer[timeridx], nexttick);
+    } else {
+        /* Timer disabled: ISTATUS and timer output always clear */
+        gt->ctl &= ~4;
+        qemu_set_irq(cpu->gt_timer_outputs[timeridx], 0);
+        qemu_del_timer(cpu->gt_timer[timeridx]);
+    }
+}
+
+static int gt_cntfrq_read(CPUARMState *env, const ARMCPRegInfo *ri,
+                          uint64_t *value)
+{
+    /* Not visible from PL0 if both PL0PCTEN and PL0VCTEN are zero */
+    if (arm_current_pl(env) == 0 && !extract32(env->cp15.c14_cntkctl, 0, 2)) {
+        return EXCP_UDEF;
+    }
+    *value = env->cp15.c14_cntfrq;
+    return 0;
+}
+
+static void gt_cnt_reset(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+    ARMCPU *cpu = arm_env_get_cpu(env);
+    int timeridx = ri->opc1 & 1;
+
+    qemu_del_timer(cpu->gt_timer[timeridx]);
+}
+
+static int gt_cnt_read(CPUARMState *env, const ARMCPRegInfo *ri,
+                       uint64_t *value)
+{
+    int timeridx = ri->opc1 & 1;
+
+    if (arm_current_pl(env) == 0 &&
+        !extract32(env->cp15.c14_cntkctl, timeridx, 1)) {
+        return EXCP_UDEF;
+    }
+    *value = gt_get_countervalue(env);
+    return 0;
+}
+
+static int gt_cval_read(CPUARMState *env, const ARMCPRegInfo *ri,
+                        uint64_t *value)
+{
+    int timeridx = ri->opc1 & 1;
+
+    if (arm_current_pl(env) == 0 &&
+        !extract32(env->cp15.c14_cntkctl, 9 - timeridx, 1)) {
+        return EXCP_UDEF;
+    }
+    *value = env->cp15.c14_timer[timeridx].cval;
+    return 0;
+}
+
+static int gt_cval_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                         uint64_t value)
+{
+    int timeridx = ri->opc1 & 1;
+
+    env->cp15.c14_timer[timeridx].cval = value;
+    gt_recalc_timer(arm_env_get_cpu(env), timeridx);
+    return 0;
+}
+static int gt_tval_read(CPUARMState *env, const ARMCPRegInfo *ri,
+                        uint64_t *value)
+{
+    int timeridx = ri->crm & 1;
+
+    if (arm_current_pl(env) == 0 &&
+        !extract32(env->cp15.c14_cntkctl, 9 - timeridx, 1)) {
+        return EXCP_UDEF;
+    }
+    *value = (uint32_t)(env->cp15.c14_timer[timeridx].cval -
+                        gt_get_countervalue(env));
+    return 0;
+}
+
+static int gt_tval_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                         uint64_t value)
+{
+    int timeridx = ri->crm & 1;
+
+    env->cp15.c14_timer[timeridx].cval = gt_get_countervalue(env) +
+        + sextract64(value, 0, 32);
+    gt_recalc_timer(arm_env_get_cpu(env), timeridx);
+    return 0;
+}
+
+static int gt_ctl_read(CPUARMState *env, const ARMCPRegInfo *ri,
+                       uint64_t *value)
+{
+    int timeridx = ri->crm & 1;
+
+    if (arm_current_pl(env) == 0 &&
+        !extract32(env->cp15.c14_cntkctl, 9 - timeridx, 1)) {
+        return EXCP_UDEF;
+    }
+    *value = env->cp15.c14_timer[timeridx].ctl;
+    return 0;
+}
+
+static int gt_ctl_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                        uint64_t value)
+{
+    ARMCPU *cpu = arm_env_get_cpu(env);
+    int timeridx = ri->crm & 1;
+    uint32_t oldval = env->cp15.c14_timer[timeridx].ctl;
+
+    env->cp15.c14_timer[timeridx].ctl = value & 3;
+    if ((oldval ^ value) & 1) {
+        /* Enable toggled */
+        gt_recalc_timer(cpu, timeridx);
+    } else if ((oldval & value) & 2) {
+        /* IMASK toggled: don't need to recalculate,
+         * just set the interrupt line based on ISTATUS
+         */
+        qemu_set_irq(cpu->gt_timer_outputs[timeridx],
+                     (oldval & 4) && (value & 2));
+    }
+    return 0;
+}
+
+void arm_gt_ptimer_cb(void *opaque)
+{
+    ARMCPU *cpu = opaque;
+
+    gt_recalc_timer(cpu, GTIMER_PHYS);
+}
+
+void arm_gt_vtimer_cb(void *opaque)
+{
+    ARMCPU *cpu = opaque;
+
+    gt_recalc_timer(cpu, GTIMER_VIRT);
+}
+
 static const ARMCPRegInfo generic_timer_cp_reginfo[] = {
-    /* Dummy implementation: RAZ/WI the whole crn=14 space */
-    { .name = "GENERIC_TIMER", .cp = 15, .crn = 14,
-      .crm = CP_ANY, .opc1 = CP_ANY, .opc2 = CP_ANY,
-      .access = PL1_RW, .type = ARM_CP_CONST | ARM_CP_NO_MIGRATE,
-      .resetvalue = 0 },
+    /* Note that CNTFRQ is purely reads-as-written for the benefit
+     * of software; writing it doesn't actually change the timer frequency.
+     * Our reset value matches the fixed frequency we implement the timer at.
+     */
+    { .name = "CNTFRQ", .cp = 15, .crn = 14, .crm = 0, .opc1 = 0, .opc2 = 0,
+      .access = PL1_RW | PL0_R,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_cntfrq),
+      .resetvalue = (1000 * 1000 * 1000) / GTIMER_SCALE,
+      .readfn = gt_cntfrq_read, .raw_readfn = raw_read,
+    },
+    /* overall control: mostly access permissions */
+    { .name = "CNTKCTL", .cp = 15, .crn = 14, .crm = 1, .opc1 = 0, .opc2 = 0,
+      .access = PL1_RW,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_cntkctl),
+      .resetvalue = 0,
+    },
+    /* per-timer control */
+    { .name = "CNTP_CTL", .cp = 15, .crn = 14, .crm = 2, .opc1 = 0, .opc2 = 1,
+      .type = ARM_CP_IO, .access = PL1_RW | PL0_R,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].ctl),
+      .resetvalue = 0,
+      .readfn = gt_ctl_read, .writefn = gt_ctl_write,
+      .raw_readfn = raw_read, .raw_writefn = raw_write,
+    },
+    { .name = "CNTV_CTL", .cp = 15, .crn = 14, .crm = 3, .opc1 = 0, .opc2 = 1,
+      .type = ARM_CP_IO, .access = PL1_RW | PL0_R,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].ctl),
+      .resetvalue = 0,
+      .readfn = gt_ctl_read, .writefn = gt_ctl_write,
+      .raw_readfn = raw_read, .raw_writefn = raw_write,
+    },
+    /* TimerValue views: a 32 bit downcounting view of the underlying state */
+    { .name = "CNTP_TVAL", .cp = 15, .crn = 14, .crm = 2, .opc1 = 0, .opc2 = 0,
+      .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, .access = PL1_RW | PL0_R,
+      .readfn = gt_tval_read, .writefn = gt_tval_write,
+    },
+    { .name = "CNTV_TVAL", .cp = 15, .crn = 14, .crm = 3, .opc1 = 0, .opc2 = 0,
+      .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, .access = PL1_RW | PL0_R,
+      .readfn = gt_tval_read, .writefn = gt_tval_write,
+    },
+    /* The counter itself */
+    { .name = "CNTPCT", .cp = 15, .crm = 14, .opc1 = 0,
+      .access = PL0_R, .type = ARM_CP_64BIT | ARM_CP_NO_MIGRATE | ARM_CP_IO,
+      .readfn = gt_cnt_read, .resetfn = gt_cnt_reset,
+    },
+    { .name = "CNTVCT", .cp = 15, .crm = 14, .opc1 = 1,
+      .access = PL0_R, .type = ARM_CP_64BIT | ARM_CP_NO_MIGRATE | ARM_CP_IO,
+      .readfn = gt_cnt_read, .resetfn = gt_cnt_reset,
+    },
+    /* Comparison value, indicating when the timer goes off */
+    { .name = "CNTP_CVAL", .cp = 15, .crm = 14, .opc1 = 2,
+      .access = PL1_RW | PL0_R,
+      .type = ARM_CP_64BIT | ARM_CP_IO,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].cval),
+      .resetvalue = 0,
+      .readfn = gt_cval_read, .writefn = gt_cval_write,
+      .raw_readfn = raw_read, .raw_writefn = raw_write,
+    },
+    { .name = "CNTV_CVAL", .cp = 15, .crm = 14, .opc1 = 3,
+      .access = PL1_RW | PL0_R,
+      .type = ARM_CP_64BIT | ARM_CP_IO,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].cval),
+      .resetvalue = 0,
+      .readfn = gt_cval_read, .writefn = gt_cval_write,
+      .raw_readfn = raw_read, .raw_writefn = raw_write,
+    },
     REGINFO_SENTINEL
 };
 
+#else
+/* In user-mode none of the generic timer registers are accessible,
+ * and their implementation depends on vm_clock and qdev gpio outputs,
+ * so instead just don't register any of them.
+ */
+static const ARMCPRegInfo generic_timer_cp_reginfo[] = {
+    REGINFO_SENTINEL
+};
+
+#endif
+
 static int par_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value)
 {
     if (arm_feature(env, ARM_FEATURE_LPAE)) {
@@ -1974,6 +2228,37 @@
        pointer.  */
 }
 
+/* Exception names for debug logging; note that not all of these
+ * precisely correspond to architectural exceptions.
+ */
+static const char * const excnames[] = {
+    [EXCP_UDEF] = "Undefined Instruction",
+    [EXCP_SWI] = "SVC",
+    [EXCP_PREFETCH_ABORT] = "Prefetch Abort",
+    [EXCP_DATA_ABORT] = "Data Abort",
+    [EXCP_IRQ] = "IRQ",
+    [EXCP_FIQ] = "FIQ",
+    [EXCP_BKPT] = "Breakpoint",
+    [EXCP_EXCEPTION_EXIT] = "QEMU v7M exception exit",
+    [EXCP_KERNEL_TRAP] = "QEMU intercept of kernel commpage",
+    [EXCP_STREX] = "QEMU intercept of STREX",
+};
+
+static inline void arm_log_exception(int idx)
+{
+    if (qemu_loglevel_mask(CPU_LOG_INT)) {
+        const char *exc = NULL;
+
+        if (idx >= 0 && idx < ARRAY_SIZE(excnames)) {
+            exc = excnames[idx];
+        }
+        if (!exc) {
+            exc = "unknown";
+        }
+        qemu_log_mask(CPU_LOG_INT, "Taking exception %d [%s]\n", idx, exc);
+    }
+}
+
 void arm_v7m_cpu_do_interrupt(CPUState *cs)
 {
     ARMCPU *cpu = ARM_CPU(cs);
@@ -1982,6 +2267,8 @@
     uint32_t lr;
     uint32_t addr;
 
+    arm_log_exception(env->exception_index);
+
     lr = 0xfffffff1;
     if (env->v7m.current_sp)
         lr |= 4;
@@ -2011,6 +2298,7 @@
             if (nr == 0xab) {
                 env->regs[15] += 2;
                 env->regs[0] = do_arm_semihosting(env);
+                qemu_log_mask(CPU_LOG_INT, "...handled as semihosting call\n");
                 return;
             }
         }
@@ -2064,6 +2352,8 @@
 
     assert(!IS_M(env));
 
+    arm_log_exception(env->exception_index);
+
     /* TODO: Vectored interrupt controller.  */
     switch (env->exception_index) {
     case EXCP_UDEF:
@@ -2091,6 +2381,7 @@
                     || (mask == 0xab && env->thumb))
                   && (env->uncached_cpsr & CPSR_M) != ARM_CPU_MODE_USR) {
                 env->regs[0] = do_arm_semihosting(env);
+                qemu_log_mask(CPU_LOG_INT, "...handled as semihosting call\n");
                 return;
             }
         }
@@ -2108,18 +2399,23 @@
                   && (env->uncached_cpsr & CPSR_M) != ARM_CPU_MODE_USR) {
                 env->regs[15] += 2;
                 env->regs[0] = do_arm_semihosting(env);
+                qemu_log_mask(CPU_LOG_INT, "...handled as semihosting call\n");
                 return;
             }
         }
         env->cp15.c5_insn = 2;
         /* Fall through to prefetch abort.  */
     case EXCP_PREFETCH_ABORT:
+        qemu_log_mask(CPU_LOG_INT, "...with IFSR 0x%x IFAR 0x%x\n",
+                      env->cp15.c5_insn, env->cp15.c6_insn);
         new_mode = ARM_CPU_MODE_ABT;
         addr = 0x0c;
         mask = CPSR_A | CPSR_I;
         offset = 4;
         break;
     case EXCP_DATA_ABORT:
+        qemu_log_mask(CPU_LOG_INT, "...with DFSR 0x%x DFAR 0x%x\n",
+                      env->cp15.c5_data, env->cp15.c6_data);
         new_mode = ARM_CPU_MODE_ABT;
         addr = 0x10;
         mask = CPSR_A | CPSR_I;
diff --git a/target-arm/machine.c b/target-arm/machine.c
index 6d4c2d4..5b6f375 100644
--- a/target-arm/machine.c
+++ b/target-arm/machine.c
@@ -222,9 +222,9 @@
 
 const VMStateDescription vmstate_arm_cpu = {
     .name = "cpu",
-    .version_id = 12,
-    .minimum_version_id = 12,
-    .minimum_version_id_old = 12,
+    .version_id = 13,
+    .minimum_version_id = 13,
+    .minimum_version_id_old = 13,
     .pre_save = cpu_pre_save,
     .post_load = cpu_post_load,
     .fields = (VMStateField[]) {
@@ -257,6 +257,8 @@
         VMSTATE_UINT32(env.exclusive_val, ARMCPU),
         VMSTATE_UINT32(env.exclusive_high, ARMCPU),
         VMSTATE_UINT64(env.features, ARMCPU),
+        VMSTATE_TIMER(gt_timer[GTIMER_PHYS], ARMCPU),
+        VMSTATE_TIMER(gt_timer[GTIMER_VIRT], ARMCPU),
         VMSTATE_END_OF_LIST()
     },
     .subsections = (VMStateSubsection[]) {
diff --git a/target-arm/translate.c b/target-arm/translate.c
index 6db4c50..d1e8538 100644
--- a/target-arm/translate.c
+++ b/target-arm/translate.c
@@ -6280,6 +6280,10 @@
             break;
         }
 
+        if (use_icount && (ri->type & ARM_CP_IO)) {
+            gen_io_start();
+        }
+
         if (isread) {
             /* Read */
             if (is64) {
@@ -6369,14 +6373,20 @@
                     store_cpu_offset(tmp, ri->fieldoffset);
                 }
             }
+        }
+
+        if (use_icount && (ri->type & ARM_CP_IO)) {
+            /* I/O operations must end the TB here (whether read or write) */
+            gen_io_end();
+            gen_lookup_tb(s);
+        } else if (!isread && !(ri->type & ARM_CP_SUPPRESS_TB_END)) {
             /* We default to ending the TB on a coprocessor register write,
              * but allow this to be suppressed by the register definition
              * (usually only necessary to work around guest bugs).
              */
-            if (!(ri->type & ARM_CP_SUPPRESS_TB_END)) {
-                gen_lookup_tb(s);
-            }
+            gen_lookup_tb(s);
         }
+
         return 0;
     }