- 使用中断
先前采用查询方式的按键驱动占用过多内存,不适合产品的实际使用,这就要使用中断。
Linux系统也随着芯片技术的发展对中断处理不断优化,具体的演进如下(参考韦东山老师):
Linux(4.1.15内核)中断系统中的重要数据结构:
Linux对中断的处理过程,放两张图片在这里仅作为引子,不作深入探讨,实际上我还没弄明白。深入研究可以“肝”一下内核源码。 - 按键设备驱动文件
button_drv.c文件中,button_drv_read函数调用wait_event_interruptible,当没有按键时将进入休眠;有按键时(上升沿、下降沿或双边沿触发),进入中断服务函数gpio_btn_isr,在此中断服务函数调用wake_up_interruptible函数唤醒等待队列,button_drv_read函数得以继续执行,返回按键数据给应用程序。
/**
* 文件 : button_drv.c
* 作者 : glen
* 描述 : button driver文件
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
struct gbtn_irq {
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
int idx;
};
struct button_drv {
struct class *class;
struct gbtn_irq *gbtn_irq;
char *name;
char kval;
int count;
int major;
};
static struct button_drv *btn_drv;
/* 等待队列头的静态初始化 */
static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);
/* 实现file_operations结构体成员 read 函数 */
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
int minor = iminor(filp->f_inode);
struct button_drv *ops = (struct button_drv *)filp->private_data;
size = (size >= 1) ? 1 : 0;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->count) {
printk("Reading button value error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
wait_event_interruptible(gpio_button_wait, btn_drv->kval);
if (copy_to_user(buf, &ops->kval, size))
return -EFAULT;
btn_drv->kval = 0;
printk("Read button%d value successfully:", minor);
return size;
}
/* 实现file_operations结构体成员 open 函数 */
int button_drv_open (struct inode *nd, struct file *filp)
{
int ret;
int minor = iminor(nd);
struct button_drv *ops = btn_drv;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->count) {
printk("Openning button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
ret = gpiod_direction_input(ops->gbtn_irq[minor].gpiod);
if (ret)
printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
else
printk("Set the button%d pin as input successfully!\n", minor);
filp->private_data = btn_drv;
return 0;
}
/* 实现file_operations结构体成员 release 函数 */
int button_drv_release (struct inode *nd, struct file *filp)
{
int minor = iminor(nd);
struct button_drv *ops = (struct button_drv *)filp->private_data;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->count) {
printk("Closing button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
filp->private_data = NULL;
return 0;
}
/* 实现file_operations结构体成员 poll 函数 */
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
return 0;
}
/**
* 1. 构造file_operations结构体
*/
static struct file_operations button_drv_ops = {
.owner = THIS_MODULE,
.read = button_drv_read,
.open = button_drv_open,
.release = button_drv_release,
.poll = button_drv_poll,
};
/* 中断服务函数 */
static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
{
int val;
struct gbtn_irq *ops = dev_id;
/* 读取按键的值 */
val = gpiod_get_value(ops->gpiod);
printk("button%d %d %d\n", ops->idx, ops->gpio, val);
btn_drv->kval = (ops->gpio << 4) | val;
/* 唤醒等待队列 */
wake_up_interruptible(&gpio_button_wait);
return IRQ_HANDLED;
}
/* platform_driver结构体的 probe成员函数实现 */
int btn_hw_drv_probe (struct platform_device *pdev)
{
int i;
int ret;
int count;
struct device_node *node = pdev->dev.of_node;
/* 从设备节点获取gpio数量 */
count = of_gpio_count(node);
if (!count) {
printk("%s %s line %d, there isn't any gpio available!\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
btn_drv = kzalloc(sizeof(struct button_drv), GFP_KERNEL);
if (btn_drv == NULL)
return -ENOMEM;
btn_drv->gbtn_irq = kzalloc(sizeof(struct gbtn_irq) * count, GFP_KERNEL);
if (btn_drv->gbtn_irq == NULL)
return -ENOMEM;
for (i = 0; i < count; i++) {
btn_drv->gbtn_irq[i].gpiod = gpiod_get_index_optional(&pdev->dev, NULL, i, GPIOD_ASIS);
if (btn_drv->gbtn_irq[i].gpiod == NULL) {
printk("%s %s line %d, gpiod_get_index_optional failed!\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
btn_drv->gbtn_irq[i].irq = gpiod_to_irq(btn_drv->gbtn_irq[i].gpiod);
btn_drv->gbtn_irq[i].gpio = desc_to_gpio(btn_drv->gbtn_irq[i].gpiod);
btn_drv->gbtn_irq[i].idx = i;
}
for (i = 0; i < count; i++)
/* 申请irq中断, 将中断服务程序注册到上半部 */
ret = request_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"gpio_btn", &btn_drv->gbtn_irq[i]);
/* 注册file_operationss结构体对象 -- button_drv_ops */
btn_drv->major = register_chrdev(btn_drv->major, "gbtn", &button_drv_ops);
btn_drv->class = class_create(THIS_MODULE, "gbtn");
if (IS_ERR(btn_drv->class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(btn_drv->major, "gbtn");
return PTR_ERR(btn_drv->class);
}
for (i = 0; i < count; i++)
device_create(btn_drv->class, NULL, MKDEV(btn_drv->major, i), NULL, "gbtn%d", i);
btn_drv->count = count;
return 0;
}
/* platform_driver结构体的 remove成员函数实现 */
int btn_hw_drv_remove(struct platform_device *pdev)
{
int i;
struct device_node *node = pdev->dev.of_node;
int count = of_gpio_count(node);
for (i = 0; i < count; i++) {
device_destroy(btn_drv->class, MKDEV(btn_drv->major, i));
free_irq(btn_drv->gbtn_irq[i].irq, &btn_drv->gbtn_irq[i]);
}
class_destroy(btn_drv->class);
unregister_chrdev(btn_drv->major, "gbtn");
kfree(btn_drv);
return 0;
}
/* 构造用于配置的设备属性 */
static const struct of_device_id gbtns_id[] = {
{.compatible = "glen,gbtn"},
{ },
};
/* 构造(初始化)file_operations结构体 */
static struct platform_driver btn_hw_drv = {
.driver = {
.name = "gbtn",
.of_match_table = gbtns_id,
},
.probe = btn_hw_drv_probe,
.remove = btn_hw_drv_remove,
};
/* 初始化 */
static int __init button_drv_init(void)
{
int ret;
ret = platform_driver_register(&btn_hw_drv);
if (ret)
pr_err("Unable to initialize button driver\n");
else
pr_info("The button driver is registered.\n");
return 0;
}
module_init(button_drv_init);
static void __exit button_drv_exit(void)
{
platform_driver_unregister(&btn_hw_drv);
printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_exit(button_drv_exit);
/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
probe函数采用了先获取按键节点数量,然后分别读取gpio描述符并通过其获取为gpio和irq号,并申请注册中断服务程序。
3. 微调设备树文件
pinctrl_btn0:btn0 {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
pinctrl_btn1:btn1 {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xF080 /* KEY1 此按键不存在 */
>;
};
/* 在根节点下添加基于pinctrl的gbtns设备节点 */
gbtns {
compatible = "glen,gbtn";
#address-cells = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_btn0
&pinctrl_btn1>;
gpio-controller;
#gpio-cells = <2>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
&gpio1 3 GPIO_ACTIVE_LOW>; /* button1 */
};
- 应用程序(不作修改)
/*
* 文件名 : button_drv_test.c
* 作者 : glen
* 描述 : button_drv应用程序
*/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/**
* @brief : main函数
* @par : argc argv数组元素的个数
* argv 参数数组
* @retval : 0 成功 其它 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
char kval;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &kval, 1);
if (ret <= 0) {
printf("Read glen button value failed!\r\n");
close(fd);
return -1;
} else {
printf("The glen button value is: %d!\r\n", kval);
}
sleep(1);
}
/* 关闭文件 */
ret = close(fd);
if (ret < 0) {
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
- 在alientek_linux_alpha开发板实测验证如下
/ # cd drv_module/
/drv_module # insmod button_drv.ko
The button driver is registered.
/drv_module # ./btn_drv_test /dev/gbtn0
Set the button0 pin as input successfully!
button0 18 1
Read button0 value successfully:The glen button value is: 33!
button0 18 0
Read button0 value successfully:The glen button value is: 32!
random: nonblocking pool is initialized
button0 18 1
Read button0 value successfully:The glen button value is: 33!
button0 18 0
Read button0 value successfully:The glen button value is: 32!
button0 18 1
Read button0 value successfully:The glen button value is: 33!
button0 18 0
Read button0 value successfully:The glen button value is: 32!