linux驱动学习--中断

linux驱动–基于platform和设备树下的按键中断

一、linux中断管理

  首先先看一下几个概念

  • 中断号:每个中断都有一个中断号,中断控制器根据中断号区分不同的中断(中断号由MCU厂商规定)
  • 中断服务函数:发生中断时执行的函数
  • 中断向量:中断号和中断服务函数的地址一一对应起来的一个记录,发生中断时根据中断号查找中断服务函数
  • 中断向量表:多个中断向量组成的一张表,一款CPU可以支持多个中断,这些中断向量组成了中断向量表

  在发生中断时,进入中断向量表,根据中断号查找对应的中断服务函数地址,进而去执行中断服务函数,执行完成后继续中断前的任务。
  在一般的MCU中,中断号,中断服务函数名,中断向量表一般都是固定的,都在启动文件中定义,例如STM32,但是在linux中,中断服务函数名和中断向量表不是固定的,可以自定义中断服务函数名,所以就需要实现中断号和中断服务函数的绑定,进而构造出中断向量表,在linux中使用request_irq() 函数来实现这一需求,该函数可以用来实现中断号与中断服务函数的绑定,设置中断触发方式等功能,函数原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)

参数:
- irq:中断号
- handler:中断服务函数
- flags:中断标志
- name:中断名
- dev:设备结构体

返回值:
- 0:申请成功
- -EBUSY:已经被申请了
- 其他负数:申请失败

  irg为需要申请中断的中断号,可以通过查找芯片手册的中断表来查看,当然该值一般都在设备树节点中体现了,可以直接使用相关的API函数来获取,比如platform_get_irq函数,该函数从设备树节点中获取interrupts属性的值,该值即为中断号。
  handler为中断服务函数,也就是发生中断后执行的函数,该参数是一个函数指针,类型为irq_handler_t,原型如下,第一个参数是中断号,第二个参数是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备。

1
irqreturn_t (*irq_handler_t) (int, void *)

  flags为中断标志,可以设置中断的触发方式,可选的值如下所示,这些标志可以通过 “|” 来组合。

标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。
IRQF_ONESHOT 单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发

  name为中断名,指定申请的中断的名称,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
  dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
  中断申请成功后如果不再使用了需要注销掉这个中断,使用free_irq() 函数,该函数会解除中断号和中断服务函数之间的绑定关系,并且会禁止掉该中断,原型如下,参数irg表示需要注销中断的中断号,dev用来区分共享中断的设备。

1
2
3
4
5
6
7
void free_irq(unsigned int irq,void *dev)

参数:
- irq:中断号
- dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断

返回值:无

二、其他API函数

1.获取中断号
  在申请中断的时候需要使用到中断号,可以直接查找芯片手册上的中断描述,但是这样太麻烦了,现在都将所有的设备信息写到了设备树上了,所以可以直接去设备树节点上找,比如通过irq_of_parse_and_map() 函数,该函数从指定的节点中获取interrupts属性的值,该值描述了中断号,这个函数适合于使用了设备树的驱动,函数原型如下。

1
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

  然后还有一个函数是 platform_get_irq() ,该函数也是从设备树节点中获取 interrupts 属性的值,不同的是这个函数需要基于 platform总线 使用,在编写platform_driver结构体的时候,有个probe成员,该成员指向一个函数,该函数的参数就是匹配到的设备树节点,platform的驱动和设备匹配成功后,就会向probe函数传递一个匹配到的设备树节点,我们可以直接使用这个参数,而 不用手动的获取某个节点信息 ,函数原型如下。

1
int platform_get_irq(struct platform_device *dev, unsigned int num)

  如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下,gpio即为要获取中断的 GPIO 编号。

1
int gpio_to_irq(unsigned int gpio)

  设备树中关于中断的描述如下所示。

1
2
3
4
5
6
7
8
9
10
11
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-key"; //兼容性属性
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>; //该属性描述了父中断控制器
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; //该属性描述了中断号
status = "okay";
};

  综上所述,获取中断号的方法有以下几种。

方法 适用情况
芯片手册 不太建议
irq_of_parse_and_map 基于设备树
platform_get_irq 基于设备树和platform总线
gpio_to_irq 基于GPIO

2.中断使能与禁能

1
2
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

  enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。

三、示例:基于platform下的按键中断

设备树节点:
20250109150727
设备树主要设备父中断控制器(interrupt-parent)和中断号(interrupts),这里的18其实不是中断号,是GPIO1_IO18中的18,因为按键接在了GPIO1上,所以中断号由父中断控制器管理,根据IO编号来选择对应的中断号,例如这里的按键接在了GPIO1_IO18,GPIO1的中断号有两个,0~15共用一个中断号,16~31共用一个中断号,则IO18的中断号就是16~31对应的中断号。
驱动代码链接:key_interuptDriver.c
加载步骤:

  • 编译驱动文件,并复制到开发板中
  • depmod , modprobe key_interuptDriver

实验现象:
按下按键时,会打印Thr INT is Triged