文章目录
一、流程
① 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-