Linux 中注册中断

当写一个带中断信号的设备的驱动程序的时候,注册中断函数最重要的部分其实就是两点:

  • 声明一个返回值类型是irqreturn_t的中断响应程序
  • 在request_irq()函数中提交中断号和中断响应程序的名字、参数,把他们连接起来,以达到产生中断时系统就调用相应中断响应程序的目的

在第二步中,request_irq()所提交的中断号曾经是硬件中断号,与硬件芯片有关。但在最新的 Linux Kernel 中,这一点已经改变了 (其实已经改变很久了,只是 PetaLinux 最近才把这个变更吸收进来产生影响)。简单地说,就是现在如果仍然提交这个硬件中断号,则不能得到正确的中断注册。而现在的处理方法对用户来说其实也并没有变得比原先复杂,只是把复杂的映射关系放在后台看不见的地方自动做掉了。

1. 为 Custom IP 注册中断的流程

在 ZYNQ 中,PS 相关的 IP 都已经有了驱动程序,因此不需要我们手工再写驱动,做绑定中断等工作。只要 device-tree 写得正确,系统启动的时候自动会调用相关驱动程序。

如果是用户自定义的 IP,这个 IP 需要产生中断信号,让系统做及时响应,那么我们就需要为它写驱动。主要步骤如下:

1.1 在 Vivado 中将 IP 的中断信号连接到 ZYNQ 的 IRQF2P

  • 启动 IRQF2P,需要在 ZYNQ 的配置中,找到 Interrupt,启用 IRQF2P。
  • IRQF2P 的意思是 IRQ Fabric to Processsor
  • IRQF2P 是一个总线。如果有多个中断信号要连接到 IRQF2P,需要经过一个 Concat IP 将多根独立信号合并成一个总线

1.2 在 Vivado 中 Generate Output Products,然后 Export Hardware 产生 HDF 文件

1.3 在 PetaLinux 工程中导入 HDF,让 PetaLinux 自动产生 device-tree

  • petalinux-config --get-hw-description=
  • 产生的 device-tree 在 components/plnx_workspace/device-tree/device-tree/ (subsystems/linux/configs/device-tree) 目录中
  • 如果 PL 中的 IP 是 Xilinx IP, 那么它的参数所对应的 device-tree 会生成在 pl.dtsi 中,它会被 system-top.dts 引用
  • pl.dtsi 中应该包含类似 interrupt-parent = <&intc>;interrupts = <0 29 4>;的两句话。其中interrupt-parent指定了中断控制器是哪个 IP,interrupts 后的三个参数的意思分别是:Shared Peripheral Interrupt(0) 还是 Private Peripheral Interrupt(1), 硬件中断号, 中断采集方式(边沿敏感还是电压敏感)。具体意义参考 Linux Kernel 源文件的 Documentation/devicetree/bindings/arm/gic.txt
  • 如果产生中断的是自定义的 IP,或者是使用 HDL 实现的设计,直接连线到 ZYNQ 的 IRQF2P,device-tree-generator 可能不能识别这个 IP 的名字和参数,需要用户参考 Xilinx IP 的 device-tree,写到 project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi (system-top.dts) 中。请包含 interrupt-parentinterrupts 参数
  • 注意 PL IP的device-tree描述中应该包含 compatible 参数,它的值一般写为 vendor,name-version 的形式。记住这个参数的值,在软件驱动中需要使用它。

1.4 在 PetaLinux 工程中产生驱动模板

  • petalinux-create -t module -n
  • 驱动作为 Kernel Module,产生的目录是 components/modules/

1.5 修改 PetaLinux Module 文件

  • project-spec/meta-user/recipes-modules/module-name/files/module-name.c (components/modules/.c) 中搜索 compatible, 填入步骤1.3中 device-tree 对应的 compatible 的值。这样,驱动加载的时候就会自动在 device-tree 中搜索相同 compatible 值的模块,并读取这个模块的其他参数值,比如 interrupt-parentinterrupt
  • Kernel 读取了 interruptinterrupt parent 参数后,会自动将硬件的 IRQ 号码映射为虚拟的 IRQ 号码,并存在 r_irq->start 中。request_irq 使用 r_irq->start 的 IRQ 号来注册中断。

1.6 配置rootfs包含module

petalinux-config -c rootfs
Module中选中创建的module

1.7 编译

  • petalinux-build 可以做完整编译,生成新的 image.ub

1.8 验证

  • 用新的 image.ub 启动 Linux 后,运行 insmod /lib/modules/4.0.0-xilinx/extra/.ko,就能加载这个 Kernel Module。
  • 正常情况下,console 中应该打印类似信息: at
    mapped to
    , irq=
  • 运行 cat /proc/interrupts 后应该可以看到新注册的中断的虚拟中断号、硬件中断号(device-tree 的中断号 + 32)、中断源模块名称等信息。

2. 总结

新的 Kernel 中虽然会将硬件中断号映射为虚拟中断号,但是对于使用 device-tree 描述 custom IP,并且使用 PetaLinux 自带 example code 来解析 device tree 的情况,coding 并没有因此变得复杂,底层系统自动处理了这个映射的流程。

3. 附录

一个使用 AXI Timer 作为 Custom IP 的例子。

device-tree in pl.dtsi

/ {
    amba_pl: amba_pl {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges ;
        axi_timer_0: timer@42800000 {
            clock-frequency = <100000000>;
            clock-names = "ref_clk";
            clocks = <&clkc 0>;
            compatible = "ricky,xps-timer-1.00.a";
            interrupt-parent = <&intc>;
            interrupts = <0 29 4>;
            reg = <0x42800000 0x10000>;
            xlnx,count-width = <0x20>;
            xlnx,gen0-assert = <0x1>;
            xlnx,gen1-assert = <0x1>;
            xlnx,one-timer-only = <0x0>;
            xlnx,trig0-assert = <0x1>;
            xlnx,trig1-assert = <0x1>;
        };
    };
};

Kernel Module

/*  intr_example.c - The simplest kernel module.
 */
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

/* Standard module information, edit as appropriate */
MODULE_LICENSE("GPL");
MODULE_AUTHOR
    ("Xilinx Inc.");
MODULE_DESCRIPTION
    ("intr_example - loadable module template generated by petalinux-create -t modules");

#define DRIVER_NAME "intr_example"

#define XIL_AXI_TIMER_BASEADDR 0x42800000
#define XIL_AXI_TIMER_HIGHADDR 0x4280FFFF

#define XIL_AXI_TIMER_TCSR_OFFSET       0x0
#define XIL_AXI_TIMER_TLR_OFFSET        0x4
#define XIL_AXI_TIMER_TCR_OFFSET        0x8
#define XIL_AXI_TIMER_CSR_INT_OCCURED_MASK  0x00000100

#define XIL_AXI_TIMER_CSR_CASC_MASK         0x00000800
#define XIL_AXI_TIMER_CSR_ENABLE_ALL_MASK   0x00000400
#define XIL_AXI_TIMER_CSR_ENABLE_PWM_MASK   0x00000200
#define XIL_AXI_TIMER_CSR_INT_OCCURED_MASK  0x00000100
#define XIL_AXI_TIMER_CSR_ENABLE_TMR_MASK   0x00000080
#define XIL_AXI_TIMER_CSR_ENABLE_INT_MASK   0x00000040
#define XIL_AXI_TIMER_CSR_LOAD_MASK         0x00000020
#define XIL_AXI_TIMER_CSR_AUTO_RELOAD_MASK  0x00000010
#define XIL_AXI_TIMER_CSR_EXT_CAPTURE_MASK  0x00000008
#define XIL_AXI_TIMER_CSR_EXT_GENERATE_MASK 0x00000004
#define XIL_AXI_TIMER_CSR_DOWN_COUNT_MASK   0x00000002
#define XIL_AXI_TIMER_CSR_CAPTURE_MODE_MASK 0x00000001

#define TIMER_CNT   0xF8000000

/* Simple example of how to receive command line parameters to your module.
   Delete if you don't need them */
unsigned myint = 0xdeadbeef;
char *mystr = "default";

module_param(myint, int, S_IRUGO);
module_param(mystr, charp, S_IRUGO);

struct intr_example_local {
    int irq;
    unsigned long mem_start;
    unsigned long mem_end;
    void __iomem *base_addr;
};

static int int_cnt;

static irqreturn_t intr_example_irq(int irq, struct intr_example_local *lp)
{
    unsigned int data;

    /* 
     * Check Timer Counter Value
     */
    data = ioread32(lp->base_addr + XIL_AXI_TIMER_TCR_OFFSET);
    printk("%s: Interrupt Occurred ! Timer Count = 0x%08X\n",__func__,data);

    /* 
     * Clear Interrupt
     */
    data = ioread32(lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);
    iowrite32(data | XIL_AXI_TIMER_CSR_INT_OCCURED_MASK,
    lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);

    /* 
     * Disable Timer after 100 Interrupts
     */
    int_cnt++;

    if (int_cnt>=100)
    {
        printk("%s: 100 interrupts have been occurred. Disabling timer", __func__);
        data = ioread32(lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);
        iowrite32(data & ~(XIL_AXI_TIMER_CSR_ENABLE_TMR_MASK),
        lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);
    }


    return IRQ_HANDLED;
}

static int intr_example_probe(struct platform_device *pdev)
{
    struct resource *r_irq; /* Interrupt resources */
    struct resource *r_mem; /* IO mem resources */
    struct device *dev = &pdev->dev;
    struct intr_example_local *lp = NULL;

    unsigned int data = 0;

    int rc = 0;
    
    dev_info(dev, "Device Tree Probing\n");

    /* Get iospace for the device */
    r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!r_mem) {
        dev_err(dev, "invalid address\n");
        return -ENODEV;
    }
    
    lp = (struct intr_example_local *) kmalloc(sizeof(struct intr_example_local), GFP_KERNEL);
    if (!lp) {
        dev_err(dev, "Cound not allocate intr_example device\n");
        return -ENOMEM;
    }
    
    dev_set_drvdata(dev, lp);
    
    lp->mem_start = r_mem->start;
    lp->mem_end = r_mem->end;

    if (!request_mem_region(lp->mem_start,
                lp->mem_end - lp->mem_start + 1,
                DRIVER_NAME)) {
        dev_err(dev, "Couldn't lock memory region at %p\n",
            (void *)lp->mem_start);
        rc = -EBUSY;
        goto error1;
    }

    lp->base_addr = ioremap_nocache(lp->mem_start, lp->mem_end - lp->mem_start + 1);
    if (!lp->base_addr) {
        dev_err(dev, "intr_example: Could not allocate iomem\n");
        rc = -EIO;
        goto error2;
    }

    /* Get IRQ for the device */
    r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (!r_irq) {
        dev_info(dev, "no IRQ found\n");
        dev_info(dev, "intr_example at 0x%08x mapped to 0x%08x\n",
            (unsigned int __force)lp->mem_start,
            (unsigned int __force)lp->base_addr);
        return 0;
    }
    lp->irq = r_irq->start;

    rc = request_irq(lp->irq, &intr_example_irq, 0, DRIVER_NAME, lp);
    if (rc) {
        dev_err(dev, "testmodule: Could not allocate interrupt %d.\n",
            lp->irq);
        goto error3;
    }

    dev_info(dev,"intr_example at 0x%08x mapped to 0x%08x, irq=%d\n",
        (unsigned int __force)lp->mem_start,
        (unsigned int __force)lp->base_addr,
        lp->irq);

    /* 
     * Set Timer Counter
     */
    iowrite32(TIMER_CNT,
        lp->base_addr + XIL_AXI_TIMER_TLR_OFFSET);
    data = ioread32(lp->base_addr + XIL_AXI_TIMER_TLR_OFFSET);
    printk("%s: Set timer count 0x%08X at 0x%08x\n",
        __func__, data, lp->mem_start + XIL_AXI_TIMER_TLR_OFFSET);

    /* 
     * Set Timer mode and enable interrupt
     */
    iowrite32(XIL_AXI_TIMER_CSR_LOAD_MASK,
        lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);
    iowrite32(XIL_AXI_TIMER_CSR_ENABLE_INT_MASK | XIL_AXI_TIMER_CSR_AUTO_RELOAD_MASK,
        lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);

    /* 
     * Start Timer
     */
    data = ioread32(lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);
    iowrite32(data | XIL_AXI_TIMER_CSR_ENABLE_TMR_MASK,
        lp->base_addr + XIL_AXI_TIMER_TCSR_OFFSET);
    return 0;
error3:
    free_irq(lp->irq, lp);
error2:
    release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
error1:
    kfree(lp);
    dev_set_drvdata(dev, NULL);
    return rc;
}

static int intr_example_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct intr_example_local *lp = dev_get_drvdata(dev);
    free_irq(lp->irq, lp);
    release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
    kfree(lp);
    dev_set_drvdata(dev, NULL);
    return 0;
}

#ifdef CONFIG_OF
static struct of_device_id intr_example_of_match[] = {
    { .compatible = "ricky,xps-timer-1.00.a", },
    { /* end of list */ },
};
MODULE_DEVICE_TABLE(of, intr_example_of_match);
#else
# define intr_example_of_match
#endif


static struct platform_driver intr_example_driver = {
    .driver = {
        .name = DRIVER_NAME,
        .owner = THIS_MODULE,
        .of_match_table = intr_example_of_match,
    },
    .probe      = intr_example_probe,
    .remove     = intr_example_remove,
};

static int __init intr_example_init(void)
{
    printk("<1>Hello module world.\n");
    printk("<1>Module parameters were (0x%08x) and \"%s\"\n", myint,
           mystr);

    return platform_driver_register(&intr_example_driver);
}


static void __exit intr_example_exit(void)
{
    platform_driver_unregister(&intr_example_driver);
    printk(KERN_ALERT "Goodbye module world.\n");
}

module_init(intr_example_init);
module_exit(intr_example_exit);

你可能感兴趣的:(Linux 中注册中断)