设备树中的中断

设备树中的中断

原文 100ask

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位)来表示是否为子中断控制器,看下章节

2440中使用设备树

mark

具体的使用看代码就明白了

  1. 新增一个匹配条件of_match_table

    struct platform_driver buttons_drv = {
        .probe		= buttons_probe,
        .remove		= buttons_remove,
        .driver		= {
            .name	= "mybuttons",
            .of_match_table = of_match_buttons, /* 能支持哪些来自于dts的platform_device */
        }
    };
    
    static const struct of_device_id of_match_buttons[] = {
        { .compatible = "jz2440_button", .data = NULL },
        { /* sentinel */ }
    };
    
  2. probe中获取资源方式,这里获取硬件中断号,以及pin的信息,这里已经转换了虚拟中断

    static int buttons_probe(struct platform_device *pdev)
    {
    	struct device *dev = &pdev->dev;		
    	struct device_node *dp_node = dev->of_node;
    	struct resource		*res;
    	int i;
    
    	for (i = 0; i < sizeof(pins_desc)/sizeof(pins_desc[0]); i++)
    	{
    		/* 根据platform_device的资源进行获得中断号,触发类型 */
    		res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
    		if (res) {
    			pins_desc[i].irq  = res->start;
    			printk("get irq %d\n", pins_desc[i].irq);
    		}
    		else {
    			printk("can not get irq res for eint0\n");
    			return -1;
    		}
    
    		pins_desc[i].pin = of_get_named_gpio(dp_node, "eint-pins", i);
    		printk("pins_desc[%d].pin = %d\n", i, pins_desc[i].pin);
    	}
    	return sixth_drv_init();
    }
    
  3. 打开设备时申请中断,这里的虚拟中断在资源获取时已经转换了

    static int sixth_drv_open(struct inode *inode, struct file *file)
    {
    ret = request_irq(pins_desc[0].irq,  buttons_irq, 0, "S2", &pins_desc[0]);
    ....
    }
    

内核处理中断

中断结构

硬件结构上看, 处理过程分上下两个层面: 中断控制器, 使用中断的设备;

软件结构上看, 处理过程分左右两个部分: 在设备树中描述信息, 在驱动中处理设备树;

(1) 中断控制器

这又分为root irq controller, gpf/gpg irq controller

a. root irq controller

a.1 在设备树中的描述

a.2 在内核中的驱动

b. 对于S3C2440, 还有: gpf/gpg irq controller

b.1 在设备树中的描述(在pinctrl节点里)

b.2 在内核中的驱动 (在pinctrl驱动中)

(2) 设备的中断

a.1 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式")

a.2 在内核中的驱动 (在platform_driver.probe中获得IRQ资源, 即中断号)

irq_domain是核心:

a. 每一个中断控制器都有一个irq_domain

b. 对设备中断信息的解析,

b.1 需要调用 irq_domain->ops->xlate (即从设备树中获得hwirq, type)

b.2 获取未使用的virq, 保存: irq_domain->linear_revmap[hwirq] = virq;

b.3 在hwirq和virq之间建立联系

中断相关代码调用关系

s3c2440设备树中断相关代码调用关系:

(1) 上述处理过程如何触发?

a. 内核启动时初始化中断的入口:

start_kernel // init/main.c
    init_IRQ();
        if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
            irqchip_init();   // 一般使用它
        else
            machine_desc->init_irq();

b. 设备树中的中断控制器的处理入口:

irqchip_init // drivers/irqchip/irqchip.c
    of_irq_init(__irqchip_of_table);  // 对设备树文件中每一个中断控制器节点, 调用对应的处理函数
        为每一个符合的"interrupt-controller"节点,
        分配一个of_intc_desc结构体, desc->irq_init_cb = match->data; // = IRQCHIP_DECLARE中传入的函数
        并调用处理函数
        
        (先调用root irq controller对应的函数, 再调用子控制器的函数, 再调用更下一级控制器的函数...)

(2) root irq controller的驱动调用过程

a. 为root irq controller定义处理函数:

IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);  //drivers/irqchip/irq-s3c24xx.c

其中:

#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type)           \
    static const struct of_device_id __of_table_##name      \
        __used __section(__##table##_of_table)          \
         = { .compatible = compat,              \
             .data = (fn == (fn_type)NULL) ? fn : fn  }

展开为:

    static const struct of_device_id __of_table_s3c2410_irq     \
        __used __section("__irqchip_of_table")          \
         = { .compatible = "samsung,s3c2410-irq",               \
             .data = s3c2410_init_intc_of  }

它定义了一个of_device_id结构体, 段属性为__irqchip_of_table, 在编译内核时这些段被放在__irqchip_of_table地址处。

__irqchip_of_table起始地址处,放置了一个或多个 of_device_id, 它含有compatible成员;

设备树中的设备节点含有compatible属性,

如果双方的compatible相同, 并且设备节点含有interrupt-controller属性,则调用of_device_id中的函数来处理该设备节点。

所以IRQCHIP_DECLARE是用来声明设备树中的中断控制器的处理函数

b. root irq controller处理函数的执行过程:

s3c2410_init_intc_of  // drivers/irqchip/irq-s3c24xx.c
    // 初始化中断控制器: intc, subintc
    s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
                
        // 为中断控制器创建irq_domain
        domain = irq_domain_add_linear(np, num_ctrl * 32,
                                 &s3c24xx_irq_ops_of, NULL);

        intc->domain = domain;

        // 设置handle_arch_irq, 即中断处理的C语言总入口函数
        set_handle_irq(s3c24xx_handle_irq);

(3) pinctrl系统中gpf/gpg irq controller的驱动调用过程

a. pinctrl系统的驱动程序:

a.1 源代码: drivers/pinctrl/samsung/pinctrl-samsung.c

static struct platform_driver samsung_pinctrl_driver = {
    .probe      = samsung_pinctrl_probe,
    .driver = {
        .name   = "samsung-pinctrl",
        .of_match_table = samsung_pinctrl_dt_match, // 含有 { .compatible = "samsung,s3c2440-pinctrl", .data = &s3c2440_of_data },
        .suppress_bind_attrs = true,
        .pm = &samsung_pinctrl_pm_ops,
    },
};

a.2 设备树中:

pinctrl@56000000 {
    reg = <0x56000000 0x1000>;
    compatible = "samsung,s3c2440-pinctrl";  // 据此找到驱动

a.3 驱动中的操作:

samsung_pinctrl_probe  // drivers/pinctrl/samsung/pinctrl-samsung.c
    最终会调用到 s3c24xx_eint_init // drivers/pinctrl/samsung/pinctrl-s3c24xx.c
    
        // eint0,1,2,3的处理函数在处理root irq controller时已经设置; 
        // 设置eint4_7, eint8_23的处理函数(它们是分发函数)
        for (i = 0; i < NUM_EINT_IRQ; ++i) {
            unsigned int irq;

            if (handlers[i]) /* add by weidongshan@qq.com, 不再设置eint0,1,2,3的处理函数 */
            {
                irq = irq_of_parse_and_map(eint_np, i);
                if (!irq) {
                    dev_err(dev, "failed to get wakeup EINT IRQ %d\n", i);
                    return -ENXIO;
                }

                eint_data->parents[i] = irq;
                irq_set_chained_handler_and_data(irq, handlers[i], eint_data);
            }
        }

        // 为GPF、GPG设置irq_domain
        for (i = 0; i < d->nr_banks; ++i, ++bank) {
        
            ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops
                               : &s3c24xx_gpg_irq_ops;

            bank->irq_domain = irq_domain_add_linear(bank->of_node, bank->nr_pins, ops, ddata);
        }

(4) 使用中断的驱动调用过程:

a. 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式"),比如:

    buttons {
        compatible = "jz2440_button";
        eint-pins  = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
        interrupts-extended = <&intc 0 0 0 3>,
                              <&intc 0 0 2 3>,
                              <&gpg 3 3>,
                              <&gpg 11 3>;
    };

b. 设备节点会被转换为 platform_device, "中断的硬件信息" 会转换为"中断号", 保存在platform_device的"中断资源"里

  第3课第05节_device_node转换为platform_device, 讲解了设备树中设备节点转换为 platform_device 的过程;

我们只关心里面对中断信息的处理:

of_device_alloc (drivers/of/platform.c)
    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);  // 分配 platform_device
    
    num_irq = of_irq_count(np);  // 计算中断数
    
    of_irq_to_resource_table(np, res, num_irq) // drivers/of/irq.c, 根据设备节点中的中断信息, 构造中断资源
        of_irq_to_resource
            int irq = of_irq_get(dev, index);  // 获得virq, 中断号
                            rc = of_irq_parse_one(dev, index, &oirq); // drivers/of/irq.c, 解析设备树中的中断信息, 保存在of_phandle_args结构体中
                            
                            domain = irq_find_host(oirq.np);   // 查找irq_domain, 每一个中断控制器都对应一个irq_domain
                            
                            irq_create_of_mapping(&oirq);             // kernel/irq/irqdomain.c, 创建virq和中断信息的映射
                                irq_create_fwspec_mapping(&fwspec);
                                    irq_create_fwspec_mapping(&fwspec);
                                        irq_domain_translate(domain, fwspec, &hwirq, &type) // 调用irq_domain->ops->xlate, 把设备节点里的中断信息解析为hwirq, type
                                        
                                        virq = irq_find_mapping(domain, hwirq); // 看看这个hwirq是否已经映射, 如果virq非0就直接返回
                                        
                                        virq = irq_create_mapping(domain, hwirq); // 否则创建映射
                                                    virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);  // 返回未占用的virq
                                                    
                                                    irq_domain_associate(domain, virq, hwirq) // 调用irq_domain->ops->map(domain, virq, hwirq), 做必要的硬件设置
posted @ 2019-04-29 21:43  zongzi10010  阅读(994)  评论(0编辑  收藏  举报