From patchwork Mon Jul 15 05:19:08 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Peter Crosthwaite X-Patchwork-Id: 258956 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id C7F372C018B for ; Mon, 15 Jul 2013 15:24:06 +1000 (EST) Received: from localhost ([::1]:58163 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UybGV-0003gS-3y for incoming@patchwork.ozlabs.org; Mon, 15 Jul 2013 01:24:03 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:41654) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UybGE-0003gM-K8 for qemu-devel@nongnu.org; Mon, 15 Jul 2013 01:23:49 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UybGC-0001BL-Ds for qemu-devel@nongnu.org; Mon, 15 Jul 2013 01:23:46 -0400 Received: from mail-pb0-f49.google.com ([209.85.160.49]:52585) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UybGC-0001BF-34 for qemu-devel@nongnu.org; Mon, 15 Jul 2013 01:23:44 -0400 Received: by mail-pb0-f49.google.com with SMTP id jt11so10804844pbb.36 for ; Sun, 14 Jul 2013 22:23:43 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=sender:from:to:cc:subject:date:message-id:x-mailer:mime-version :content-type:content-transfer-encoding:x-gm-message-state; bh=7l+Fq0pboIPTSnTiwDizgkvOaWwzeWSBmu1qSiWA6BM=; b=a0XVgNw1tKzZBShatBWg8XiKtBUsf3ULol64wlYDDsr72LsPa5rx+J2lAhsk+3kZyp OLb6suVd2masMq3QjKlmHkapJYfK77SbT1RtUQNWWnVMvMfjYBuaCew4dwc8OYCz345o aasURYHAPzCx4mRW05YZ5e7KGakv0rU50f0Jdpht3RNHEk9DNb7ZqJIEVULXKmcwRDmJ VMQVwJX8jKVQie7TtmISByqgtmAOfpajl9Z2AB1PtHKGMGgbqB55Mp55Ng4ttZKfIUxd d1tGxgq+cE98aQUcVN0NLkVJ/4KyEmdQPJPvHNPxiC7H/Z9294w4Q8wyS7tNsKKRc3vt CC7g== X-Received: by 10.68.201.6 with SMTP id jw6mr46001188pbc.202.1373865822934; Sun, 14 Jul 2013 22:23:42 -0700 (PDT) Received: from localhost ([203.126.243.116]) by mx.google.com with ESMTPSA id aj3sm62256624pad.8.2013.07.14.22.23.38 for (version=TLSv1.1 cipher=RC4-SHA bits=128/128); Sun, 14 Jul 2013 22:23:41 -0700 (PDT) From: peter.crosthwaite@xilinx.com To: peter.maydell@linaro.org Date: Mon, 15 Jul 2013 15:19:08 +1000 Message-Id: X-Mailer: git-send-email 1.8.3.rc1.44.gb387c77.dirty MIME-Version: 1.0 X-Gm-Message-State: ALoCoQm5qy6X7/1nQJRB7ZUOiRGigayPDMczhNHRIEYBi6zqkOOfxE1lbOpZoRl/MADvt0raUsHm X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.85.160.49 Cc: devel@thom.fr.eu.org, afaerber@suse.de, qemu-devel@nongnu.org Subject: [Qemu-devel] [PATCH arm-devs v2 1/2] hw/timer: Introduce ARM A9 Global Timer. X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Peter Crosthwaite The ARM A9 MPCore has a timer that is global to all CPUs in the mpcore. The timer is shared but each CPU has a private independent comparator and interrupt. Based on version contributed by Francois LEGAL. Signed-off-by: François LEGAL [PC changes: * New commit message * Re-implemented as single timer model * Fixed backwards counting issue in polled mode * completed VMSD fields * macroified magic numbers (and headerified reg definitions) * split of as device-model-only patch * use bitops for 64 bit register access * Fixed auto increment mode to check condition properly * general cleanup (names etc). ] Signed-off-by: Peter Crosthwaite --- Changed since v1: Added /*< private >*/ to struct definition. Pulled register definitions out into a header (AF review) SOB Francois LEGAL with PC changes indicated. hw/timer/Makefile.objs | 2 +- hw/timer/a9gtimer.c | 412 ++++++++++++++++++++++++++++++++++++++++++++ include/hw/timer/a9gtimer.h | 33 ++++ 3 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 hw/timer/a9gtimer.c create mode 100644 include/hw/timer/a9gtimer.h diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs index eca5905..42d96df 100644 --- a/hw/timer/Makefile.objs +++ b/hw/timer/Makefile.objs @@ -1,5 +1,5 @@ common-obj-$(CONFIG_ARM_TIMER) += arm_timer.o -common-obj-$(CONFIG_ARM_MPTIMER) += arm_mptimer.o +common-obj-$(CONFIG_ARM_MPTIMER) += arm_mptimer.o a9gtimer.o common-obj-$(CONFIG_CADENCE) += cadence_ttc.o common-obj-$(CONFIG_DS1338) += ds1338.o common-obj-$(CONFIG_HPET) += hpet.o diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c new file mode 100644 index 0000000..72ebeba --- /dev/null +++ b/hw/timer/a9gtimer.c @@ -0,0 +1,412 @@ +/* + * Global peripheral timer block for ARM A9MP + * + * (C) 2013 Xilinx Inc. + * + * Written by François LEGAL + * Written by Peter Crosthwaite + * + * 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 along + * with this program; if not, see . + */ + +#include "hw/sysbus.h" +#include "qemu/timer.h" +#include "qemu/bitops.h" +#include "qemu/log.h" + +#include "hw/timer/a9gtimer.h" + +#ifndef ARM_GTIMER_ERR_DEBUG +#define ARM_GTIMER_ERR_DEBUG 0 +#endif + +#define DB_PRINT_L(level, ...) do { \ + if (ARM_GTIMER_ERR_DEBUG > (level)) { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } \ +} while (0); + +#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__) + +#define MAX_CPUS 4 + +typedef struct A9GlobalTimerPerCPU A9GlobalTimerPerCPU; +typedef struct A9GlobalTimerState A9GlobalTimerState; + +struct A9GlobalTimerPerCPU { + A9GlobalTimerState *parent; + + uint32_t control; /* only per cpu banked bits valid */ + uint64_t compare; + uint32_t status; + uint32_t inc; + + MemoryRegion iomem; + qemu_irq irq; /* PPI interrupts */ +}; + +struct A9GlobalTimerState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + /* static props */ + uint32_t num_cpu; + + QEMUTimer *timer; + + uint64_t counter; /* current timer value */ + + uint64_t ref_counter; + uint64_t cpu_ref_time; /* the cpu time as of last update of ref_counter */ + uint32_t control; /* only non per cpu banked bits valid */ + + A9GlobalTimerPerCPU per_cpu[MAX_CPUS]; +}; + +typedef struct A9GTimerUpdate { + uint64_t now; + uint64_t new; +} A9GTimerUpdate; + +static inline int a9_gtimer_get_current_cpu(A9GlobalTimerState *s) +{ + if (current_cpu->cpu_index >= s->num_cpu) { + hw_error("arm_mptimer: num-cpu %d but this cpu is %d!\n", + s->num_cpu, current_cpu->cpu_index); + } + return current_cpu->cpu_index; +} + +static inline uint64_t a9_gtimer_get_conv(A9GlobalTimerState *s) +{ + uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT, + R_CONTROL_PRESCALER_LEN); + + return (prescale + 1) * 100; +} + +static A9GTimerUpdate a9_gtimer_get_update(A9GlobalTimerState *s) +{ + A9GTimerUpdate ret; + + ret.now = qemu_get_clock_ns(vm_clock); + ret.new = s->ref_counter + + (ret.now - s->cpu_ref_time) / a9_gtimer_get_conv(s); + return ret; +} + +static void a9_gtimer_update(A9GlobalTimerState *s, bool sync) +{ + + A9GTimerUpdate update = a9_gtimer_get_update(s); + int i; + int64_t next_cdiff = 0; + + for (i = 0; i < s->num_cpu; ++i) { + A9GlobalTimerPerCPU *gtb = &s->per_cpu[i]; + int64_t cdiff = 0; + + if ((s->control & R_CONTROL_TIMER_ENABLE) && + (gtb->control & R_CONTROL_COMP_ENABLE)) { + /* R2p0+, where the compare function is > */ + while (gtb->compare < update.new) { + DB_PRINT("Compare event happened for CPU %d\n", i); + gtb->status = 1; + if (gtb->control & R_CONTROL_AUTO_INCREMENT) { + DB_PRINT("Auto incrementing timer compare by %" PRId32 "\n", + gtb->inc); + gtb->compare += gtb->inc; + } else { + break; + } + } + cdiff = (int64_t)gtb->compare - (int64_t)update.new + 1; + if (cdiff > 0 && (cdiff < next_cdiff || !next_cdiff)) { + next_cdiff = cdiff; + } + } + + qemu_set_irq(gtb->irq, + gtb->status && (gtb->control & R_CONTROL_IRQ_ENABLE)); + } + + qemu_del_timer(s->timer); + if (next_cdiff) { + DB_PRINT("scheduling qemu_timer to fire again in %" + PRIx64 " cycles\n", next_cdiff); + qemu_mod_timer(s->timer, + update.now + next_cdiff * a9_gtimer_get_conv(s)); + } + + if (s->control & R_CONTROL_TIMER_ENABLE) { + s->counter = update.new; + } + + if (sync) { + s->cpu_ref_time = update.now; + s->ref_counter = s->counter; + } +} + +static void a9_gtimer_update_no_sync(void *opaque) +{ + A9GlobalTimerState *s = ARM_GTIMER(opaque); + + return a9_gtimer_update(s, false); +} + +static uint64_t a9_gtimer_read(void *opaque, hwaddr addr, unsigned size) +{ + A9GlobalTimerPerCPU *gtb = (A9GlobalTimerPerCPU *)opaque; + A9GlobalTimerState *s = gtb->parent; + A9GTimerUpdate update; + uint64_t ret = 0; + int shift = 0; + + switch (addr) { + case R_COUNTER_HI: + shift = 32; + /* fallthrough */ + case R_COUNTER_LO: + update = a9_gtimer_get_update(s); + ret = extract64(update.new, shift, 32); + break; + case R_CONTROL: + ret = s->control | gtb->control; + break; + case R_INTERRUPT_STATUS: + ret = gtb->status; + break; + case R_COMPARATOR_HI: + shift = 32; + /* fallthrough */ + case R_COMPARATOR_LO: + ret = extract64(gtb->compare, shift, 32); + break; + case R_AUTO_INCREMENT: + ret = gtb->inc; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "bad a9gtimer register: %x\n", + (unsigned)addr); + return 0; + } + + DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, ret); + return ret; +} + +static void a9_gtimer_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + A9GlobalTimerPerCPU *gtb = (A9GlobalTimerPerCPU *)opaque; + A9GlobalTimerState *s = gtb->parent; + int shift = 0; + + DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, value); + + switch (addr) { + case R_COUNTER_HI: + shift = 32; + /* fallthrough */ + case R_COUNTER_LO: + /* + * Keep it simple - ARM docco explicitly says to disable timer before + * modding it, so dont bother trying to do all the difficult on the fly + * timer modifications - (if they even work in real hardware??). + */ + if (s->control & R_CONTROL_TIMER_ENABLE) { + qemu_log_mask(LOG_GUEST_ERROR, "Cannot mod running ARM gtimer\n"); + return; + } + s->counter = deposit64(s->counter, shift, 32, value); + return; + case R_CONTROL: + a9_gtimer_update(s, (value ^ s->control) & R_CONTROL_NEEDS_SYNC); + gtb->control = value & R_CONTROL_BANKED; + s->control = value & ~R_CONTROL_BANKED; + break; + case R_INTERRUPT_STATUS: + a9_gtimer_update(s, false); + gtb->status &= ~value; + break; + case R_COMPARATOR_HI: + shift = 32; + /* fallthrough */ + case R_COMPARATOR_LO: + a9_gtimer_update(s, false); + gtb->compare = deposit64(gtb->compare, shift, 32, value); + break; + case R_AUTO_INCREMENT: + gtb->inc = value; + return; + default: + return; + } + + a9_gtimer_update(s, false); +} + +/* Wrapper functions to implement the "read global timer for + * the current CPU" memory regions. + */ +static uint64_t a9_gtimer_this_read(void *opaque, hwaddr addr, + unsigned size) +{ + A9GlobalTimerState *s = ARM_GTIMER(opaque); + int id = a9_gtimer_get_current_cpu(s); + + /* no \n so concatenates with message from read fn */ + DB_PRINT("CPU:%d:", id); + + return a9_gtimer_read(&s->per_cpu[id], addr, size); +} + +static void a9_gtimer_this_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + A9GlobalTimerState *s = ARM_GTIMER(opaque); + int id = a9_gtimer_get_current_cpu(s); + + /* no \n so concatentates with message from write fn */ + DB_PRINT("CPU:%d:", id); + + a9_gtimer_write(&s->per_cpu[id], addr, value, size); +} + +static const MemoryRegionOps a9_gtimer_this_ops = { + .read = a9_gtimer_this_read, + .write = a9_gtimer_this_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const MemoryRegionOps a9_gtimer_ops = { + .read = a9_gtimer_read, + .write = a9_gtimer_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void a9_gtimer_reset(DeviceState *dev) +{ + A9GlobalTimerState *s = ARM_GTIMER(dev); + int i; + + s->counter = 0; + s->control = 0; + + for (i = 0; i < s->num_cpu; i++) { + A9GlobalTimerPerCPU *gtb = &s->per_cpu[i]; + + gtb->control = 0; + gtb->status = 0; + gtb->compare = 0; + } + a9_gtimer_update(s, false); +} + +static void a9_gtimer_realize(DeviceState *dev, Error **errp) +{ + A9GlobalTimerState *s = ARM_GTIMER(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + int i; + + if (s->num_cpu < 1 || s->num_cpu > MAX_CPUS) { + error_setg(errp, "%s: num-cpu must be between 1 and %d\n", + __func__, MAX_CPUS); + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &a9_gtimer_this_ops, s, + "MMIO shared", 0x20); + sysbus_init_mmio(sbd, &s->iomem); + s->timer = qemu_new_timer_ns(vm_clock, a9_gtimer_update_no_sync, s); + + for (i = 0; i < s->num_cpu; i++) { + A9GlobalTimerPerCPU *gtb = &s->per_cpu[i]; + + gtb->parent = s; + sysbus_init_irq(sbd, >b->irq); + memory_region_init_io(>b->iomem, OBJECT(dev), &a9_gtimer_ops, gtb, + "MMIO per cpu", 0x20); + sysbus_init_mmio(sbd, >b->iomem); + } +} + +static const VMStateDescription vmstate_a9_gtimer_per_cpu = { + .name = "arm.cortex-a9-global-timer.percpu", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(control, A9GlobalTimerPerCPU), + VMSTATE_UINT64(compare, A9GlobalTimerPerCPU), + VMSTATE_UINT32(status, A9GlobalTimerPerCPU), + VMSTATE_UINT32(inc, A9GlobalTimerPerCPU), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_a9_gtimer = { + .name = "arm.cortex-a9-global-timer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_TIMER(timer, A9GlobalTimerState), + VMSTATE_UINT64(counter, A9GlobalTimerState), + VMSTATE_UINT64(cpu_ref_time, A9GlobalTimerState), + VMSTATE_STRUCT_VARRAY_UINT32(per_cpu, A9GlobalTimerState, num_cpu, + 1, vmstate_a9_gtimer_per_cpu, + A9GlobalTimerPerCPU), + VMSTATE_END_OF_LIST() + } +}; + +static Property a9_gtimer_properties[] = { + DEFINE_PROP_UINT32("num-cpu", A9GlobalTimerState, num_cpu, 0), + DEFINE_PROP_END_OF_LIST() +}; + +static void a9_gtimer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = a9_gtimer_realize; + dc->vmsd = &vmstate_a9_gtimer; + dc->reset = a9_gtimer_reset; + dc->no_user = 1; + dc->props = a9_gtimer_properties; +} + +static const TypeInfo a9_gtimer_info = { + .name = TYPE_ARM_GTIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(A9GlobalTimerState), + .class_init = a9_gtimer_class_init, +}; + +static void a9_gtimer_register_types(void) +{ + type_register_static(&a9_gtimer_info); +} + +type_init(a9_gtimer_register_types) diff --git a/include/hw/timer/a9gtimer.h b/include/hw/timer/a9gtimer.h new file mode 100644 index 0000000..f4c1499 --- /dev/null +++ b/include/hw/timer/a9gtimer.h @@ -0,0 +1,33 @@ +#ifndef A9GTIMER_H +#define A9GTIMER_H + +#include "qom/object.h" + +#define TYPE_ARM_GTIMER "arm.cortex-a9-global-timer" +#define ARM_GTIMER(obj) OBJECT_CHECK(A9GlobalTimerState, (obj), TYPE_ARM_GTIMER) + +#define R_COUNTER_LO 0x00 +#define R_COUNTER_HI 0x04 + +#define R_CONTROL 0x08 +#define R_CONTROL_TIMER_ENABLE (1 << 0) +#define R_CONTROL_COMP_ENABLE (1 << 1) +#define R_CONTROL_IRQ_ENABLE (1 << 2) +#define R_CONTROL_AUTO_INCREMENT (1 << 2) +#define R_CONTROL_PRESCALER_SHIFT 8 +#define R_CONTROL_PRESCALER_LEN 8 +#define R_CONTROL_PRESCALER_MASK (((1 << R_CONTROL_PRESCALER_LEN) - 1) << \ + R_CONTROL_PRESCALER_SHIFT) + +#define R_CONTROL_BANKED (R_CONTROL_COMP_ENABLE | \ + R_CONTROL_IRQ_ENABLE | \ + R_CONTROL_AUTO_INCREMENT) +#define R_CONTROL_NEEDS_SYNC (R_CONTROL_TIMER_ENABLE | \ + R_CONTROL_PRESCALER_MASK) + +#define R_INTERRUPT_STATUS 0x0C +#define R_COMPARATOR_LO 0x10 +#define R_COMPARATOR_HI 0x14 +#define R_AUTO_INCREMENT 0x18 + +#endif /* #ifdef A9GTIMER_H */