27、基于Firefly-rk3399中断休眠唤醒实验(按键中断)

文章目录

一、流程

① APP调用read试图读取按键数据

② APP 进入内核态,也就是调用驱动中的对应函数,发现有数据则复制到用户空间并马上返回

③ 如果 APP 在内核态,也就是在驱动程序中发现没有数据,则 APP 休眠

④ 当有数据时,比如当按下按键时,驱动程序的中断服务程序被调用,它会记录数据、唤醒 APP

⑤ APP 继续运行它的内核态代码,也就是驱动程序中的函数,复制数据到用户空间并马上返回

流程图:

在中断处理函数中,不能休眠,也就不能调用会导致休眠的函数

二、相关函数

1、休眠函数

内核目录源码:include\linux\wait.h

Function Note
wait_event_interruptible(wq, condition) 休眠,直到 condition 为真,休眠期间是可被打断的,可以被信号打断
wait_event(wq, condition) 休眠,直到 condition 为真;退出的唯一条件是 condition 为真,信号也不好使
wait_event_interruptible_timeout(wq, condition, timeout) 休眠,直到 condition 为真或超时;休眠期间是可被打断的,可以被信号打断
wait_event_timeout(wq, condition, timeout) 休眠,直到 condition 为真,退出的唯一条件是 condition 为真,信号也不好使

注意: condition非零即为真。

参数:

​ (1)wq:等待队列,休眠时除了把程序状态改为非 RUNNING 之外,还要把进程/进程放入 wq 中,以后中断服务程序要从 wq

中把它取出来唤醒 。

​ (2)condition:这可以是一个变量,也可以是任何表达式。表示"一直等待,直到 condition 为真"。

2、唤醒函数
Function Note
wake_up_interruptible(x) 唤醒 x 队列中状态为"TASK_INTERRUPTIBLE"的线程,只唤醒其中的一个线程
wake_up_interruptible_nr(x, nr) 唤醒 x 队列中状态为"TASK_INTERRUPTIBLE"的线程,只唤醒其中的 nr 个线程
wake_up_interruptible_all(x) 唤醒 x 队列中状态为"TASK_INTERRUPTIBLE"的线程,唤醒其中的所有线程
wake_up(x) 唤醒 x 队列中状态为"TASK_INTERRUPTIBLE"或"TASK_UNINTERRUPTIBLE"的线程,只唤醒其中的一个线程
wake_up_nr(x, nr) 唤醒 x 队列中状态为"TASK_INTERRUPTIBLE"或"TASK_UNINTERRUPTIBLE"的线程,只唤醒其中 nr个线程
wake_up_all(x) 唤醒 x 队列中状态为"TASK_INTERRUPTIBLE"或"TASK_UNINTERRUPTIBLE"的线程,唤醒其中的所有线程
3、宏

DECLARE_WAIT_QUEUE_HEAD 是 Linux 内核中的一个宏,用于定义并初始化一个等待队列头。等待队列(wait queue)是 Linux 内核中的一种同步机制,主要用于进程在某些条件未满足时进行睡眠,等待某种事件发生再被唤醒。

宏定义及作用:

C 复制代码
wait_queue_head_t name;
DECLARE_WAIT_QUEUE_HEAD(name);

功能 :定义一个名为 name 的等待队列头,并初始化它。

等价操作:该宏等价于手动声明和初始化等待队列头

参数name:等待队列头的名字。

内部实现 : 宏会定义一个 wait_queue_head_t 类型的变量并调用 init_waitqueue_head 函数完成初始化。

使用场景:

当需要实现进程的等待和唤醒机制时。

典型场景包括设备驱动中,当某些硬件事件尚未发生时,让进程进入睡眠,直到事件发生后将其唤醒。

三、编写框架

(1)基本框架

不做过多说明:

头文件抄的其他驱动的

C 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>

入口、出口、许可证

C 复制代码
static int gpio_interrupt_init(void) {
	return platform_driver_register(&key_interrupt_drv);
}

static void gpio_interrupt_exit(void) {
	platform_driver_unregister(&key_interrupt_drv);
}

module_init(gpio_interrupt_init);
module_exit(gpio_interrupt_exit);
MODULE_LICENSE("GPL");

③ 入口函数注册platform驱动platform_driverriver:key_interrupt_drv

C 复制代码
struct platform_driver key_interrupt_drv = {
	.probe = rk3399_key_probe,
	.remove = rk3399_key_remove,
	.driver = {
		.name = "rk3399",
        /* 此处匹配设备树 node*/
		.of_match_table = firefly_rk3399_key,
	},
};

④ match函数调用的firefly_rk3399_key填充:

C 复制代码
static const struct of_device_id firefly_rk3399_key[] = {
	{ .compatible = "rk3399, keydrv" },
	{},
};

注意: 设备树的compatible属性节点需为:"rk3399, keydrv"

⑤ 填充platform的probe函数:

C 复制代码
static int rk3399_key_probe(struct platform_device *pdev) {
	int count = 0;
	int i = 0;
	int err = 0;

	printk("enter interrupt\r\n");
	count = of_gpio_count(pdev->dev.of_node);
	printk("count is %d\r\n", count);
	gpios_key = kzalloc(count * sizeof(struct gpio_key), GFP_KERNEL);
	for(i=0; i<count; i++) {
		gpios_key[i].gpio = of_get_gpio_flags(pdev->dev.of_node, i, &(gpios_key[i].flags));
		gpios_key[i].irq = gpio_to_irq(gpios_key[i].gpio);
		err = request_irq(gpios_key[i].irq, gpio_key_rk3399, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "rk3399_key", &gpios_key[i]);
	}
	
    /* 补充注册设备驱动部分 */

	major = register_chrdev(0, "KEY_L", &key_fops);
	led_class = class_create(THIS_MODULE, "KEY_CLASS");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "diy_key_double");
    
	return err;
}

/* 中断处理函数:gpio_key_rk3399 */
static irqreturn_t gpio_key_rk3399(int irq, void *dev_id)
{
	struct gpio_key *gpios_key1 = dev_id;

	printk("key %d val %d\r\n", irq, gpio_get_value(gpios_key1->gpio));

	return IRQ_HANDLED;
}

⑤ 填充platform的remove函数:

C 复制代码
static int rk3399_key_remove(struct platform_device *pdev) {
	int count = 0;
	int i = 0;
	
	count = of_gpio_count(pdev->dev.of_node);
	for(i=0; i<count; i++) {
		free_irq(gpios_key[i].irq, &gpios_key[i]);
	}
	/* 待补充 */
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class); 
	unregister_chrdev(major, "KEY_L");
    
	return 0;
}

⑥ 填充key_fops

C 复制代码
static int key_open(struct inode *inode, struct file *file) {
	int err;

	printk("%s  %s  %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) {
	printk("%s  %s  %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

static ssize_t key_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) {
	printk("%s  %s  %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

static int key_release(struct inode *inode, struct file *file) {
	printk("%s  %s  %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = key_open,	
	.release = key_release,	
	.read = key_read,	
	.write = key_write,
};
(2)修改read函数

背景:

​ 设备树配置两个GPIO为按键输入引脚。由于上面代码只生成了一个设备文件,即我们可以操作的设备文件只有一个。所以我们得区分获取到的按键值是哪个GPIO的。我们只需在static irqreturn_t gpio_key_rk3399(int irq, void *dev_id)此中断处理函数中做好相应的处理即可。

C 复制代码
static irqreturn_t gpio_key_rk3399(int irq, void *dev_id)
{
	struct gpio_key *gpios_key1 = dev_id;
	/*key_signal包含gpio引脚信息以及电平值*/
	key_signal = ((gpio_get_value(gpios_key1->gpio)) << 8) |(gpios_key1->gpio);
	printk("key %d val %d\r\n", irq, gpio_get_value(gpios_key1->gpio));
	wake_up_interruptible(&gpio_key_wait);

	return IRQ_HANDLED;
}

static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) {
	int err;
	
	printk("%s  %s  %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	wait_event_interruptible(gpio_key_wait, key_signal);
	printk("led read : key statu is %d\r\n", key_signal);

	err = copy_to_user(buf, &key_signal, 4);
	key_signal = 0;
	
	return 0;
}

四、源码

driver

C 复制代码
#include <linux/module.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>

struct gpio_key {
	int gpio;
	int irq;
	enum of_gpio_flags flags;
};
static int major = 0;
static struct class *led_class;
static unsigned int key_signal = 0;
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
struct gpio_key *gpios_key;


static int key_open(struct inode *inode, struct file *file) {
	printk("%s  %s  %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) {
	int err;
	
	printk("%s  %s  %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	wait_event_interruptible(gpio_key_wait, key_signal);
	printk("led read : key statu is %d\r\n", key_signal);

	err = copy_to_user(buf, &key_signal, 4);
	key_signal = 0;
	
	return 0;
}

static ssize_t key_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) {
	printk("%s  %s  %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

static int key_release(struct inode *inode, struct file *file) {
	printk("%s  %s  %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

const struct file_operations key_fops = {
	.owner = THIS_MODULE,
	.open = key_open,	
	.release = key_release,	
	.read = key_read,	
	.write = key_write,
};

static irqreturn_t gpio_key_rk3399(int irq, void *dev_id)
{
	struct gpio_key *gpios_key1 = dev_id;

	key_signal = ((gpio_get_value(gpios_key1->gpio)) << 8) |(gpios_key1->gpio);
	printk("key %d val %d\r\n", irq, gpio_get_value(gpios_key1->gpio));
	wake_up_interruptible(&gpio_key_wait);

	return IRQ_HANDLED;
}

static int rk3399_key_probe(struct platform_device *pdev) {
	int count = 0;
	int i = 0;
	int err = 0;

	printk("enter interrupt\r\n");
	count = of_gpio_count(pdev->dev.of_node);
	printk("count is %d\r\n", count);
	gpios_key = kzalloc(count * sizeof(struct gpio_key), GFP_KERNEL);
	for(i=0; i<count; i++) {
		gpios_key[i].gpio = of_get_gpio_flags(pdev->dev.of_node, i, &(gpios_key[i].flags));
		gpios_key[i].irq = gpio_to_irq(gpios_key[i].gpio);
		err = request_irq(gpios_key[i].irq, gpio_key_rk3399, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "rk3399_key", &gpios_key[i]);
	}

	major = register_chrdev(0, "KEY_L", &key_fops);
	led_class = class_create(THIS_MODULE, "KEY_CLASS");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "diy_key_double");

	return err;
}

static int rk3399_key_remove(struct platform_device *pdev) {
	int count = 0;
	int i = 0;
	
	count = of_gpio_count(pdev->dev.of_node);
	for(i=0; i<count; i++) {
		free_irq(gpios_key[i].irq, &gpios_key[i]);
	}
	
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class); 
	unregister_chrdev(major, "KEY_L");
	
	return 0;
}


static const struct of_device_id firefly_rk3399_key[] = {
	{ .compatible = "rk3399, keydrv" },
	{},
};

struct platform_driver key_interrupt_drv = {
	.probe = rk3399_key_probe,
	.remove = rk3399_key_remove,
	.driver = {
		.name = "rk3399",
		.of_match_table = firefly_rk3399_key,
	},
};

static int gpio_interrupt_init(void) {
	return platform_driver_register(&key_interrupt_drv);
}

static void gpio_interrupt_exit(void) {
	platform_driver_unregister(&key_interrupt_drv);
}

module_init(gpio_interrupt_init);
module_exit(gpio_interrupt_exit);
MODULE_LICENSE("GPL");

app

C 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>  // 定义 open 函数及相关常量(如 O_RDONLY, O_WRONLY 等)
#include <sys/types.h>  // 定义数据类型,例如 mode_t
#include <sys/stat.h>   // 定义文件权限相关的常量和类型
#include <unistd.h>     // 定义 POSIX 系统调用,例如 close、read、write 等


int main(int argc, const char* argv[])
{
    int value = 0;
    int fd = open(argv[1], O_RDWR);
    int gpio = 0;
    int level = 0;
    int bank = 0;
    char group = 0;
    int number = 0;
    if(fd < 0) {
        perror("open");
        return -1;
    }
    while(1) {
        int ret = read(fd, &value, sizeof(value));
        // if(ret <= 0) {
        //     break;
        // }
        gpio = value & 0x00ff;
        bank = gpio / 32;
        group = ((gpio % 32) / 8) + 'A';
        number = gpio % 8;
        value = (value >> 8) & 0xff;
        printf("Received gpio%d_%c%d value:%d\n", bank, group, number, value);
    }

    return 0;
}

五、编译

编译驱动即可

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

相关推荐
skaiuijing18 分钟前
Sparrow系列拓展篇:消息队列和互斥锁等IPC机制的设计
c语言·开发语言·算法·操作系统·arm
davenian1 小时前
<OS 有关> ubuntu 24 不同版本介绍 安装 Vmware tools
linux·ubuntu·vmware
Simulink_1 小时前
ROS学习笔记15——Xacro
linux·笔记·学习·机器人·ros
雯0609~2 小时前
c#:winform调用bartender实现打印(学习整理笔记)
开发语言·c#
北京迅为2 小时前
【北京迅为】iTOP-4412全能版使用手册- 第五章 Linux常用命令
linux·嵌入式硬件·4412开发板
南暮思鸢2 小时前
应急响应靶机——linux2
linux·网络安全·wireshark·write up·应急响应靶机系列·宝塔面板利用·webshell流量分析
sun0077003 小时前
ubuntu增加swap交换空间
linux·运维·服务器
胜天半子_王二_王半仙3 小时前
c++源码阅读__smart_ptr__正文阅读
开发语言·c++·开源
沐泽Mu3 小时前
嵌入式学习-C嘎嘎-Day08
开发语言·c++·算法
Non importa3 小时前
汉诺塔(hanio)--C语言函数递归
c语言·开发语言·算法·学习方法