文章目录
全系列传送门
Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)
Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写
Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)
Linux嵌入式驱动开发11——平台总线模型修改为设备树实例
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作
Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)
Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)
等待队列
阻塞与非阻塞
等待队列基础知识
所以用在中断中,我们如果有一个应用场景,就是app程序一直在运行,但是我们不希望一直在执行,就可以只有在我们按键后,才会相应执行的操作,所以使用等待队列的知识来进行程序设计。让app程序进入等待队列,以免app一直运行,占用了大部分的资源导致其他程序无法正常的使用。
等待队列头
等待队列相关函数
init_waitqueue_head宏
wait_event宏
wait_event_interruptible宏
wake_up宏
wake_up_interruptible宏
等待队列示例代码
分析
我们现在的app程序,使用read函数,不断地读取底层的value数据,会导致占用率过高,影响其他的程序工作,实际上我们只需要在按键执行后才可以读取到value的有效值
所以,使用等待队列,把read函数的操作挂起,然后当发生按键中断的时候,我们再唤醒传递value的数值
代码编写
在编译完成代码之后,运行我们的驱动模块,报错
通过对报错信息的分析,可以看到是杂项设备注册后面的程序出现了问题,经过排查,发现了两个错误
第一个就是与设备节点的compatible属性匹配的问题,一定要一致,所以这里要修改成{.compatible = "keys"},
第二个就是注册了杂项设备之后,一定要在注销驱动的函数里面注销杂项设备
验证
排查完错误,安装我们的模块,可以发现一切正常
接下来就是运行我们的app文件,运行后就可以发现这次卡在了驱动程序中的misc_read函数中,柱塞住了,只有按下按键的时候,才会执行操作,并且返回value的值
这里出现了一个小问题,就是在做实验时时候,需要反复的调整代码,然后发现执行了app程序之后关不掉了,因此也没有办法卸载driver.ko模块,所以,这里需要一个总结指令killall + 程序名称
killall app
然后就可以继续实验了
我们把打印信息都去除掉以后,再来使用
top
可以发现,我们的app程序,占用的资源瞬间降了下来。
到这里,我们就完成了,只有按键操作,才会执行app的read操作。
源码
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>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
struct device_node *test_device_node;
struct tasklet_struct test_key_tasklet_struct;
DECLARE_WAIT_QUEUE_HEAD(key_wq); // 定义并初始化等待队列头
int gpio_name; // gpio编号
int irq; // 中断号
int value = 0; // 模拟管脚的状态
int wq_flags = 0; // 中断标志位
/*中断上文*/
irq_handler_t test_key_handle(int irq, void *args) // 中断处理函数
{
value = !value; // 模拟中断处理函数一直在监测
wq_flags = 1; // 中断被触发,说明按键按下了
/*唤醒睡眠进程*/
wake_up(&key_wq);
return IRQ_HANDLED; // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}
int misc_open (struct inode *inode, struct file *file){ // 对底层操作一般就是通过应用层使用open read等函数时来实现
printk("hello misc_open!!!\n");
return 0;
}
int misc_release(struct inode *inode, struct file *file){
printk("bye bye misc_release!!!\n");
return 0;
}
/*底层数据传到应用层*/
ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
// char kbuf[64] = "copy to user!!!\n";
wait_event_interruptible(key_wq, wq_flags); // 先让这个读进入等待队列 wq_flags是等待的标志位
if( copy_to_user(ubuf, &value, sizeof(value)) != 0 ){ // 底层数据传递给应用层的ubuf
printk("copy_to_user error!!!\n");
return -1;
}
wq_flags = 0; // 标志位清零
return 0;
}
/*应用层传入的数据*/
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = {0};
printk("hello misc_write!!!\n");
if( copy_from_user(kbuf, ubuf, size) != 0 ){ // 应用层传递过来的数据到底层的kbuf
printk("copy_from_user error!!!\n");
return -1;
}
printk("buf is:%s\n", kbuf);
/*********实现IO的逻辑功能*********************************************************************/
if(kbuf[0] == 1){ //对蜂鸣器的控制,如果是1,控制gpio口
// *vir_gpio5_dr |= (1 << 1); //因为是gpio5的01,所以左移一位就可以,给一个高电平,使蜂鸣器工作
gpio_set_value(gpio_name, 1); //Linux体现出来了通用性,不需要寄存器地址了
printk("gpio_set_value is high!!!\n");
}else if(kbuf[0] == 0){
// *vir_gpio5_dr &= ~(1 << 1);
gpio_set_value(gpio_name, 0);
printk("gpio_set_value is low!!!\n");
}
return 0;
}
/*构建杂项设备驱动的file_operations*/
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
/*杂项设备信息*/
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR, // 次设备号
.name = "hello_misc", // 生成一个名字叫做hello_misc的设备节点
.fops = &misc_fops // 完善file_operations
};
/*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_key
if(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子系统*/
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");
}
/*注册杂项设备,生成dev驱动节点*/
ret = misc_register(&misc_dev);
if(ret < 0){
printk("misc_register failed!!!\n");
return -1;
}else{
printk("misc_register succeed!!!\n");
}
return 0;
}
int beep_remove(struct platform_device *pdev){
printk("beep_remove ok!!!\n");
return 0;
}
const struct platform_device_id beep_id_table = {
.name = "keys",
};
const struct of_device_id of_match_table_test[] = {
{.compatible = "keys"}, // 匹配设备树节点中的compatible属性信息
{} // 不写会提示警告
};
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语言库,所以不用printf
ret = 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);
misc_deregister(&misc_dev);
platform_driver_unregister(&beep_device);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char *argv[])
{
int fd;
int value;
fd = open("/dev/hello_misc", O_RDWR); // 打开节点时候触发open函数
if(fd < 0){
perror("open error\n"); // perror在应用中打印
return fd;
}
while(1){
read(fd, &value, sizeof(value));
// printf("value is %d\n", value);
}
return 0;
}
工作队列
什么是工作队列
工作队列的工作原理
工作队列相关API
跟等待队列一样,先定义结构体,然后初始化
DECLARE_WORK宏
INIT_WORK宏
schedule_work
工作队列示例代码
分析
这里修改之前的等待队列的代码,把中断下文设置成工作队列的模式。
当检测到按键中断之后,才会触发工作队列中的函数。
代码
这里是很简单的,相比之前的代码,只需要替换掉tasklet相应的函数即可。
-
首先就是初始化INIT_WORK
-
然后编写工作队列函数
-
最后在中断中调度工作队列
#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;
struct work_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);
/*调度工作队列*/
schedule_work(&test_key_tasklet_struct);
printk("schedule_work 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_key
if(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, 100);
/* INIT_WORK 初始化
* 第一个参数:work_struct结构体
* 第二个参数:工作队列执行函数
*/
INIT_WORK(&test_key_tasklet_struct, test);
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语言库,所以不用printf
ret = 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"); //声明模块拥有开源许可