qemu-devel
[Top][All Lists]
Advanced

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

Re: [Qemu-devel] [RESEND][PATCH] booke timers


From: Fabien Chouteau
Subject: Re: [Qemu-devel] [RESEND][PATCH] booke timers
Date: Fri, 09 Sep 2011 15:27:28 +0200
User-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.20) Gecko/20110805 Lightning/1.0b2 Mnenhy/0.8.3 Thunderbird/3.1.12

On 09/09/2011 12:55, Alexander Graf wrote:
> 
> On 09.09.2011, at 12:36, Fabien Chouteau wrote:
> 
>> On 07/09/2011 21:59, Alexander Graf wrote:
>>>
>>> On 07.09.2011, at 16:41, Fabien Chouteau wrote:
>>>
>>>> On 06/09/2011 21:33, Alexander Graf wrote:
>>>>>
>>>>>
>>>>> Am 01.09.2011 um 10:20 schrieb Fabien Chouteau <address@hidden 
>>>>> <mailto:address@hidden>>:
>>>>>
>>>>>> 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).
>>>>>>
>>>>>> Signed-off-by: Fabien Chouteau <address@hidden <mailto:address@hidden>>
>>>>>> ---
>>>>>> Makefile.target             |    2 +-
>>>>>> hw/ppc.c                    |  133 ++++++++--------------
>>>>>> hw/ppc.h                    |   25 ++++-
>>>>>> hw/ppc4xx_devs.c            |    2 +-
>>>>>> hw/ppc_booke.c              |  263 
>>>>>> +++++++++++++++++++++++++++++++++++++++++++
>>>>>> hw/ppce500_mpc8544ds.c      |    4 +-
>>>>>> hw/virtex_ml507.c           |   11 +--
>>>>>> target-ppc/cpu.h            |   29 +++++
>>>>>> target-ppc/translate_init.c |   43 +++++++-
>>>>>> 9 files changed, 412 insertions(+), 100 deletions(-)
>>>>>> create mode 100644 hw/ppc_booke.c
>>>>>>
>>>>>> diff --git a/Makefile.target b/Makefile.target
>>>>>> index 07af4d4..c8704cd 100644
>>>>>> --- a/Makefile.target
>>>>>> +++ b/Makefile.target
>>>>>> @@ -234,7 +234,7 @@ obj-i386-$(CONFIG_KVM) += kvmclock.o
>>>>>> obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
>>>>>>
>>>>>> # shared objects
>>>>>> -obj-ppc-y = ppc.o
>>>>>> +obj-ppc-y = ppc.o ppc_booke.o
>>>>>> obj-ppc-y += vga.o
>>>>>> # PREP target
>>>>>> obj-ppc-y += i8259.o mc146818rtc.o
>>>>>> diff --git a/hw/ppc.c b/hw/ppc.c
>>>>>> index 8870748..bcb1e91 100644
>>>>>> --- a/hw/ppc.c
>>>>>> +++ b/hw/ppc.c
>>>>>> @@ -50,7 +50,7 @@
>>>>>> static void cpu_ppc_tb_stop (CPUState *env);
>>>>>> static void cpu_ppc_tb_start (CPUState *env);
>>>>>>
>>>>>> -static void ppc_set_irq (CPUState *env, int n_IRQ, int level)
>>>>>> +void ppc_set_irq(CPUState *env, int n_IRQ, int level)
>>>>>> {
>>>>>>   unsigned int old_pending = env->pending_interrupts;
>>>>>>
>>>>>> @@ -423,25 +423,8 @@ void ppce500_irq_init (CPUState *env)
>>>>>> }
>>>>>> /*****************************************************************************/
>>>>>> /* PowerPC time base and decrementer emulation */
>>>>>> -struct ppc_tb_t {
>>>>>> -    /* Time base management */
>>>>>> -    int64_t  tb_offset;    /* Compensation                    */
>>>>>> -    int64_t  atb_offset;   /* Compensation                    */
>>>>>> -    uint32_t tb_freq;      /* TB frequency                    */
>>>>>> -    /* Decrementer management */
>>>>>> -    uint64_t decr_next;    /* Tick for next decr interrupt    */
>>>>>> -    uint32_t decr_freq;    /* decrementer frequency           */
>>>>>> -    struct QEMUTimer *decr_timer;
>>>>>> -    /* Hypervisor decrementer management */
>>>>>> -    uint64_t hdecr_next;    /* Tick for next hdecr interrupt  */
>>>>>> -    struct QEMUTimer *hdecr_timer;
>>>>>> -    uint64_t purr_load;
>>>>>> -    uint64_t purr_start;
>>>>>> -    void *opaque;
>>>>>> -};
>>>>>>
>>>>>> -static inline uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk,
>>>>>> -                                      int64_t tb_offset)
>>>>>> +uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk, int64_t 
>>>>>> tb_offset)
>>>>>> {
>>>>>>   /* TB time in tb periods */
>>>>>>   return muldiv64(vmclk, tb_env->tb_freq, get_ticks_per_sec()) + 
>>>>>> tb_offset;
>>>>>> @@ -611,10 +594,13 @@ static inline uint32_t _cpu_ppc_load_decr(CPUState 
>>>>>> *env, uint64_t next)
>>>>>>   int64_t diff;
>>>>>>
>>>>>>   diff = next - qemu_get_clock_ns(vm_clock);
>>>>>> -    if (diff >= 0)
>>>>>> +    if (diff >= 0) {
>>>>>>       decr = muldiv64(diff, tb_env->decr_freq, get_ticks_per_sec());
>>>>>> -    else
>>>>>> +    } else if (env->insns_flags & PPC_BOOKE) {
>>>>>> +        decr = 0;
>>>>>
>>>>> I don't think we should expose instruction interpreter details into the
>>>>> timing code. It'd be better to have a separate flag that gets set on the 
>>>>> booke
>>>>> timer init function which would be stored in tb_env. Keeps things better
>>>>> separated :)
>>>>
>>>> Good idea, but how do we set the flags? ppc_booke_timers_init doesn't know
>>>> which processor is emulated.
>>>
>>> We could have two different init functions. One for normal booke and one for
>>> e500. Or we pass in timer flags to the init function.
>>
>> How can we handle the -cpu option? For example if I want to test a ppc604 on 
>> my
>> p2010 board? I'm not sure if it really makes sense...
> 
> I guess at the end of the day we need to have the CPU initialize its timers. 
> They are tightly coupled with the CPU anyways. For now, we can leave the 
> instantiation as is though.
> 
>>
>>
>>>>
>>>>
>>>>>
>>>>>> +    }  else {
>>>>>>       decr = -muldiv64(-diff, tb_env->decr_freq, get_ticks_per_sec());
>>>>>> +    }
>>>>>>   LOG_TB("%s: %08" PRIx32 "\n", __func__, decr);
>>>>>>
>>>>>>   return decr;
>>>>>> @@ -678,18 +664,23 @@ static void __cpu_ppc_store_decr (CPUState *env, 
>>>>>> uint64_t *nextp,
>>>>>>               decr, value);
>>>>>>   now = qemu_get_clock_ns(vm_clock);
>>>>>>   next = now + muldiv64(value, get_ticks_per_sec(), tb_env->decr_freq);
>>>>>> -    if (is_excp)
>>>>>> +    if (is_excp) {
>>>>>>       next += *nextp - now;
>>>>>> -    if (next == now)
>>>>>> +    }
>>>>>> +    if (next == now) {
>>>>>>       next++;
>>>>>> +    }
>>>>>>   *nextp = next;
>>>>>>   /* Adjust timer */
>>>>>>   qemu_mod_timer(timer, next);
>>>>>>   /* If we set a negative value and the decrementer was positive,
>>>>>> -     * raise an exception.
>>>>>> +     * raise an exception (not for booke).
>>>>>>    */
>>>>>> -    if ((value & 0x80000000) && !(decr & 0x80000000))
>>>>>> +    if (!(env->insns_flags & PPC_BOOKE)
>>>>>> +        && (value & 0x80000000)
>>>>>> +        && !(decr & 0x80000000)) {
>>>>>>       (*raise_excp)(env);
>>>>>
>>>>> Please make this a separate flag too. IIRC this is not unified behavior 
>>>>> on all ppc CPUs, not even all server type ones.
>>>>>
>>>>
>>>> This come from a comment by Scott (CC'd), I don't know much about it. Can 
>>>> you
>>>> help me to find a good name for this feature?
>>>
>>> PPC_DECR_TRIGGER_ON_NEGATIVE? :)
>>>
>>
>> After a quick check, PPC_DECR_UNDERFLOW_TRIGGERED seems more appropriate.
> 
> Good :).
> 
> [...]
> 
>>>>>> +/* Return the location of the bit of time base at which the FIT will 
>>>>>> raise an
>>>>>> +   interrupt */
>>>>>> +static uint8_t booke_get_fit_target(CPUState *env)
>>>>>> +{
>>>>>> +    uint8_t fp = (env->spr[SPR_BOOKE_TCR] & TCR_FP_MASK) >> 
>>>>>> TCR_FP_SHIFT;
>>>>>> +
>>>>>> +    /* Only for e500 */
>>>>>> +    if (env->insns_flags2 & PPC2_E500) {
>>>>>
>>>>> Same here again :). Do you have test cases for fit? IIRC Linux doesn't 
>>>>> use it.
>>>>
>>>> VxWorks uses it.
>>>
>>> If even remotely possible, I'd really like to have something in my portfolio
>>> of guest code to test this case - especially given that KVM implements its
>>> own timers. I assume your binary is non-redistributable?
>>
>> No I can't give you the binary. Maybe the best solution is to write a simple
>> test case from scratch.
> 
> Yeah, I'll poke and see if someone already has something there.
> 
>>
>>
>>>>
>>>>>
>>>>>> +        uint32_t fpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_FPEXT_MASK)
>>>>>> +            >> TCR_E500_FPEXT_SHIFT;
>>>>>> +        fp = 63 - (fp | fpext << 2);
>>>>>> +    } else {
>>>>>> +        fp = env->fit_period[fp];
>>>>>> +    }
>>>>>> +
>>>>>> +    return fp;
>>>>>> +}
>>>>>> +
>>>>>> +/* Return the location of the bit of time base at which the WDT will 
>>>>>> raise an
>>>>>> +   interrupt */
>>>>>> +static uint8_t booke_get_wdt_target(CPUState *env)
>>>>>> +{
>>>>>> +    uint8_t wp = (env->spr[SPR_BOOKE_TCR] & TCR_WP_MASK) >> 
>>>>>> TCR_WP_SHIFT;
>>>>>> +
>>>>>> +    /* Only for e500 */
>>>>>> +    if (env->insns_flags2 & PPC2_E500) {
>>>>>> +        uint32_t wpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_WPEXT_MASK)
>>>>>> +            >> TCR_E500_WPEXT_SHIFT;
>>>>>> +        wp = 63 - (wp | wpext << 2);
>>>>>> +    } else {
>>>>>> +        wp = env->wdt_period[wp];
>>>>>> +    }
>>>>>> +
>>>>>> +    return wp;
>>>>>> +}
>>>>>> +
>>>>>> +static void booke_update_fixed_timer(CPUState         *env,
>>>>>> +                                     uint8_t           target_bit,
>>>>>> +                                     uint64_t          *next,
>>>>>> +                                     struct QEMUTimer *timer)
>>>>>> +{
>>>>>> +    ppc_tb_t *tb_env = env->tb_env;
>>>>>> +    uint64_t lapse;
>>>>>> +    uint64_t tb;
>>>>>> +    uint64_t period = 1 << (target_bit + 1);
>>>>>> +    uint64_t now;
>>>>>> +
>>>>>> +    now = qemu_get_clock_ns(vm_clock);
>>>>>> +    tb  = cpu_ppc_get_tb(tb_env, now, tb_env->tb_offset);
>>>>>> +
>>>>>> +    lapse = period - ((tb - (1 << target_bit)) & (period - 1));
>>>>>> +
>>>>>> +    *next = now + muldiv64(lapse, get_ticks_per_sec(), tb_env->tb_freq);
>>>>>> +
>>>>>> +    if (*next == now) {
>>>>>> +        (*next)++;
>>>>>
>>>>> Huh? If we hit the timer we don't fire it?
>>>>
>>>> Yes we do, but one nanosecond later :)
>>>
>>> Well, why trigger a timer when we can just as well call the callback 
>>> immediately? :)
>>>
>>
>> This come from ppc.c, QEMUTimer is kind of a private type so we don't have
>> access to the callback.
> 
> Oh, I see. Please just add that as an XXX comment so we can resolve it when 
> we get around to it.
> 
>>
>>>>
>>>>>
>>>>>> +    }
>>>>>> +
>>>>>> +    qemu_mod_timer(timer, *next);
>>>>>> +}
>>>>>> +
>>>>>> +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, PPC_INTERRUPT_DECR, 1);
>>>>>> +    }
>>>>>
>>>>> You will need this more often - also for the TCR write case - so please 
>>>>> put
>>>>> the 3 lines above into a separate function and just call that here :)
>>>>
>>>> Actually the checks are never exactly the same, here we test DIE in TCR...
>>>
>>> Sure, we have 3 different tests for the 3 different bits we can potentially 
>>> set in TCR. The check always ends up being the same though:
>>>
>>>  if (TSR & bit && TCR & bit)
>>>    set_irq(irq_for_bit);
>>>
>>> Most device emulators have a simple function for this called "update_irq" 
>>> that checks for all the bits and sets the respective irq lines.
>>>
>>
>> I know but we have two cases:
>>
>> - Timer hit: we check DIE in TCR
>> - Rising edge of DIE in TCR (from user): check if DIS is set
>>
>> I don't think we can have a good generic function for this, and I don't
>> forecast any improvement in code readability.
> 
> update_decr_irq() {
>   if (TSR.DIS && TCR.DIE) {
>     set_irq(DECR);
>   } else {
>     unset_irq(DECR);
>   }
> }
> 
> Timer hit:
> 
>   TSR |= DIS;
>   update_decr_irq();
> 
> Setting TCR:
> 
>   TCR |= DIE;
>   update_decr_irq();
>
>  Or am I misunderstanding the conditions under which the irq actually
> triggers? TCR.DIE is only the interrupt enabled flag - the timer can still
> hit nevertheless. The level of the interrupt is determined by TSR.DIR which
> is what the timer sets when it hits. Unless I completely misread the spec, an
> interrupt occurs when both of them are true. So all we need to do is have
> that check and run it every time we change a value in TSR or TCR.

Well OK, this can work to trigger the interrupts, not to clear them though.
And it will call ppc_set_irq when it's not required.

>>
>>>>
>>>>
>>>>>> +
>>>>>> +    if (env->spr[SPR_BOOKE_TCR] & TCR_ARE) {
>>>>>> +        /* Auto Reload */
>>>>>> +        cpu_ppc_store_decr(env, env->spr[SPR_BOOKE_DECAR]);
>>>>>> +    }
>>>>>
>>>>> Ah nice - never seen this one used. Do you have a test case?
>>>>>
>>>>
>>>> VxWorks :)
>>>>
>>>>
>>>>>> +}
>>>>>> +
>>>>>> +static void booke_fit_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_FIS;
>>>>>> +    if (env->spr[SPR_BOOKE_TCR] & TCR_FIE) {
>>>>>> +        ppc_set_irq(env, PPC_INTERRUPT_FIT, 1);
>>>>>> +    }
>>>>>
>>>>> Same as above :)
>>>>>
>>>>>> +
>>>>>> +    booke_update_fixed_timer(env,
>>>>>> +                             booke_get_fit_target(env),
>>>>>> +                             &booke_timer->fit_next,
>>>>>> +                             booke_timer->fit_timer);
>>>>>> +}
>>>>>> +
>>>>>> +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 */
>>>>>> +
>>>>>> +    booke_update_fixed_timer(env,
>>>>>> +                             booke_get_wdt_target(env),
>>>>>> +                             &booke_timer->wdt_next,
>>>>>> +                             booke_timer->wdt_timer);
>>>>>> +}
>>>>>> +
>>>>>> +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;
>>>>>> +
>>>>>> +    if (val & TSR_DIS) {
>>>>>> +        ppc_set_irq(env, PPC_INTERRUPT_DECR, 0);
>>>>>> +    }
>>>>>> +
>>>>>> +    if (val & TSR_FIS) {
>>>>>> +        ppc_set_irq(env, PPC_INTERRUPT_FIT, 0);
>>>>>> +    }
>>>>>> +
>>>>>> +    if (val & TSR_WIS) {
>>>>>> +        ppc_set_irq(env, PPC_INTERRUPT_WDT, 0);
>>>>>> +    }
>>>>>
>>>>> Why do you need the above? Shouldn't they still be active if the guest 
>>>>> didn't
>>>>> reset the bit? This is probably hiding a bug in the interrupt delivery
>>>>> mechanism automatically unmasking an irq bit when it's delivered, right?
>>>>
>>>> They are active until the bit is cleared by user, and TSR is a 
>>>> write-1-to-clear
>>>> register.
>>>
>>> Yes, that's what I mean. There shouldn't be a need to set the irq again 
>>> because it should still be active. These interrupts are level based.
>>>
>>
>> I feel like there's a misunderstanding here. I do not set the interrupts I
>> clear them.
> 
> Oh. Yes, I misread that. Sorry :). That indeed does make a lot of sense. I 
> can however be folded in with the normal "is DECR active right now" check.
> 
>>
>>
>>>>
>>>>>> +}
>>>>>> +
>>>>>> +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;
>>>>>> +
>>>>>> +    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);
>>>>>> +
>>>>>> +    if (val & TCR_DIE && env->spr[SPR_BOOKE_TSR] & TSR_DIS) {
>>>>>> +        ppc_set_irq(env, PPC_INTERRUPT_DECR, 1);
>>>>>> +    }
>>>>>> +
>>>>>> +    if (val & TCR_FIE && env->spr[SPR_BOOKE_TSR] & TSR_FIS) {
>>>>>> +        ppc_set_irq(env, PPC_INTERRUPT_FIT, 1);
>>>>>> +    }
>>>>>> +
>>>>>> +    if (val & TCR_WIE && env->spr[SPR_BOOKE_TSR] & TSR_WIS) {
>>>>>> +        ppc_set_irq(env, PPC_INTERRUPT_WDT, 1);
>>>>>> +    }
>>>>>
>>>>> Here is the second user of the checks. It really does make sense to only 
>>>>> have
>>>>> them in a single spot, so that ever ppc_set_irq(DECR) always goes through 
>>>>> the
>>>>> TSR and TCR checks for example :).
>>>>
>>>> ...here we test DIE in TCR and DIS in TSR.
>>>
>>> Yes, both of which are basically the prerequisites for actually triggering 
>>> the internal DECR interrupt line.
>>>
>>
>> Not exactly, see above.
> 
> [...]
> 
>>>>
>>>>> Very nice patch however! Thanks a lot for sitting down and fixing the 
>>>>> timer
>>>>> mess on booke that we currently have.
>>>>
>>>> You are welcome, It's my thank you gift for the MMU ;)
>>>
>>> Haha thanks :). So you're going to fix the MPIC mess for me implementing 
>>> SMP support? :D
>>>
>>
>> I'll see, but I'm not sure you deserve it :)
> 
> Probably not :). What else is on your todo list to get your awesome guest 
> running? :)
>

Actually it works pretty well with VxWorks already, I still have to clean up
few things and play with this new memory API to implement the CCSR
reallocation.

I've tried to look at Liu's patch, but I really don't know nothing about KVM so
I won't be able to make any valuable comment...

Regards,

-- 
Fabien Chouteau



reply via email to

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