QEMU ARM versatilepb 平台上 SP804 双定时器模块的 IRQ 频率不一致

Inconsistent IRQ frequency with SP804 Dual-Timer Module on QEMU ARM versatilepb platform

我用QEMU在ARM平台上写了一个裸机定时器程序。该平台是versatilepb。计时器是 SP804 ARM Dual-Timer Module.

SP804提供了2个定时器模块。每个模块提供 2 个定时器。所以一共有4个定时器,分别是Timer 0, 1, 2, 3.

Timer 0/1 共享 IRQ 4.

Timer 2/3 共享 IRQ 5.

我注意到如果我启动一个单个定时器,或者来自不同定时器模块的2个定时器,比如说,0/2或1 /3。中断频率很正常。

但是如果我从 相同的 计时器模块启动 2 个计时器,比如 0/1 或 2/3。中断频率要高很多。

当我从同一个定时器模块启动 2 个定时器时,我启动的 第一个 定时器比 第二个 获得了更多的 IRQ 频率] 一个,而后一个似乎有一个正常的IRQ频率。

下面是我的一些 IRQ 处理程序代码:

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
}

截图:

单定时器:(定时器正常频率)

2 个定时器 1/3 来自不同的模块:(两个定时器都有正常频率)

2 个定时器 2/3 来自同一模块:(定时器 2 首先启动,它的频率比定时器 3 高得多。并且两个 2/3 的总频率高得多)

添加 1 - 6:23 2020 年 5 月 4 日下午

感谢 jcmvbkb 的评论。我改为使用 Masked Interrupt Status 寄存器(偏移量 = TMIS)来检测哪个定时器正在发出中断。

根据规范:

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.

来自同一个定时器模块的2个定时器的中断频率现在显示正常。

我还在想为什么以前的 Current Value Register 方法行不通。虽然看起来很自然。

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);
    }
}

下面是从同一个计时器模块启动计时器 2/3 的屏幕截图:

IRQ_handler 使用 Current Value Register 来检测哪个计时器引起了中断,它看起来不对。 Masked Interrupt Status Register 显然是为了这个,我建议改用它。除了检查 IRQ 源,记录该检查的结果并通过写入 Interrupt Clear Register 清除 IRQ 必须防止重入,否则单个定时器 IRQ 仍可能被多次计数。

我的猜测是观察到最初的问题是因为计时器 IRQ 是由其他东西触发的,或者当计时器计数器为零时为其他某个 IRQ 调用了 IRQ_handler。