0
votes

I wrote a bare-metal timer program on an ARM platform with QEMU. The platform is versatilepb. The timer is SP804 ARM Dual-Timer Module.

The SP804 provides 2 timer modules. Each module provides 2 timers. So there are totally 4 timers, namely Timer 0, 1, 2, 3.

Timer 0/1 share the IRQ 4.

Timer 2/3 share the IRQ 5.

I noticed that if I start a single timer, or 2 timers from different timer modules, say, 0/2 or 1/3. The interrupt frequency is quite normal.

But If I start the 2 timers from the same timer module, say 0/1 or 2/3. The interrupt frequency is much higher.

And when I start the 2 timers from the same timer module, the first timer I started got much more IRQ frequency than the second one, while the latter one seems to have a normal IRQ frequency.

Below is some of my IRQ handler code:

void IRQ_handler()
{
    u32 vicstatus = VIC_STATUS;

    // VIC status BITs: timer0,1=4, timer2,3=5
    if (vicstatus & (1<<4))
    {// bit4=1:timer0,1, handle timer 0 and 1 one by one
        if (*(timer[0].base + TVALUE) == 0) // timer 0
            timer_handler(0);
        if (*(timer[1].base + TVALUE) == 0) // timer 1
            timer_handler(1);
    }
    if (vicstatus & (1<<5))
    {// bit5=1:timer2,3, handle timer 2 and 3 one by one
        if (*(timer[2].base + TVALUE) == 0) // timer 2
            timer_handler(2);
        if (*(timer[3].base + TVALUE) == 0) // timer 3
            timer_handler(3);
    }
}



void timer_handler(u32 n)
{
    TIMER *t = &timer[n];
    t->tick++; // Assume 20 ticks per second. Need to calculate it for more accuracy.
    if (t->tick == 20)
    {
        t->tick = 0;
        t->ss++;
        if (t->ss == 60)
        {
            t->ss = 0;
            t->mm++;
            if (t->mm == 60)
            {
                t->mm = 0;
                t->hh++; // no 24 hour roll around
            }
        }
        t->clock[7] = '0' + (t->ss % 10);
        t->clock[6] = '0' + (t->ss / 10);
        t->clock[4] = '0' + (t->mm % 10);
        t->clock[3] = '0' + (t->mm / 10);
        t->clock[1] = '0' + (t->hh % 10);
        t->clock[0] = '0' + (t->hh / 10);

        kprintf("Timer [%d]: %s\n", n, (u8 *)&t->clock[0]);
    }
    timer_clearInterrupt(n); // clear timer interrupt
}

Screenshot:

Single timer: (the timer has normal frequency)

enter image description here

2 timers 1/3 from different module: (both timers have normal frequency)

enter image description here

2 timers 2/3 from same module: (timer 2 started first and it has much higher frequency than 3. And the overall frequency of both 2/3 are much higher)

enter image description here

ADD 1 - 6:23 PM 5/4/2020

Thanks to jcmvbkb's comment. I change to use the Masked Interrupt Status register (offset = TMIS) to detect which timer is issuing the interrupt.

According to the spec:

This value is the logical AND of the raw interrupt status with the Timer Interrupt Enable bit from the control register, and is the same value which is passed to the interrupt output pin, TIMINTX.

The interrupt frequency of the 2 timers from the same timer module appears normal now.

I am still thinking about why the previous approach with the Current Value Register cannot work. It seems so natural though.

void IRQ_handler()
{
    u32 vicstatus = VIC_STATUS;

    //UART 0
    if (vicstatus & UART0_IRQ_VIC_BIT)
    {
        uart_handler(&uart[0]);
    }

    //UART 1
    if (vicstatus & UART1_IRQ_VIC_BIT)
    {
        uart_handler(&uart[1]);
    }

    // VIC status BITs: timer0,1=4, uart0=13, uart1=14
    if (vicstatus & TIMER01_IRQ_VIC_BIT)
    {// bit4=1:timer0,1, handle timer 0 and 1 one by one
        if (*(timer[0].base + TMIS) == 1) // timer 0 <===== HERE changed to use TMIS
            timer_handler(0);
        if (*(timer[1].base + TMIS) == 1) // timer 1 <===== HERE changed to use TMIS
            timer_handler(1);
    }
    if (vicstatus & TIMER23_IRQ_VIC_BIT)
    {// bit5=1:timer2,3, handle timer 2 and 3 one by one
        if (*(timer[2].base + TMIS) == 1) // timer 2 <===== HERE changed to use TMIS
            timer_handler(2);
        if (*(timer[3].base + TMIS) == 1) // timer 3 <===== HERE changed to use TMIS
            timer_handler(3);
    }
}

Below is screenshot for starting timer 2/3 from the same timer module:

enter image description here

1
I dont think these emulators are meant to be cycle accurate, they obviously cant make the processor or storage/memory systems cycle accurate, I know the uart transmitter is not cycle accurate, wouldnt expect a timer to be either.old_timer
The way you detect which timer is the source of interrupt in the IRQ_handler doesn't look robust to me. There's Masked Interrupt Status Register that is apparently meant for that, I'd suggest to use it instead. Also the code is not complete, so it's unclear how you set up the timers. It would be easier if you provided a self-sufficient reproducer for this issue.jcmvbkbc
@jcmvbkbc Thanks. I will provide more details later.smwikipedia
@jcmvbkbc It works with the Masked Interrupt Status Register now. If you post your comment as an answer, I will be honored to mark it as the answer. I am still wondering why the Current Value Register doesn't work. It seems so natural though...smwikipedia

1 Answers

1
votes

IRQ_handler uses Current Value Register to detect which timer caused an interrupt, it doesn't look right. There's Masked Interrupt Status Register that is apparently meant for that, I'd suggest to use it instead. In addition checking for the IRQ source, recording the result of that check and clearing IRQ by writing to Interrupt Clear Register must be protected from reentrancy, otherwise single timer IRQ may still be counted multiple times.

My guess is that the original issue is observed because timer IRQ is triggered by something else or IRQ_handler is called for some other IRQ when timer counter is zero.