🐱作者:一只大喵咪1201
🐱专栏:《Linux驱动》
🔥格言:你只管努力,剩下的交给时间!
目录
- 🏀在设备树中指定中断
- 🏀代码中获得中断
- 🏀按键中断
- ⚽驱动程序
- ⚽设备树
- ⚽上机实验
- 🏀总结
🏀在设备树中指定中断
继续拿这个中断流程图来说话。
在硬件上,中断控制器只有GIC
这一个,但是我们在软件上可以把GPIO
也归类为中断控制器。
芯片会有多个GPIO模块,所以软件上的中断控制器就会有很多个:GIC,GPIO1,GPIO2,GPIO3…等等。
其中GPIO1、GPIO2、GPIO3这些中断控制器模块都连接汇集到GIC模块,所以GIC模块就是GPIOx模块的父亲。
假设GPIO1有32个中断源,但是它把其中的16个汇聚起来向GIC发出一 个中断,把另外16个汇聚起来向GIC发出另一个中断。这就意味着GPIO1会用到 GIC 的两个中断,会涉及 GIC 里的 2 个 hwirq
。
- 这些层级关系、中断号(hwirq),都会在设备树中有所体现。
dtsi:
如上图所示由BSP工程师提供的dtsi
设备树文件:
intc
:表示GIC中断控制器。gpio1
:表示GPIO1中断控制器。gpio2
:表示GPIO2中断控制器。
GIC是顶层中断控制器,所以它没有父亲,而GPIO1和GPIO2都是soc
的子节点,子节点会继承父节点的属性:
interrupt-parent = <&gpc>
:表示父亲节点,soc
节点的interrupt-parent = <&gpc>
。- GPIO1和GPIO2继承
soc
的interrupt-parent = <&gpc>
属性,所以它们的父节点也是gpc
。
本喵没有列出来,gpc
的父节点是GIC中断控制器,所以从设备树反推出IMX6ULL
的中断框图,它比之前多了一个GPC INTC
:
如上图所示新的中断框图,GPC INTC的功能是提供中断屏蔽、中断状态查询等功能。
- 实际上这些功能在GIC中也实现了。
- 之所以保留它是因为它还能提供唤醒功能。
继续回到dtsi
设备树文件来看,每一个中断控制器中都有两个必须的属性:
interrupt-controller
:该属性表明它是中断控制器。#interrupt-cells
:该属性表明引用这个中断控制器的话需要多少个cell
。#interrupt-cells=<1>
:别的节点要使用这个中断控制器时,只需要一个cell
来表明使用哪一个中断。#interrupt-cells=<2>
:别的节点要使用这个中断控制器时,需要两个cell
,其中第一个来表明使用哪一个中断,第二个一般来描述中断的触发类型。
由于GIC规定了要引用该中断控制器需要使用3个cell
,所以GPIO1和GPIO2就使用了<GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>
这样的3个cell
:
GIC_SPI
:GPIO模块向GIC发出中断的类型。66
:GPIO模块向GIC发出中断的硬件中断号hirq
。IRQ_TYPE_LEVEL_HIGH
:GPIO模块向GIC发出中断的触发类型。
除了引用GIC中断控制器外,GPIOx模块也有自己的interrupt-controller
属性和#interrupt-cells = <2>
属性:
- GPIO1和GPIO2中断控制器模块规定引用该控制器时需要使用两个
cell
。
这是都是芯片厂家写好的,提供给我们使用的,我们只需要知道怎么去用它们就行。
dts:
如上图我们写的dts
设备树文件中,这是一个spidev
节点:
interrupt-parent = <&gpio1>
:该设备的父节点是gpio1
,表明该外部设备引用GPIO1控制器。interrupts = <1 1>
:表示该设备使用的硬件中断号hirq
是1,触发类型是1。- 新写法
interrupts-extend
:一个这样的属性就可以既指定interrupt-parent
又指定interrupts
。 - 比如
interrupts-extend = <&gpio1 1 1>
。
- 新写法
触发类型这里写的1,代表什么意思呢?有写什么类型呢?
如上图所示,1就代表着low-to-high edge triggered
上升沿触发。
如此一来,在设备树中指定了该外部设备的中断,在驱动程序中就可以直接使用了。
🏀代码中获得中断
我们知道,设备树中的节点有的会被内核转化为platform_device
结构体,有些则不会。
对于能转化为platform_device
结构体的节点,如果它在设备树里指定了中断属性,那么可以从platform_device
中获得中断资源:
如上图所示platform_get_resource
函数,用来获取中断资源:
dev
:转化后的platform_device
结构体指针。type
:获取哪类资源。IORESOURCE_MEM
、IORESOURCE_REG
、IORESOURCE_IRQ
等。
num
:这类资源中的哪一个。- 一个节点中使用不止一个中断源。
对于I2C设备、SPI设备,总线驱动在处理设备树里的I2C子节点时,也会处理其中的中断信息。
- 一个I2C设备会被转换为一个
i2c_client
结构体,中断号会保存在i2c_client
的irq
成员里。
如上图代码所示,当I2C总线和驱动程序匹配以后,会自动调用probe
函数,在该函数中使用of_irq_get
函数,从它的子节点I2C设备中解析出中断号irq
。
- 一个 SPI 设备会被转换为一个
spi_device
结构体,中断号会保存在spi_device
的irq
成员里。
如上图,当SPI总线和驱动程序匹配后,在它的probe
函数中,也会调用of_irq_get
从子节点SPI设备中解析出中断号irq
。
如果设备节点既不能转换为platform_device
,也不是I2C和SPI设备,那么在驱动程序中,可以自行调用of_irq_get
函数去解析得到中断号。
对于GPIO引脚,芯片厂家提供了专门的接口函数来获取中断:
of_get_gpio_flags
:使用该函数获取GPIO引脚,此时获取到的是使用老的方式描述GPIO引脚信息的那个整数。gpio_to_desc
:使用该函数获取GPIO引脚,获得是使用新方式描述GPIO引脚信息的那个desc
结构体。gpiod_to_irq
:最后使用该函数从得到的GPIO引脚信息中获得软件中断号。
🏀按键中断
对于 GPIO 按键,我们并不需要去写驱动程序,使用内核自带的驱动程序 drivers/input/keyboard/gpio_keys.c
就可以,然后需要做的只是修改设备树指定引脚及键值。
但是本喵还是要从头写一遍按键驱动程序,特别是如何使用中断,因为中断是其他基础知识的前提,以后的休眠-唤醒,POLL机制,异步通知,定时器,中断的线程化处理等内容都离不开中断。
如上图所示本喵使用的IMX6ULL
开发板原理图,按键KEY1
和按键KEY2
使用的是GPIO5_1
和GPIO4_14
两个引脚,并且是低电平有效。
同样的,按键驱动程序也要分为驱动程序和设备树两部分。
⚽驱动程序
如上图所示gpio_key_drv.c
文件中的驱动程序:
- 创建
platform_driver
结构体gpio_keys_driver
,并且进行初始化。- 使用
gpio_key_probe
初始化probe
函数。 - 使用
gpio_key_remove
初始化remove
函数。 - 使用
of_device_id
数组Big_Miaomi_keys
初始化driver.of_match_table
。- 数组中
compatible
属性的值是"Big_Miaomi,gpio_keys"
,用来和设备节点匹配。
- 数组中
- 使用
- 在入口函数
gpio_key_init
中使用platform_driver_register
函数向内核注册驱动程序。 - 在出口函数
gpio_key_exit
中使用platform_driver_unregister
从内核中取消驱动程序注册。 - 完善设备驱动信息。
- 由于这是按键中断,不需要应用层来调用,所以不用在
/dev
下创建设备节点,也不用注册file_operations
结构体。
probe函数:
如上图所示probe
函数的实现:
- 定义全局一个结构体指针
gpio_keys_Big_Miaomi
,该结构体是struct gpio_key
类型,用来存放中断的信息。 - 使用
of_gpio_count
获得中断源个数,因为一个设备节点可能有多个中断源。- 按键设备可能有多个按键,此时就有多个中断源。
- 使用内核的
kzalloc
函数在堆区上开辟一段堆空间,来存放中断信息。 - 每一个中断源,都需要获取它的详细信息:
- 使用
of_get_gpio_flags
获取中断的引脚信息gpio
和有效标志。 - 将用整数描述的引脚信息转换成使用
gpio_desc
类型描述的引脚信息。 - 将中断引脚设置成低电平有效,与
OF_GPIO_ACTIVE_LOW
相与。- 因为电路中按键是低电平有效,原本的引脚标志是输入
GPIOF_IN
。
- 因为电路中按键是低电平有效,原本的引脚标志是输入
- 再使用
devm_gpio_request_one
将引脚状态设置成逻辑值。- 按键按下后,读取到的引脚值是1,尽管物理值是0。
- 使用
gpio_to_irq
获取中断引脚的软件中断号irq
。
- 使用
将所有中断信息获取到以后,再为每一个中断注册中断函数:
- 使用
request_irq
注册中断服务函数:- 第一个参数传入中断的软件中断号
gpio_keys_Big_Miaomi[i].irq
。 - 第二个参数传入中断服务函数
gpio_key_isr
。 - 第三个参数传入中断触发方式
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
双边沿触发。 - 第四个参数传入中断名称
"100ask_gpio_key"
,该参数不重要。 - 第五个参数传入
dev_id
,也就是中断信息所在的结构体指针&gpio_keys_Big_Miaomi[i]
。
- 第一个参数传入中断的软件中断号
此时中断函数就注册完成了,接下来就是实现中断服务函数中要做什么:
如上图所示中断服务函数,在里面仅获取引脚电平的逻辑值,并且打印出描述引脚的那个整数编号和引脚状态。
remove函数:
如上图所示remove
函数,在卸载驱动程序时:
- 使用
free_irq
将所有前面注册的中断释放掉。 - 使用
kfree
将存放引脚信息的堆空间释放掉。
⚽设备树
如上图所示蓝色框中代码,使用图形化工具生成GPIO5_1
和GPIO4_14
的两个pin-controller
节点代码。
如上图所示,将生成的pin-controller
节点代码复制到我们要写的dts
设备树文件中:
GPIO5_1
:节点名称为Big_Miaomi_key1
,表示按键1。GPIO4_14
:节点名称为Big_Miaomi_key2
,表示按键2。
如上图所示,在dts
设备树文件的根节点下,增加按键外部设备节点gpio_keys_Big_Miaomi
:
- 使用
GPIO
子系统指定按键引脚和有效电平。 - 使用
Pinctrl
子系统将引脚复用为通用GPIO功能。 - 在原本的
gpio-keys
节点中,使用status = "disabled"
属性让该节点失能,防止影响我们自己创建的按键节点。
- 这里并没有在设备树中指定按键的
interrupts-extend
。- 因为对于GPIO,芯片厂家提供了驱动程序中的一些列接口,可以直接获取GPIO的中断信息,包括中断号以及中断控制器等。
⚽上机实验
如上图所示,使用上面的makefile
文件编译驱动程序,生成gpio_key_drv.ko
驱动模块。
如上图所示,将写好的gpio_key_drv.c
驱动程序和设备树文件,以及Makefile
上传到服务器上,分别进行编译,编程成功后将生成的gpio_key.ko
和dtb
文件都拷贝到网络文件系统中供开发板使用。
如上图所示,先让开发板使用新的dtb
设备树文件,再使用insmod
安装gpio_key_drv.ko
驱动模块,可以看到安装成功后输出匹配porbe
函数的调试信息。
如上图所示,按下开发板上的KEY1
和KEY2
时:
- 按键按下,按键值是1,松开按键值是0。
- 按键值前面的整数就是描述按键引脚的编号。
🏀总结
要知道在设备树中是如何描述一个设备的中断的,包括父节点interrupt-parent
属性,以及描述中断引脚的interrupts
属性的用法。
还要知道在中断程序中是如何获取设备树中的中断信息的,以及如何使用这些中断信息。
最后要会实现按键中断程序。