Patchwork [RFC,v2,2/4] cadence_ttc: initial version of device model

login
register
mail settings
Submitter Peter A. G. Crosthwaite
Date Feb. 7, 2012, 6:19 a.m.
Message ID <11ee1a59f2bffdbdf37e49911df0428635cbb36f.1328594621.git.peter.crosthwaite@petalogix.com>
Download mbox | patch
Permalink /patch/139878/
State New
Headers show

Comments

Peter A. G. Crosthwaite - Feb. 7, 2012, 6:19 a.m.
Implemented cadence Triple Timer Counter (TCC)

Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
Signed-off-by: John Linn <john.linn@xilinx.com>
---
changes from v1
refactored event driven code
marked vmsd as unmigratable

 Makefile.target  |    1 +
 hw/cadence_ttc.c |  399 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 400 insertions(+), 0 deletions(-)
 create mode 100644 hw/cadence_ttc.c
Paul Brook - Feb. 7, 2012, 11:28 a.m.
> Implemented cadence Triple Timer Counter (TCC)

It looks like you're implementing a periodic timer as sequence of chained 
oneshot timers.  This is a bad idea.  In qemu interrupt latency may be high, 
so you're likely to suffer from significant time skew.

Paul
Peter A. G. Crosthwaite - Feb. 8, 2012, 7:27 a.m.
2012/2/7 Paul Brook <paul@codesourcery.com>

> > Implemented cadence Triple Timer Counter (TCC)
>
> It looks like you're implementing a periodic timer as sequence of chained
> oneshot timers.  This is a bad idea.  In qemu interrupt latency may be
> high,
> so you're likely to suffer from significant time skew.
>
> Ok, I could implemented the wraparound event as a periodic timer and the
match events are kicked off as seperate one-shot timers when the wrap
around occurs? There would still be a small delay on match events, but it
would get rid of the integration effect of lots of little delays (over many
wrap arounds) add up to a significant skew.


> Paul
>
Paul Brook - Feb. 8, 2012, 10:15 a.m.
> > > Implemented cadence Triple Timer Counter (TCC)
> > 
> > It looks like you're implementing a periodic timer as sequence of chained
> > oneshot timers.  This is a bad idea.  In qemu interrupt latency may be
> > high,
> > so you're likely to suffer from significant time skew.
> > 
> Ok, I could implemented the wraparound event as a periodic timer and the
> match events are kicked off as seperate one-shot timers when the wrap
> around occurs? There would still be a small delay on match events, but it
> would get rid of the integration effect of lots of little delays (over many
> wrap arounds) add up to a significant skew.

I'm not sure why you need the oneshot timers at all. But then again I'm not 
really sure what the desired semantics are either :-)

It would help me if you could describe how these timers operate.
In particular:

- Are they free running.  i.e. keep counting until explicitly stopped by the 
user, or stop when an event occurs.
- When are interrupts raised.  You mention a user specified match value.  Do 
we also get an interrupt on wraparound?
- What happens when the timer hits the limit (zero if count-down, match value 
if count-up)? Does it wrap? or load a fixed value? 

If you've got independent wrap and match events then I guess yes, a periodic 
wrap plus a oneshot match event is probably appropriate.

If wrapping does not generate an interrupt, or wrap and match are effectively 
the same thing then you just need to transpose the counter onto a single 
periodic timer.

If the timers can be configured in both periodic and oneshot modes, then you 
may want to have different implementations based on that.

Paul
Peter A. G. Crosthwaite - Feb. 8, 2012, 10:35 a.m.
2012/2/8 Paul Brook <paul@codesourcery.com>

> > > > Implemented cadence Triple Timer Counter (TCC)
> > >
> > > It looks like you're implementing a periodic timer as sequence of
> chained
> > > oneshot timers.  This is a bad idea.  In qemu interrupt latency may be
> > > high,
> > > so you're likely to suffer from significant time skew.
> > >
> > Ok, I could implemented the wraparound event as a periodic timer and the
> > match events are kicked off as seperate one-shot timers when the wrap
> > around occurs? There would still be a small delay on match events, but it
> > would get rid of the integration effect of lots of little delays (over
> many
> > wrap arounds) add up to a significant skew.
>
> I'm not sure why you need the oneshot timers at all. But then again I'm not
> really sure what the desired semantics are either :-)
>

It would help me if you could describe how these timers operate.
> In particular:
>
> - Are they free running.  i.e. keep counting until explicitly stopped by
> the
> user, or stop when an event occurs.
>

Free running


> - When are interrupts raised.  You mention a user specified match value.
>  Do
> we also get an interrupt on wraparound?
>

Yes, an interrupts occur on wrap around of the 16 bit timer value. There
are three match registers which correspond to three more
 (separately maskable) interrupts which are risen when the timer crosses
that value. My implementation will figure out which of the three events (or
the wraparound) will occur next, and one shot the corresponding period of
time. Note that a match can occur an raise an interrupt without a wrap or
reset occuring. E.G. i could set my timer counting up from 0 and when the
value hits BEEF, i get an interrupt, but the timer still counts all the way
to FFFF before wrapping.


> - What happens when the timer hits the limit (zero if count-down, match
> value
> if count-up)? Does it wrap? or load a fixed value?
>
> Either wrap or load a fixed value, there is a control bit to determine
which.


> If you've got independent wrap and match events then I guess yes, a
> periodic
> wrap plus a oneshot match event is probably appropriate.
>
>
Yes this is the case. I will look into making it happen.


> If wrapping does not generate an interrupt, or wrap and match are
> effectively
> the same thing then you just need to transpose the counter onto a single
> periodic timer.
>


> If the timers can be configured in both periodic and oneshot modes, then
> you
> may want to have different implementations based on that.
>
>
I dont think this will be needed, the match mechanism detail above is more
of an issue and is the underlying reason for the one shot chaining
implementation.


> Paul
>

Peter
Paul Brook - Feb. 8, 2012, 12:35 p.m.
> > - When are interrupts raised.  You mention a user specified match value.
> >  Do we also get an interrupt on wraparound?
> 
> Yes, an interrupts occur on wrap around of the 16 bit timer value. There
> are three match registers which correspond to three more
>  (separately maskable) interrupts which are risen when the timer crosses
> that value. My implementation will figure out which of the three events (or
> the wraparound) will occur next, and one shot the corresponding period of
> time. Note that a match can occur an raise an interrupt without a wrap or
> reset occuring. E.G. i could set my timer counting up from 0 and when the
> value hits BEEF, i get an interrupt, but the timer still counts all the way
> to FFFF before wrapping.

Ok. I'd missed that there are 3 matches per timer.

> > If you've got independent wrap and match events then I guess yes, a
> > periodic
> > wrap plus a oneshot match event is probably appropriate.
> 
> Yes this is the case. I will look into making it happen.

It's probably not worth using ptimer at all.

Instead use QEMUTimer/qemu_mod_timer directly.  The trick is to call 
qemu_get_clock_ns when the timer is started and calculate all deadlines 
incrementally from that, not from the time when the last timeout happened to 
fire.  See ptimer.c:ptimer_reload/tick or stellaris.c:gptm_reload/tick for 
examples.

ptimer.c provides a common implementation of a simple periodic timer.  
Previously we had a dozen different implementations, most of which were broken 
in one way or annother.  For more complicated devices you need to know what 
you're doing anyway :-)

Paul
Peter A. G. Crosthwaite - Feb. 8, 2012, 12:47 p.m.
2012/2/8 Paul Brook <paul@codesourcery.com>

> > > - When are interrupts raised.  You mention a user specified match
> value.
> > >  Do we also get an interrupt on wraparound?
> >
> > Yes, an interrupts occur on wrap around of the 16 bit timer value. There
> > are three match registers which correspond to three more
> >  (separately maskable) interrupts which are risen when the timer crosses
> > that value. My implementation will figure out which of the three events
> (or
> > the wraparound) will occur next, and one shot the corresponding period of
> > time. Note that a match can occur an raise an interrupt without a wrap or
> > reset occuring. E.G. i could set my timer counting up from 0 and when the
> > value hits BEEF, i get an interrupt, but the timer still counts all the
> way
> > to FFFF before wrapping.
>
> Ok. I'd missed that there are 3 matches per timer.
>
> > > If you've got independent wrap and match events then I guess yes, a
> > > periodic
> > > wrap plus a oneshot match event is probably appropriate.
> >
> > Yes this is the case. I will look into making it happen.
>
> It's probably not worth using ptimer at all.
>
> Instead use QEMUTimer/qemu_mod_timer directly.  The trick is to call
> qemu_get_clock_ns when the timer is started and calculate all deadlines
> incrementally from that, not from the time when the last timeout happened
> to
> fire.  See ptimer.c:ptimer_reload/tick or stellaris.c:gptm_reload/tick for
> examples.
>
> ptimer.c provides a common implementation of a simple periodic timer.
> Previously we had a dozen different implementations, most of which were
> broken
> in one way or annother.  For more complicated devices you need to know what
> you're doing anyway :-)
>
>
Ok, this would lead to a more minimal change then :). Just replace ptimer
with QEMUTimer and stick with the incremental deadlines approach which is
pretty much the code is as it stands.


> Paul
>

Peter

Patch

diff --git a/Makefile.target b/Makefile.target
index 620a91d..feefafa 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -338,6 +338,7 @@  obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
 obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += cadence_uart.o
+obj-arm-y += cadence_ttc.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o
diff --git a/hw/cadence_ttc.c b/hw/cadence_ttc.c
new file mode 100644
index 0000000..5074e2c
--- /dev/null
+++ b/hw/cadence_ttc.c
@@ -0,0 +1,399 @@ 
+/*
+ * Xilinx Zynq cadence TTC model
+ *
+ * Copyright (c) 2011 Xilinx Inc.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ * Written By Haibing Ma
+ *            M. Habib
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
+ * 02139, USA.
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+#include "ptimer.h"
+
+#ifdef CADENCE_TTC_ERR_DEBUG
+#define qemu_debug(...) do { \
+    fprintf(stderr,  ": %s: ", __func__); \
+    fprintf(stderr, ## __VA_ARGS__); \
+    fflush(stderr); \
+    } while (0);
+#else
+    #define qemu_debug(...)
+#endif
+
+#define COUNTER_INTR_IV     0x00000001
+#define COUNTER_INTR_M1     0x00000002
+#define COUNTER_INTR_M2     0x00000004
+#define COUNTER_INTR_M3     0x00000008
+#define COUNTER_INTR_OV     0x00000010
+#define COUNTER_INTR_EV     0x00000020
+
+#define COUNTER_CTRL_DIS    0x00000001
+#define COUNTER_CTRL_INT    0x00000002
+#define COUNTER_CTRL_DEC    0x00000004
+#define COUNTER_CTRL_MATCH  0x00000008
+#define COUNTER_CTRL_RST    0x00000010
+
+#define CLOCK_CTRL_PS_EN    0x00000001
+#define CLOCK_CTRL_PS_V     0x0000001e
+
+typedef struct {
+    ptimer_state *timer;
+    int freq;
+
+    uint32_t reg_clock;
+    uint32_t reg_count;
+    uint16_t reg_value;
+    uint16_t reg_interval;
+    uint16_t reg_match[3];
+    uint32_t reg_intr;
+    uint32_t reg_intr_en;
+    uint32_t reg_event_ctrl;
+    uint32_t reg_event;
+
+    uint32_t event_interval;
+    int serviced;
+
+    qemu_irq irq;
+} CadenceTimerState;
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    CadenceTimerState * timer[3];
+} cadence_ttc_state;
+
+static void cadence_timer_update(CadenceTimerState *s)
+{
+    qemu_set_irq(s->irq, !!(s->reg_intr & s->reg_intr_en));
+}
+
+static CadenceTimerState *cadence_timer_from_addr(void *opaque,
+                                        target_phys_addr_t offset)
+{
+    unsigned int index;
+    cadence_ttc_state *s = (cadence_ttc_state *)opaque;
+
+    index = (offset >> 2) % 3;
+
+    return s->timer[index];
+}
+
+static inline int is_between(int x, int a, int b)
+{
+    if (a < b) {
+        return x > a && x < b;
+    }
+    return x < a && x > b;
+}
+
+static void cadence_timer_run(CadenceTimerState *s)
+{
+    int i;
+    int32_t event_interval;
+    int32_t interval = (s->reg_count & COUNTER_CTRL_INT) ?
+            ((int32_t)s->reg_interval + 1) : 0x10000;
+    int32_t next_value = (s->reg_count & COUNTER_CTRL_DEC) ? -1 : interval;
+
+    for (i = 0; i < 3; ++i) {
+        if (is_between((int)s->reg_match[i], (int)s->reg_value, next_value)) {
+            next_value = s->reg_match[i];
+        }
+    }
+    event_interval = next_value - (int32_t)s->reg_value;
+    s->event_interval = (event_interval < 0) ? -event_interval : event_interval;
+    s->serviced = 0;
+
+    ptimer_set_limit(s->timer, (uint64_t)s->event_interval, 1);
+    ptimer_run(s->timer, 1);
+}
+
+static uint32_t cadence_counter_value(CadenceTimerState *s)
+{
+    int i;
+    int32_t interval = (s->reg_count & COUNTER_CTRL_INT) ?
+            (int)(s->reg_interval + 1) : 0x10000;
+
+    int32_t r = s->event_interval - ptimer_get_count(s->timer);
+    int32_t x = s->reg_value + ((s->reg_count & COUNTER_CTRL_DEC) ? -r : r);
+    int32_t x_mod = (x + interval) % interval;
+
+    if (!s->serviced) {
+        for (i = 0; i < 3; ++i) {
+            if (is_between(s->reg_match[i], s->reg_value, x) ||
+                    s->reg_match[i] == r) {
+                s->reg_intr |= (2 << i);
+            }
+        }
+        if (x_mod != x) {
+            s->reg_intr |= (s->reg_count & COUNTER_CTRL_INT) ?
+                COUNTER_INTR_IV : COUNTER_INTR_OV;
+        }
+        s->serviced = 1;
+        cadence_timer_update(s);
+    }
+
+    return (uint32_t)x_mod;
+}
+
+static void cadence_counter_clock(CadenceTimerState *s , uint32_t value)
+{
+    int freq;
+
+    s->reg_clock = value & 0x3f;
+    if (s->reg_clock & CLOCK_CTRL_PS_EN) {
+        freq = s->freq;
+        freq >>= ((value & CLOCK_CTRL_PS_V) >> 1) + 1;
+        ptimer_set_freq(s->timer, freq);
+    }
+}
+
+static void cadence_counter_control(CadenceTimerState *s , uint32_t value)
+{
+    if (value & COUNTER_CTRL_RST) {
+        ptimer_stop(s->timer);
+        s->reg_value = 0;
+        s->event_interval = 0;
+    }
+    if ((s->reg_count ^  value) & COUNTER_CTRL_DIS) { /* start or stop */
+        if (value & COUNTER_CTRL_DIS) { /* stop */
+            ptimer_stop(s->timer);
+            s->reg_value = cadence_counter_value(s);
+        } else {
+            cadence_timer_run(s);
+        }
+    }
+    s->reg_count = value & 0x3f & ~COUNTER_CTRL_RST;
+}
+
+static void cadence_timer_tick(void *opaque)
+{
+    CadenceTimerState *s = opaque;
+
+    s->reg_value = cadence_counter_value(s);
+    cadence_timer_run(s);
+}
+
+static uint32_t cadence_ttc_read_imp(void *opaque, target_phys_addr_t offset)
+{
+    CadenceTimerState *s = cadence_timer_from_addr(opaque, offset);
+    uint32_t value;
+
+    switch (offset) {
+    case 0x00: /* clock control */
+    case 0x04:
+    case 0x08:
+        return s->reg_clock;
+
+    case 0x0c: /* counter control */
+    case 0x10:
+    case 0x14:
+        return s->reg_count;
+
+    case 0x18: /* counter value */
+    case 0x1c:
+    case 0x20:
+        return cadence_counter_value(s);
+
+    case 0x24: /* reg_interval counter */
+    case 0x28:
+    case 0x2c:
+        return s->reg_interval;
+
+    case 0x30: /* match 1 counter */
+    case 0x34:
+    case 0x38:
+        return s->reg_match[0];
+
+    case 0x3c: /* match 2 counter */
+    case 0x40:
+    case 0x44:
+        return s->reg_match[1];
+
+    case 0x48: /* match 3 counter */
+    case 0x4c:
+    case 0x50:
+        return s->reg_match[2];
+
+    case 0x54: /* interrupt register */
+    case 0x58:
+    case 0x5c:
+        /* cleared after read */
+        value = s->reg_intr;
+        s->reg_intr = 0;
+        return value;
+
+    case 0x60: /* interrupt enable */
+    case 0x64:
+    case 0x68:
+        return s->reg_intr_en;
+
+    case 0x6c:
+    case 0x70:
+    case 0x74:
+        return s->reg_event_ctrl;
+
+    case 0x78:
+    case 0x7c:
+    case 0x80:
+        return s->reg_event;
+
+    default:
+        return 0;
+    }
+}
+
+static uint64_t cadence_ttc_read(void *opaque, target_phys_addr_t offset,
+    unsigned size)
+{
+    uint32_t ret = cadence_ttc_read_imp(opaque, offset);
+
+    qemu_debug("addr: %08x data: %08x\n", offset, ret);
+    return ret;
+}
+
+static void cadence_ttc_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    CadenceTimerState *s = cadence_timer_from_addr(opaque, offset);
+
+    qemu_debug("addr: %08x data %08x\n", offset, (unsigned)value);
+
+    switch (offset) {
+    case 0x00: /* clock control */
+    case 0x04:
+    case 0x08:
+        cadence_counter_clock(s, value);
+        break;
+
+    case 0x0c: /* conter control */
+    case 0x10:
+    case 0x14:
+        cadence_counter_control(s, value);
+        break;
+
+    case 0x24: /* interval register */
+    case 0x28:
+    case 0x2c:
+        s->reg_interval = value & 0xffff;
+        break;
+
+    case 0x30: /* match register */
+    case 0x34:
+    case 0x38:
+        s->reg_match[0] = value & 0xffff;
+
+    case 0x3c: /* match register */
+    case 0x40:
+    case 0x44:
+        s->reg_match[1] = value & 0xffff;
+
+    case 0x48: /* match register */
+    case 0x4c:
+    case 0x50:
+        s->reg_match[2] = value & 0xffff;
+        break;
+
+    case 0x54: /* interrupt register */
+    case 0x58:
+    case 0x5c:
+        s->reg_intr &= (~value & 0xfff);
+        break;
+
+    case 0x60: /* interrupt enable */
+    case 0x64:
+    case 0x68:
+        s->reg_intr_en = value & 0x3f;
+        break;
+
+    case 0x6c: /* event control */
+    case 0x70:
+    case 0x74:
+        s->reg_event_ctrl = value & 0x07;
+        break;
+
+    default:
+        return;
+    }
+
+    cadence_timer_update(s);
+}
+
+static const MemoryRegionOps cadence_ttc_ops = {
+    .read = cadence_ttc_read,
+    .write = cadence_ttc_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static CadenceTimerState *cadence_timer_init(uint32_t freq)
+{
+    CadenceTimerState *s;
+    QEMUBH *bh;
+
+    s = (CadenceTimerState *)g_malloc0(sizeof(CadenceTimerState));
+    s->freq = freq;
+    s->reg_count = 0x21;
+
+    bh = qemu_bh_new(cadence_timer_tick, s);
+    s->timer = ptimer_init(bh);
+    ptimer_set_freq(s->timer, freq);
+
+    return s;
+}
+
+static int cadence_ttc_init(SysBusDevice *dev)
+{
+    cadence_ttc_state *s = FROM_SYSBUS(cadence_ttc_state, dev);
+    int i;
+
+    for (i = 0; i < 3; ++i) {
+        s->timer[i] = cadence_timer_init(2500000);
+        sysbus_init_irq(dev, &s->timer[i]->irq);
+    }
+
+    memory_region_init_io(&s->iomem, &cadence_ttc_ops, s, "timer", 0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    return 0;
+}
+
+/* FIMXE: add vmsd support */
+
+static const VMStateDescription vmstate_cadence_ttc = {
+    .name = "cadence_TTC",
+    .unmigratable = 1,
+};
+
+static void cadence_ttc_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+    sdc->init = cadence_ttc_init;
+    dc->vmsd = &vmstate_cadence_ttc;
+}
+
+static TypeInfo cadence_ttc_info = {
+    .name  = "cadence_ttc",
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(cadence_ttc_state),
+    .class_init = cadence_ttc_class_init,
+};
+
+static void cadence_ttc_register(void)
+{
+    type_register_static(&cadence_ttc_info);
+}
+
+device_init(cadence_ttc_register)