qemu-devel
[Top][All Lists]
Advanced

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

Re: [Qemu-devel] [PATCH] [PowerPC][RFC] booke timers


From: Scott Wood
Subject: Re: [Qemu-devel] [PATCH] [PowerPC][RFC] booke timers
Date: Mon, 27 Jun 2011 15:28:51 -0500

On Mon, 27 Jun 2011 18:14:06 +0200
Fabien Chouteau <address@hidden> wrote:

> While working on the emulation of the freescale p2010 (e500v2) I realized that
> there's no implementation of booke's timers features. Currently mpc8544 uses
> ppc_emb (ppc_emb_timers_init) which is close but not exactly like booke (for
> example booke uses different SPR).

ppc_emb_timers_init should be renamed something less generic, then.

> +/* Timer Control Register */
> +
> +#define TCR_WP_MASK   0x3       /* Watchdog Timer Period */
> +#define TCR_WP_SHIFT  30
> +#define TCR_WRC_MASK  0x3       /* Watchdog Timer Reset Control */
> +#define TCR_WRC_SHIFT 28

Usually such MASK defines are before shifting right:

#define TCR_WP_MASK   0xc0000000
#define TCR_WP_SHIFT  30
#define TCR_WRC_MASK  0x30000000
#define TCR_WRC_SHIFT 28

That way you can do things like

if (tcr & TCR_WRC_MASK) {
        ...
}

> +/* Return the time base value at which the FIT will raise an interrupt */
> +static uint64_t booke_get_fit_target(CPUState *env)
> +{
> +    uint32_t fp = (env->spr[SPR_BOOKE_TCR] >> TCR_FP_SHIFT) & TCR_FP_MASK;
> +
> +    /* Only for e500 */
> +    if (env->insns_flags2 & PPC2_BOOKE206) {
> +        uint32_t fpext = (env->spr[SPR_BOOKE_TCR] >> TCR_E500_FPEXT_SHIFT)
> +            & TCR_E500_FPEXT_MASK;
> +        fp |= fpext << 2;
> +    }

BOOKE206 does not mean e500.  FPEXT does not exist in Power ISA V2.06 Book
III-E.

> +
> +    return 1 << fp;
> +}

The particular bits selected by the possible values of FP are
implementation-dependent.  e500 uses fpext to make all values possible, but
on 440 the four values of fp select from 2^13, 2^17, 2^21, and 2^25.

> +/* Return the time base value at which the WDT will raise an interrupt */
> +static uint64_t booke_get_wdt_target(CPUState *env)
> +{
> +    uint32_t fp = (env->spr[SPR_BOOKE_TCR] >> TCR_WP_SHIFT) & TCR_WP_MASK;
> +
> +    /* Only for e500 */
> +    if (env->insns_flags2 & PPC2_BOOKE206) {
> +        uint32_t fpext = (env->spr[SPR_BOOKE_TCR] >> TCR_E500_WPEXT_SHIFT)
> +            & TCR_E500_WPEXT_MASK;
> +        fp |= fpext << 2;
> +    }
> +
> +    return 1 << fp;
> +}

s/fp/wp/

Avoiding the confusion is especially important on 440, since a different
interval is selected by a given value in FP versus WP.

> +static void booke_update_fixed_timer(CPUState         *env,
> +                                     uint64_t          tb_target,
> +                                     uint64_t          *next,
> +                                     struct QEMUTimer *timer)
> +{
> +    ppc_tb_t *tb_env = env->tb_env;
> +    uint64_t lapse;
> +    uint64_t tb;
> +    uint64_t now;
> +
> +    now = qemu_get_clock_ns(vm_clock);
> +    tb  = cpu_ppc_get_tb(tb_env, now, tb_env->tb_offset);
> +
> +    if (tb_target < tb) {
> +        qemu_del_timer(timer);

You're treating the target as the timebase value that has only the selected
bit and nothing else -- you want to expire the next time that bit
transitions from zero to one, regardless of the other bits.

The timer should never be outright disabled.

> +static void booke_decr_cb (void *opaque)
> +{
> +    CPUState *env;
> +    ppc_tb_t *tb_env;
> +    booke_timer_t *booke_timer;
> +
> +    env = opaque;
> +    tb_env = env->tb_env;
> +    booke_timer = tb_env->opaque;
> +    env->spr[SPR_BOOKE_TSR] |= TSR_DIS;
> +    if (env->spr[SPR_BOOKE_TCR] & TCR_DIE) {
> +        ppc_set_irq(env, booke_timer->decr_excp, 1);
> +    }
> +
> +    if (env->spr[SPR_BOOKE_TCR] & TCR_ARE) {
> +        /* Auto Reload */
> +        cpu_ppc_store_decr(env, env->spr[SPR_BOOKE_DECAR]);
> +    }
> +}

I think some changes in the decrementer code are needed to provide booke
semantics -- no raising the interrupt based on the high bit of decr, and
stop counting when reach zero.

> +static void booke_wdt_cb (void *opaque)
> +{
> +    CPUState *env;
> +    ppc_tb_t *tb_env;
> +    booke_timer_t *booke_timer;
> +
> +    env = opaque;
> +    tb_env = env->tb_env;
> +    booke_timer = tb_env->opaque;
> +
> +    /* TODO: There's lots of complicated stuff to do here */
> +    abort();
> +
> +    booke_update_fixed_timer(env,
> +                             booke_get_wdt_target(env),
> +                             &booke_timer->wdt_next,
> +                             booke_timer->wdt_timer);
> +}

Might want to avoid arming this one until that abort() is fixed...

> +
> +void store_booke_tsr (CPUState *env, target_ulong val)
> +{
> +    ppc_tb_t *tb_env = env->tb_env;
> +    booke_timer_t *booke_timer;
> +
> +    booke_timer = tb_env->opaque;
> +
> +    env->spr[SPR_BOOKE_TSR] &= ~(val & 0xFC000000);

Do we really need the "& 0xFC000000"?  Likewise in TCR.

> +
> +    if (val & TSR_DIS) {
> +        ppc_set_irq(env, booke_timer->decr_excp, 0);
> +    }
> +
> +    if (val & TSR_FIS) {
> +        ppc_set_irq(env, booke_timer->fit_excp, 0);
> +    }
> +
> +    if (val & TSR_WIS) {
> +        ppc_set_irq(env, booke_timer->wdt_excp, 0);
> +    }
> +}

It looks like ppc_hw_interrupt() is currently treating these as
edge-triggered, deasserting upon delivery.  It should be level for booke.

> +void store_booke_tcr (CPUState *env, target_ulong val)
> +{
> +    ppc_tb_t *tb_env = env->tb_env;
> +    booke_timer_t *booke_timer = tb_env->opaque;
> +
> +    tb_env = env->tb_env;
> +    env->spr[SPR_BOOKE_TCR] = val & 0xFFC00000;
> +
> +    booke_update_fixed_timer(env,
> +                             booke_get_fit_target(env),
> +                             &booke_timer->fit_next,
> +                             booke_timer->fit_timer);
> +
> +    booke_update_fixed_timer(env,
> +                             booke_get_wdt_target(env),
> +                             &booke_timer->wdt_next,
> +                             booke_timer->wdt_timer);
> +}

Check for FIS/DIS/WIS -- the corresponding enable bit might have just been
set.

> +    booke_timer->decr_excp = PPC_INTERRUPT_DECR;
> +    booke_timer->fit_excp = PPC_INTERRUPT_FIT;
> +    booke_timer->wdt_excp = PPC_INTERRUPT_WDT;

What do we gain by using this instead of PPC_INTERRUPT_foo directly?

-SCott




reply via email to

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