Linux中断机制

设备树中断信息节点

IMX6ULL和IMX6UL的中断控制器呈兼容关系,即IMX6ULL继承IMX6UL的GIC节点信息。在imx6ul.dtsi中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
intc: interrupt-controller@a01000 {
compatible = "arm,gic-400", "arm,cortex-a7-gic";
/* GIC_PPI:私有外设中断,非共享
9:中断号为9
GIC_CPU_MASK_SIMPLE(1):PPI只发送给CPU0处理
IRQ_TYPE_LEVEL_HIGH:电平触发,高电平有效 */
interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(1) | IRQ_TYPE_LEVEL_HIGH)>;
/* 中断描述需3个单元 */
#interrupt-cells = <3>;
/* 声明节点为中断控制器 */
interrupt-controller;
/* 父类为自身,说明是顶级GIC */
interrupt-parent = <&intc>;
/* GIC寄存器基地址和大小 */
reg = <0x00a01000 0x1000>, /* GIC分发器基地址 */
<0x00a02000 0x2000>, /* GIC CPU接口基地址 */
<0x00a04000 0x2000>, /* GIC虚拟CPU接口 */
<0x00a06000 0x2000>; /* GIC虚拟分发器 */
};

这里的interrupt-cells通常格式为<中断类型 中断号 标志位><中断号 标志位>,其中

  • 中断类型:
    • 0:SPI,共享外设中断
    • 1:PPI,私有外设中断
  • 中断号:设备中断ID,SPI中断号范围为0987,PPI中断号范围为015
  • 标志位:
    • IRQ_TYPE_NONE:Kernel不参与设置,跟随uboot
    • IRQ_TYPE_EDGE_RISING:上升沿触发
    • IRQ_TYPE_EDGE_FALLING:下降沿触发
    • IRQ_TYPE_EDGE_BOTH :双边沿触发
    • IRQ_TYPE_LEVEL_HIGH:高电平触发
    • IRQ_TYPE_LEVEL_LOW:低电平触发

GPIO也可以作为GIC:

1
2
3
4
5
6
7
8
9
10
11
12
13
gpio5: gpio@20ac000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
/* 共享中断,中断号74,高电平触发
共享中断,中断号75,高电平触发 */
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO5>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller; /* 声明为中断控制器 */
gpio-ranges = <&iomuxc 0 7 10>, <&iomuxc 10 5 2>;
}

中断需要在节点中配置,如imx6ul-14x14-evk.dtsii2c1节点下的:

1
2
3
4
5
6
7
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>; /* GIC为GPIO5 */
interrupts = <0 8>; /* 中断号为0,低电平触发 */
};

简单来说,设备树节点中描述中断的信息有:

  • #interrupt-cells:中断源cell个数
  • interrupt-controller:声明当前节点为中断控制器
  • interrupts:指定中断号和中断触发模式
  • interrupt-parent:父中断(GIC)

用法

  1. 修改设备树
  2. 设备结构体中声明irqreturn_t (*handler)(int, void *)(也可以不声明)和int irqnum(存放中断号)
  3. 通过irq_of_parse_and_map向设备树申请中断信息
1
unsigned int irq_of_parse_and_map(struct device_node *node, int index);

该函数返回申请到的中断号。
4. 初始化时向kernel申请中断:

1
2
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
  • irq:中断号
  • handler:中断服务函数
  • flags:标志位,可选:
1
2
3
4
5
6
7
8
#define IRQF_TRIGGER_NONE	0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
  • *name:生成中断的设备名
  • *dev:Cookie,传递设备结构体地址
  1. 设备注销时释放中断:
1
void *free_irq(unsigned int irq, void *dev);
  • irq:中断号
  • *dev:Cookie,传递设备结构体地址

例程

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
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/libata.h> /* 新版kernel不再支持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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>

#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "imx6uirq"
#define KEY0VALUE 0x01
#define INVAKEY 0xFF
#define KEY_NUM 1

struct irq_keydesc {
int gpio;
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10];
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};

struct irq_led {
int gpio;
};

struct imx6uirq_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
atomic_t keyvalue; /* 有效按键键值 */
atomic_t releasekey; /* 标记是否完成一次按键 */
struct timer_list timer;
struct irq_keydesc irqkeydesc; /* 按键描述结构体 */
struct irq_led irqled;
};

struct imx6uirq_dev imx6uirq;

/* IRQ服务函数,延时10ms,消抖 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 延时10ms,消抖 */
return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定时器服务函数,到期后再次读取按键值,如果还是按下表示按键有效 */
void timer_function(struct timer_list *t)
{
unsigned char value;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = from_timer(dev, t, timer);

keydesc = &dev->irqkeydesc;

value = gpio_get_value(keydesc->gpio); /* 读取IO电平 */
if (value == 0){ /* 如果按下 */
int sta = gpio_get_value(dev->irqled.gpio);
gpio_set_value(dev->irqled.gpio, !sta);
atomic_set(&dev->keyvalue, keydesc->value);
}else{ /* 如果松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
}
}

static int keyio_init(void)
{
int ret = 0;

imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd == NULL){
printk("find node failed\r\n");
return -EINVAL;
}

imx6uirq.irqkeydesc.gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", 0);
if (imx6uirq.irqkeydesc.gpio < 0){
printk("unable get key gpio\r\n");
return -EINVAL;
}

imx6uirq.irqled.gpio = of_get_named_gpio(imx6uirq.nd, "led-gpio", 0);
if (imx6uirq.irqled.gpio < 0){
printk("unable get led gpio\r\n");
return -EINVAL;
}

sprintf(imx6uirq.irqkeydesc.name, "KEY0");
gpio_request(imx6uirq.irqkeydesc.gpio, imx6uirq.irqkeydesc.name);
ret = gpio_direction_input(imx6uirq.irqkeydesc.gpio);
if (ret < 0){
printk("unable set key pin level\r\n");
return ret;
}
imx6uirq.irqkeydesc.irqnum = irq_of_parse_and_map(imx6uirq.nd, 0);
printk("key0: gpio = %d, irqnum = %d\r\n", imx6uirq.irqkeydesc.gpio, imx6uirq.irqkeydesc.irqnum);

gpio_request(imx6uirq.irqled.gpio, "LED");
ret = gpio_direction_output(imx6uirq.irqled.gpio, 1);
if (ret < 0){
printk("unable set led pin level\r\n");
return ret;
}

imx6uirq.irqkeydesc.handler = key0_handler;
imx6uirq.irqkeydesc.value = KEY0VALUE;

ret = request_irq(imx6uirq.irqkeydesc.irqnum, imx6uirq.irqkeydesc.handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc.name, &imx6uirq);

if (ret < 0){
printk("IRQ request failed, error code: %d\r\n", ret);
}

timer_setup(&imx6uirq.timer, timer_function, 0);

return 0;
}

static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq;
return 0;
}

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,
};

static int __init imx6uirq_init(void)
{
if (imx6uirq.major){
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
}else{
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
printk("KEY reg ok, major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);

cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

imx6uirq.class = class_create(IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)){
return PTR_ERR(imx6uirq.class);
}

imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)){
return PTR_ERR(imx6uirq.device);
}

atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();

return 0;
}

static void __exit imx6uirq_exit(void)
{
del_timer_sync(&imx6uirq.timer);

free_irq(imx6uirq.irqkeydesc.irqnum, &imx6uirq);
gpio_free(imx6uirq.irqkeydesc.gpio);

cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}

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

Linux中断机制
http://akichen891.github.io/2025/03/26/Linux中断机制/
作者
Aki
发布于
2025年3月26日
更新于
2025年4月1日
许可协议