本文主要是介绍Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
中断
- 全系列传送门
- 中断基础
- 什么是中断?
- 中断上下文
- Linux中断现在不可以嵌套
- 设备书中的中断节点和相关函数
- 设备树中的中断节点
- 中断相关函数
- 获取中断号相关函数
- 申请中断函数
- 中断处理函数
- free_irq函数
- 按键中断实验(gpio_to_irq函数获取中断号)
- 配置设备树
- 驱动编写
- 错误排查
- oops信息
- 结果验证
- 查看中断信息
- 按键中断实验(interrupt-parent和interrupts属性信息获取中断号)
- 配置设备树
- 驱动编写
- 结果验证
- tasklet相关知识
- 什么是tasklet
- 怎么使用tasklet来设计中断下文
- tasklet定义
- tasklet相关函数
- tasklet_schedule
- tasklet_init函数
- tasklet_kill函数
- 使用tasklet设计中断步骤
- tasklet驱动程序设计
- driver.c源码
- 实验结果
全系列传送门
Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)
Linux嵌入式驱动开发02——驱动编译到内核
Linux嵌入式驱动开发03——杂项设备驱动(附源码)
Linux嵌入式驱动开发04——应用层和内核层数据传输
Linux嵌入式驱动开发05——物理地址到虚拟地址映射
Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写
Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)
Linux嵌入式驱动开发08——字符设备(步步为营)
Linux嵌入式驱动开发09——平台总线详解及实战
Linux嵌入式驱动开发10——设备树开发详解
Linux嵌入式驱动开发11——平台总线模型修改为设备树实例
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作
Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)
Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)
Linux嵌入式驱动开发15——等待队列和工作队列
Linux嵌入式驱动开发16——按键消抖实验(内核定时器)
Linux嵌入式驱动开发17——输入子系统
Linux嵌入式驱动开发18——I2C通信
中断基础
什么是中断?
中断上下文
Linux中断现在不可以嵌套
设备书中的中断节点和相关函数
设备树中的中断节点
设备树中断的参考绑定文档:
/Documentation/devicetree/bindings/arm/gic.txt
比如,在imx6qdl.dtsi文件,可以看到中断控制器的信息
这个是不需要我们进行配置的,只需要会调用就可以了
继续往下看,就可以看到子中断控制器,这个是gpio1的中断控制器
这里是gpio1的中断属性信息,这里我们可以看到是#interrupt-cells = <2>;,有两个cell,所以,有两个中断,一组gpio有32个引脚,那么这里就会分成两组,比如0-15就是中断号66,剩下的16-31中断号就是67
interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,<0 67 IRQ_TYPE_LEVEL_HIGH>;
下面通过实际的代码了解一下中断的使用。
首先,在我们的imx6qdl-sabresd.dtsi文件中, 看到了这么一段代码,显然使用了中断。
代码中,使用了interrupt-parent和interrupts属性来描述中断,interrupt-parent属性值是gpio1,也就是描述了使用了哪一个中断控制器。
interrupts属性设置的是中断源,这里有两个cells,其中18就是具体的中断号,也就是我们对应的引脚号,后面的8就是我们用到的中断的类型。
关于触发类型,有更加规范的写法,通过宏定义已经被定义好了。在如图所示的文件目录下
#define IRQ_TYPE_NONE 0
#define IRQ_TYPE_EDGE_RISING 1
#define IRQ_TYPE_EDGE_FALLING 2
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH 4
#define IRQ_TYPE_LEVEL_LOW 8
但是我们后面有相关的函数,可以直接获取到gpio的中断号,所以,使用gpio的话不需要步骤二也是可以的,但一般都写上。
中断相关函数
获取中断号相关函数
申请中断函数
#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
中断处理函数
关于中断处理函数的返回值:中断程序的返回值是一个特殊类型—irqreturn_t。但是中断程序的返回值却只有两个—IRQ_NONE和IRQ_HANDLED。
IRQ_NONE means we didn’t handle it.中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号.此时返回次值
IRQ_HANDLED means that we did have a valid interrupt and handled it.接收到了准确的中断信号,并且作了相应正确的处理
IRQ_RETVAL(x) selects on the two depending on x being non-zero (for handled)
在实际中,如图所示操作后
在编译过程中会出现警告
free_irq函数
按键中断实验(gpio_to_irq函数获取中断号)
配置设备树
把原设备树文件中的按键相关的代码注释掉,然后写一个我们测试用的设备树节点
因为我们这个是gpio的中断,所以interrupt-parent和interrupts属性信息可以不写,直接在驱动函数中用相关的of函数获取中断编号就可以了。
test_key {compatible = "keys"; // 设置compatible属性pinctrl-names = "default"; // 配置pinctrl子系统pinctrl-0 = <&pinctrl_gpio_keys>;gpios = <&gpio3 29 GPIO_ACTIVE_LOW>; // 描述管脚};
配置完之后,编译
make dtbs
启动并且烧录设备树到我们的开发板,可以看到我们自己定义的节点,信息都是匹配的。
驱动编写
这里继续复用之前实验的代码,在原有的代码基础上进行修改,整体的思路就是
- of_find_node_by_path查找指定路径的节点
- of_get_named_gpio获取设备树编号
- gpio_direction_input方向设置为输入模式
- gpio_to_irq获取中断号
- request_irq请求中断,带入中断的处理函数作为参数
首先说一下,这个driver驱动代码是有问题的,但是正好做一个报错的方案解决
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>#include <linux/irq.h>
#include <linux/irqreturn.h>struct device_node *test_device_node;int gpio_name; // gpio编号
int irq; // 中断号irq_handler_t test_key_handle(int irq, void *args) // 中断处理函数
{printk("test_key_handle ok!!!\n");return IRQ_HANDLED; // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}/*probe函数*/
int beep_probe(struct platform_device *pdev){int ret = 0;printk("beep_probe ok!!!\n");/*间接获取设备节点信息*//********查找指定路径的节点***********/test_device_node = of_find_node_by_path("/test_key"); // 节点名字叫做test_keyif(test_device_node == NULL) {printk("of_find_node_by_path error!!!\n");return -1;}else {printk("of_find_node_by_path ok!!!\n");printk("test_device_node name is %s\n", test_device_node->name);}gpio_name = of_get_named_gpio(test_device_node, "gpios", 0); //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;if(gpio_name < 0) {printk("of_get_named_gpio error!!!\n");return -1;}else{printk("of_get_named_gpio ok!!!\n");}gpio_direction_input(gpio_name); // 因为是模拟按键,方向设置成输入模式irq = gpio_to_irq(gpio_name); // 获取中断号,参数是gpio编号printk("irq is %d\n", irq);/* request_irq* 第一个参数:中断号,* 第二个参数:中断处理函数,* 第三个参数:中断标志(边沿触发方式),* 第四个函数:中断名字,* 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数*/ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL); if(ret < 0) {printk("request_irq failed!!!\n");return -1;}else{printk("request_irq successful!!!\n");}return 0;
}int beep_remove(struct platform_device *pdev){printk("beep_remove ok!!!\n");return 0;
}const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{} // 不写会提示警告
};struct platform_driver beep_device = {.probe = beep_probe, // 这个probe函数其实和 device_driver中的是一样的功能,但是一般是使用device_driver中的那个.remove = beep_remove, // 卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了.driver = {.owner = THIS_MODULE, .of_match_table = of_match_table_test, // 最优先匹配of_match_table其次是id_table最后是name}, // 内置的device_driver 结构体
};static int beep_driver_init(void)
{int ret = 0;printk("beep_driver_init ok!!!\n"); // 在内核中无法使用c语言库,所以不用printfret = platform_driver_register(&beep_device);if(ret < 0){printk("platform_driver_register error!!!\n");return ret;}else{printk("platform_driver_register ok!!!\n");}return 0;
}static void beep_driver_exit(void)
{printk("beep_driver_exit bye!!!\n");free_irq(irq, NULL);platform_driver_unregister(&beep_device);
}module_init(beep_driver_init);
module_exit(beep_driver_exit);MODULE_LICENSE("GPL"); //声明模块拥有开源许可
错误排查
上面的driver.c代码编译之后,发送到开发板,然后运行,发现开发板给我们报错。
那么来查看一下这个错误
这么一大串的错误,应该怎么办呢?
oops信息
其实错误信息打印的还是比较明确的
//无法处理内核页面请求的虚拟地址
[ 66.286951] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[ 66.295109] pgd = a966c000
[ 66.297849] [00000000] *pgd=38b1d831, *pte=00000000, *ppte=00000000
//内部错误oops
[ 66.304246] Internal error: Oops: 17 [#1] PREEMPT SMP ARM
//表示内部错误发生在driver.ko驱动模块里
[ 66.309659] Modules linked in: driver(O+)
[ 66.313736] CPU: 2 PID: 848 Comm: insmod Tainted: G O 4.1.15 #1
[ 66.320799] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[ 66.327343] task: a8e604c0 ti: a924c000 task.ti: a924c000
//PC值:程序运行成功的最后一次地址,位于first_drv_open()函数里,偏移值0x4,该函数总大小0x34
[ 66.332765] PC is at strcmp+0x4/0x34
//LR值
[ 66.336371] LR is at kset_find_obj+0x3c/0xa4/*发生错误时的各个寄存器值*/
[ 66.340662] pc : [<802b6184>] lr : [<802b0c30>] psr: a0070013
[ 66.340662] sp : a924ddf8 ip : 00000000 fp : 00000124
[ 66.352156] r10: 5640699c r9 : 00000001 r8 : 00000000
[ 66.357396] r7 : a803b688 r6 : 00000000 r5 : a803b680 r4 : a8148000
[ 66.363939] r3 : 00000043 r2 : 0000013f r1 : 00000000 r0 : a8144dc1
[ 66.370484] Flags: NzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
[ 66.377633] Control: 10c53c7d Table: 3966c04a DAC: 00000015
//发生错误时,进程名称为insmod
[ 66.383394] Process insmod (pid: 848, stack limit = 0xa924c210)
//栈信息
[ 66.389329] Stack: (0xa924ddf8 to 0xa924e000)
[ 66.393703] dde0: 7f000564 80be2860
[ 66.401904] de00: a9bf9440 7f000158 00000000 8038a570 7f000564 8038a5f4 80be2860 80be2860
[ 66.410105] de20: a9bf9440 7f00017c 80be2860 80009718 abc7cac0 800e4224 00000002 a92d6500
[ 66.418305] de40: 8040003e 00000001 00010000 800b1eb8 00000002 8040003e abc6d300 abc7cac0
[ 66.426505] de60: 80bdbafc 80bd8430 a8001f00 80bdbafc 00000124 800e59fc 7f0005a8 00000001
[ 66.434706] de80: 7f0005a8 00000001 a9bf9c00 7f0005a8 a9bf9680 808229e0 7f0005a8 a9bf9680
[ 66.442894] dea0: a924df58 00000001 a9bf9688 80095154 7f0005b4 00007fff 80092194 00000000
[ 66.451075] dec0: 00000000 7f0005f0 00000000 7f0006f0 c0e648a4 7f0005b4 00000000 80830a34
[ 66.459257] dee0: c0e63000 000018f4 00001924 00000000 00000010 00000000 00000000 00000000
[ 66.467438] df00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 66.475619] df20: 00000000 00000000 00000000 00000000 00000010 00000000 00000003 00027bf4
[ 66.483800] df40: 0000017b 8000f604 a924c000 00000000 00000000 800955b8 c0e63000 000018f4
[ 66.491981] df60: c0e64584 c0e644b0 c0e63fb0 0000070c 0000098c 00000000 00000000 00000000
[ 66.500163] df80: 00000014 00000015 0000000d 0000000a 00000008 00000000 0028c008 00000000
[ 66.508345] dfa0: 00000000 8000f480 0028c008 00000000 00000003 00027bf4 00000000 00000000
[ 66.516527] dfc0: 0028c008 00000000 00000000 0000017b 00000000 00000002 76fca000 00000000
[ 66.524708] dfe0: 7eb00c18 7eb00c08 0001f2e0 76f08e90 60070010 00000003 00000000 00000000
//回溯信息
[ 66.532898] [<802b6184>] (strcmp) from [<802b0c30>] (kset_find_obj+0x3c/0xa4)
[ 66.540045] [<802b0c30>] (kset_find_obj) from [<8038a570>] (driver_find+0x14/0x30)
[ 66.547620] [<8038a570>] (driver_find) from [<8038a5f4>] (driver_register+0x68/0xf8)
[ 66.555373] [<8038a5f4>] (driver_register) from [<7f00017c>] (beep_driver_init+0x24/0x54 [driver])
[ 66.564344] [<7f00017c>] (beep_driver_init [driver]) from [<80009718>] (do_one_initcall+0x8c/0x1d4)
[ 66.573405] [<80009718>] (do_one_initcall) from [<808229e0>] (do_init_module+0x5c/0x1a8)
[ 66.581510] [<808229e0>] (do_init_module) from [<80095154>] (load_module+0x1ba8/0x1e50)
[ 66.589523] [<80095154>] (load_module) from [<800955b8>] (SyS_finit_module+0x80/0x90)
[ 66.597364] [<800955b8>] (SyS_finit_module) from [<8000f480>] (ret_fast_syscall+0x0/0x3c)
[ 66.605549] Code: e5e32001 1afffffb e12fff1e e4d03001 (e4d12001)
[ 66.611683] ---[ end trace 44e08424ce7c760b ]---Message from syslogd@imx6qsab[ 66.616394] note: insmod[848] exited with preempt_count 1
resd at Wed Feb 24 11:05:14 2021 ...Segmentation fault
通过回溯信息,我们得到以下的信息:
SyS_finit_module -> load_module -> do_init_module -> do_one_initcall -> beep_driver_init -> driver_register -> driver_find -> kset_find_obj -> strcmp
所以我们的错误是出现在了驱动这块
回到我们的代码中,通过对驱动设置相关的模块检查,发现是代码的编写过程中,少了两部分的代码
结果验证
补齐两行相关的代码之后,我们在进行模块的安装
正常加载模块后,操作我们的按键,就可以发现,我们预设的信息正常打印,试验成功。
查看中断信息
最后再验证一下中断的信息,是否正确,前面我们已经打印出来了irq is 132
对应到
cat /proc/interrupts
看到了test_key,这个是因为这段代码中的命名
继续查看中断信息
cd /proc/irq
可以看到很多的中断号
我们查找这次测试的中断号132,打开
cat spurious
可以看到一个count 11这个值,这个值对应的是我们触发了多少次的中断,我们刚才正好是按键了11次
按键中断实验(interrupt-parent和interrupts属性信息获取中断号)
配置设备树
跟上面的一样,需要在设备树里添加信息,这里主要就是两行代码的添加。
test_key {compatible = "keys"; // 设置compatible属性pinctrl-names = "default"; // 配置pinctrl子系统pinctrl-0 = <&pinctrl_gpio_keys>;gpios = <&gpio3 29 GPIO_ACTIVE_LOW>; // 描述管脚interrupt-parent = <&gpio3>;interrupts = <29 IRQ_TYPE_EDGE_BOTH>;};
烧录到开发板,可以看到有了中断信息。
驱动编写
这里只是修改了中断号的获取方式,所以步骤与之前基本一致
- of_find_node_by_path查找指定路径的节点
- of_get_named_gpio获取设备树编号
- gpio_direction_input方向设置为输入模式
- irq_of_parse_and_map获取中断号
- request_irq请求中断,带入中断的处理函数作为参数
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>struct device_node *test_device_node;int gpio_name; // gpio编号
int irq; // 中断号irq_handler_t test_key_handle(int irq, void *args) // 中断处理函数
{printk("test_key_handle ok!!!\n");return IRQ_HANDLED; // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}/*probe函数*/
int beep_probe(struct platform_device *pdev){int ret = 0;printk("beep_probe ok!!!\n");/*间接获取设备节点信息*//********查找指定路径的节点***********/test_device_node = of_find_node_by_path("/test_key"); // 节点名字叫做test_keyif(test_device_node == NULL) {printk("of_find_node_by_path error!!!\n");return -1;}else {printk("of_find_node_by_path ok!!!\n");printk("test_device_node name is %s\n", test_device_node->name);}gpio_name = of_get_named_gpio(test_device_node, "gpios", 0); //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;if(gpio_name < 0) {printk("of_get_named_gpio error!!!\n");return -1;}else{printk("of_get_named_gpio ok!!!\n");}gpio_direction_input(gpio_name); // 因为是模拟按键,方向设置成输入模式// irq = gpio_to_irq(gpio_name); // 通过gpio函数获取中断号,参数是gpio编号/* irq_of_parse_and_map,通过设备树中interrupts获取中断号* 第一个参数:设备树节点* 第二个参数:索引值,这里只有一个,所以是0* */irq = irq_of_parse_and_map(test_device_node, 0);printk("irq is %d\n", irq);/* request_irq* 第一个参数:中断号,* 第二个参数:中断处理函数,* 第三个参数:中断标志(边沿触发方式),* 第四个函数:中断名字,* 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数*/ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL); if(ret < 0) {printk("request_irq failed!!!\n");return -1;}else{printk("request_irq successful!!!\n");}return 0;
}const struct platform_device_id beep_id_table = {.name = "keys",
};int beep_remove(struct platform_device *pdev){printk("beep_remove ok!!!\n");return 0;
}const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{} // 不写会提示警告
};struct platform_driver beep_device = {.probe = beep_probe, // 这个probe函数其实和 device_driver中的是一样的功能,但是一般是使用device_driver中的那个.remove = beep_remove, // 卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了.driver = {.owner = THIS_MODULE, .name = "keys",.of_match_table = of_match_table_test, // 最优先匹配of_match_table其次是id_table最后是name}, // 内置的device_driver 结构体.id_table = &beep_id_table,
};static int beep_driver_init(void)
{int ret = 0;printk("beep_driver_init ok!!!\n"); // 在内核中无法使用c语言库,所以不用printfret = platform_driver_register(&beep_device);if(ret < 0){printk("platform_driver_register error!!!\n");return ret;}else{printk("platform_driver_register ok!!!\n");}return 0;
}static void beep_driver_exit(void)
{printk("beep_driver_exit bye!!!\n");free_irq(irq, NULL);platform_driver_unregister(&beep_device);
}module_init(beep_driver_init);
module_exit(beep_driver_exit);MODULE_LICENSE("GPL"); //声明模块拥有开源许可
结果验证
按键之后也可以顺利进入中断。
tasklet相关知识
什么是tasklet
怎么使用tasklet来设计中断下文
tasklet定义
tasklet相关函数
tasklet_schedule
tasklet_init函数
tasklet_kill函数
使用tasklet设计中断步骤
tasklet驱动程序设计
driver.c源码
遵循上面的步骤,添加相应的代码就可以了
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>struct device_node *test_device_node;struct tasklet_struct test_key_tasklet_struct;int gpio_name; // gpio编号
int irq; // 中断号/*打印一百次,模拟一个较长实践的任务*/
void test(unsigned long data)
{int i = 100;while(i--){printk("test_key is %d\n",i);}
}/*中断上文*/
irq_handler_t test_key_handle(int irq, void *args) // 中断处理函数
{printk("start\n");/*启动中断下文*/tasklet_schedule(&test_key_tasklet_struct);printk("tasklet_schedule end!!!\n");return IRQ_HANDLED; // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}/*probe函数*/
int beep_probe(struct platform_device *pdev){int ret = 0;printk("beep_probe ok!!!\n");/*间接获取设备节点信息*//********查找指定路径的节点***********/test_device_node = of_find_node_by_path("/test_key"); // 节点名字叫做test_keyif(test_device_node == NULL) {printk("of_find_node_by_path error!!!\n");return -1;}else {printk("of_find_node_by_path ok!!!\n");printk("test_device_node name is %s\n", test_device_node->name);}gpio_name = of_get_named_gpio(test_device_node, "gpios", 0); //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;if(gpio_name < 0) {printk("of_get_named_gpio error!!!\n");return -1;}else{printk("of_get_named_gpio ok!!!\n");}gpio_direction_input(gpio_name); // 因为是模拟按键,方向设置成输入模式// irq = gpio_to_irq(gpio_name); // 通过gpio函数获取中断号,参数是gpio编号/* irq_of_parse_and_map,通过设备树中interrupts获取中断号* 第一个参数:设备树节点* 第二个参数:索引值,这里只有一个,所以是0* */irq = irq_of_parse_and_map(test_device_node, 0);printk("irq is %d\n", irq);/* request_irq申请中断* 第一个参数:中断号,* 第二个参数:中断处理函数,* 第三个参数:中断标志(边沿触发方式),* 第四个函数:中断名字,* 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数*/ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL); if(ret < 0) {printk("request_irq failed!!!\n");return -1;}else{printk("request_irq successful!!!\n");}/* tasklet_init初始化* 第一个参数:tasklet_struct结构体* 第二个参数:中断下文执行的函数* 第三个参数:传递给中断下文的参数*/tasklet_init(&test_key_tasklet_struct, test, 0);return 0;
}const struct platform_device_id beep_id_table = {.name = "keys",
};int beep_remove(struct platform_device *pdev){printk("beep_remove ok!!!\n");return 0;
}const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{} // 不写会提示警告
};struct platform_driver beep_device = {.probe = beep_probe, // 这个probe函数其实和 device_driver中的是一样的功能,但是一般是使用device_driver中的那个.remove = beep_remove, // 卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了.driver = {.owner = THIS_MODULE, .name = "keys",.of_match_table = of_match_table_test, // 最优先匹配of_match_table其次是id_table最后是name}, // 内置的device_driver 结构体.id_table = &beep_id_table,
};static int beep_driver_init(void)
{int ret = 0;printk("beep_driver_init ok!!!\n"); // 在内核中无法使用c语言库,所以不用printfret = platform_driver_register(&beep_device);if(ret < 0){printk("platform_driver_register error!!!\n");return ret;}else{printk("platform_driver_register ok!!!\n");}return 0;
}static void beep_driver_exit(void)
{printk("beep_driver_exit bye!!!\n");free_irq(irq, NULL);tasklet_kill(&test_key_tasklet_struct);platform_driver_unregister(&beep_device);
}module_init(beep_driver_init);
module_exit(beep_driver_exit);MODULE_LICENSE("GPL"); //声明模块拥有开源许可
实验结果
我们也可以传入参数,只需要把我们之前设置的i=100,修改成如下所示代码
可以得到正确的实验结果,循环计数了一百次
到这里可能会有一个问题,就是为什么我们在test_key_handle里面打印的开始和结束的信息,会一起显示
这是因为tasklet_schedule绑定的函数不会立即执行,而是等我们的中断结束后的不确定时间,再开始执行我们的中断下文函数。
这篇关于Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!