文章目录
1.中断子系统
1.1中断子系统实现原理
异常处理流程:4大步,3小步
1.2中断子系统的API
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
功能:解析得到软中断号
参数:
@dev:节点的指针
@inde:索引号
返回值:成功返回软中断,失败返回0
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
功能:注册中断
参数:
@irq:软中断号
@handler:中断处理函数
irqreturn_t irq_handle(int irqno, void * dev)
{
return IRQ_NONE; //中断处理函数执行失败
return IRQ_HANDLED; //中断处理函数执行成功了
}
@flags:中断触发方式
IRQF_TRIGGER_RISING :上升沿
IRQF_TRIGGER_FALLING :下降沿
IRQF_TRIGGER_HIGH :高电平
RQF_TRIGGER_LOW :低电平
IRQF_SHARED:共享中断
@name:中断的名字
cat /proc/interrupts
@dev:向中断处理函数传递的参数
返回值:成功返回0,失败返回错误码
const void *free_irq(unsigned int irq, void *dev_id)
功能:释放中断
参数:
@irq:软中断号
@dev_id:向中断处理函数传递的参数
返回值:是中断注册时候的第四个参数
1.3中断子系统的设备树添加
1.3.1按键中断的硬件原理图
1.3.2控制器的设备树
stm32mp151.dtsi
gic设备树
intc: interrupt-controller@a0021000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xa0021000 0x1000>,
<0xa0022000 0x2000>;
};
exti设备树
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
exti: interrupt-controller@5000d000 {
compatible = "st,stm32mp1-exti", "syscon";
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x5000d000 0x400>;
};
};
gpiof的设备树
pinctrl: pin-controller@50002000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "st,stm32mp157-pinctrl";
interrupt-parent = <&exti>;
gpiof: gpio@50007000 {
interrupt-controller;
#interrupt-cells = <2>;
status = "okay";
};
};
1.3.3参考内核帮助文档,编写自己的设备树
Documentation/devicetree/bindings/interrupt-controller$ vi interrupts.txt
b) two cells
------------
The #interrupt-cells property is set to 2 and the first cell defines the
index of the interrupt within the controller, while the second cell is used
to specify any of the following flags:
- bits[3:0] trigger type and level flags
1 = low-to-high edge triggered
2 = high-to-low edge triggered
4 = active high level-sensitive
8 = active low level-sensitive
myirq{
compatilbe = "hqyj,irq";
interrupt-parent = <&gpiof>;
interrupts = <9 0>, <7 0>,<8 0>;
};
1.4按键中断的实例(一个按键)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
// myirq{
// compatilbe = "hqyj,irq";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>, <7 0>,<8 0>;
// };
struct device_node *node;
unsigned int irqno;
irqreturn_t key_irq(int irq, void *dev)
{
printk("key1 down..........\n");
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
int ret;
//1.获取到设备树的节点
node = of_find_compatible_node(NULL,NULL,"hqyj,irq");
if(node == NULL){
printk("get node error\n");
return -ENODATA;
}
//2.解析得到软中断号
irqno = irq_of_parse_and_map(node,0);
if(irqno==0){
printk("get irq number error\n");
return -EAGAIN;
}
//3.注册中断
ret = request_irq(irqno,key_irq,IRQF_TRIGGER_FALLING,"keyirq",NULL);
if(ret){
printk("reqest irq error\n");
return ret;
}
return 0;
}
static void __exit myirq_exit(void)
{
free_irq(irqno,NULL);
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
1.5按键中断的实例(三个按键)
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
// myirq{
// compatilbe = "hqyj,irq";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>, <7 0>,<8 0>;
// };
struct device_node* node;
unsigned int irqno[3] = { 0 };
char* name[] = { "key1", "key2", "key3" };
irqreturn_t key_irq(int irq, void* dev)
{
switch ((int)dev) {
case 0:
printk("key1 down..........\n");
break;
case 1:
printk("key2 down..........\n");
break;
case 2:
printk("key3 down..........\n");
break;
}
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
int ret, i;
// 1.获取到设备树的节点
node = of_find_compatible_node(NULL, NULL, "hqyj,irq");
if (node == NULL) {
printk("get node error\n");
return -ENODATA;
}
// 2.解析得到软中断号
for (i = 0; i < ARRAY_SIZE(irqno); i++) {
irqno[i] = irq_of_parse_and_map(node, i);
if (irqno[i] == 0) {
printk("get irq number error\n");
return -EAGAIN;
}
// 3.注册中断
ret = request_irq(irqno[i], key_irq, IRQF_TRIGGER_FALLING,
name[i], (void*)i);
if (ret) {
printk("reqest irq error\n");
return ret;
}
}
return 0;
}
static void __exit myirq_exit(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(irqno); i++) {
free_irq(irqno[i], (void *)i);
}
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
2.Linux内核定时器
2.1内核定时器的定时时间问题
1.当前的时间如何获取?
jiffies:内核时钟节拍数,从内核启动开始,一直计数的变量。
2.定时的数值加1走的时间?
在linux内核中可以通过make menuconfig选配时间,选配后的选项保存在.config中
CONFIG_HZ=100 ----> 10ms 开发板
2.2内核定时器的API
1.分配对象
struct timer_list {
struct hlist_node entry; //构成链表
unsigned long expires; //定时时间 jiffies+100 定时1s jiffies+1 定时10ms
void (*function)(struct timer_list *); //定时器处理函数
u32 flags; //填写为0
};
struct timer_list mytimer;
2.对象初始化
mytimer.expires = jiffies+HZ; //# define HZ CONFIG_HZ,任意内核版本中定时时间都是1s
timer_setup(&mytimer, 定时器处理函数, 0)
3.注册定时器
void add_timer(struct timer_list *timer);
//定时器在注册的时候就会启动了,并只会启动执行1次,定时器只能注册一次
mod_timer(struct timer_list *timer, unsigned long expires);
//再次启动定时器
4.删除定时器
del_timer(struct timer_list * timer);
//删除
2.3定时器控制LED闪烁的实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
// myleds{
// compatible = "hqyj,leds";
// core_leds {
// led1 = <&gpioz 5 0>;
// led2 = <&gpioz 6 0>;
// led3 = <&gpioz 7 0>;
// };
// extend_leds{
// led1 = <&gpioe 10 0>;
// led2 = <&gpiof 10 0>;
// led3 = <&gpioe 8 0>;
// };
// };
struct device_node* node;
char* pname[] = { "led1", "led2", "led3" };
int gpiono[3] = { 0 };
struct timer_list mytimer;
void timer_led_blink(struct timer_list *timer)
{
gpio_set_value(gpiono[0],!gpio_get_value(gpiono[0]));
mod_timer(&mytimer,jiffies+HZ);
}
static int __init myleds_init(void)
{
int i, ret;
// 1.获取device_node
node = of_find_node_by_path("/myleds/extend_leds");
if (node == NULL) {
printk("parse device node error\n");
return -ENODATA;
}
// 2.获取gpio编号
for (i = 0; i < ARRAY_SIZE(gpiono); i++) {
gpiono[i] = of_get_named_gpio(node, pname[i], 0);
if (gpiono[i] < 0) {
printk("get gpio number error\n");
return -EINVAL;
}
// 3.申请要使用的gpio
if ((ret = gpio_request(gpiono[i], NULL)) != 0) {
printk("reqeust gpio error");
return ret;
}
// 4.操作gpio
gpio_direction_output(gpiono[i], 1);
}
// 3.初始化并启动定时器
mytimer.expires = jiffies + HZ;
timer_setup(&mytimer,timer_led_blink,0);
add_timer(&mytimer);
return 0;
}
static void __exit myleds_exit(void)
{
int i;
del_timer(&mytimer);
for (i = 0; i < ARRAY_SIZE(gpiono); i++) {
gpio_set_value(gpiono[i], 0);
gpio_free(gpiono[i]);
}
}
module_init(myleds_init);
module_exit(myleds_exit);
MODULE_LICENSE("GPL");
2.4使用定时器完成按键中断的消抖
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
// myirq{
// compatilbe = "hqyj,irq";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>, <7 0>,<8 0>;
// key = <&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;
// };
struct device_node* node;
unsigned int irqno[3] = { 0 };
char* name[] = { "key1", "key2", "key3" };
struct timer_list mytimer;
int gpiono;
void timer_irq(struct timer_list *timer)
{
if(!gpio_get_value(gpiono)){
printk("key1 down.........\n");
}
}
irqreturn_t key_irq(int irq, void* dev)
{
mod_timer(&mytimer,jiffies+1);
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
int ret, i;
//0.定时器的初始化
mytimer.expires = jiffies+1;
timer_setup(&mytimer,timer_irq,0);
add_timer(&mytimer);
// 1.获取到设备树的节点
node = of_find_compatible_node(NULL, NULL, "hqyj,irq");
if (node == NULL) {
printk("get node error\n");
return -ENODATA;
}
// 2.解析得到软中断号
for (i = 0; i < ARRAY_SIZE(irqno); i++) {
irqno[i] = irq_of_parse_and_map(node, i);
if (irqno[i] == 0) {
printk("get irq number error\n");
return -EAGAIN;
}
// 3.注册中断
ret = request_irq(irqno[i], key_irq, IRQF_TRIGGER_FALLING,
name[i], (void*)i);
if (ret) {
printk("reqest irq error\n");
return ret;
}
}
//4.获取gpio号
gpiono = of_get_named_gpio(node,"key",0);
if(gpiono < 0){
printk("get gpio number error\n");
return gpiono;
}
return 0;
}
static void __exit myirq_exit(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(irqno); i++) {
free_irq(irqno[i], (void *)i);
}
del_timer(&mytimer);
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
3.中断底半部
3.1为什么需要中断底半部
在中断顶半部中不能够做延时,耗时,甚至休眠的动作,也就是说在中断顶半部中
只能做简短不耗时的操作。但是有的时候又希望在中断到来的时候做尽可能多的事情。
所以两者就产生矛盾,内核为了解决这一矛盾引入了中断底半部的机制。中断底半部的
机制有软中断(32个),tasklet,工作队列。
eg:在网卡中断到来的时候,需要从网络上获取网络数据。这一过程就是耗时的操作,
所以将这个读取数据的过程放在中断底半部中完成即可。
3.2中断底半部之tasklet
3.2.1tasklet的原理
tasklet是基于软中断实现的,tasklet没有个数限制,因为它是通过链表实现的。
tasklet是中断的一个部分,不能脱离中断单独执行。tasklet工作在中断上下文。
可以在tasklet的底半部处理函数中做耗时操作,但是也不能够做延时或者休眠的
动作。在中断顶半部执行即将结束的时候开启底半部(标志位置位),在中断顶
半部执行结束的时候,发现底半部标志位置位了,就执行底半部。
3.2.2tasklet的API
1.分配对象
struct tasklet_struct
{
struct tasklet_struct *next; //构成链表的成员
unsigned long state; //触发的状态
atomic_t count; //触发次数
bool use_callback; // 真,新版本,假,旧版本
union {
void (*func)(unsigned long data); //旧版本
void (*callback)(struct tasklet_struct *t); //新版本
};
unsigned long data; //向底半部处理函数传参
};
struct tasklet_struct tasklet;
2.对象初始化
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data) //旧版本初始化
void tasklet_setup(struct tasklet_struct *t,
void (*callback)(struct tasklet_struct *)) //新版本的初始化
3.调用执行
void tasklet_schedule(struct tasklet_struct *t)
3.2.3tasklet的实例
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
// myirq{
// compatilbe = "hqyj,irq";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>, <7 0>,<8 0>;
// key = <&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;
// };
struct device_node* node;
unsigned int irqno;
int gpiono;
struct tasklet_struct tasklet;
void irq_tasklet(struct tasklet_struct *ts)
{
int i=30;
while(--i){
printk("i = %d\n",i);
}
}
irqreturn_t key_irq(int irq, void* dev)
{
tasklet_schedule(&tasklet);
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
int ret;
//0.初始化tasklet
tasklet_setup(&tasklet,irq_tasklet);
// 1.获取到设备树的节点
node = of_find_compatible_node(NULL, NULL, "hqyj,irq");
if (node == NULL) {
printk("get node error\n");
return -ENODATA;
}
// 2.解析得到软中断号
irqno = irq_of_parse_and_map(node, 0);
if (irqno == 0) {
printk("get irq number error\n");
return -EAGAIN;
}
// 3.注册中断
ret = request_irq(irqno, key_irq, IRQF_TRIGGER_FALLING,
"key1", NULL);
if (ret) {
printk("reqest irq error\n");
return ret;
}
return 0;
}
static void __exit myirq_exit(void)
{
free_irq(irqno, NULL);
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
3.3中断底半部之工作队列
3.3.1工作队列的原理
工作队列:工作队列工作进程上下文,没有个数限制。工作队列可以和中断顶半部结合使用,
也可以脱离中断单独执行。在工作队列的底半部处理函数中可以做延时,耗时,甚至休眠的
动作。(原理:在内核启动的时候默认会启动一个events的线程,这个线程默认处于休眠的
状态。如果需要让它执行你的底半部任务,就需要将你的任务放在队列的队列,唤醒这个线
程后这个线程就能够执行你的底半部处理函数了)
3.3.2工作队列的API
1.分配对象
struct work_struct {
atomic_long_t data; //传递的数据
struct list_head entry; //构成队列
work_func_t func; //底半部处理函数
//typedef void (*work_func_t)(struct work_struct *work);
};
struct work_struct work;
2.对象初始化
INIT_WORK(&work, 底半部处理函数);
3.调用执行
schedule_work(struct work_struct *work)
4.取消工作队列的执行
cancel_work_sync(&work);
3.3.3工作队列的实例1
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/delay.h>
// myirq{
// compatilbe = "hqyj,irq";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>, <7 0>,<8 0>;
// key = <&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;
// };
struct device_node* node;
unsigned int irqno;
int gpiono;
struct work_struct work;
void irq_work(struct work_struct *work)
{
int i=30;
while(--i){
printk("i = %d\n",i);
mdelay(1000);
}
}
irqreturn_t key_irq(int irq, void* dev)
{
schedule_work(&work);
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
int ret;
//0.初始化工作队列
INIT_WORK(&work,irq_work);
// 1.获取到设备树的节点
node = of_find_compatible_node(NULL, NULL, "hqyj,irq");
if (node == NULL) {
printk("get node error\n");
return -ENODATA;
}
// 2.解析得到软中断号
irqno = irq_of_parse_and_map(node, 0);
if (irqno == 0) {
printk("get irq number error\n");
return -EAGAIN;
}
// 3.注册中断
ret = request_irq(irqno, key_irq, IRQF_TRIGGER_FALLING,
"key1", NULL);
if (ret) {
printk("reqest irq error\n");
return ret;
}
return 0;
}
static void __exit myirq_exit(void)
{
free_irq(irqno, NULL);
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
3.3.4工作队列的实例2
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
struct work_struct work;
void work_func(struct work_struct* wk)
{
printk("******************\n");
mdelay(1000);
schedule_work(&work);
}
static int __init mywork_init(void)
{
INIT_WORK(&work, work_func);
schedule_work(&work);
return 0;
}
static void __exit mywork_exit(void)
{
cancel_work_sync(&work);
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
struct timer_list mytimer;
struct work_struct work;
void work_func(struct work_struct* wk)
{
//1.读取数据,上报数据
printk("******************\n");
mod_timer(&mytimer,jiffies+HZ);
}
void timer_work(struct timer_list *tm)
{
//定时时间到了就启动工作队列
schedule_work(&work);
}
static int __init mywork_init(void)
{
//初始化工作队列
INIT_WORK(&work, work_func);
//初始化定时
mytimer.expires = jiffies + HZ;
timer_setup(&mytimer,timer_work,0);
add_timer(&mytimer);
return 0;
}
static void __exit mywork_exit(void)
{
del_timer(&mytimer);
cancel_work_sync(&work);
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
4.作业
1.使用驱动代码实现如下要求
a.应用程序通过阻塞的io模型来读取status变量的值 //等待队列
b.status是内核驱动中的一个变量,代表LED1的状态
c.status的值随着按键按下而改变(按键中断) //按键中断
例如status=0 按下按键status=1 ,再次按下按键status=0
d.在按下按键的时候需要同时将led1的状态取反 //gpio子系统
e.驱动中需要编写字符设备驱动 //字符设备驱动
f.驱动中需要自动创建设备节点 //自动创建设备节点
g.这个驱动需要的所有设备信息放在设备树的同一个节点中 //设备树
key_irq_led{
compatible = "hqyj,key_irq_led";
interrupt-parent = <&gpiof>;
interrupts = <9 0>;
led1 = <&gpioe 10 0>;
};
key_irq_led.c
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
// key_irq_led{
// compatible = "hqyj,key_irq_led";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>;
// led1 = <&gpioe 10 0>;
// };
#define CNAME "key_irq_led"
struct device_node* node;
struct gpio_desc* desc;
unsigned int irqno;
int major;
const int count = 1;
struct class* cls;
struct device* dev;
wait_queue_head_t wq;
int condition = 0;
int status = 0;
irqreturn_t key_irq_led_handle(int irq, void* dev)
{
//取反灯的状态
status = gpiod_get_value(desc);
status = !status;
gpiod_set_value(desc, status);
//唤醒阻塞
condition = 1;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
int gpio_irq_init(void)
{
int ret;
//获取节点
node = of_find_node_by_path("/key_irq_led");
if (node == NULL) {
printk("get node error\n");
ret = -ENODATA;
goto ERR1;
}
//获取并初始化gpio
desc = gpiod_get_from_of_node(node, "led1", 0, GPIOD_OUT_LOW, NULL);
if (IS_ERR(desc)) {
printk("get gpio error\n");
ret = PTR_ERR(desc);
goto ERR1;
}
//获取并注册中断
irqno = irq_of_parse_and_map(node, 0);
if (irqno == 0) {
printk("parse irqno error\n");
ret = -EINVAL;
goto ERR2;
}
ret = request_irq(irqno, key_irq_led_handle,
IRQF_TRIGGER_FALLING, CNAME, NULL);
if (ret) {
printk("request irq error\n");
goto ERR2;
}
return 0;
ERR2:
gpiod_put(desc);
ERR1:
return ret;
}
void gpio_irq_deinit(void)
{
free_irq(irqno, NULL);
gpiod_put(desc);
}
int key_irq_led_open(struct inode* inode, struct file* file)
{
return 0;
}
ssize_t key_irq_led_read(struct file* filp,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
// 1.判断用户是否是阻塞打开
if (filp->f_flags & O_NONBLOCK) {
return -EINVAL;
} else {
// 2.阻塞
ret = wait_event_interruptible(wq, condition);
if (ret) {
printk("receive signal....\n");
return ret;
}
}
// 3.将灯的状态返回到用户空间
if (size > sizeof(status))
size = sizeof(status);
ret = copy_to_user(ubuf, &status, size);
if (ret) {
printk("copy data to user error\n");
return -EIO;
}
//4.条件清零
condition = 0;
return size;
}
int key_irq_led_close(struct inode* inode, struct file* file)
{
return 0;
}
static struct file_operations fops = {
.open = key_irq_led_open,
.read = key_irq_led_read,
.release = key_irq_led_close,
};
static int __init key_irq_led_init(void)
{
int ret;
// 1.解析设备树,获取并初始化gpio,获取并注册中断
if((ret = gpio_irq_init())!=0)
return ret;
// 2.注册字符设备驱动
major = register_chrdev(0, CNAME, &fops);
if (major < 0) {
printk("register chrdev error\n");
ret = -EAGAIN;
goto ERR1;
}
// 3.创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR2;
}
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
if (IS_ERR(dev)) {
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR3;
}
//初始化等待队列头
init_waitqueue_head(&wq);
return 0;
ERR3:
class_destroy(cls);
ERR2:
unregister_chrdev(major, CNAME);
ERR1:
gpio_irq_deinit();
return ret;
}
static void __exit key_irq_led_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, CNAME);
gpio_irq_deinit();
}
module_init(key_irq_led_init);
module_exit(key_irq_led_exit);
MODULE_LICENSE("GPL");
test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int fd,status;
if((fd = open("/dev/key_irq_led",O_RDWR)) < 0){
perror("open error");
exit(EXIT_FAILURE);
}
while(1){
read(fd,&status,sizeof(status));
printf("status = %d\n",status);
}
close(fd);
return 0;
}
ror\n");
ret = PTR_ERR(dev);
goto ERR3;
}
//初始化等待队列头
init_waitqueue_head(&wq);
return 0;
ERR3:
class_destroy(cls);
ERR2:
unregister_chrdev(major, CNAME);
ERR1:
gpio_irq_deinit();
return ret;
}
static void __exit key_irq_led_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, CNAME);
gpio_irq_deinit();
}
module_init(key_irq_led_init);
module_exit(key_irq_led_exit);
MODULE_LICENSE(“GPL”);
## test.c
```c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int fd,status;
if((fd = open("/dev/key_irq_led",O_RDWR)) < 0){
perror("open error");
exit(EXIT_FAILURE);
}
while(1){
read(fd,&status,sizeof(status));
printf("status = %d\n",status);
}
close(fd);
return 0;
}