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 <char...@matrox.com> --- 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