第五课. 中断系统中的设备树

来自百问网嵌入式Linux wiki

这节课讲解如何在中断系统中使用设备树,也就是用设备树如何描述中断。
中断体系在4.x内核中变化很大,中断体系又跟pinctrl系统密切相关,pinctrl中又涉及GPIO子系统,这样讲下去的话,设备树课程就变成驱动专题了,所以我打算只讲中断体系统,对于pinctrl、gpio等系统留待以后在驱动课程中扩展。

这一课的参考资料如下:

本课视频预计分为五节。 其中第01节描述中断概念的引入与处理流程,这节视频来自"韦东山第1期裸板视频加强版", 如果已经理解了中断的概念, 请忽略该节。

第01节_中断概念的引入与处理流程

  • 点击下面链接跳转到相应文章页面

第01节_中断概念的引入与处理流程文章地址

第02节_Linux对中断处理的框架及代码流程简述

当CPU发生中断时,CPU会跳到一个固定的地址去执行中断,对于中断来说, 中断地址是在24的地方,

.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

这些地址就是vector,可以放在0地址,也可以放在0xffff0000(对于这个地址是启动mmu之后才存在的)
对于其它芯片,向量所在地址可能不同,但都是用来处理异常
打开内核源码 a. 异常向量入口: 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_stub表示这个vector_irq b. 中断向量: 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

arch\arm\kernel\entry-armv.S

__irq_usr

	.align	5
__irq_usr:
	usr_entry
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq
 UNWIND(.fnend		)
ENDPROC(__irq_usr)

处理完成后返回到被中断的现场

__irq_svc

	.align	5
__irq_svc:
	svc_entry
	irq_handler

#ifdef CONFIG_PREEMPT
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	ldr	r0, [tsk, #TI_FLAGS]		@ get flags
	teq	r8, #0				@ if preempt count != 0
	movne	r0, #0				@ force flags to 0
	tst	r0, #_TIF_NEED_RESCHED
	blne	svc_preempt
#endif

	svc_exit r5, irq = 1			@ return from exception
 UNWIND(.fnend		)
ENDPROC(__irq_svc)

c. __irq_usr/__irq_svc

  这2个函数的处理过程类似:
  保存现场
  调用 irq_handler
  恢复现场


d. 核心是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
linux-4.19-rc3\kernel\irq\handle.c

e. handle_arch_irq的处理过程: 请看视频和图片

  1. 读取寄存器获得中断信息: hwirq
  2. 把hwirq转换为virq
  3. 调用 irq_desc[virq].handle_irq

对于S3C2440,通过set_handle_irq函数设置 s3c24xx_handle_irq 是用于处理中断的C语言入口函数
中断处理流程:
假设中断结构如下:

sub int controller ---> int controller ---> cpu

中断控制中有32位,每一位代表一种中断,也就是控制器可以向CPU发出32种中断,每一种中断的处理方式不同,如何管理这些中断呢?
最简单方法是创建指针数组,每一项对应一个中断,在这个中断里存放处理函数,这个数组用irq_desc中断结构体来表示
Ldd devicetree chapter5 2 001.png

handle_irq操作

  1. 调用irqaction链表中的handler
  2. 清中断(芯片相关)irq_data.chip

发生中断时内核的执行过程
cpu跳到vector_irq, 保存现场, 调用C函数handle_arch_irq

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的函数清中断

对于0号中断,加上一个或控制器,形成一个共享中断,可以控制网卡中断irq_net和摄像头中断irq_camera,
在0号中断上有两个设备,那么就需要有两个irqaction,其中irqaction *next指向下一个irqaction
当irq_net发生中断时,会执行irq_desc.handle_irq会把链表里面所有的handler都取出来执行一遍,在irq_net中要访问网卡来判断下中断是否是网卡产生的,如果不是则返回不做任何处理,如果是则做网络处理

链表支持 共享中断,

Ldd devicetree chapter5 2 002.png

如果使用中断4_7则一旦产生中断,那么都会向cpu发出4号中断,也可以通过irqdesc.irq_action构造4个irqaction结构体,
将四个中断链接到一起,当发生中断时,这四个函数都会被调用一次,这种方式可以用,但是并不好用
对于sub interrupt controller(子中断控制器)对于可以读取SUBSRCPND寄存器用来确定是哪一个产生了更加细致的中断
那么我们就可以让 irq_desc.handle_irq指向s3c_irq_demux(中断分发函数)

  • hwirq(表示硬件中断号)
  • (virq(表示虚拟中断号)

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

硬件中断号和虚拟中断号

cpu
||
\/
INTC //读intc得到hwirq
||
\/
SUB INTC//读subintc 得到hwirq'

我们可以通过硬件中断号,得到虚拟中断号,这些虚拟中断号就是irq_desc[]数组项的下标 可以定义这么一个公式

virq = hwirq  + offset + 1
     = hwirq + 16
virq = hwirq' + offset + 2
     = hwirq' + 58 + 16

我们假设中断5接有一个按键irq_key,我们注册这个中断时会注册对应的中断号(这里是37),这时irq_desc会创建一个irqaction这个handle就等于irq_key,当我们按下时,这个子中断控制器会向上一级中断控制器发出信号,上一级中断控制器就会向cpu发出信号,cpu读取控制器时会找到对应的virq通过irq_desc找到这一项对应的handle_irq,让后去读寄存器进一步来分辨是发生了哪一个子中断,得到虚拟中断号,进入irq_desc链表得到irqaction取出handler来执行irq_key

第03节_中断号的演变与irq_domain

图1.。。。。。。。。。。

irq_desc[]下标是中断号virq(对应硬件中断),根据硬件中断确定是那个中断发生

得出公式

virq  = hwirq + 1
hwirq = virq - 1

假设增加一个子中断控制器

virq =hwirq' + 36
hwirq' = virq - 36

在加一个中断控制器 在这种情况下如何使用中断?
以前,对于每一个硬件中断(hwirq)都预先确定它的中断号(virq),这些中断号一般都写在一个头文件里,
比如arch\arm\mach-s3c24xx\include\mach\irqs.h使用时

a.执行reguest_irq(virq,my_handler):内核根据virq可以知道对应的硬件中断,让后去设置,使能中断等操作
b.发生硬件中断时,内核读取硬件信息,确定hwirq,反算出virq,让后调用irq_desc[virq].handle_irq,最终会用到my_handler

内核怎么根据硬件中断号,反算出虚拟中断号?

对于不同中断控制器里面的硬件中断号,它们的转化公式是不同的
硬件上有多个intc(中断控制器),对于同一个hwirq数值,会对应不同的virq所以在讲hwirq时,应该强调“是哪一个intc的hwirq”,在描述hwirq转换为virq时,
引入一个概念:irq_domain,域,在这个域里hwirq转换为某个virq
以前中断号(virq)跟硬件密切相关,现在的趋势是中断号跟软件无关,仅仅是一个标号而已

这种老方法的缺陷
当cpu只有一两个中断控制器时这种一一对应的方法很好用,当中断控制器数量变多时,有成百上千,这种虚拟中断号和硬件中断号一一对应的方式就很麻烦

解决办法
virq和hwirq之间的联系取消掉,当我想使用硬件中断时,查找这个irq_desc数组里的空余项

图2.。。。。。。。。。。。

假设使用 int2 如何查找空闲项?
从中断号开始依次查找,直到找到最后空闲项,
如果bit=0则虚拟中断号就是2

虚拟中断号保存在irq_domain结构体中 irq_domain linear_revmap[hwirq] = virq

把hwirq对应的virq保存在irq_domain的linear_revmap

假如使用子EINT4中断

使用子中断EINT4的过程
1.使用父中断(intc,4)设置irq_desc:

a.找空闲项,virq=4,保存起来:intc's irq_domain.linear_revmap[4] = 4
b.设置irq_desc[4].handle_irq = s3c_irq_demux

2.为子中断eint4(subintc,4)设置irq_desc:

a.找空闲项,virq=5,保存起来:subintc's irq_domain.linear_revmap[4] = 5

3.驱动程序request_irq(5.my_handler),

会把my_handler保存在irq_desc[5].action链表中

4.发生了中断,内核读取intc,得到hwirq=4,virq = intc's irq_domain.liner_revmap[4] = 4调用irq_desc[4].handle_irq,即s3c_irq_demux

5.s3c_irq_demux:
读取subintc,得到hwirq=4,virq = subintc's irq_domain.liner_remap[4] = 5,调用irq_desc[5].handle_irq,它会调用action链表中保存的my_handler

每一个中断控制器都有一个irq_domain.linera_revmap得到虚拟中断号,
调用irq_desc.handle_irq;这个分发函数会读取下级中断控制器,得到子中断控制器的4号中断,
再次读取子中断控制器对应的irq_domain.liner_revmap[4] = 5对应的虚拟中断号是5,那么就会调用第五个irq_desc,执行handle_irq
irq_domain.linear_revmap[]大部分数组项都是空闲的

怎么兼容老的固定中断号?

也要保存的硬件中断号和虚拟中断号之间的对应关系,在irq_domain也有liner_revmap[]对每一个[hwirq] = 预先设置号的virq
我们写驱动程序直接 request_irq(virq,....),中断号是通过宏方式进行定义的,所以直接使用中断号进行注册

如何使用新型中断号,注册irq
先在设备树中表明要使用那个中断,内核解析设备树时才会把这个中断号和某一个虚拟中断号挂钩,这些信息会转换成(intc,hwirq) ==> virq 这时才可以 request_irq

通过irq_domain得到

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

第04节_示例_在S3C2440上使用设备树描述中断体验

我们这节课之前所使用的设备树和内核是一个阉割版本,这个版本根本没有能力去描述中断,
在以前的mach-s3c24xx.c中手动注册了很多平台设备,这些平台设备中指定了很多设备资源,比如i2c控制器提前指定了中断号和内存等资源
这些中断号可以从某个头文件指定

内核不断演变,虚拟中断号和硬件中断号不再绑定,这也就意味着不能在平台资源里事先确定所使用的中断资源,就需要用设备树描述这些中断资源
所用文件在: doc_and_sources_for_device_tree\source_and_images\第5,6课的源码及映像文件(使用了完全版的设备树)\内核补丁及设备树

先解压原始内核(source_and_images\kernel):

  tar xzf linux-4.19-rc3.tar.gz

打上补丁:

  cd linux-4.19-rc3
  patch -p1 < ../linux-4.19-rc3_device_tree_for_irq_jz2440.patch

在内核目录下执行:

 export  PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin
 cp config_ok  .config
 make uImage   // 生成 arch/arm/boot/uImage
 make dtbs     // 生成 arch/arm/boot/dts/jz2440_irq.dtb

把jz2440_irq.dtb反汇编成 jz2440_irq_all.dts便于分析

老内核中断:

/ # cat /proc/interrupts
          CPU0
29:      17593       s3c  13 Edge      samsung_time_irq 
42:          0       s3c  26 Edge      ohci_hcd:usb1
43:          0       s3c  27 Edge      s3c2440-i2c.0
74:         86  s3c-level   0 Edge      s3c2440-uart
75:        561  s3c-level   1 Edge      s3c2440-uart
83:          0  s3c-level   9 Edge      ts_pen
84:          0  s3c-level  10 Edge      adc
87:          0  s3c-level  13 Edge      s3c2410-wdt

samsung_time_irq对应中断号是29,在irqs.h中对应的宏是 #define IRQ_TIMER3 S3C2410_IRQ(13) 2440使用第三个定时器作为系统滴答,使用老内核中断号都是固定


使用新内核启动:

nfs 30000000 192.168.1.124:/work/nfs_root/uImage; nfs 32000000 192.168.1.124:/work/nfs_root/jz2440_irq.dtb; bootm 30000000 - 32000000
/ # cat /proc/interrupts
          CPU0
 8:          0       s3c   8 Edge      s3c2410-rtc tick
13:        936       s3c  13 Edge      samsung_time_irq
30:          0       s3c  30 Edge      s3c2410-rtc alarm
32:         15  s3c-level  32 Level     50000000.serial
33:         60  s3c-level  33 Level     50000000.serial
59:          0  s3c-level  59 Edge      53000000.watchdog

samsung_time_irq中断号从以前的29变成了现在的13,现在我们不能指定中断号只能在设备树描述会用到什么中断号。

我们如何描述这些信息? 参考 jz2440_irq_all.dts


图1.。。。。。。。。。。。。 gpio控制器实际并不是中断控制器,但我们可以在软件上认为这是一个控制器

如何描述硬件资源?

比如我们网卡要使用中断资源

	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 = <7 IRQ_TYPE_EDGE_RISING>;/*使用gpf控制器中的第七号中断,IRQ_TYPE_EDGE_RISING为中断触发方式*/
			local-mac-address = [00 00 de ad be ef];
			davicom,no-eeprom;
		};
	};
interrupt-parent = <&gpf>; ---------------->使用gpf中断控制器
interrupts = <7 IRQ_TYPE_EDGE_RISING>;-----><hwirq type> 具体含义由中断控制器解释

用多少个u32也是由中断控制器指定

a. 某个设备要使用中断, 需要在设备树中描述中断, 如何描述?
它要用哪一个中断? 这个中断连接到哪一个中断控制器去?
即: 使用哪一个中断控制器的哪一个中断?
至少有有2个属性:

interrupt-parent  // 这个中断要接到哪一个设备去? 即父中断控制器是谁
interrupts        // 表示要使用哪一个中断, 中断的触发类型等等
 

中断控制器如何表述

总中断控制器,也就是跟中断控制器

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

GPIO子中断控制器

		gpf {
			gpio-controller;
			#gpio-cells = <0x2>;
			interrupt-controller;
			#interrupt-cells = <0x2>;
			phandle = <0x6>;
		};

		gpg {
			gpio-controller;
			#gpio-cells = <0x2>;
			interrupt-controller;
			#interrupt-cells = <0x2>;
		};

如何查找父节点控制器,通过一级一级往上查找最后找到interrupt-controller@4a000000

tu2...................

b. 上述的interrupts属性用多少个u32来表示? 这应该由它的父中断控制器来描述,
在父中断控制器中, 至少有2个属性:

interrupt-controller;   // 表示自己是一个中断控制器
#interrupt-cells       // 表示自己的子设备里应该 有几个U32的数据来描述中断

第05节_示例_使用设备树描述按键中断

在上节视频里我们体验了怎么在设备树中描述中断,这一节我们来写一个按键驱动程序来看看怎么使用设备树来描述按键驱动程序所使用的引脚和所使用的中断。
这个驱动程序就不现场编写了,毕竟我们主题是讲设备树,而不是讲怎么写驱动程序。

源码路径

我们在以前按键驱动程序的基础上修改按键驱动程序。
按键驱动程序百度云盘路径:

100ask分享的所有文件
	009_UBOOT移植_LINUX移植_驱动移植(免费)
		源码文档图片.zip
			源码文档图片
				源码文档
					毕业班_文档_图片_源码_bin
						毕业班第4课移植驱动到3.4.2内核_文档_图片_源码
							drivers_and_test_new
								jz2440
									7th_buttons_all

该源码已经先下载下来,放在设备树的文件夹里,路径为:

100ask分享的所有文件
	018_设备树详解
		doc_and_sources_for_device_tree
			source_and_images
				5,6课的源码及映像文件(使用了完全版的设备树)
					5课第5_按键驱动_源码_设备树
						000th_origin_code

000th_origin_code是从前面毕业班视频里直接拷贝过来的;
001th_buttons_drv是用在之前阉割版本的内核,里面没有支持用设备树描述中断;
002th_buttons_drv是本节视频使用的驱动程序,在设备树里可以描述中断了;
000th001th进行对比:

  1. Makefile有变化,两个的内核路径不一样,因为编译驱动需要借助内核源码;
  2. 驱动有少量变量,比如头文件,一些结构体名字,定时器相关的函数,gpio读取函数名字等;

可以看到这两个驱动程序的变化不大,使用的中断号都是和硬件绑定的;

按键中断

在上节我们知道了中断抽象出了三个中断控制器。
根据原理图可知,我们的按键涉及eint0、eint2、eint11、eint19。
其中eint0、eint2接在最顶层的中断控制器,eint11、eint19接在gpf中断控制器。
以前我们在设备树中描述中断时,需要指定这个中断是发给哪一个中断控制器,它属于这个中断中的哪一个中断。即在interrupt-parent指定中断控制器,在interrupts指定是该中断控制器中的哪个中断,并且指定中断类型。

现在我们有四个中断,分别属于两个中断控制器,它向两个中断控制器发送信号,就不能使用老方法了,我们需要引入一个新的属性。

参考设备树的官方文档,里面有个中断扩展属性,在里面可以指定多个中断,参考如下:

interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;

比如该示例有两个中断控制器,每个后面紧跟对应得描述内容,描述内容的多少由中断控制器决定。

如下图截取按键中断的描述:

Ldd devicetree chapter5 5 001.jpg

首先是指定中断控制器,让再描述哪一个中断。


对于intc中断控制器:
第一个表示是发给主控制器还是子控制器;
第二个表示子中断控制器发给主控制器的哪一个;
第三个表示是这个中断控制器里的哪一个中断;
第四个表示中断的触发方式;

对于gpg中断控制器:
第一个表示是这个中断控制器里的哪一个中断;
第二个表表示中断的触发方式;
可以看到两个不同的中断控制器,它们后面的描述数据的数量是不一样的,这个数量的多少,是在设备树里面#interrupt-cells里定义的。

至此,对中断属性的解释已经清楚了,我们的驱动程序需要设备号,中断号是一个软件的概念,那么这些中断信息怎么转换成中断号呢?
在设备树的设备节点中描述"中断的硬件信息",表明使用了"哪一个中断控制器里的哪一个中断, 及中断触发方式"。
设备节点会被转换为 platform_device, "中断的硬件信息" 会转换为"中断号", 保存在platform_device的"中断资源"里。
驱动程序从platform_device的"中断资源"取出中断号, 就可以request_irq了。

实验

a. 把"002th_buttons_drv/jz2440_irq.dts" 放入内核 arch/arm/boot/dts目录, 在内核根目录下执行:

make dtbs   // 得到 arch/arm/boot/dts/jz2440_irq.dtb

使用上节视频的uImage或这个jz2440_irq.dtb启动内核;


b. 编译、测试驱动:

b.1 把 002th_buttons_drv 上传到ubuntu

b.2 编译驱动:

export  PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin

cd 002th_buttons_drv

make   // 得到 buttons.ko

b.3 编译测试程序:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/arm/4.3.2/bin

cd 002th_buttons_drv

arm-linux-gcc -o buttons_test  buttons_test.c

b.4 测试:

insmod buttons.ko
./buttons_test &

然后按键

第06节_内核对设备树中断信息的处理过程

中断结构

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

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

(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之间建立联系:

要调用 irq_domain->ops->map, 比如根据hwirq的属性设置virq的中断处理函数(是一个分发函数还是可以直接处理中断)
irq_desc[virq].handle_irq = 常规函数;
如果这个hwirq有上一级中断, 假设它的中断号为virq', 还要设置:
irq_desc[virq'].handle_irq = 中断分发函数;


中断相关代码调用关系

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), 做必要的硬件设置

c. 驱动程序从platform_device的"中断资源"取出中断号, 就可以request_irq