引言
本篇博文承接上一篇博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145176361继续完善代码,在上一篇博文的代码中,只实现了对中断的处理,并没有用户空间的事(我们并未创建内核空间与用户空间进行交互的设备文件),即处于用户空间的应用程序还无法读取按键值。
为了节约CPU的资源,读取按键值的程序显然不能一直在那里死循环读取按键的值嘛,因为大部分情况下按键都是没有值的,按键按下的时间毕竟是少数时间嘛。
在本篇博文中,我们让读取按键的程序在没有按键按下时就进入睡眠状态,但这种睡眠状态是可以被打断并唤醒的,即这种睡眠状态是可以被中断唤醒的,有按键按下的时候,这种“可中断的睡眠状态”被唤醒,然后去读取按钮值。
要特别注意的是:
虽然在本篇博文中咱们是在对中断事件的处理中唤醒处于睡眠状态的线程,但是这种“可中断的睡眠状态”并不是只能被中断事件唤醒,也能被别的事件唤醒,即wake_up_interruptible()
这个函数不仅可以在中断服务程序中被运行,在别的函数中或其它地方也可以运行,千万不要认为wake_up_interruptible()
这个函数只能在中断服务程序中运行。其实这里准确的称呼应该叫做“可被打断的睡眠状态(即线程能被某个事件唤醒)”
代码的修改比较简单,主要就是加入创建设备号、设备类、设备文件的代码,并运用相关的睡眠和唤醒函数。
完整代码
驱动程序gpio_key_drv.c
中的代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_100ask;
/* 主设备号 */
static int major = 0;
static struct class *gpio_key_class;
static int g_key = 0;
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
/* 实现文件操作结构体中的read函数 */
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int err;
wait_event_interruptible(gpio_key_wait, g_key);
err = copy_to_user(buf, &g_key, 4);
g_key = 0;
// 返回值为4表明读到了4字节的数据
return 4;
}
/* 定义自己的file_operations结构体 */
static struct file_operations gpio_key_drv = {
.owner = THIS_MODULE,
.read = gpio_key_drv_read,
};
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val;
// 返回引脚电平的逻辑值,注意:如果是低电平有效,则当物理电平为低电平时,其返回值为1;则当物理电平为高电平时,其返回值为0.
// 如果要得到物理电平值,可以用函数gpiod_get_raw_value()得到
val = gpiod_get_value(gpio_key->gpiod);
// 打印中断号、GPIO引脚编号和电平值
// printk("Interrupt number: %d; GPIO pin number: %d; Pin Logical value: %d\n", irq, gpio_key->gpio, val);
// g_key的高8位中存储的是GPIO口的编号,低8位中存储的是按键按下时的逻辑值
g_key = (gpio_key->gpio << 8) | val;
wake_up_interruptible(&gpio_key_wait);
return IRQ_HANDLED; // 表示中断已处理
}
/* 1. 从platform_device获得GPIO
* 2. gpio=>irq
* 3. request_irq
*/
static int gpio_key_probe(struct platform_device *pdev)
{
int err;
// 获取设备树节点指针
struct device_node *node = pdev->dev.of_node;
// count用于存储设备树中描述的GPIO口的数量
int count;
int i;
enum of_gpio_flags flag;
unsigned flags = GPIOF_IN;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
count = of_gpio_count(node);
if (!count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;