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

来自百问网嵌入式Linux wiki

这节课讲解如何在中断系统中使用设备树,也就是用设备树如何描述中断。

我发现给自己挖了一个大坑,如果我想设备树课程中我想把中断讲清楚, 就必须把中断体系讲清楚。

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


这一课的参考资料如下:

基于设备树的TQ2440的中断(1)

基于设备树的TQ2440的中断(2)

基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)

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代码分析


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


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

举例

取个场景解释中断。

假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。

问:这个母亲怎么才能知道这个小孩醒?

  1. 过一会打开一次房门,看婴儿是否睡醒,让后接着看书
  2. 一直等到婴儿发出声音以后再过去查看,期间都在读书

第一种 叫做查询方式

  • 优点:简单
  • 缺点: 累

写程序如何:

while(1)
{
	1 read book(读书)
	2 open door(开门)
	if()
		return(read book)
	else
		照顾小孩
	
}


第二种叫中断方式

  • 优点:不累
  • 缺点:复杂

写程序:

 

while(1)
{
	read book
	中断服务程序()//如何被调用?
	{
	处理照顾小孩
	}
}

我们看看母亲被小孩哭声打断如何照顾小孩?

母亲的处理过程:

1 平时看书

2 发生了各种声音,如何处理这些声音

有远处的猫叫(听而不闻,忽略)
门铃声有快递(开门收快递)
小孩哭声(打开房门,照顾小孩)

3 母亲的处理

只会处理门铃声和小孩哭声
a 现在书中放入书签,合上书(保存现场)
b 去处理 (调用对应的中断服务程序)
c 继续看书(恢复现场)


不同情况,不同处理:

a 对于门铃:开门取快件

b 对于哭声:照顾小孩

类比CPU

我们将母亲的处理过程抽象化——母亲的头脑相当于CPU

耳朵听到声音会发送信号给脑袋,声音来源有很多种,有远处的猫叫,门铃声,小孩哭声。这些声音传入耳朵,再由耳朵传给大脑,除了这些可以中断母亲的看书,还有其他情况,比如身体不舒服,有只蜘蛛掉下来,对于特殊情况无法回避,必须立即处理


对比我们的arm系统

Chapter14 lesson1 001.png

有CPU,有中断控制器。

中断控制器可以发信号给CPU告诉它发生了那些紧急情况

中断源有按键、定时器、有其它的(比如网络数据)

这些信号都可以发送信号给中断控制器,再由中断控制器发送信号给CPU表明有这些中断产生了,这些成为中断(属于一种异常)


还有什么可以中断CPU运行?

指令不对,数据访问有问题

reset信号,这些都可以中断CPU 这些成为异常中断


保存现场以及恢复现场

重点在于保存现场以及恢复现场


处理过程

a 保存现场(各种寄存器)

b 处理异常(中断属于一种异常)

c 恢复现场


arm对异常(中断)处理过程

1 初始化:

a 设置中断源,让它可以产生中断
b 设置中断控制器(可以屏蔽某个中断,优先级)
c 设置CPU总开关,(使能中断)

2 执行其他程序:正常程序

3 产生中断:按下按键--->中断控制器--->CPU

4 cpu每执行完一条指令都会检查有无中断/异常产生

5 发现有中断/异常产生,开始处理。对于不同的异常,跳去不同的地址执行程序。这地址上,只是一条跳转指令,跳去执行某个函数(地址),这个就是异常向量。如下就是异常向量表,对于不同的异常都有一条跳转指令。

.globl _start
_start: b	reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18**
	ldr	pc, _fiq
//我们先在0x18这里放 ldr pc ,__irq,于是cpu最终会跳去执行__irq代码
//保护现场,调用处理函数,恢复现场

(3-5都是硬件强制做的)

6 这些函数做什么事情?

软件做的:
a 保存现场(各种寄存器)
b 处理异常(中断):
分辨中断源
再调用不同的处理函数
c 恢复现场

对比母亲的处理过程来比较arm中断的处理过程。


中断处理程序怎么被调用?

CPU--->0x18 --跳转到其他函数->

做保护现场
调用函数
分辨中断源
调用对应函数
恢复现场

cpu到0x18是由硬件决定的,跳去执行更加复杂函数(由软件决定)

第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节_内核对设备树中断信息的处理过程