1、按键中断实现led灯操作
代码的文件框架:
![](https://i-blog.csdnimg.cn/blog_migrate/14c1e4c428a25e61096af279aa0fd821.png)
(1)button_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>
#include "button_drv.h"
//存储从内核获得的数据的结构体
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_100ask;
static int major;
struct class *key_class;
static struct gpio_desc * led_get_gpiod;
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__);
//这里先不做处理。
return 0;
}
static ssize_t gpio_key_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
//获得led的gpiod参数的函数,在led_drv驱动中进行调用
int get_led_gpiod(struct gpio_desc * gpiod)
{
led_get_gpiod = gpiod;
return 0;
}
EXPORT_SYMBOL(get_led_gpiod);
//按键中断函数,获得led的gpiod之后,通过gpiod操作函数,对led的gpio引脚进行操作
static irqreturn_t gpio_key_isr (int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val;
gpiod_direction_output(led_get_gpiod, 0);
val = gpiod_get_value(gpio_key->gpiod);
//在这里做按键中断控制led灯的处理,需要在led驱动中获得gpiod的结构体
if(val)
{
gpiod_set_value(led_get_gpiod, val);
}else
{
gpiod_set_value(led_get_gpiod, val);
}
printk("Key gpio:%d val:%d",gpio_key->gpio,val);
return IRQ_HANDLED;
}
static struct file_operations button_file_operation =
{
.owner = THIS_MODULE,
.open = gpio_key_drv_open,
.release = gpio_key_drv_close,
.read = gpio_key_drv_read,
.write = gpio_key_drv_write,
};
static int gpio_key_probe(struct platform_device *pdev)
{
int button_count,i,err;
struct device_node *node = pdev->dev.of_node;
enum of_gpio_flags flag;
unsigned flags = GPIOF_IN;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
button_count = of_gpio_count(node);
if (!button_count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask = kzalloc(sizeof(struct gpio_key)*button_count, GFP_KERNEL);
for(i=0; i<button_count; i++)
{
//获得gpio
gpio_keys_100ask[i].gpio = of_get_gpio_flags(node,i,&flag);
if (gpio_keys_100ask[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
//获得gpiod
gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
if (flag & OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);
//获得软件中断号,后面根据这个软件中断号注册中断
gpio_keys_100ask[i].irq = gpio_to_irq(gpio_keys_100ask[i].gpio);
}
printk("button_count:%d",button_count);
for(i=0; i<button_count; i++)
{
//函数的入口参数内容:中断号、中断函数、中断的出发方式(也可以传入数值)、传入中断函数的数据。
err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "hong_key", &gpio_keys_100ask[i]);
printk("irq_request:%d",gpio_keys_100ask[i].irq);
}
//下面就是设备的创建以及驱动的配置代码了,
//只是对中断的处理配置是不需要注册设备,注册设备是为了方便对驱动进行操作。
/* major = register_chrdev(0, "hong_key", &button_file_operation);
key_class = class_create(THIS_MODULE, "hong_key");
if (IS_ERR(key_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "hong_key");
return PTR_ERR(key_class);
}
//两个中断函数,只注册了一个驱动,两个按键驱动调用同一个驱动文件。
//这种方式无法通过查询的方式对按键的数值进行读取。
device_create(key_class, NULL, MKDEV(major, 0), NULL, "hong_key_dev%d", i);
*/
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
int button_count,i;
struct device_node *node = pdev->dev.of_node;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//卸载和创建的函数流程要反着来(最好遵循这个原则,防止出现某些问题)
//卸载注册的结构体。
//device_destroy(key_class, MKDEV(major, 0));
//class_destroy(key_class);
//unregister_chrdev(major, "hong_key");
//只需要获得gpio的数量,之后删除对应的中断函数,然后清空申请的结构体内存
button_count = of_gpio_count(node);
for(i=0; i<button_count; i++)
{
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
}
kfree(gpio_keys_100ask);
return 0;
}
static const struct of_device_id button_of_device_id[]=
{
{ .compatible = "hong, gpio_key" },
{ },
};
static struct platform_driver chip_gpio_driver=
{
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "hong_button_dev",
.of_match_table = button_of_device_id,
},
};
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&chip_gpio_driver);
return err;
}
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&chip_gpio_driver);
}
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");
(2)button_drv.h
#ifndef __button_drv_h__
#define __button_drv_h__
int get_led_gpiod(struct gpio_desc * gpiod);
#endif
(3)led_drv.c
#include <linux/module.h>
#include <linux/platform_device.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/of.h>
#include "button_drv.h"
static int major;
static struct class * led_class;
static struct gpio_desc * led_gpiod;
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
gpiod_set_value(led_gpiod, status);
return 0;
}
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
gpiod_direction_output(led_gpiod, 0);
return 0;
}
static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct file_operations led_file_operations =
{
.owner = THIS_MODULE,
.open = led_drv_open,
.release = led_drv_close,
.read = led_drv_read,
.write = led_drv_write,
};
/* 4. 从platform_device获得GPIO
* 把file_operations结构体告诉内核:注册驱动程序
*/
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//获得系统分析设备树之后,后面使用函数获得GPIO,后面对其进行操作。
//对照设备树中的“[name]-gpios”中的name,这里为“led-gpios”
led_gpiod = gpiod_get(&pdev->dev, "led", GPIOD_OUT_HIGH);
//调用button里面的获得函数,将资源led_gpiod给到按键驱动程序。
get_led_gpiod(led_gpiod);
major = register_chrdev(0, "hong_led_dev", &led_file_operations);
led_class = class_create(THIS_MODULE, "hong_led_dev");
if (IS_ERR(led_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "led");
gpiod_put(led_gpiod);
return PTR_ERR(led_class);
}
device_create(led_class, NULL, MKDEV(major,0), NULL, "hong_led_treedrv" );
return 0;
}
static int chip_demo_gpio_remove(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//首先要知道设备树中gpios和pinctrl-0指定多个设备
device_destroy(led_class, MKDEV(major,0)) ;
class_destroy(led_class);
unregister_chrdev(major, "hong_led_dev");
return 0;
}
//可以支持多种设备节点的匹配,但是自己还没有试过
static const struct of_device_id led_of_device_id[] =
{
{ .compatible = "hong, leddrv" },
{ },
};
/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "led_drv_test",
.of_match_table = led_of_device_id,
},
};
static int __init led_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&chip_demo_gpio_driver);
return 0;
}
static void __exit led_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&chip_demo_gpio_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
2、休眠与唤醒和poll
休眠-唤醒:进去房间陪小孩一起睡觉,小孩醒了会吵醒她。不累,但是妈妈干不了活了。
这种方式的代码思想是:在read()函数中,采用等待队列事件,在没有被唤醒的时候,就进入休眠的状态(后面的阻塞和非阻塞也要使用这种方法。)。然后会在中断里调用队列事件唤醒函数,对read()里面的休眠函数进行唤醒。对应的驱动代码和用户测试代码如下所示。
poll:是在用户函数哪里,首先调用poll()函数,将用户线程放进驱动程序中注册的队列中去 ,然后判断是否有事件(这个事件可以由用户自己指定,例如数据缓冲区是否为空)发生,如果没有就开始休眠,(1)如果有,中断唤醒,就休眠结束,回来继续判断是否有事件发生,这时候就会跳出poll循环了,用户程序那边就可以判断到poll函数的返回数据有事件发生,就可以进行事件操作了(例如读取数据);(2)如果没有事件发生,就等待超时以后,内核唤醒,休眠也就结束了,poll函数返回没有事件的操作,用户程序那判断没有事件发生,就处理其他的事情。
(1)button_drv.c
#include <linux/module.h>
#include <linux/poll.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>
#include "button_drv.h"
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_100ask;
static int major;
struct class *key_class;
static struct gpio_desc * led_get_gpiod;
//(1)注册一个队列,用于数据阻塞。
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
// 下面回环存储代码的实现方式比较实用
//回环数据存储的代码,数据必须一个一个的存放,不能一次存放多个
#define BUF_LEN 128
#define NEXT_POS(x) ( (x + 1) % BUF_LEN )
static int key_data_buf[BUF_LEN];
static int r=0,w=0;
//返回1,说明回环存储区是空的。
static int is_key_buff_empty(void)
{
return (r == w);
}
//返回1,说明回环存储区满了。
static int is_key_buff_full(void)
{
return (r == NEXT_POS(w));
}
static void put_key_data(int key)
{
if(!is_key_buff_full())
{
key_data_buf[w]=key;
w = NEXT_POS(w);
}
}
static int get_key_data(void)
{
int key_data;
if(!is_key_buff_empty())
{
key_data = key_data_buf[r];
r = NEXT_POS(r);
return key_data;
}
return -1;
}
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int key_data,err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//(2)函数的第二个入口参数是队列唤醒的条件。会将函数本身放进等待队列中,等待唤醒
//或者!is_key_buff_empty()条件满足,也就是循环数组中有数据。
wait_event_interruptible(gpio_key_wait, !is_key_buff_empty());
key_data = get_key_data();
if(key_data == -1)
{
printk("the buff is full\n");
}
err = copy_to_user(buf, &key_data, 4);
return 0;
}
static ssize_t gpio_key_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
int get_led_gpiod(struct gpio_desc * gpiod)
{
led_get_gpiod = gpiod;
return 0;
}
EXPORT_SYMBOL(get_led_gpiod);
static irqreturn_t gpio_key_isr (int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val,gpio_val;
gpiod_direction_output(led_get_gpiod, 0);
val = gpiod_get_value(gpio_key->gpiod);
//在这里做按键中断控制led灯的处理,需要在led驱动中获得gpiod的结构体
if(val)
{
gpiod_set_value(led_get_gpiod, val);
}else
{
gpiod_set_value(led_get_gpiod, val);
}
//按键的状态值,只需要8位就可以了。
gpio_val = ((gpio_key->gpio<<8)|val);
put_key_data(gpio_val);
//(3)唤醒队列中放入的用户线程。
wake_up_interruptible(&gpio_key_wait);
printk("Key gpio:%d val:%d",gpio_key->gpio,val);
return IRQ_HANDLED;
}
static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
//用户测试函数中,不需要直接调用read()函数进入阻塞状态,而是通过poll()
//判断是否调用read()函数。
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//将fp线程放入等待队列gpio_key_wait中
//两种唤醒方式:(1)时间超时,内核将其唤醒。
// (2)中断产生中断函数将其唤醒。
poll_wait(fp, &gpio_key_wait, wait);
//以存储数据的唤醒数据存储区为是否有时间的判断。
return is_key_buff_empty()? 0 : POLLIN | POLLRDNORM;
}
static struct file_operations button_file_operation =
{
.owner = THIS_MODULE,
.open = gpio_key_drv_open,
.release = gpio_key_drv_close,
.read = gpio_key_drv_read,
.write = gpio_key_drv_write,
.poll = gpio_key_drv_poll,
};
static int gpio_key_probe(struct platform_device *pdev)
{
int button_count,i,err;
struct device_node *node = pdev->dev.of_node;
enum of_gpio_flags flag;
unsigned flags = GPIOF_IN;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
button_count = of_gpio_count(node);
if (!button_count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask = kzalloc(sizeof(struct gpio_key)*button_count, GFP_KERNEL);
for(i=0; i<button_count; i++)
{
//获得gpio
gpio_keys_100ask[i].gpio = of_get_gpio_flags(node,i,&flag);
if (gpio_keys_100ask[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
//获得gpiod
gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
if (flag & OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);
//获得软件中断号,后面根据这个软件中断号注册中断
gpio_keys_100ask[i].irq = gpio_to_irq(gpio_keys_100ask[i].gpio);
}
printk("button_count:%d",button_count);
for(i=0; i<button_count; i++)
{
//函数的入口参数内容:中断号、中断函数、中断的出发方式(也可以传入数值)、传入中断函数的数据。
err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "hong_key", &gpio_keys_100ask[i]);
printk("irq_request:%d",gpio_keys_100ask[i].irq);
}
//下面就是设备的创建以及驱动的配置代码,创建设备是为了提供一个按键操作的平台。
major = register_chrdev(0, "hong_key", &button_file_operation);
key_class = class_create(THIS_MODULE, "hong_key");
if (IS_ERR(key_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "hong_key");
return PTR_ERR(key_class);
}
//两个中断函数,只注册了一个驱动,两个按键驱动调用同一个驱动文件。
device_create(key_class, NULL, MKDEV(major, 0), NULL, "hong_key_dev0");
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
int button_count,i;
struct device_node *node = pdev->dev.of_node;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//卸载和创建的函数流程要反着来(最好遵循这个原则,防止出现某些问题)
//卸载注册的结构体。
device_destroy(key_class, MKDEV(major, 0));
class_destroy(key_class);
unregister_chrdev(major, "hong_key");
//只需要获得gpio的数量,之后删除对应的中断函数,然后清空申请的结构体内存
button_count = of_gpio_count(node);
for(i=0; i<button_count; i++)
{
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
}
kfree(gpio_keys_100ask);
return 0;
}
static const struct of_device_id button_of_device_id[]=
{
{ .compatible = "hong, gpio_key" },
{ },
};
//首先利用button_of_device_id结构体数组与设备树进行关联
//之后在调用gpio_key_probe进行设备的创建。
static struct platform_driver chip_gpio_driver=
{
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "hong_button_dev",
.of_match_table = button_of_device_id,
},
};
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&chip_gpio_driver);
return err;
}
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&chip_gpio_driver);
}
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");
(2)led_test.c
//用于按键中断的设备数据读取测试
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <poll.h>
/*
./button_test /dev/hong_key_dev0
*/
int main(int argc, char** argv)
{
//要检测的文件的结构体数组。poll可以检测多个文件
struct pollfd fds[1];
int fd,val,poll_return;
if(argc<2)
{
printf("Usage:%s is err\n",argv[0]);
printf(" ./button_test /dev/hong_key_dev0\n");
return -1;
}
fd = open(argv[1],O_RDWR);
if(fd == -1)
{
printf("can not open %s\n",argv[1]);
return -1;
}
/*
//此程序在读运行的时候,只能在中断运行的时候进行读取,
//中断不运行的时候,是不能对数据进行读取的。
//要不然就是在驱动中对中断的次数进行记录,然后将次数传给用户端。
while(1)
{
//这里的循环就不需要延时,read驱动程序中有阻塞睡眠等待
read(fd,&val,4);
printf("key_gpio:%d, val:%d\n",(val>>8),(val&0xff));
}
*/
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1)
{
//通过对poll函数的判断,当poll驱动函数返回时是对应形式的时候,就可以读取数据了
poll_return = poll(fds,1,5000);
if((poll_return == 1)&&(fds[0].revents == POLLIN))
{
read(fd,&val,4);
printf("gpio:%d val:%d\n",(val>>8),(val&0xff));
}else
{
printf("timer is over\n");
}
}
close(fd);
return 0;
}
3、异步通知
![](https://i-blog.csdnimg.cn/blog_migrate/77aad3aeb8e323faad1433d84fc183bc.png)
具体驱动代码如下所示:
(1)button_drv.c
#include <linux/module.h>
#include <linux/poll.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>
#include "button_drv.h"
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_100ask;
static int major;
struct class *key_class;
static struct gpio_desc * led_get_gpiod;
struct fasync_struct *button_fasync;
//注册一个队列,用于数据阻塞。
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
//回环数据存储的代码,数据必须一个一个的存放,不能一次存放多个
#define BUF_LEN 128
#define NEXT_POS(x) ( (x + 1) % BUF_LEN )
static int key_data_buf[BUF_LEN];
static int r=0,w=0;
//返回1,说明回环存储区是空的。
static int is_key_buff_empty(void)
{
return (r == w);
}
//返回1,说明回环存储区满了。
static int is_key_buff_full(void)
{
return (r == NEXT_POS(w));
}
static void put_key_data(int key)
{
if(!is_key_buff_full())
{
key_data_buf[w]=key;
w = NEXT_POS(w);
}
}
static int get_key_data(void)
{
int key_data;
if(!is_key_buff_empty())
{
key_data = key_data_buf[r];
r = NEXT_POS(r);
return key_data;
}
return -1;
}
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int key_data,err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//函数的第二个入口参数是队列唤醒的条件。会将函数本身放进等待队列中,等待唤醒
//或者!is_key_buff_empty()条件满足,也就是循环数组中有数据。
//wait_event_interruptible(gpio_key_wait, !is_key_buff_empty());
key_data = get_key_data();
if(key_data == -1)
{
printk("the buff is full\n");
}
err = copy_to_user(buf, &key_data, 4);
return 0;
}
static ssize_t gpio_key_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
int get_led_gpiod(struct gpio_desc * gpiod)
{
led_get_gpiod = gpiod;
return 0;
}
EXPORT_SYMBOL(get_led_gpiod);
static irqreturn_t gpio_key_isr (int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val,gpio_val;
gpiod_direction_output(led_get_gpiod, 0);
val = gpiod_get_value(gpio_key->gpiod);
//在这里做按键中断控制led灯的处理,需要在led驱动中获得gpiod的结构体
if(val)
{
gpiod_set_value(led_get_gpiod, val);
}else
{
gpiod_set_value(led_get_gpiod, val);
}
//按键的状态值,只需要8位就可以了。
gpio_val = ((gpio_key->gpio<<8)|val);
put_key_data(gpio_val);
// 唤醒队列中放入的用户线程。
//wake_up_interruptible(&gpio_key_wait);
//(4)根据结构体button_fasync->fa_file的变量去调用对应线程的函数。
kill_fasync(&button_fasync, SIGIO, POLL_IN);
printk("Key gpio:%d val:%d",gpio_key->gpio,val);
return IRQ_HANDLED;
}
// (2)晚上fasync对应的函数gpio_key_drv_fasync
static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{
//(3)调用fasync_helper()函数设置结构体button_fasync->fa_file的变量。
if (fasync_helper(fd, file, on, &button_fasync) >= 0)
return 0;
else
return -EIO;
}
static struct file_operations button_file_operation =
{
.owner = THIS_MODULE,
.open = gpio_key_drv_open,
.release = gpio_key_drv_close,
.read = gpio_key_drv_read,
.write = gpio_key_drv_write,
//(1)在结构体中添加fasync成员。
.fasync = gpio_key_drv_fasync,
};
static int gpio_key_probe(struct platform_device *pdev)
{
int button_count,i,err;
struct device_node *node = pdev->dev.of_node;
enum of_gpio_flags flag;
unsigned flags = GPIOF_IN;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
button_count = of_gpio_count(node);
if (!button_count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask = kzalloc(sizeof(struct gpio_key)*button_count, GFP_KERNEL);
for(i=0; i<button_count; i++)
{
//获得gpio
gpio_keys_100ask[i].gpio = of_get_gpio_flags(node,i,&flag);
if (gpio_keys_100ask[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
//获得gpiod
gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
if (flag & OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);
//获得软件中断号,后面根据这个软件中断号注册中断
gpio_keys_100ask[i].irq = gpio_to_irq(gpio_keys_100ask[i].gpio);
}
printk("button_count:%d",button_count);
for(i=0; i<button_count; i++)
{
//函数的入口参数内容:中断号、中断函数、中断的出发方式(也可以传入数值)、传入中断函数的数据。
err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "hong_key", &gpio_keys_100ask[i]);
printk("irq_request:%d",gpio_keys_100ask[i].irq);
}
//下面就是设备的创建以及驱动的配置代码,创建设备是为了提供一个按键操作的平台。
major = register_chrdev(0, "hong_key", &button_file_operation);
key_class = class_create(THIS_MODULE, "hong_key");
if (IS_ERR(key_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "hong_key");
return PTR_ERR(key_class);
}
//两个中断函数,只注册了一个驱动,两个按键驱动调用同一个驱动文件。
device_create(key_class, NULL, MKDEV(major, 0), NULL, "hong_key_dev0");
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
int button_count,i;
struct device_node *node = pdev->dev.of_node;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//卸载和创建的函数流程要反着来(最好遵循这个原则,防止出现某些问题)
//卸载注册的结构体。
device_destroy(key_class, MKDEV(major, 0));
class_destroy(key_class);
unregister_chrdev(major, "hong_key");
//只需要获得gpio的数量,之后删除对应的中断函数,然后清空申请的结构体内存
button_count = of_gpio_count(node);
for(i=0; i<button_count; i++)
{
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
}
kfree(gpio_keys_100ask);
return 0;
}
static const struct of_device_id button_of_device_id[]=
{
{ .compatible = "hong, gpio_key" },
{ },
};
//首先利用button_of_device_id结构体数组与设备树进行关联
//之后在调用gpio_key_probe进行设备的创建。
static struct platform_driver chip_gpio_driver=
{
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "hong_button_dev",
.of_match_table = button_of_device_id,
},
};
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&chip_gpio_driver);
return err;
}
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&chip_gpio_driver);
}
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");
(2)button_test.c
//用于按键中断的设备数据读取测试
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
static int fd;
//(6)编写异步通知的函数。
static void sig_func(int sig)
{
int val;
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}
/*
./button_test /dev/hong_key_dev0
*/
//异步通知的方式的代码修改
int main(int argc, char** argv)
{
//要检测的文件的结构体数组。poll可以检测多个文件
struct pollfd fds[1];
int val,poll_return;
int flags;
if(argc<2)
{
printf("Usage:%s is err\n",argv[0]);
printf(" ./button_test /dev/hong_key_dev0\n");
return -1;
}
//(1)注册对应信号的函数。
signal(SIGIO,sig_func);
//(2)打开设备,和(1)的顺序是都可以的。
fd = open(argv[1],O_RDWR);
if(fd == -1)
{
printf("can not open %s\n",argv[1]);
return -1;
}
//(3)将进程id给驱动
fcntl(fd, F_SETOWN, getpid());
//(4)获得open打开的文件的标志位
flags = fcntl(fd, F_GETFL);
//(5)设置获得的标志位中的FASYNC,使其支持异步通知功能
fcntl(fd, F_SETFL, flags | FASYNC);
while(1)
{
printf("test28 \n");
sleep(3);
}
close(fd);
return 0;
}
4、阻塞与非阻塞
阻塞与非阻塞的实现,也是要靠前面介绍的队列中的中断事件等待函数实现的。在open一个设备节点的时候,在内核就会创建struct file结构体,结构体中的f_flags参数代表打开的这个驱动函数进行调用的时候,是否允许阻塞,这个可以在open()驱动设备的时候进行设置,也通过fcntl()函数获得此标志,然后对此标志进行配置。
(1)button_drv.c
#include <linux/module.h>
#include <linux/poll.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>
#include "button_drv.h"
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_100ask;
static int major;
struct class *key_class;
static struct gpio_desc * led_get_gpiod;
struct fasync_struct *button_fasync;
//注册一个队列,用于数据阻塞。
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
//回环数据存储的代码,数据必须一个一个的存放,不能一次存放多个
#define BUF_LEN 128
#define NEXT_POS(x) ( (x + 1) % BUF_LEN )
static int key_data_buf[BUF_LEN];
static int r=0,w=0;
//返回1,说明回环存储区是空的。
static int is_key_buff_empty(void)
{
return (r == w);
}
//返回1,说明回环存储区满了。
static int is_key_buff_full(void)
{
return (r == NEXT_POS(w));
}
static void put_key_data(int key)
{
if(!is_key_buff_full())
{
key_data_buf[w]=key;
w = NEXT_POS(w);
}
}
static int get_key_data(void)
{
int key_data;
if(!is_key_buff_empty())
{
key_data = key_data_buf[r];
r = NEXT_POS(r);
return key_data;
}
return -1;
}
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int key_data,err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//在没有阻塞的情况下,在缓冲区没有数据的时候,直接将函数返回,结束函数。
//当状态设置为有阻塞的情况下,这部分不会运行,就会运行下面的等待中断事件的函数。
if(is_key_buff_empty()&&(file->f_flags & O_NONBLOCK))
{
return -EAGAIN;
}
//函数的第二个入口参数是队列唤醒的条件。会将函数本身放进等待队列中,等待唤醒
//或者!is_key_buff_empty()条件满足,也就是循环数组中有数据。
//阻塞和非阻塞中的阻塞还是需要在自己通过代码实现,就利用下面的中断等待函数实现。
wait_event_interruptible(gpio_key_wait, !is_key_buff_empty());
key_data = get_key_data();
if(key_data == -1)
{
printk("the buff is full\n");
}
err = copy_to_user(buf, &key_data, 4);
return 0;
}
static ssize_t gpio_key_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int gpio_key_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
int get_led_gpiod(struct gpio_desc * gpiod)
{
led_get_gpiod = gpiod;
return 0;
}
EXPORT_SYMBOL(get_led_gpiod);
static irqreturn_t gpio_key_isr (int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val,gpio_val;
gpiod_direction_output(led_get_gpiod, 0);
val = gpiod_get_value(gpio_key->gpiod);
//在这里做按键中断控制led灯的处理,需要在led驱动中获得gpiod的结构体
if(val)
{
gpiod_set_value(led_get_gpiod, val);
}else
{
gpiod_set_value(led_get_gpiod, val);
}
//按键的状态值,只需要8位就可以了。
gpio_val = ((gpio_key->gpio<<8)|val);
put_key_data(gpio_val);
//唤醒队列中放入的用户线程。
wake_up_interruptible(&gpio_key_wait);
printk("Key gpio:%d val:%d",gpio_key->gpio,val);
return IRQ_HANDLED;
}
static struct file_operations button_file_operation =
{
.owner = THIS_MODULE,
.open = gpio_key_drv_open,
.release = gpio_key_drv_close,
.read = gpio_key_drv_read,
.write = gpio_key_drv_write,
};
static int gpio_key_probe(struct platform_device *pdev)
{
int button_count,i,err;
struct device_node *node = pdev->dev.of_node;
enum of_gpio_flags flag;
unsigned flags = GPIOF_IN;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
button_count = of_gpio_count(node);
if (!button_count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask = kzalloc(sizeof(struct gpio_key)*button_count, GFP_KERNEL);
for(i=0; i<button_count; i++)
{
//获得gpio
gpio_keys_100ask[i].gpio = of_get_gpio_flags(node,i,&flag);
if (gpio_keys_100ask[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
//获得gpiod
gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
if (flag & OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);
//获得软件中断号,后面根据这个软件中断号注册中断
gpio_keys_100ask[i].irq = gpio_to_irq(gpio_keys_100ask[i].gpio);
}
printk("button_count:%d",button_count);
for(i=0; i<button_count; i++)
{
//函数的入口参数内容:中断号、中断函数、中断的出发方式(也可以传入数值)、传入中断函数的数据。
err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "hong_key", &gpio_keys_100ask[i]);
printk("irq_request:%d",gpio_keys_100ask[i].irq);
}
//下面就是设备的创建以及驱动的配置代码,创建设备是为了提供一个按键操作的平台。
major = register_chrdev(0, "hong_key", &button_file_operation);
key_class = class_create(THIS_MODULE, "hong_key");
if (IS_ERR(key_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "hong_key");
return PTR_ERR(key_class);
}
//两个中断函数,只注册了一个驱动,两个按键驱动调用同一个驱动文件。
device_create(key_class, NULL, MKDEV(major, 0), NULL, "hong_key_dev0");
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
int button_count,i;
struct device_node *node = pdev->dev.of_node;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//卸载和创建的函数流程要反着来(最好遵循这个原则,防止出现某些问题)
//卸载注册的结构体。
device_destroy(key_class, MKDEV(major, 0));
class_destroy(key_class);
unregister_chrdev(major, "hong_key");
//只需要获得gpio的数量,之后删除对应的中断函数,然后清空申请的结构体内存
button_count = of_gpio_count(node);
for(i=0; i<button_count; i++)
{
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
}
kfree(gpio_keys_100ask);
return 0;
}
static const struct of_device_id button_of_device_id[]=
{
{ .compatible = "hong, gpio_key" },
{ },
};
//首先利用button_of_device_id结构体数组与设备树进行关联
//之后在调用gpio_key_probe进行设备的创建。
static struct platform_driver chip_gpio_driver=
{
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "hong_button_dev",
.of_match_table = button_of_device_id,
},
};
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&chip_gpio_driver);
return err;
}
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&chip_gpio_driver);
}
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");
(2)button_test.c
//用于按键中断的设备数据读取测试
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
/*
./button_test /dev/hong_key_dev0
*/
//异步通知的方式的代码修改
int main(int argc, char** argv)
{
//要检测的文件的结构体数组。poll可以检测多个文件
struct pollfd fds[1];
int val,poll_return;
int flags,i,fd;
ssize_t err;
if(argc<2)
{
printf("Usage:%s is err\n",argv[0]);
printf(" ./button_test /dev/hong_key_dev0\n");
return -1;
}
fd = open(argv[1],O_RDWR|O_NONBLOCK);
if(fd == -1)
{
printf("can not open %s\n",argv[1]);
return -1;
}
for(i=0; i<10; i++)
{
//read()函数的默认状态是非阻塞态,
//所以下面的read()函数,在没有获得数据的时候,会直接返回。
err = read(fd, &val, 4);
if(err == 4)
{
printf("get data:%d\n",val);
}else
{
printf("not get data\n");
}
}
//(4)获得open打开的文件的标志位
flags = fcntl(fd, F_GETFL);
//(5)设置获得的标志位中的FASYNC,使其支持异步通知功能
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
while(1)
{
//因为上面设置了结构体的成员的阻塞的标志位,
//所以下面的read()函数在没有获得数据的时候,就会进入阻塞态
if(read(fd, &val, 4) == 4)
{
printf("get val:%d\n",val);
}else
{
printf("while not get val\n");
}
}
close(fd);
return 0;
}