Linux子系统之中断

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;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值