diff mbox

sparc64: reimplement tick timers v4

Message ID 20100127210053.18945.35057.stgit@skyserv
State New
Headers show

Commit Message

Igor V. Kovalenko Jan. 27, 2010, 9 p.m. UTC
From: Igor V. Kovalenko <igor.v.kovalenko@gmail.com>

sparc64 timer has tick counter which can be set and read,
and tick compare value used as deadline to fire timer interrupt.
The timer is not used as periodic timer, instead deadline
is set each time new timer interrupt is needed.

v3 -> v4:
- coding style

v2 -> v3:
- added missing timer debug output macro
- CPUTimer struct and typedef moved to cpu.h
- change CPU_SAVE_VERSION to 6, older save formats not supported

v1 -> v2:
- new conversion helpers cpu_to_timer_ticks and timer_to_cpu_ticks
- save offset from clock source to implement cpu_tick_set_count
- renamed struct sun4u_timer to CPUTimer
- load and save cpu timers

v0 -> v1:
- coding style

Signed-off-by: Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
---
 hw/sun4u.c             |  206 ++++++++++++++++++++++++++++++++++++++++--------
 target-sparc/cpu.h     |   28 +++++--
 target-sparc/machine.c |   14 ++-
 3 files changed, 202 insertions(+), 46 deletions(-)

Comments

Blue Swirl Jan. 27, 2010, 9:40 p.m. UTC | #1
Thanks, applied.


On Wed, Jan 27, 2010 at 9:00 PM, Igor V. Kovalenko
<igor.v.kovalenko@gmail.com> wrote:
> From: Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
>
> sparc64 timer has tick counter which can be set and read,
> and tick compare value used as deadline to fire timer interrupt.
> The timer is not used as periodic timer, instead deadline
> is set each time new timer interrupt is needed.
>
> v3 -> v4:
> - coding style
>
> v2 -> v3:
> - added missing timer debug output macro
> - CPUTimer struct and typedef moved to cpu.h
> - change CPU_SAVE_VERSION to 6, older save formats not supported
>
> v1 -> v2:
> - new conversion helpers cpu_to_timer_ticks and timer_to_cpu_ticks
> - save offset from clock source to implement cpu_tick_set_count
> - renamed struct sun4u_timer to CPUTimer
> - load and save cpu timers
>
> v0 -> v1:
> - coding style
>
> Signed-off-by: Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
> ---
>  hw/sun4u.c             |  206 ++++++++++++++++++++++++++++++++++++++++--------
>  target-sparc/cpu.h     |   28 +++++--
>  target-sparc/machine.c |   14 ++-
>  3 files changed, 202 insertions(+), 46 deletions(-)
>
> diff --git a/hw/sun4u.c b/hw/sun4u.c
> index c1530a6..1e01123 100644
> --- a/hw/sun4u.c
> +++ b/hw/sun4u.c
> @@ -40,6 +40,7 @@
>
>  //#define DEBUG_IRQ
>  //#define DEBUG_EBUS
> +//#define DEBUG_TIMER
>
>  #ifdef DEBUG_IRQ
>  #define CPUIRQ_DPRINTF(fmt, ...)                                \
> @@ -55,6 +56,13 @@
>  #define EBUS_DPRINTF(fmt, ...)
>  #endif
>
> +#ifdef DEBUG_TIMER
> +#define TIMER_DPRINTF(fmt, ...)                                  \
> +    do { printf("TIMER: " fmt , ## __VA_ARGS__); } while (0)
> +#else
> +#define TIMER_DPRINTF(fmt, ...)
> +#endif
> +
>  #define KERNEL_LOAD_ADDR     0x00404000
>  #define CMDLINE_ADDR         0x003ff000
>  #define INITRD_LOAD_ADDR     0x00300000
> @@ -282,6 +290,12 @@ void cpu_check_irqs(CPUState *env)
>     }
>  }
>
> +static void cpu_kick_irq(CPUState *env)
> +{
> +    env->halted = 0;
> +    cpu_check_irqs(env);
> +}
> +
>  static void cpu_set_irq(void *opaque, int irq, int level)
>  {
>     CPUState *env = opaque;
> @@ -303,6 +317,52 @@ typedef struct ResetData {
>     uint64_t prom_addr;
>  } ResetData;
>
> +void cpu_put_timer(QEMUFile *f, CPUTimer *s)
> +{
> +    qemu_put_be32s(f, &s->frequency);
> +    qemu_put_be32s(f, &s->disabled);
> +    qemu_put_be64s(f, &s->disabled_mask);
> +    qemu_put_sbe64s(f, &s->clock_offset);
> +
> +    qemu_put_timer(f, s->qtimer);
> +}
> +
> +void cpu_get_timer(QEMUFile *f, CPUTimer *s)
> +{
> +    qemu_get_be32s(f, &s->frequency);
> +    qemu_get_be32s(f, &s->disabled);
> +    qemu_get_be64s(f, &s->disabled_mask);
> +    qemu_get_sbe64s(f, &s->clock_offset);
> +
> +    qemu_get_timer(f, s->qtimer);
> +}
> +
> +static CPUTimer* cpu_timer_create(const char* name, CPUState *env,
> +                                  QEMUBHFunc *cb, uint32_t frequency,
> +                                  uint64_t disabled_mask)
> +{
> +    CPUTimer *timer = qemu_mallocz(sizeof (CPUTimer));
> +
> +    timer->name = name;
> +    timer->frequency = frequency;
> +    timer->disabled_mask = disabled_mask;
> +
> +    timer->disabled = 1;
> +    timer->clock_offset = qemu_get_clock(vm_clock);
> +
> +    timer->qtimer = qemu_new_timer(vm_clock, cb, env);
> +
> +    return timer;
> +}
> +
> +static void cpu_timer_reset(CPUTimer *timer)
> +{
> +    timer->disabled = 1;
> +    timer->clock_offset = qemu_get_clock(vm_clock);
> +
> +    qemu_del_timer(timer->qtimer);
> +}
> +
>  static void main_cpu_reset(void *opaque)
>  {
>     ResetData *s = (ResetData *)opaque;
> @@ -310,15 +370,11 @@ static void main_cpu_reset(void *opaque)
>     static unsigned int nr_resets;
>
>     cpu_reset(env);
> -    env->tick_cmpr = TICK_INT_DIS | 0;
> -    ptimer_set_limit(env->tick, TICK_MAX, 1);
> -    ptimer_run(env->tick, 1);
> -    env->stick_cmpr = TICK_INT_DIS | 0;
> -    ptimer_set_limit(env->stick, TICK_MAX, 1);
> -    ptimer_run(env->stick, 1);
> -    env->hstick_cmpr = TICK_INT_DIS | 0;
> -    ptimer_set_limit(env->hstick, TICK_MAX, 1);
> -    ptimer_run(env->hstick, 1);
> +
> +    cpu_timer_reset(env->tick);
> +    cpu_timer_reset(env->stick);
> +    cpu_timer_reset(env->hstick);
> +
>     env->gregs[1] = 0; // Memory start
>     env->gregs[2] = ram_size; // Memory size
>     env->gregs[3] = 0; // Machine description XXX
> @@ -335,44 +391,127 @@ static void tick_irq(void *opaque)
>  {
>     CPUState *env = opaque;
>
> -    if (!(env->tick_cmpr & TICK_INT_DIS)) {
> -        env->softint |= SOFTINT_TIMER;
> -        cpu_interrupt(env, CPU_INTERRUPT_TIMER);
> +    CPUTimer* timer = env->tick;
> +
> +    if (timer->disabled) {
> +        CPUIRQ_DPRINTF("tick_irq: softint disabled\n");
> +        return;
> +    } else {
> +        CPUIRQ_DPRINTF("tick: fire\n");
>     }
> +
> +    env->softint |= SOFTINT_TIMER;
> +    cpu_kick_irq(env);
>  }
>
>  static void stick_irq(void *opaque)
>  {
>     CPUState *env = opaque;
>
> -    if (!(env->stick_cmpr & TICK_INT_DIS)) {
> -        env->softint |= SOFTINT_STIMER;
> -        cpu_interrupt(env, CPU_INTERRUPT_TIMER);
> +    CPUTimer* timer = env->stick;
> +
> +    if (timer->disabled) {
> +        CPUIRQ_DPRINTF("stick_irq: softint disabled\n");
> +        return;
> +    } else {
> +        CPUIRQ_DPRINTF("stick: fire\n");
>     }
> +
> +    env->softint |= SOFTINT_STIMER;
> +    cpu_kick_irq(env);
>  }
>
>  static void hstick_irq(void *opaque)
>  {
>     CPUState *env = opaque;
>
> -    if (!(env->hstick_cmpr & TICK_INT_DIS)) {
> -        cpu_interrupt(env, CPU_INTERRUPT_TIMER);
> +    CPUTimer* timer = env->hstick;
> +
> +    if (timer->disabled) {
> +        CPUIRQ_DPRINTF("hstick_irq: softint disabled\n");
> +        return;
> +    } else {
> +        CPUIRQ_DPRINTF("hstick: fire\n");
>     }
> +
> +    env->softint |= SOFTINT_STIMER;
> +    cpu_kick_irq(env);
> +}
> +
> +static int64_t cpu_to_timer_ticks(int64_t cpu_ticks, uint32_t frequency)
> +{
> +    return muldiv64(cpu_ticks, get_ticks_per_sec(), frequency);
> +}
> +
> +static uint64_t timer_to_cpu_ticks(int64_t timer_ticks, uint32_t frequency)
> +{
> +    return muldiv64(timer_ticks, frequency, get_ticks_per_sec());
>  }
>
> -void cpu_tick_set_count(void *opaque, uint64_t count)
> +void cpu_tick_set_count(CPUTimer *timer, uint64_t count)
>  {
> -    ptimer_set_count(opaque, -count);
> +    uint64_t real_count = count & ~timer->disabled_mask;
> +    uint64_t disabled_bit = count & timer->disabled_mask;
> +
> +    int64_t vm_clock_offset = qemu_get_clock(vm_clock) -
> +                    cpu_to_timer_ticks(real_count, timer->frequency);
> +
> +    TIMER_DPRINTF("%s set_count count=0x%016lx (%s) p=%p\n",
> +                  timer->name, real_count,
> +                  timer->disabled?"disabled":"enabled", timer);
> +
> +    timer->disabled = disabled_bit ? 1 : 0;
> +    timer->clock_offset = vm_clock_offset;
>  }
>
> -uint64_t cpu_tick_get_count(void *opaque)
> +uint64_t cpu_tick_get_count(CPUTimer *timer)
>  {
> -    return -ptimer_get_count(opaque);
> +    uint64_t real_count = timer_to_cpu_ticks(
> +                    qemu_get_clock(vm_clock) - timer->clock_offset,
> +                    timer->frequency);
> +
> +    TIMER_DPRINTF("%s get_count count=0x%016lx (%s) p=%p\n",
> +           timer->name, real_count,
> +           timer->disabled?"disabled":"enabled", timer);
> +
> +    if (timer->disabled)
> +        real_count |= timer->disabled_mask;
> +
> +    return real_count;
>  }
>
> -void cpu_tick_set_limit(void *opaque, uint64_t limit)
> +void cpu_tick_set_limit(CPUTimer *timer, uint64_t limit)
>  {
> -    ptimer_set_limit(opaque, -limit, 0);
> +    int64_t now = qemu_get_clock(vm_clock);
> +
> +    uint64_t real_limit = limit & ~timer->disabled_mask;
> +    timer->disabled = (limit & timer->disabled_mask) ? 1 : 0;
> +
> +    int64_t expires = cpu_to_timer_ticks(real_limit, timer->frequency) +
> +                    timer->clock_offset;
> +
> +    if (expires < now) {
> +        expires = now + 1;
> +    }
> +
> +    TIMER_DPRINTF("%s set_limit limit=0x%016lx (%s) p=%p "
> +                  "called with limit=0x%016lx at 0x%016lx (delta=0x%016lx)\n",
> +                  timer->name, real_limit,
> +                  timer->disabled?"disabled":"enabled",
> +                  timer, limit,
> +                  timer_to_cpu_ticks(now - timer->clock_offset,
> +                                     timer->frequency),
> +                  timer_to_cpu_ticks(expires - now, timer->frequency));
> +
> +    if (!real_limit) {
> +        TIMER_DPRINTF("%s set_limit limit=ZERO - not starting timer\n",
> +                timer->name);
> +        qemu_del_timer(timer->qtimer);
> +    } else if (timer->disabled) {
> +        qemu_del_timer(timer->qtimer);
> +    } else {
> +        qemu_mod_timer(timer->qtimer, expires);
> +    }
>  }
>
>  static void ebus_mmio_mapfunc(PCIDevice *pci_dev, int region_num,
> @@ -559,9 +698,12 @@ device_init(ram_register_devices);
>  static CPUState *cpu_devinit(const char *cpu_model, const struct hwdef *hwdef)
>  {
>     CPUState *env;
> -    QEMUBH *bh;
>     ResetData *reset_info;
>
> +    uint32_t   tick_frequency = 100*1000000;
> +    uint32_t  stick_frequency = 100*1000000;
> +    uint32_t hstick_frequency = 100*1000000;
> +
>     if (!cpu_model)
>         cpu_model = hwdef->default_cpu_model;
>     env = cpu_init(cpu_model);
> @@ -569,17 +711,15 @@ static CPUState *cpu_devinit(const char *cpu_model, const struct hwdef *hwdef)
>         fprintf(stderr, "Unable to find Sparc CPU definition\n");
>         exit(1);
>     }
> -    bh = qemu_bh_new(tick_irq, env);
> -    env->tick = ptimer_init(bh);
> -    ptimer_set_period(env->tick, 1ULL);
>
> -    bh = qemu_bh_new(stick_irq, env);
> -    env->stick = ptimer_init(bh);
> -    ptimer_set_period(env->stick, 1ULL);
> +    env->tick = cpu_timer_create("tick", env, tick_irq,
> +                                  tick_frequency, TICK_NPT_MASK);
> +
> +    env->stick = cpu_timer_create("stick", env, stick_irq,
> +                                   stick_frequency, TICK_INT_DIS);
>
> -    bh = qemu_bh_new(hstick_irq, env);
> -    env->hstick = ptimer_init(bh);
> -    ptimer_set_period(env->hstick, 1ULL);
> +    env->hstick = cpu_timer_create("hstick", env, hstick_irq,
> +                                    hstick_frequency, TICK_INT_DIS);
>
>     reset_info = qemu_mallocz(sizeof(ResetData));
>     reset_info->env = env;
> diff --git a/target-sparc/cpu.h b/target-sparc/cpu.h
> index 842a2f4..5980deb 100644
> --- a/target-sparc/cpu.h
> +++ b/target-sparc/cpu.h
> @@ -292,6 +292,22 @@ typedef struct SparcTLBEntry {
>     uint64_t tte;
>  } SparcTLBEntry;
>
> +struct CPUTimer
> +{
> +    const char *name;
> +    uint32_t    frequency;
> +    uint32_t    disabled;
> +    uint64_t    disabled_mask;
> +    int64_t     clock_offset;
> +    struct QEMUTimer  *qtimer;
> +};
> +
> +typedef struct CPUTimer CPUTimer;
> +
> +struct QEMUFile;
> +void cpu_put_timer(struct QEMUFile *f, CPUTimer *s);
> +void cpu_get_timer(struct QEMUFile *f, CPUTimer *s);
> +
>  typedef struct CPUSPARCState {
>     target_ulong gregs[8]; /* general registers */
>     target_ulong *regwptr; /* pointer to current register window */
> @@ -393,14 +409,14 @@ typedef struct CPUSPARCState {
>     uint64_t mgregs[8]; /* mmu general registers */
>     uint64_t fprs;
>     uint64_t tick_cmpr, stick_cmpr;
> -    void *tick, *stick;
> +    CPUTimer *tick, *stick;
>  #define TICK_NPT_MASK        0x8000000000000000ULL
>  #define TICK_INT_DIS         0x8000000000000000ULL
>     uint64_t gsr;
>     uint32_t gl; // UA2005
>     /* UA 2005 hyperprivileged registers */
>     uint64_t hpstate, htstate[MAXTL_MAX], hintp, htba, hver, hstick_cmpr, ssr;
> -    void *hstick; // UA 2005
> +    CPUTimer *hstick; // UA 2005
>     uint32_t softint;
>  #define SOFTINT_TIMER   1
>  #define SOFTINT_STIMER  (1 << 16)
> @@ -536,7 +552,7 @@ int cpu_sparc_signal_handler(int host_signum, void *pinfo, void *puc);
>  #define cpu_signal_handler cpu_sparc_signal_handler
>  #define cpu_list sparc_cpu_list
>
> -#define CPU_SAVE_VERSION 5
> +#define CPU_SAVE_VERSION 6
>
>  /* MMU modes definitions */
>  #define MMU_MODE0_SUFFIX _user
> @@ -615,9 +631,9 @@ static inline void cpu_clone_regs(CPUState *env, target_ulong newsp)
>
>  #ifdef TARGET_SPARC64
>  /* sun4u.c */
> -void cpu_tick_set_count(void *opaque, uint64_t count);
> -uint64_t cpu_tick_get_count(void *opaque);
> -void cpu_tick_set_limit(void *opaque, uint64_t limit);
> +void cpu_tick_set_count(CPUTimer *timer, uint64_t count);
> +uint64_t cpu_tick_get_count(CPUTimer *timer);
> +void cpu_tick_set_limit(CPUTimer *timer, uint64_t limit);
>  trap_state* cpu_tsptr(CPUState* env);
>  #endif
>
> diff --git a/target-sparc/machine.c b/target-sparc/machine.c
> index c7c03b6..4b87ec6 100644
> --- a/target-sparc/machine.c
> +++ b/target-sparc/machine.c
> @@ -84,8 +84,8 @@ void cpu_save(QEMUFile *f, void *opaque)
>     qemu_put_be64s(f, &env->fprs);
>     qemu_put_be64s(f, &env->tick_cmpr);
>     qemu_put_be64s(f, &env->stick_cmpr);
> -    qemu_put_ptimer(f, env->tick);
> -    qemu_put_ptimer(f, env->stick);
> +    cpu_put_timer(f, env->tick);
> +    cpu_put_timer(f, env->stick);
>     qemu_put_be64s(f, &env->gsr);
>     qemu_put_be32s(f, &env->gl);
>     qemu_put_be64s(f, &env->hpstate);
> @@ -96,7 +96,7 @@ void cpu_save(QEMUFile *f, void *opaque)
>     qemu_put_be64s(f, &env->hver);
>     qemu_put_be64s(f, &env->hstick_cmpr);
>     qemu_put_be64s(f, &env->ssr);
> -    qemu_put_ptimer(f, env->hstick);
> +    cpu_put_timer(f, env->hstick);
>  #endif
>  }
>
> @@ -106,7 +106,7 @@ int cpu_load(QEMUFile *f, void *opaque, int version_id)
>     int i;
>     uint32_t tmp;
>
> -    if (version_id != 5)
> +    if (version_id < 6)
>         return -EINVAL;
>     for(i = 0; i < 8; i++)
>         qemu_get_betls(f, &env->gregs[i]);
> @@ -180,8 +180,8 @@ int cpu_load(QEMUFile *f, void *opaque, int version_id)
>     qemu_get_be64s(f, &env->fprs);
>     qemu_get_be64s(f, &env->tick_cmpr);
>     qemu_get_be64s(f, &env->stick_cmpr);
> -    qemu_get_ptimer(f, env->tick);
> -    qemu_get_ptimer(f, env->stick);
> +    cpu_get_timer(f, env->tick);
> +    cpu_get_timer(f, env->stick);
>     qemu_get_be64s(f, &env->gsr);
>     qemu_get_be32s(f, &env->gl);
>     qemu_get_be64s(f, &env->hpstate);
> @@ -192,7 +192,7 @@ int cpu_load(QEMUFile *f, void *opaque, int version_id)
>     qemu_get_be64s(f, &env->hver);
>     qemu_get_be64s(f, &env->hstick_cmpr);
>     qemu_get_be64s(f, &env->ssr);
> -    qemu_get_ptimer(f, env->hstick);
> +    cpu_get_timer(f, env->hstick);
>  #endif
>     tlb_flush(env, 1);
>     return 0;
>
>
>
>
Blue Swirl Jan. 27, 2010, 9:51 p.m. UTC | #2
On Wed, Jan 27, 2010 at 9:00 PM, Igor V. Kovalenko
<igor.v.kovalenko@gmail.com> wrote:
> From: Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
>
> sparc64 timer has tick counter which can be set and read,
> and tick compare value used as deadline to fire timer interrupt.
> The timer is not used as periodic timer, instead deadline
> is set each time new timer interrupt is needed.

Now also IDE probe succeeds, almost:

cmd64x 0000:00:05.0: IDE controller (0x1095:0x0646 rev 0x07)
cmd64x 0000:00:05.0: 100% native mode on irq 1
    ide0: BM-DMA at 0x1fe02000700-0x1fe02000707
    ide1: BM-DMA at 0x1fe02000708-0x1fe0200070f
hdc: EQUMD DVR-MO, ATAPI cdrom or floppy?, assuming FLOPPY drive
ide0 at 0x1fe02000500-0x1fe02000507,0x1fe02000582 on irq 1 (serialized)
ide1 at 0x1fe02000600-0x1fe02000607,0x1fe02000682 on irq 1 (serialized)
ide-gd driver 1.18
diff mbox

Patch

diff --git a/hw/sun4u.c b/hw/sun4u.c
index c1530a6..1e01123 100644
--- a/hw/sun4u.c
+++ b/hw/sun4u.c
@@ -40,6 +40,7 @@ 
 
 //#define DEBUG_IRQ
 //#define DEBUG_EBUS
+//#define DEBUG_TIMER
 
 #ifdef DEBUG_IRQ
 #define CPUIRQ_DPRINTF(fmt, ...)                                \
@@ -55,6 +56,13 @@ 
 #define EBUS_DPRINTF(fmt, ...)
 #endif
 
+#ifdef DEBUG_TIMER
+#define TIMER_DPRINTF(fmt, ...)                                  \
+    do { printf("TIMER: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define TIMER_DPRINTF(fmt, ...)
+#endif
+
 #define KERNEL_LOAD_ADDR     0x00404000
 #define CMDLINE_ADDR         0x003ff000
 #define INITRD_LOAD_ADDR     0x00300000
@@ -282,6 +290,12 @@  void cpu_check_irqs(CPUState *env)
     }
 }
 
+static void cpu_kick_irq(CPUState *env)
+{
+    env->halted = 0;
+    cpu_check_irqs(env);
+}
+
 static void cpu_set_irq(void *opaque, int irq, int level)
 {
     CPUState *env = opaque;
@@ -303,6 +317,52 @@  typedef struct ResetData {
     uint64_t prom_addr;
 } ResetData;
 
+void cpu_put_timer(QEMUFile *f, CPUTimer *s)
+{
+    qemu_put_be32s(f, &s->frequency);
+    qemu_put_be32s(f, &s->disabled);
+    qemu_put_be64s(f, &s->disabled_mask);
+    qemu_put_sbe64s(f, &s->clock_offset);
+
+    qemu_put_timer(f, s->qtimer);
+}
+
+void cpu_get_timer(QEMUFile *f, CPUTimer *s)
+{
+    qemu_get_be32s(f, &s->frequency);
+    qemu_get_be32s(f, &s->disabled);
+    qemu_get_be64s(f, &s->disabled_mask);
+    qemu_get_sbe64s(f, &s->clock_offset);
+
+    qemu_get_timer(f, s->qtimer);
+}
+
+static CPUTimer* cpu_timer_create(const char* name, CPUState *env,
+                                  QEMUBHFunc *cb, uint32_t frequency,
+                                  uint64_t disabled_mask)
+{
+    CPUTimer *timer = qemu_mallocz(sizeof (CPUTimer));
+
+    timer->name = name;
+    timer->frequency = frequency;
+    timer->disabled_mask = disabled_mask;
+
+    timer->disabled = 1;
+    timer->clock_offset = qemu_get_clock(vm_clock);
+
+    timer->qtimer = qemu_new_timer(vm_clock, cb, env);
+
+    return timer;
+}
+
+static void cpu_timer_reset(CPUTimer *timer)
+{
+    timer->disabled = 1;
+    timer->clock_offset = qemu_get_clock(vm_clock);
+
+    qemu_del_timer(timer->qtimer);
+}
+
 static void main_cpu_reset(void *opaque)
 {
     ResetData *s = (ResetData *)opaque;
@@ -310,15 +370,11 @@  static void main_cpu_reset(void *opaque)
     static unsigned int nr_resets;
 
     cpu_reset(env);
-    env->tick_cmpr = TICK_INT_DIS | 0;
-    ptimer_set_limit(env->tick, TICK_MAX, 1);
-    ptimer_run(env->tick, 1);
-    env->stick_cmpr = TICK_INT_DIS | 0;
-    ptimer_set_limit(env->stick, TICK_MAX, 1);
-    ptimer_run(env->stick, 1);
-    env->hstick_cmpr = TICK_INT_DIS | 0;
-    ptimer_set_limit(env->hstick, TICK_MAX, 1);
-    ptimer_run(env->hstick, 1);
+
+    cpu_timer_reset(env->tick);
+    cpu_timer_reset(env->stick);
+    cpu_timer_reset(env->hstick);
+
     env->gregs[1] = 0; // Memory start
     env->gregs[2] = ram_size; // Memory size
     env->gregs[3] = 0; // Machine description XXX
@@ -335,44 +391,127 @@  static void tick_irq(void *opaque)
 {
     CPUState *env = opaque;
 
-    if (!(env->tick_cmpr & TICK_INT_DIS)) {
-        env->softint |= SOFTINT_TIMER;
-        cpu_interrupt(env, CPU_INTERRUPT_TIMER);
+    CPUTimer* timer = env->tick;
+
+    if (timer->disabled) {
+        CPUIRQ_DPRINTF("tick_irq: softint disabled\n");
+        return;
+    } else {
+        CPUIRQ_DPRINTF("tick: fire\n");
     }
+
+    env->softint |= SOFTINT_TIMER;
+    cpu_kick_irq(env);
 }
 
 static void stick_irq(void *opaque)
 {
     CPUState *env = opaque;
 
-    if (!(env->stick_cmpr & TICK_INT_DIS)) {
-        env->softint |= SOFTINT_STIMER;
-        cpu_interrupt(env, CPU_INTERRUPT_TIMER);
+    CPUTimer* timer = env->stick;
+
+    if (timer->disabled) {
+        CPUIRQ_DPRINTF("stick_irq: softint disabled\n");
+        return;
+    } else {
+        CPUIRQ_DPRINTF("stick: fire\n");
     }
+
+    env->softint |= SOFTINT_STIMER;
+    cpu_kick_irq(env);
 }
 
 static void hstick_irq(void *opaque)
 {
     CPUState *env = opaque;
 
-    if (!(env->hstick_cmpr & TICK_INT_DIS)) {
-        cpu_interrupt(env, CPU_INTERRUPT_TIMER);
+    CPUTimer* timer = env->hstick;
+
+    if (timer->disabled) {
+        CPUIRQ_DPRINTF("hstick_irq: softint disabled\n");
+        return;
+    } else {
+        CPUIRQ_DPRINTF("hstick: fire\n");
     }
+
+    env->softint |= SOFTINT_STIMER;
+    cpu_kick_irq(env);
+}
+
+static int64_t cpu_to_timer_ticks(int64_t cpu_ticks, uint32_t frequency)
+{
+    return muldiv64(cpu_ticks, get_ticks_per_sec(), frequency);
+}
+
+static uint64_t timer_to_cpu_ticks(int64_t timer_ticks, uint32_t frequency)
+{
+    return muldiv64(timer_ticks, frequency, get_ticks_per_sec());
 }
 
-void cpu_tick_set_count(void *opaque, uint64_t count)
+void cpu_tick_set_count(CPUTimer *timer, uint64_t count)
 {
-    ptimer_set_count(opaque, -count);
+    uint64_t real_count = count & ~timer->disabled_mask;
+    uint64_t disabled_bit = count & timer->disabled_mask;
+
+    int64_t vm_clock_offset = qemu_get_clock(vm_clock) -
+                    cpu_to_timer_ticks(real_count, timer->frequency);
+
+    TIMER_DPRINTF("%s set_count count=0x%016lx (%s) p=%p\n",
+                  timer->name, real_count,
+                  timer->disabled?"disabled":"enabled", timer);
+
+    timer->disabled = disabled_bit ? 1 : 0;
+    timer->clock_offset = vm_clock_offset;
 }
 
-uint64_t cpu_tick_get_count(void *opaque)
+uint64_t cpu_tick_get_count(CPUTimer *timer)
 {
-    return -ptimer_get_count(opaque);
+    uint64_t real_count = timer_to_cpu_ticks(
+                    qemu_get_clock(vm_clock) - timer->clock_offset,
+                    timer->frequency);
+
+    TIMER_DPRINTF("%s get_count count=0x%016lx (%s) p=%p\n",
+           timer->name, real_count,
+           timer->disabled?"disabled":"enabled", timer);
+
+    if (timer->disabled)
+        real_count |= timer->disabled_mask;
+
+    return real_count;
 }
 
-void cpu_tick_set_limit(void *opaque, uint64_t limit)
+void cpu_tick_set_limit(CPUTimer *timer, uint64_t limit)
 {
-    ptimer_set_limit(opaque, -limit, 0);
+    int64_t now = qemu_get_clock(vm_clock);
+
+    uint64_t real_limit = limit & ~timer->disabled_mask;
+    timer->disabled = (limit & timer->disabled_mask) ? 1 : 0;
+
+    int64_t expires = cpu_to_timer_ticks(real_limit, timer->frequency) +
+                    timer->clock_offset;
+
+    if (expires < now) {
+        expires = now + 1;
+    }
+
+    TIMER_DPRINTF("%s set_limit limit=0x%016lx (%s) p=%p "
+                  "called with limit=0x%016lx at 0x%016lx (delta=0x%016lx)\n",
+                  timer->name, real_limit,
+                  timer->disabled?"disabled":"enabled",
+                  timer, limit,
+                  timer_to_cpu_ticks(now - timer->clock_offset,
+                                     timer->frequency),
+                  timer_to_cpu_ticks(expires - now, timer->frequency));
+
+    if (!real_limit) {
+        TIMER_DPRINTF("%s set_limit limit=ZERO - not starting timer\n",
+                timer->name);
+        qemu_del_timer(timer->qtimer);
+    } else if (timer->disabled) {
+        qemu_del_timer(timer->qtimer);
+    } else {
+        qemu_mod_timer(timer->qtimer, expires);
+    }
 }
 
 static void ebus_mmio_mapfunc(PCIDevice *pci_dev, int region_num,
@@ -559,9 +698,12 @@  device_init(ram_register_devices);
 static CPUState *cpu_devinit(const char *cpu_model, const struct hwdef *hwdef)
 {
     CPUState *env;
-    QEMUBH *bh;
     ResetData *reset_info;
 
+    uint32_t   tick_frequency = 100*1000000;
+    uint32_t  stick_frequency = 100*1000000;
+    uint32_t hstick_frequency = 100*1000000;
+
     if (!cpu_model)
         cpu_model = hwdef->default_cpu_model;
     env = cpu_init(cpu_model);
@@ -569,17 +711,15 @@  static CPUState *cpu_devinit(const char *cpu_model, const struct hwdef *hwdef)
         fprintf(stderr, "Unable to find Sparc CPU definition\n");
         exit(1);
     }
-    bh = qemu_bh_new(tick_irq, env);
-    env->tick = ptimer_init(bh);
-    ptimer_set_period(env->tick, 1ULL);
 
-    bh = qemu_bh_new(stick_irq, env);
-    env->stick = ptimer_init(bh);
-    ptimer_set_period(env->stick, 1ULL);
+    env->tick = cpu_timer_create("tick", env, tick_irq,
+                                  tick_frequency, TICK_NPT_MASK);
+
+    env->stick = cpu_timer_create("stick", env, stick_irq,
+                                   stick_frequency, TICK_INT_DIS);
 
-    bh = qemu_bh_new(hstick_irq, env);
-    env->hstick = ptimer_init(bh);
-    ptimer_set_period(env->hstick, 1ULL);
+    env->hstick = cpu_timer_create("hstick", env, hstick_irq,
+                                    hstick_frequency, TICK_INT_DIS);
 
     reset_info = qemu_mallocz(sizeof(ResetData));
     reset_info->env = env;
diff --git a/target-sparc/cpu.h b/target-sparc/cpu.h
index 842a2f4..5980deb 100644
--- a/target-sparc/cpu.h
+++ b/target-sparc/cpu.h
@@ -292,6 +292,22 @@  typedef struct SparcTLBEntry {
     uint64_t tte;
 } SparcTLBEntry;
 
+struct CPUTimer
+{
+    const char *name;
+    uint32_t    frequency;
+    uint32_t    disabled;
+    uint64_t    disabled_mask;
+    int64_t     clock_offset;
+    struct QEMUTimer  *qtimer;
+};
+
+typedef struct CPUTimer CPUTimer;
+
+struct QEMUFile;
+void cpu_put_timer(struct QEMUFile *f, CPUTimer *s);
+void cpu_get_timer(struct QEMUFile *f, CPUTimer *s);
+
 typedef struct CPUSPARCState {
     target_ulong gregs[8]; /* general registers */
     target_ulong *regwptr; /* pointer to current register window */
@@ -393,14 +409,14 @@  typedef struct CPUSPARCState {
     uint64_t mgregs[8]; /* mmu general registers */
     uint64_t fprs;
     uint64_t tick_cmpr, stick_cmpr;
-    void *tick, *stick;
+    CPUTimer *tick, *stick;
 #define TICK_NPT_MASK        0x8000000000000000ULL
 #define TICK_INT_DIS         0x8000000000000000ULL
     uint64_t gsr;
     uint32_t gl; // UA2005
     /* UA 2005 hyperprivileged registers */
     uint64_t hpstate, htstate[MAXTL_MAX], hintp, htba, hver, hstick_cmpr, ssr;
-    void *hstick; // UA 2005
+    CPUTimer *hstick; // UA 2005
     uint32_t softint;
 #define SOFTINT_TIMER   1
 #define SOFTINT_STIMER  (1 << 16)
@@ -536,7 +552,7 @@  int cpu_sparc_signal_handler(int host_signum, void *pinfo, void *puc);
 #define cpu_signal_handler cpu_sparc_signal_handler
 #define cpu_list sparc_cpu_list
 
-#define CPU_SAVE_VERSION 5
+#define CPU_SAVE_VERSION 6
 
 /* MMU modes definitions */
 #define MMU_MODE0_SUFFIX _user
@@ -615,9 +631,9 @@  static inline void cpu_clone_regs(CPUState *env, target_ulong newsp)
 
 #ifdef TARGET_SPARC64
 /* sun4u.c */
-void cpu_tick_set_count(void *opaque, uint64_t count);
-uint64_t cpu_tick_get_count(void *opaque);
-void cpu_tick_set_limit(void *opaque, uint64_t limit);
+void cpu_tick_set_count(CPUTimer *timer, uint64_t count);
+uint64_t cpu_tick_get_count(CPUTimer *timer);
+void cpu_tick_set_limit(CPUTimer *timer, uint64_t limit);
 trap_state* cpu_tsptr(CPUState* env);
 #endif
 
diff --git a/target-sparc/machine.c b/target-sparc/machine.c
index c7c03b6..4b87ec6 100644
--- a/target-sparc/machine.c
+++ b/target-sparc/machine.c
@@ -84,8 +84,8 @@  void cpu_save(QEMUFile *f, void *opaque)
     qemu_put_be64s(f, &env->fprs);
     qemu_put_be64s(f, &env->tick_cmpr);
     qemu_put_be64s(f, &env->stick_cmpr);
-    qemu_put_ptimer(f, env->tick);
-    qemu_put_ptimer(f, env->stick);
+    cpu_put_timer(f, env->tick);
+    cpu_put_timer(f, env->stick);
     qemu_put_be64s(f, &env->gsr);
     qemu_put_be32s(f, &env->gl);
     qemu_put_be64s(f, &env->hpstate);
@@ -96,7 +96,7 @@  void cpu_save(QEMUFile *f, void *opaque)
     qemu_put_be64s(f, &env->hver);
     qemu_put_be64s(f, &env->hstick_cmpr);
     qemu_put_be64s(f, &env->ssr);
-    qemu_put_ptimer(f, env->hstick);
+    cpu_put_timer(f, env->hstick);
 #endif
 }
 
@@ -106,7 +106,7 @@  int cpu_load(QEMUFile *f, void *opaque, int version_id)
     int i;
     uint32_t tmp;
 
-    if (version_id != 5)
+    if (version_id < 6)
         return -EINVAL;
     for(i = 0; i < 8; i++)
         qemu_get_betls(f, &env->gregs[i]);
@@ -180,8 +180,8 @@  int cpu_load(QEMUFile *f, void *opaque, int version_id)
     qemu_get_be64s(f, &env->fprs);
     qemu_get_be64s(f, &env->tick_cmpr);
     qemu_get_be64s(f, &env->stick_cmpr);
-    qemu_get_ptimer(f, env->tick);
-    qemu_get_ptimer(f, env->stick);
+    cpu_get_timer(f, env->tick);
+    cpu_get_timer(f, env->stick);
     qemu_get_be64s(f, &env->gsr);
     qemu_get_be32s(f, &env->gl);
     qemu_get_be64s(f, &env->hpstate);
@@ -192,7 +192,7 @@  int cpu_load(QEMUFile *f, void *opaque, int version_id)
     qemu_get_be64s(f, &env->hver);
     qemu_get_be64s(f, &env->hstick_cmpr);
     qemu_get_be64s(f, &env->ssr);
-    qemu_get_ptimer(f, env->hstick);
+    cpu_get_timer(f, env->hstick);
 #endif
     tlb_flush(env, 1);
     return 0;