linux驱动由浅入深系列:输入子系统之二(编写一个gpio_key驱动)

时间:2022-08-29 17:56:56

本系列导航:

linux驱动由浅入深系列:输入子系统之一(input子系统概述、应用层读取event)

linux驱动由浅入深系列:输入子系统之二(编写一个gpio_key驱动)

linux驱动由浅入深系列:输入子系统之三(应用层模拟input_event)


在上一篇文章中我们大致了解了linux input subsystem的功能及应用层的使用,本文我们一起来看一看驱动代码的编写。接下来一篇,计划写一下应用层如何模拟按键消息,产生与按下实际按键相同的效果。

在“linux驱动由浅入深系列:驱动程序的基本结构概览”一文中已经解释的驱动程序的基本结构,今天我们以上一篇文章中的程序为基本结构,添加相关内容来构成一个gpio按键的驱动程序。

先来看看修改完后的代码:

#include <linux/init.h>
#include <linux/module.h>

#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>

#define DRIVER_NAME "hello"
#define DEVICE_NAME "hello"

#define GPIO_KEY 72

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Radia");

static struct input_dev *input;
static irqreturn_t button_interrupt(int irq, void *dev_id)
{
int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1;
printk("hello report event val:%d\n", state);
input_report_key(input, KEY_VOLUMEUP, !!state);
input_sync(input);
return IRQ_HANDLED;
}

static int hello_probe(struct platform_device *pdv)
{
int irq = gpio_to_irq(GPIO_KEY);
printk(KERN_EMERG "hello key probe\n");
gpio_request(GPIO_KEY, "gpio_key_test_button");
gpio_direction_input(GPIO_KEY);

request_irq(irq, button_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_irq", NULL);
input = input_allocate_device();
set_bit(EV_KEY, input->evbit);
input->name = "hello_gpio_key";
input->id.bustype = BUS_HOST;
set_bit(KEY_VOLUMEUP, input->keybit);
input_register_device(input);
//misc_register(&hello_dev);
return 0;
}

static int hello_remove(struct platform_device *pdv)
{
struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdv);
printk(KERN_EMERG "hello key remove\n");
input_unregister_device(input);
//misc_deregister(&hello_dev);
return 0;
}

static void hello_shutdown(struct platform_device *pdv)
{
}

static int hello_suspend(struct platform_device *pdv, pm_message_t pmt)
{
return 0;
}

static int hello_resume(struct platform_device *pdv)
{
return 0;
}

static struct platform_driver hello_driver = {
.probe = hello_probe,
.remove = hello_remove,
.shutdown = hello_shutdown,
.suspend = hello_suspend,
.resume = hello_resume,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
}
};

static int hello_init(void)
{
int driver_state;
printk(KERN_EMERG "hello module has been mount!\n");
driver_state = platform_driver_register(&hello_driver);
printk(KERN_EMERG "platform_driver_register driver_state is %d\n", driver_state);
platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
printk(KERN_EMERG "platform_device_register_simple end\n");
return 0;
}

static void hello_exit(void)
{
printk(KERN_EMERG "hello module has been remove!\n");
platform_driver_unregister(&hello_driver);
}

module_init(hello_init);
module_exit(hello_exit);

使用beyond compare对比修改前后的结果,发现删除了misc设备的文件操作结构体hello_fops相关的open、read、write接口,增加了probe中的相关操作。因为input子系统是使用event与应用层交互的,下一篇文章具体分析。对于新增内容展示在下图,下面具体分析一下:

linux驱动由浅入深系列:输入子系统之二(编写一个gpio_key驱动)

1,  hello_remove函数是模块卸载时调用的,其中只是增加了input设备的卸载过程,其实此处还应有gpio、irq、mem等资源的释放动作,为了便于更为简洁的演示,先了解输入设备的基本注册过程,此处都省略了(同理,各个函数的返回值也都没有处理)。在实际项目中是必须要写完整的,否则驱动层的资源泄漏对整个系统的影响是十分严重的。

2,  hello_probe函数是在驱动挂载时调用的。其中主要对input设备进行初始化,注册到input子系统。首先申请了gpio资源(我使用的系统gpio72连接了按键,其它平台可能不同),是为了中断函数中读取gpio状态做的准备。接着申请了该gpio对应的中断,同时声明了上升、下降沿触发,设定了终端处理函数button_interrupt。之后配置了input_dev结构体的相关成员(这是一个最简配置,仅供参考),向系统注册了input设备。

3,  button_interrupt终端处理函数中,在gpio72上发生上升沿或下降沿时执行。其中首先判断了gpio72的电平值,以确定按键的状态。之后根据按键相应的状态调用input子系统中的消息发送函数,向应用层发送了不同键值的消息。

 

测试:

采用与“ linux驱动由浅入深系列:输入输出子系统之一”一文中相同的方法,查看 cat /proc/bus/input/devices文件发现,我们新加的hello_gpio_key对应系统的/dev/input/event7节点。

linux驱动由浅入深系列:输入子系统之二(编写一个gpio_key驱动)

使用busybox hexdump /dev/input /event7 命令查看该节点中的二进制消息,当gpio72对应的按键按下一次后如下图(相关解析参照“linux驱动由浅入深系列:输入输出子系统之一”):

linux驱动由浅入深系列:输入子系统之二(编写一个gpio_key驱动)

因为button_interrupt中断处理函数中加了打印,下图为kernel对应的log

linux驱动由浅入深系列:输入子系统之二(编写一个gpio_key驱动)