如何从内核模块为 ARM 通用定时器注册中断处理程序?

如何从内核模块为 ARM 通用定时器注册中断处理程序?

我目前正在尝试为 Raspberry Pi 5 上的通用计时器的计时器之一注册自定义处理程序,但不幸的是无法让它工作。

到目前为止,我所做的就是查看设备树中的计时器条目,其中提到了 4 个中断,即 PPI 10、11、13 和 14:

timer {
        compatible = "arm,armv8-timer";
        interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) |
                      IRQ_TYPE_LEVEL_LOW)>,
                 <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) |
                      IRQ_TYPE_LEVEL_LOW)>,
                 <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) |
                      IRQ_TYPE_LEVEL_LOW)>,
                 <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) |
                      IRQ_TYPE_LEVEL_LOW)>;
        /* This only applies to the ARMv7 stub */
        arm,cpu-registers-not-fw-configured;
    };

看看/proc/interrupts这些中断,10 似乎还没有安装处理程序:

           CPU0       CPU1       CPU2       CPU3       
  9:          0          0          0          0     GICv2  25 Level     vgic
 11:          0          0          0          0     GICv2  30 Level     kvm guest ptimer
 12:          0          0          0          0     GICv2  27 Level     kvm guest vtimer
 13:       4018       9245       1668       9893     GICv2  26 Level     arch_timer
 14:       1147          0          0          0     GICv2  65 Level     107c013880.mailbox
 15:          5          0          0          0     GICv2 153 Level     uart-pl011
...

(另外,我想知道“kvm guest vtimer”来自哪里,如果设备树中没有提到的话。有人知道吗?)

有了这些知识,我现在插入了以下简单的内核模块:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>

MODULE_LICENSE("GPL");

DEFINE_PER_CPU(int, dev_id);

irqreturn_t test_handler(int irq, void *dev_id)
{
        int this_cpu = smp_processor_id();

        printk("CPU %i received interrupt!\n", this_cpu);

        return IRQ_HANDLED;
}

int test_init(void)
{
        u64 ctl = 0, cval = 0, tval = 0;
        int err = 0, cpu = 0;

        cpu = get_cpu();
        printk("CPU: %i\n", cpu);

        err = request_percpu_irq(10, test_handler, "test_module", &dev_id);
        printk("request_percpu_irq() err: %i\n", err);

        ctl = read_sysreg(CNTHP_CTL_EL2);
        cval = read_sysreg(CNTHP_CVAL_EL2);
        tval = read_sysreg(CNTHP_TVAL_EL2);
        printk("CNTHP_CTL_EL2: %llu\n", ctl);
        printk("CNTHP_CVAL_EL2: %llu\n", cval);
        printk("CNTHP_TVAL_EL2: %llu\n", tval);

        write_sysreg(1000, CNTHP_TVAL_EL2);

        cval = read_sysreg(CNTHP_CVAL_EL2);
        tval = read_sysreg(CNTHP_TVAL_EL2);
        printk("CNTHP_CVAL_EL2: %llu\n", cval);
        printk("CNTHP_TVAL_EL2: %llu\n", tval);

        put_cpu();

        return 0;
}

void test_exit(void)
{
        free_percpu_irq(10, &dev_id);
}

module_init(test_init);
module_exit(test_exit);

再看/proc/interrupts一下,我的处理程序似乎已正确注册:

           CPU0       CPU1       CPU2       CPU3       
  9:          0          0          0          0     GICv2  25 Level     vgic
 10:          0          0          0          0     GICv2  29 Level     test_module
 11:          0          0          0          0     GICv2  30 Level     kvm guest ptimer
 12:          0          0          0          0     GICv2  27 Level     kvm guest vtimer
 13:       2597       1798       3172       1910     GICv2  26 Level     arch_timer
 14:        249          0          0          0     GICv2  65 Level     107c013880.mailbox
 15:          5          0          0          0     GICv2 153 Level     uart-pl011
...

但我还在代码中尝试做的是生成一个中断来触发我的新处理程序,但不幸的是这不起作用。根据 Generic Timer 参考,中断号 10(如果添加 PPI 偏移量 16,则为 26)应与 关联Non-secure EL2 Physical Timer,关联的系统寄存器具有前缀CNTHP和后缀EL2

以下是相关的 dmesg 条目,它们表明通过系统寄存器配置定时器似乎已经起作用。还应根据以下条件启用中断生成CNTHP_CTL_EL2

[   26.007926] test: loading out-of-tree module taints kernel.
[   26.008154] CPU: 3
[   26.008167] request_percpu_irq() err: 0
[   26.008169] CNTHP_CTL_EL2: 1
[   26.008172] CNTHP_CVAL_EL2: 1409277728
[   26.008174] CNTHP_TVAL_EL2: 185726
[   26.008177] CNTHP_CVAL_EL2: 1409093405
[   26.008179] CNTHP_TVAL_EL2: 998

但里面的打印输出test_handler()丢失了,所以中断似乎没有到达我的处理程序。不幸的是,这就是我已经被困了一段时间的地方。

那么如何从 Linux 内核模块为 ARM 通用定时器注册中断处理程序呢? (为什么它不按我的方式工作?)

相关内容