linux中断程序编写

中断的API函数

request_irq

1
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)

**irq:**要申请中断的中断号。

**handler:**中断处理函数,当中断发生以后就会执行此中断处理函数。

**flags:**中断标志,可以在文件include/linux/interrupt.h 里面查看所有的中断标志

标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq 函数的dev参数就是唯一区分他们的标志。
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的第二个参数。

**返回值:**0中断申请成功,其他负值中断申请失败,如果返回-EBUSY的话表示中断已经被申请了。

free_irq

1
void free_irq(unsigned int irq,void *dev)

**irq:**要释放的中断。

**dev:**如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉

**返回值:**无。

中断服务函数

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

第一个参数:是要中断处理函数要相应的中断号。

**第二个参数:**是一个指向void 的指针,也就是个通用指针,需要与request_irq 函数的dev 参数保持一致。

**返回值:**中断处理函数的返回值为irqreturn_t 类型

1
2
3
4
5
6
7
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};

typedef enum irqreturn irqreturn_t;

一般使用:return IRQ_RETVAL(IRQ_HANDLED)

中断使能函数

  1. 控制当前 CPU 的全局中断状态(修改 CPSR 寄存器):

    函数 作用 关键实现
    local_irq_enable() 使能当前 CPU 的 IRQ 中断(全局) asm volatile("cpsie i" ::: "memory", "cc")
    local_irq_disable() 禁止当前 CPU 的 IRQ 中断(全局) asm volatile("cpsid i" ::: "memory", "cc")
    local_fiq_enable() 使能当前 CPU 的 FIQ 中断(全局) asm volatile("cpsie f" ::: "memory", "cc")
    local_fiq_disable() 禁止当前 CPU 的 FIQ 中断(全局) ‘asm volatileasm volatile("cpsid f" ::: "memory", "cc")
    local_irq_save(flags) 保存当前中断状态到 ,并flags禁止IRQ 中断 mrs %0, cpsr+cpsid i(保存 CPSR 后关中断)
    local_irq_restore(flags) 恢复之前保存的中断状态(通过 )flags msr cpsr_c, %0(恢复 CPSR 原始值)
    ‘本地local_save_flags(flags) 仅保存当前中断状态到 (中断状态)flags不改变 mrs %0, cpsr(仅读取 CPSR)
  2. 特定 IRQ 线控制函数(中断控制器级)

    函数 作用 关键实现
    enable_irq(unsigned int irq) 使能指定中断线(全局生效) 调用中断控制器驱动使能该 IRQ;若之前被 禁止,会重新触发挂起中断disable_irq
    disable_irq(unsigned int irq) 禁止指定中断线(全局生效),并等待中断处理完成(同步) 禁止 IRQ + 等待处理完成(可能睡眠)synchronize_irq()
    disable_irq_nosync(unsigned int irq) 禁止指定中断线(全局生效),不等待中断处理完成(异步) 仅禁止 IRQ,不等待处理完成(不可在中断上下文调用

设备树

Linux 内核通过读取设备树中的中断属性信息来配置中断,参考文档:Documentation/devicetree/bindings/arm/gic.txt imx6ull.dtsi文件找到

顶层中断控制器intc–gic

1
2
3
4
5
6
7
8
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";/*compatible 属性用于平台设备驱动的匹配*/
#interrupt-cells = <3>;/*指定它的“子”中断控制器用几个cells 来描述一个中断,
可理解为用几个参数来描述一个中断信息*/
interrupt-controller;/*声明该设备树节点是一个中断控制器*/
reg = <0x00a01000 0x1000>,/*reg 指定中断控制器相关寄存器的地址及大小*/
<0x00a02000 0x100>;
};

第三行:#interrupt-cells 和#address-cells、#size-cells 一样。,#interrupt-cells 描述了interrupts 属性的cells 大小,也就是一条信息有几个cells。每个cells都是32位整形值,对于ARM处理的==GIC 来说,一共有3 个cells==,这三个cells 的含义如下:

  • 第一个cells:中断类型,0表示SPI中断,1表示PPI中断。
  • 第二个cells:中断号,对于SPI中断来说中断号的范围为0987,对于PPI中断来说中断号的范围为 015。
  • 第三个cells:标志,bit[3:0]表示中断触发类型,为1的时候表示上升沿触发,为2的时候表示下降沿触发,为4的时候表示高电平触发,为8的时候表示低电平触发。bit[15:8]为PPI 中断的CPU掩码。

第五行:表示当前节点是中断控制器。

一级子中断控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gpc: gpc@020dc000 {
compatible = "fsl,imx6ul-gpc", "fsl,imx6q-gpc";
reg = <0x020dc000 0x4000>;
interrupt-controller;/*声明该设备树节点是一个中断控制器*/
#interrupt-cells = <3>;/*使用三个参数来描述子控制器的信息*/
interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
/*具体的中断描述信息
在该节点的中断控制器的“父”中断控制器intc中规定了使用三个cells来描述子控制器的信息
第一个参数用于指定中断类型:SPI 共享中断、PPI 私有中断、SGI 软件中断
第二个参数用于设定中断编号
第三个参数指定中断触发方式,
*/
interrupt-parent = <&intc>;/*指定该中断控制器的“父”中断控制器为intc,
且除了“顶层中断控制器”其他中断控制器都要声明“父”中断控制器*/
fsl,mf-mix-wakeup-irq = <0xfc00000 0x7d00 0x0 0x1400640>;
};

次级中断控制器–gpio控制器

GPIO控制器作为次级中断控制器,其中断源是GPIO引脚。每个引脚的中断配置只需:

  • 引脚号:标识具体的GPIO引脚(例如引脚9)。
  • 触发类型:定义中断的触发方式(如边沿、电平)。

因此,不需要像GIC(主中断控制器)那样需要3个单元格(类型、全局中断号、标志),而是简化为 2 个单元格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
soc{
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;

gpio5: gpio@020ac000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};

第4 行,interrupts 描述中断源信息,对于gpio5 来说一共有两条信息,中断类型都是SPI,触发电平都是IRQ_TYPE_LEVEL_HIGH。GPIO5一共用了2个中断号,一个是74,一个是75。其中74 对应GPIO5_IO00~GPIO5_IO15 这低16个IO,75 对GPIO5_IO16~GPIOI5_IO31 这高16 位IO。

GPIO 引脚与 GIC 中断的映射

  • GPIO5 的引脚范围被分为两组:
    • 引脚 0~15 映射到 GIC 的 SPI 中断 74。
    • 引脚 16~31 映射到 GIC 的 SPI 中断 75。
  • 当 GPIO5 的某个引脚触发中断时,硬件会将对应组的 GIC 中断(74 或 75)上报。

image-20250509205227708

第8 行,interrupt-controller 表明了gpio5 节点也是个中断控制器,用于控制gpio5 所有IO的中断。

第9 行,将#interrupt-cells 修改为2。

子中断控制器

1
2
3
4
5
6
7
8
9
10
11
gt9147:gt9147@14 {
compatible = "goodix,gt9147", "goodix,gt9xx","edt,edt-ft5406";
reg = <0x14>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
interrupt-parent = <&gpio1>;//指定该设备的中断信号连接到哪个中断控制器。
interrupts = <9 0>;//定义中断信号的引脚和触发方式。
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
status = "okay";
};

关键参数详解

interrupt-parent = <&gpio1>;

  • 作用:指定该设备的中断信号连接到哪个中断控制器。
  • 参数gpio1 表示此设备的中断信号由 GPIO 控制器 1 管理。
  • 背景:在 SoC 中,GPIO 控制器通常也承担中断控制器的角色,负责将 GPIO 引脚的中断信号转发给主中断控制器(如 GIC)。

interrupts = <9 0>;

  • 作用:定义中断信号的引脚和触发方式。
  • 参数
    • 第一个参数(9):在 interrupt-parent 指定的控制器(GPIO1)中,使用 第9号引脚 作为中断信号线。
    • 第二个参数(0):中断触发类型,此处 0 表示 低电平触发IRQ_TYPE_LEVEL_LOW)。
      其他常见值:
      • 1:上升沿触发(IRQ_TYPE_EDGE_RISING
      • 2:下降沿触发(IRQ_TYPE_EDGE_FALLING
      • 3:双边沿触发(IRQ_TYPE_EDGE_BOTH
      • 4:高电平触发(IRQ_TYPE_LEVEL_HIGH

interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;

  • 作用:指定中断引脚的具体 GPIO 控制器、引脚号和电平极性。
  • 参数
    • &gpio1:GPIO 控制器 1。
    • 9:GPIO1 的 9 号引脚。
    • GPIO_ACTIVE_LOW:低电平有效(即引脚变为低电平时触发中断)

中断有关的设备树属性信息:

  • #interrupt-cells,指定中断源的信息cells 个数。

  • interrupt-controller,表示当前节点为中断控制器。

  • interrupts,指定中断号,触发方式等。

  • interrupt-parent,指定父中断,也就是中断控制器。

    两者区别

特性 GT9147(GPIO中断) gpc(GIC中断)
中断控制器 GPIO控制器(如 gpio1 GIC(通用中断控制器)
中断号来源 GPIO引脚号(如 9 GIC分配的硬件中断号(如 42
触发类型编码 数字编码(0表示上升沿) 直接使用宏(如 IRQ_TYPE_EDGE_RISING
适用场景 外部设备通过GPIO引脚触发中断(如触摸屏) 片内外设直接连接到GIC(如DMA、USB)
  • GT9147节点:使用GPIO引脚作为中断源,硬件中断号是GPIO引脚号(需通过SoC文档或DTSI文件映射到GIC中断号)。
  • gpc节点:直接使用GIC分配的硬件中断号,无需二次映射。

实验一

本次实验我们只使用按键中断,并通过驱动注册一个按键的中断服务函数

设备树添加节点:

1
2
3
4
5
6
7
8
9
10
key{
compatible = "kevin,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
status = "okay";
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};

驱动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */

/* 中断IO描述结构体 */
struct irq_keydesc {
struct gpio_desc *gpiod; /* GPIO描述符 */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct platform_device *pdev; /* platform设备 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */
struct timer_list timer;/* 定义一个定时器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};

/* @description : 中断服务函数,开启定时器,延时10ms,
* 定时器用于按键消抖。
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param - arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];

value = gpiod_get_value(keydesc->gpiod); /* 读取IO值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
}

/*
* @description : 按键IO初始化
* @param - dev : 设备结构体
* @return : 0 成功;其他 失败
*/
static int keyio_init(struct imx6uirq_dev *dev)
{
unsigned char i = 0;
int ret = 0;
struct device *pdev = &dev->pdev->dev;

/* 使用devm_gpiod_get获取GPIO描述符 */
dev->irqkeydesc[0].gpiod = devm_gpiod_get(pdev, "key", GPIOD_IN);
if (IS_ERR(dev->irqkeydesc[0].gpiod)) {
dev_err(pdev, "can't get key gpio\n");
return PTR_ERR(dev->irqkeydesc[0].gpiod);
}

/* 使用platform_get_irq获取中断号 */
dev->irqkeydesc[0].irqnum = platform_get_irq(dev->pdev, 0);
if (dev->irqkeydesc[0].irqnum < 0) {
dev_err(pdev, "can't get key irq number\n");
return dev->irqkeydesc[0].irqnum;
}

/* 初始化key描述 */
sprintf(dev->irqkeydesc[0].name, "KEY0");
dev->irqkeydesc[0].handler = key0_handler;
dev->irqkeydesc[0].value = KEY0VALUE;

printk("key0: gpio=%d, irqnum=%d\r\n",
desc_to_gpio(dev->irqkeydesc[0].gpiod),
dev->irqkeydesc[0].irqnum);

/* 申请中断 */
ret = devm_request_irq(pdev, dev->irqkeydesc[0].irqnum,
dev->irqkeydesc[0].handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
dev->irqkeydesc[0].name, dev);
if (ret < 0) {
dev_err(pdev, "irq %d request failed!\r\n",
dev->irqkeydesc[0].irqnum);
return ret;
}

/* 创建定时器 */
init_timer(&dev->timer);
dev->timer.function = timer_function;

return 0;
}

/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
struct imx6uirq_dev *dev = container_of(inode->i_cdev, struct imx6uirq_dev, cdev);
filp->private_data = dev; /* 设置私有数据 */
return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);

if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;

data_error:
return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};

/*
* @description : platform驱动的probe函数,驱动与设备匹配成功以后此函数就会执行
* @param - pdev: platform设备
* @return : 0,成功;其他负值,失败
*/
static int imx6uirq_probe(struct platform_device *pdev)
{
struct imx6uirq_dev *dev;
int ret = 0;

/* 1、分配设备结构体 */
dev = devm_kzalloc(&pdev->dev, sizeof(struct imx6uirq_dev), GFP_KERNEL);
if (!dev) {
return -ENOMEM;
}

dev->pdev = pdev;

/* 2、构建设备号 */
if (dev->major) {
dev->devid = MKDEV(dev->major, 0);
ret = register_chrdev_region(dev->devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
ret = alloc_chrdev_region(&dev->devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
dev->major = MAJOR(dev->devid);
dev->minor = MINOR(dev->devid);
}
if (ret < 0) {
goto fail_devid;
}

/* 3、注册字符设备 */
cdev_init(&dev->cdev, &imx6uirq_fops);
dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&dev->cdev, dev->devid, IMX6UIRQ_CNT);
if (ret < 0) {
goto fail_cdev;
}

/* 4、创建类 */
dev->class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(dev->class)) {
ret = PTR_ERR(dev->class);
goto fail_class;
}

/* 5、创建设备 */
dev->device = device_create(dev->class, NULL, dev->devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(dev->device)) {
ret = PTR_ERR(dev->device);
goto fail_device;
}

/* 6、初始化按键 */
atomic_set(&dev->keyvalue, INVAKEY);
atomic_set(&dev->releasekey, 0);
ret = keyio_init(dev);
if (ret < 0) {
goto fail_keyinit;
}

/* 7、保存设备结构体到平台设备中 */
platform_set_drvdata(pdev, dev);

dev_info(&pdev->dev, "imx6uirq driver probe success\n");
return 0;

fail_keyinit:
device_destroy(dev->class, dev->devid);
fail_device:
class_destroy(dev->class);
fail_class:
cdev_del(&dev->cdev);
fail_cdev:
unregister_chrdev_region(dev->devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}

/*
* @description : platform驱动的remove函数,移除platform驱动时此函数会执行
* @param - pdev: platform设备
* @return : 0,成功;其他负值,失败
*/
static int imx6uirq_remove(struct platform_device *pdev)
{
struct imx6uirq_dev *dev = platform_get_drvdata(pdev);

/* 删除定时器 */
del_timer_sync(&dev->timer);

/* 注销字符设备 */
cdev_del(&dev->cdev);
unregister_chrdev_region(dev->devid, IMX6UIRQ_CNT);
device_destroy(dev->class, dev->devid);
class_destroy(dev->class);

return 0;
}

/* 匹配列表 */
static const struct of_device_id imx6uirq_of_match[] = {
{ .compatible = "kevin,key" },
{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, imx6uirq_of_match);

/* platform驱动结构体 */
static struct platform_driver imx6uirq_driver = {
.driver = {
.name = "imx6uirq",
.of_match_table = imx6uirq_of_match,
},
.probe = imx6uirq_probe,
.remove = imx6uirq_remove,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init imx6uirq_init(void)
{
return platform_driver_register(&imx6uirq_driver);
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit imx6uirq_exit(void)
{
platform_driver_unregister(&imx6uirq_driver);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kevin");

实验二

共享中断实验,本次实验进一步来了解共享中断,我们首先来学习理论知识:

  • 理论知识:

    当在 request_irq 函数中设置 IRQF_SHARED 标志时,表示我们希望共享中断线,即多个设备可以使用同一个硬件中断号。在这种情况下,dev 参数变得至关重要。

  • dev 参数必须满足以下要求:

    1. 必须是唯一的:每个共享同一中断线的设备必须提供不同的 dev
    2. 不能为 NULL:必须指向一个设备特定的数据结构
    3. 用于标识中断源:当中断发生时,内核会使用这个值来确定是哪个设备触发了中断

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/device.h>

#define SHARED_IRQ_NUM 17 // 假设我们要共享的中断号是17

// 第一个设备的数据结构
struct device_one_data {
char *name;
int id;
// 其他设备特定数据...
};

// 第二个设备的数据结构
struct device_two_data {
char *name;
int id;
// 其他设备特定数据...
};

// 设备实例
static struct device_one_data dev1_data = {
.name = "device_one",
.id = 1
};

static struct device_two_data dev2_data = {
.name = "device_two",
.id = 2
};

// 中断处理函数 - 设备1
static irqreturn_t device_one_interrupt(int irq, void *dev_id)
{
struct device_one_data *data = (struct device_one_data *)dev_id;

printk(KERN_INFO "Interrupt handled by %s (ID: %d)\n",
data->name, data->id);

// 检查是否真的是这个设备产生的中断
// if (!is_device_one_interrupt())
// return IRQ_NONE;

// 处理设备1的中断
// ...

return IRQ_HANDLED;
}

// 中断处理函数 - 设备2
static irqreturn_t device_two_interrupt(int irq, void *dev_id)
{
struct device_two_data *data = (struct device_two_data *)dev_id;

printk(KERN_INFO "Interrupt handled by %s (ID: %d)\n",
data->name, data->id);

// 检查是否真的是这个设备产生的中断
// if (!is_device_two_interrupt())
// return IRQ_NONE;

// 处理设备2的中断
// ...

return IRQ_HANDLED;
}

static int __init my_init(void)
{
int ret;

printk(KERN_INFO "Registering shared interrupts\n");

// 注册第一个设备的中断处理程序
ret = request_irq(SHARED_IRQ_NUM, device_one_interrupt,
IRQF_SHARED, "my_device_one", &dev1_data);
if (ret) {
printk(KERN_ERR "Failed to request IRQ for device one: %d\n", ret);
return ret;
}

// 注册第二个设备的中断处理程序
ret = request_irq(SHARED_IRQ_NUM, device_two_interrupt,
IRQF_SHARED, "my_device_two", &dev2_data);
if (ret) {
printk(KERN_ERR "Failed to request IRQ for device two: %d\n", ret);
free_irq(SHARED_IRQ_NUM, &dev1_data); // 清理第一个设备
return ret;
}

return 0;
}

static void __exit my_exit(void)
{
printk(KERN_INFO "Unregistering shared interrupts\n");

// 必须使用与注册时相同的dev参数来释放中断
free_irq(SHARED_IRQ_NUM, &dev1_data);
free_irq(SHARED_IRQ_NUM, &dev2_data);
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("KEVIN");
MODULE_DESCRIPTION("Example of shared interrupt handling");