设备树中的中断


title: 设备树中的中断
date: 2019/4/29 17:38:38
toc: true
---

TODO

基于设备树的TQ2440的中断(1)
https://www.cnblogs.com/pengdonglin137/p/6847685.html

基于设备树的TQ2440的中断(2)
https://www.cnblogs.com/pengdonglin137/p/6848851.html

基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
http://www.cnblogs.com/pengdonglin137/p/6349209.html

Linux kernel的中断子系统之(一):综述
Linux kernel的中断子系统之(二):IRQ Domain介绍
linux kernel的中断子系统之(三):IRQ number和中断描述符
linux kernel的中断子系统之(四):High level irq event handler
Linux kernel中断子系统之(五):驱动申请中断API
Linux kernel的中断子系统之(六):ARM中断处理过程
linux kernel的中断子系统之(七):GIC代码分析
http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html

中断概述

这里就比较复杂了,暂时不去做深入的学习,以后再来挖坑学习,太难了 哈哈.这里放一个框图大概理解下基本流程

详细流程看窝窝科技

mark

以前的内核中断描述符的数组序号与硬件是对应的,后来出了个虚拟中断号,不再强调一定是线性关系.

中断入口

2440的中断向量表是这样的(参考uboot)

.globl _start
0--->   _start: b   reset
4--->   ldr pc, _undefined_instruction
8--->   ldr pc, _software_interrupt
c--->   ldr pc, _prefetch_abort
16-->   ldr pc, _data_abort
20-->   ldr pc, _not_used
24-->   ldr pc, _irq //发生中断时,CPU跳到这个地址执行该指令 
    ldr pc, _fiq

内核的向量表是这么定义的,在arch\arm\kernel\entry-armv.S

    .section .vectors, "ax", %progbits
.L__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)  pc, .L__vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq
    W(b)    vector_fiq

我们可以看到中断是跳转到vector_irq,这个东西是个宏定义

/*
 * Interrupt dispatcher
 */
    vector_stub irq, IRQ_MODE, 4   // 相当于 vector_irq: ..., 
                                   // 它会根据SPSR寄存器的值,
                                   // 判断被中断时CPU是处于USR状态还是SVC状态, 
                                   // 然后调用下面的__irq_usr或__irq_svc

    .long   __irq_usr               @  0  (USR_26 / USR_32)
    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
    .long   __irq_svc               @  3  (SVC_26 / SVC_32)
    .long   __irq_invalid           @  4
    .long   __irq_invalid           @  5
    .long   __irq_invalid           @  6
    .long   __irq_invalid           @  7
    .long   __irq_invalid           @  8
    .long   __irq_invalid           @  9
    .long   __irq_invalid           @  a
    .long   __irq_invalid           @  b
    .long   __irq_invalid           @  c
    .long   __irq_invalid           @  d
    .long   __irq_invalid           @  e
    .long   __irq_invalid           @  f

@ 下面是宏
.macro  vector_stub, name, mode, correction=0
.align  5

vector_\name:
.....
  • __irq_usr/__irq_svc作用是保存现场,调用irq_handler,恢复现场

  • irq_handler将会调用C函数 handle_arch_irq

        .macro  irq_handler
    #ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
        ldr r1, =handle_arch_irq
        mov r0, sp
        badr    lr, 9997f
        ldr pc, [r1]
    #else
        arch_irq_handler_default
    #endif
    9997:
        .endm
  • 也就是调用handle_arch_irq来处理中断

第一个C函数handle_arch_irq

这个函数是在哪里被设置的?

int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    if (handle_arch_irq)
        return -EBUSY;

    handle_arch_irq = handle_irq;
    return 0;
}

搜索下可以看到在s3c24xx_init_intc中设置了s3c24xx_handle_irq

s3c24xx_init_intc
    s3c24xx_clear_intc(intc);
    intc->domain = irq_domain_add_legacy(np, irq_num, irq_start,
                         0, &s3c24xx_irq_ops,
                         intc);
    set_handle_irq(s3c24xx_handle_irq);

具体的s3c24xx_handle_irq会调用s3c24xx_handle_intc

/*
 * Array holding pointers to the global controller structs
 * [0] ... main_intc
 * [1] ... sub_intc
 * [2] ... main_intc2 on s3c2416
 */
static struct s3c_irq_intc *s3c_intc[3];


这里有子中断的概念在硬件上,初始化如下
s3c2410_init_irq
    s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2410base[0], NULL,0x4a000000);
    //  s3c24xx_init_intc 的第三个参数是 parent 也就是表示 s3c_intc[1] 是 s3c_intc[0] 的一个子控制器
    s3c_intc[1] = s3c24xx_init_intc(NULL, &init_s3c2410subint[0],s3c_intc[0], 0x4a000018);
    // s3c24xx_init_intc 里面会设置如下结构体
    struct s3c_irq_intc {
        void __iomem        *reg_pending;
        void __iomem        *reg_intpnd;
        void __iomem        *reg_mask;
        struct irq_domain   *domain;
        struct s3c_irq_intc *parent;
        struct s3c_irq_data *irqs;
    };

    
s3c24xx_handle_intc
    // 1. 读取 reg_intpnd 这个应该就是顶级中断控制器的 pend 标志
    readl_relaxed
    // 2. 计算出硬件中断号
    irq_domain_get_of_node
    offset = readl_relaxed(intc->reg_intpnd + 4);
    offset =  __ffs(pnd);
    
    handle_domain_irq(domain,hwirq,regs)
        __handle_domain_irq
            // 找到虚拟中断号
            irq = irq_find_mapping(domain, hwirq);
            // 根据虚拟中断号,找到描述符所在,执行 irq_flow_handler_t    handle_irq
            generic_handle_irq
                // 这里就是找到中断描述符的结构
                desc = irq_to_desc(irq)
                // 执行 irq_flow_handler_t    handle_irq
                generic_handle_irq_desc((desc))

流程小结

handle_arch_irq:
a. 读 int controller, 得到hwirq
b. 根据hwirq得到virq
c. 调用 irq_desc[virq].handle_irq

如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
a. 取出irq_desc[virq].action链表中的每一个handler, 执行它
b. 使用irq_desc[virq].irq_data.chip的函数清中断

如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
a. 读 sub int controller, 得到hwirq'
b. 根据hwirq'得到virq
c. 调用 irq_desc[virq].handle_irq

下面老师的这个图很形象具体了

mark

中断号的演变

虚拟中断号与实际的硬件中断源一一对应,以前我们是定死的提前写好的,需要保证所有虚拟中断号不重合,2440定义在这里arch\arm\mach-s3c24xx\include\mach\irqs.h

/* main cpu interrupts */
#define IRQ_EINT0      S3C2410_IRQ(0)       /* 16 */
#define IRQ_EINT1      S3C2410_IRQ(1)
#define IRQ_EINT2      S3C2410_IRQ(2)
#define IRQ_EINT3      S3C2410_IRQ(3)
#define IRQ_EINT4t7    S3C2410_IRQ(4)       /* 20 */
.....
// 子中断号
#define S3C2410_IRQSUB(x)   S3C2410_IRQ((x)+58)
#define IRQ_S3CUART_RX0     S3C2410_IRQSUB(0)   /* 74 */
#define IRQ_S3CUART_TX0     S3C2410_IRQSUB(1)
#define IRQ_S3CUART_ERR0    S3C2410_IRQSUB(2)

那么我们是怎么通过硬件中断号,反算出虚拟中断号?

  • 对于不同中断控制器里面的硬件中断号,它们的转化公式是不同的
  • 根中断控制器和子中断控制器的公式是不一样的,称之为域rq_domain

缺陷

  • 当中断控制器数量变多时,有成百上千,这种虚拟中断号和硬件中断号一一对应的方式就很麻烦
  • 所以之后虚拟中断号后来只是表示序号,可以自由分配

查找空余项

  • 内核中使用一个位图数组allocated_irqs标记是否为空闲,1表示占用了
  • 从中断号开始依次查找,直到找到最后空闲项.

IRQ domain

Linux使用IRQ domain来描述一个中断控制器(IRQ Controller)所管理的中断源.

// \include\linux\irqdomain.h
struct irq_domain {
    linear_revmap[]   //linear_revmap[2] 存储着这个控制器硬件中断2号对应的虚拟中断号

例子

使用子中断EINT4的过程

mark

  1. 初始化为注册,首先注册主中断控制器,这里硬件中断号为4,我们去查找第4项空,存进去linear_revmap[4]=4
  2. 为子中断控制器注册虚拟中断号,这里硬件中断号是4,先查找4,发现被占用,则使用5.存到sub/linear_revmap[4]=5
  3. 驱动程序request_irq(5.my_handler),会把my_handler保存在irq_desc[5].action链表中
  4. 中断发生时,cpu先读取顶级的中断控制器,在顶级中断控制器的irq_domain找到linear_revmap[4]找到虚拟中断号是4,去执行中断描述符数组的第4项,这里会调用中断分发函数s3c_irq_demux
  5. s3c_irq_demux又去读取下一级别的sub irq_domain,找到了linear_revmap[4]=5.然后去执行中断描述符数组的第5项
  6. 所以也就是说,在irq_domain.linear_revmap[]大部分数组项都是空闲的

兼容老的固定中断号

  1. 很明显的要先设置好linear_revmap,也就是一般就是硬件中断号鱼虚拟中断号

  2. 以前写驱动程序直接 request_irq(virq,....),中断号是通过宏方式进行定义的,所以直接使用中断号进行注册

  3. 现在需要先在设备树表明使用哪个中断,内核会把这个中断号和某一个虚拟中断号挂钩,这些信息会转换成(intc,hwirq) ==> virq 这时才可以 request_irq

    .liner_revmap[4] = 5
    .xlate (解析设备树,得到hwirq,irq_type)
    .map(hwirq,virq) (map就是建立联系的作用,若是子中断,去设置父中断)

设备树描述中断

具体的中断

  • interrupt-parent属于哪个中断控制器
  • interrupts中断号和触发方式,这个里面的成员具体怎么表述需要去看具体中断控制器的描述

中断控制器的描述

  • interrupt-controller属性名表示是中断控制器
  • #interrupt-cells,表明下一级的设备要用多少个32位的数据来描述这个中断
    interrupt-controller@4a000000 {
        compatible = "samsung,s3c2410-irq";
        reg = <0x4a000000 0x100>;
        interrupt-controller;    //---表示是中断控制器
        #interrupt-cells = <0x4>;
        phandle = <0x1>;
    };

2440的表述

    interrupt-controller@4a000000 {
        compatible = "samsung,s3c2410-irq";
        reg = <0x4a000000 0x100>;
        interrupt-controller;
        #interrupt-cells = <0x4>;
        phandle = <0x1>;
    };
    
    gpf {
        gpio-controller;
        #gpio-cells = <0x2>;
        interrupt-controller;
        #interrupt-cells = <0x2>;
        phandle = <0x6>;
    };

    gpg {
        gpio-controller;
        #gpio-cells = <0x2>;
        interrupt-controller;
        #interrupt-cells = <0x2>;
    };
    
    
    srom-cs4@20000000 {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        reg = <0x20000000 0x8000000>;
        ranges;


        ethernet@20000000 {
            compatible = "davicom,dm9000";
            reg = <0x20000000 0x2 0x20000004 0x2>;
            interrupt-parent = <&gpf>; /*使用gpf中断控制器*/
            // interrupts 这个数据的解析是由 gpf中断控制器 的cell 表达的
            interrupts = <7 IRQ_TYPE_EDGE_RISING>;/*使用gpf控制器中的第七号中断,IRQ_TYPE_EDGE_RISING为中断触发方式*/
            local-mac-address = [00 00 de ad be ef];
            davicom,no-eeprom;
        };
    };

在这里并没有用单独的软件结构去描述硬件中的子中断控制器,而是使用interrupt-controller@4a000000中的一个32位(#interrupt-cells = <0x4>; 总共有4个32位)来表示是否为子中断控制器

猜你喜欢

转载自www.cnblogs.com/zongzi10010/p/10793087.html