qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH] Implement the global timer present in ARM MPCore ch


From: Christopher Harvey
Subject: [Qemu-devel] [PATCH] Implement the global timer present in ARM MPCore chips.
Date: Wed, 6 Jul 2011 16:04:35 -0400
User-agent: Mutt/1.5.21 (2010-09-15)

I just rebased this from a REALLY old version and made some
changes. Before I go ahead and clean it up and properly test I wanted
to run it by this list to make sure it wont get rejected after all
that work. 

You can read about the hardware here:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0407f/BEJHAGEE.html

Reminder: 
There are probably bugs in this code, do not commit :P

I will be happy to answer any questions. 

Signed-off-by: Christopher Harvey <address@hidden>
---
 hw/mpcore.c |  188 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 177 insertions(+), 11 deletions(-)

diff --git a/hw/mpcore.c b/hw/mpcore.c
index 379065a..a66cb0c 100644
--- a/hw/mpcore.c
+++ b/hw/mpcore.c
@@ -20,6 +20,12 @@ gic_get_current_cpu(void)
 
 #include "arm_gic.c"
 
+/* Global timer data */
+static uint64_t global_timer_count = 0;
+static uint64_t global_timer_this_inc = 0;
+  /* Only for prescale and enable bits */
+static uint32_t global_timer_control = 0;
+
 /* MPCore private memory region.  */
 
 typedef struct {
@@ -34,11 +40,30 @@ typedef struct {
     int id; /* Encodes both timer/watchdog and CPU.  */
 } mpcore_timer_state;
 
+typedef struct {
+    uint64_t compare;
+    uint32_t inc;
+    uint32_t status;
+    /* Only used for Auto-inc, IRQ enable and comp-enable (banked bits) */
+    /* Prescaler and enable bits are in the global control variable. */
+    uint32_t control; 
+
+    /* 
+     We actually need one of these for each core even for the global timer
+     because we don't simulate every tick, just the interrupt intervals, 
+     which can be different for each core.
+    */
+    QEMUTimer *timer;
+    int id;  /* Encodes only CPU id. */
+    struct mpcore_priv_state *mpcore;
+} mpcore_global_timer_state;
+
 typedef struct mpcore_priv_state {
     gic_state gic;
     uint32_t scu_control;
     int iomemtype;
-    mpcore_timer_state timer[8];
+    mpcore_timer_state timer[2*NCPU];
+    mpcore_global_timer_state g_timers[NCPU];
     uint32_t num_cpu;
 } mpcore_priv_state;
 
@@ -46,16 +71,41 @@ typedef struct mpcore_priv_state {
 
 static inline void mpcore_timer_update_irq(mpcore_timer_state *s)
 {
-    if (s->status & ~s->old_status) {
+    if (s->status & ~s->old_status) { /* status == 1 && old_status == 0 */
         gic_set_pending_private(&s->mpcore->gic, s->id >> 1, 29 + (s->id & 1));
     }
     s->old_status = s->status;
 }
 
 /* Return conversion factor from mpcore timer ticks to qemu timer ticks.  */
-static inline uint32_t mpcore_timer_scale(mpcore_timer_state *s)
+static inline uint32_t mpcore_timer_scale(uint32_t control)
+{   
+    /* (Prescaler + 1) * 10 */
+    return (((control >> 8) & 0xff) + 1) * 10;
+}
+
+static uint64_t get_instantaneous_count(void) 
+{
+    uint64_t ret;
+    ret = qemu_get_clock_ns(vm_clock) - global_timer_count;
+    ret /= mpcore_timer_scale(global_timer_control);
+    ret += global_timer_count;
+    return ret;
+}
+
+static void set_next_global_tick(mpcore_global_timer_state *s) 
 {
-    return (((s->control >> 8) & 0xff) + 1) * 10;
+    int64_t to_wait;
+    if(s->compare < get_instantaneous_count())
+        return; /* Cortex-A9 r2p0 and later fire even when count >= compare, 
+                  not only on equality. Worth simulating? */
+
+    if(global_timer_control & 1) { /* Only set next tick if enabled. */
+       to_wait = qemu_get_clock_ns(vm_clock); 
+       to_wait += (s->compare - global_timer_count) * 
mpcore_timer_scale(global_timer_control);
+       global_timer_this_inc = s->compare - global_timer_count;
+       qemu_mod_timer(s->timer, to_wait);
+    }
 }
 
 static void mpcore_timer_reload(mpcore_timer_state *s, int restart)
@@ -64,10 +114,27 @@ static void mpcore_timer_reload(mpcore_timer_state *s, int 
restart)
         return;
     if (restart)
         s->tick = qemu_get_clock_ns(vm_clock);
-    s->tick += (int64_t)s->count * mpcore_timer_scale(s);
+    s->tick += (int64_t)s->count * mpcore_timer_scale(s->control);
     qemu_mod_timer(s->timer, s->tick);
 }
 
+static void mpcore_global_timer_tick(void *opaque) 
+{
+    mpcore_global_timer_state *s = (mpcore_global_timer_state *)opaque;
+    global_timer_count += global_timer_this_inc;
+    if(s->control & 0xB)/*test for auto-inc bit, comp and timer enable bits*/
+        s->compare += s->inc;
+
+    if(s->control & 7) { /* test for IRQ enable bit, compare enable and,
+                           timer enable */
+        if(!s->status) {
+            s->status = 1;
+            gic_set_pending_private(&s->mpcore->gic, s->id >> 1, 27);
+       }
+    }
+    set_next_global_tick(s);
+}
+
 static void mpcore_timer_tick(void *opaque)
 {
     mpcore_timer_state *s = (mpcore_timer_state *)opaque;
@@ -93,7 +160,7 @@ static uint32_t mpcore_timer_read(mpcore_timer_state *s, int 
offset)
             return 0;
         /* Slow and ugly, but hopefully won't happen too often.  */
         val = s->tick - qemu_get_clock_ns(vm_clock);
-        val /= mpcore_timer_scale(s);
+        val /= mpcore_timer_scale(s->control);
         if (val < 0)
             val = 0;
         return val;
@@ -106,6 +173,39 @@ static uint32_t mpcore_timer_read(mpcore_timer_state *s, 
int offset)
     }
 }
 
+static uint32_t mpcore_global_timer_read(mpcore_global_timer_state *s, int 
offset) 
+{
+    uint32_t ret;
+    offset &= 0xff; 
+    switch(offset) {
+    case 0:    /* Counter lower bits */
+    case 0x04: /* Counter high bits */
+
+        break;
+    case 0x08: /* Control */
+        ret = (s->control              & 0xE)     |    /* Bits [3:1]  */
+             (global_timer_control    & 0xFF01);      /* Bits [15:8] and 0 */
+       break;
+    case 0x0C: /* Status */
+        ret = s->status & 1;
+       break;
+    case 0x10: /* Compare lower */
+        ret = s->compare & 0xffffffff;
+       break;
+    case 0x14: /* Compare upper  */
+        ret = s->compare >> 32;
+       break;
+    case 0x18: /* Auto increment amount */
+        ret = s->inc;
+       break;
+    default:
+        hw_error("mpcore_priv_read: Bad offset %x\n", (int)offset);
+       break;
+    }
+    return ret;
+}
+
+
 static void mpcore_timer_write(mpcore_timer_state *s, int offset,
                                uint32_t value)
 {
@@ -116,7 +216,7 @@ static void mpcore_timer_write(mpcore_timer_state *s, int 
offset,
         /* Fall through.  */
     case 4: /* Counter.  */
         if ((s->control & 1) && s->count) {
-            /* Cancel the previous timer.  */
+            /* Cancel the previous timer. */
             qemu_del_timer(s->timer);
         }
         s->count = value;
@@ -133,13 +233,49 @@ static void mpcore_timer_write(mpcore_timer_state *s, int 
offset,
             mpcore_timer_reload(s, 1);
         }
         break;
-    case 12: /* Interrupt status.  */
+    case 12: /* Interrupt status. */
         s->status &= ~value;
         mpcore_timer_update_irq(s);
         break;
     }
 }
 
+static void mpcore_global_timer_write(mpcore_global_timer_state *s, int offset,
+                                     uint32_t value) 
+{
+    offset &= 0xff;
+    switch (offset) {
+    case 0:     /* lower 32 bits. */
+    case 0x04:  /* upper 32 bits. */
+        /* TODO: implement counter modify */
+        break;
+    case 0x08:
+        /* Control */
+        s->control = value & 0xE;
+       global_timer_control = value & 0xFF01;
+       set_next_global_tick(s);
+       break;
+    case 0x0C:
+        if (value & 1)
+           s->status = 0;
+       break;
+    case 0x10:
+        /* Compare registers */
+        s->compare &= 0xffffffff00000000;
+       s->compare |= value;
+       set_next_global_tick(s);
+       break;
+    case 0x14:
+        s->compare = (s->compare & 0xffffffff ) |
+                    ((uint64_t)value << 32);
+       set_next_global_tick(s);
+       break;
+    case 0x18:
+        s->inc = value;
+       break;
+    }
+}
+
 static void mpcore_timer_init(mpcore_priv_state *mpcore,
                               mpcore_timer_state *s, int id)
 {
@@ -148,6 +284,18 @@ static void mpcore_timer_init(mpcore_priv_state *mpcore,
     s->timer = qemu_new_timer_ns(vm_clock, mpcore_timer_tick, s);
 }
 
+static void mpcore_global_timer_init(mpcore_priv_state *mpcore,
+                                    mpcore_global_timer_state *s, int id) 
+{
+    s->status = 0; /* 0 is the correct power-on/reset value. */
+    s->inc = 0;
+    s->compare = 0;
+    s->control = 0;
+    s->id = id;
+    s->mpcore = mpcore;
+    s->timer = qemu_new_timer_ns(vm_clock, mpcore_global_timer_tick, s);
+}
+
 
 /* Per-CPU private memory mapped IO.  */
 
@@ -171,7 +319,7 @@ static uint32_t mpcore_priv_read(void *opaque, 
target_phys_addr_t offset)
         default:
             goto bad_reg;
         }
-    } else if (offset < 0x600) {
+    } else if (offset < 0x200) {
         /* Interrupt controller.  */
         if (offset < 0x200) {
             id = gic_get_current_cpu();
@@ -182,6 +330,13 @@ static uint32_t mpcore_priv_read(void *opaque, 
target_phys_addr_t offset)
             }
         }
         return gic_cpu_read(&s->gic, id, offset & 0xff);
+    } else if (offset < 0x300) {
+      /* Global timer */
+      id = gic_get_current_cpu();
+      return mpcore_global_timer_read(&s->g_timers[id], offset);
+    } else if (offset < 0x600) {
+      /* Non-existent */
+      goto bad_reg;
     } else if (offset < 0xb00) {
         /* Timers.  */
         if (offset < 0x700) {
@@ -203,7 +358,7 @@ bad_reg:
 }
 
 static void mpcore_priv_write(void *opaque, target_phys_addr_t offset,
-                          uint32_t value)
+                             uint32_t value)
 {
     mpcore_priv_state *s = (mpcore_priv_state *)opaque;
     int id;
@@ -220,7 +375,7 @@ static void mpcore_priv_write(void *opaque, 
target_phys_addr_t offset,
         default:
             goto bad_reg;
         }
-    } else if (offset < 0x600) {
+    } else if (offset < 0x200) {
         /* Interrupt controller.  */
         if (offset < 0x200) {
             id = gic_get_current_cpu();
@@ -230,6 +385,13 @@ static void mpcore_priv_write(void *opaque, 
target_phys_addr_t offset,
         if (id < s->num_cpu) {
             gic_cpu_write(&s->gic, id, offset & 0xff, value);
         }
+    } else if (offset < 0x300) {
+        /* Global timer */
+        id = gic_get_current_cpu();
+        mpcore_global_timer_write(&s->g_timers[id], offset, value);
+    } else if (offset < 0x600) {
+        /* Non-existent */
+        goto bad_reg;
     } else if (offset < 0xb00) {
         /* Timers.  */
         if (offset < 0x700) {
@@ -282,5 +444,9 @@ static int mpcore_priv_init(SysBusDevice *dev)
     for (i = 0; i < s->num_cpu * 2; i++) {
         mpcore_timer_init(s, &s->timer[i], i);
     }
+    /* Assuming that num_cpu is 4 or less */
+    for (i = 0; i < s->num_cpu; i++) {
+        mpcore_global_timer_init(s, &s->g_timers[i], i);
+    }
     return 0;
 }
-- 
1.7.3.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]