输入子系统编写按键驱动

回顾:以中断方式处理的“按键驱动”程序。
1,确定主设备号。
2,构造一个“file_operations”结构体。
3,open 函数中申请中断:
4,read 函数中,若没有按键按下就休眠:
有按键按下后,中断程序被调用:在中断服务程序里确定是哪个按键按下。最后唤醒按键程序:

而这个驱动的测试程序是:
1,先 open 某个设备文件:
2,接着读:

上面的驱动程序和测试程序的缺点:
上面打开了一个特定的设备文件“/dev/buttons”。而一般写的应用程序不会去打开这个
“/dev/buttons”。一般打开的都是原有的文件,如“dev/tty*”,还有可能是不需要打开什么
设备文件,而 是直接“scanf()”就去获得了按键的输入。
以前写的那些驱动程序只能自已使用而非通用。要写一个通用的驱动程序,让其他应用
程序“无缝”的使用, 就是说不需要修改人家的应用程序。这需要使用现成的驱动,把自
已的设备相关的驱动放到内核中这种驱动架构 中去。这个现成的驱动就是“输入子系统–
input 子系统”。

要将自已的驱动整合进“input 子系统”,就得把它的框架理清楚:

一、 输入子系统框架:

1. 最上层(核心层):
drivers/input.c 所以输入子系统的代码在这个 c 文件中。
看一个驱动程序是从“入口函数”开始查看。
在这里插入图片描述
以前注册字符设备驱动的函数是自已写的。上面的注册是内核有的。
在这里插入图片描述
在这里插入图片描述
这里注册了一个主设备号“INPUT_MAJOR”为 13 的字符设备,名字为“input”,它的
file_operations 结构是“input_fops”。这个结构中只有一个“.open”函数。
输入子系统,如这里想读按键,而这里只有一个“open”函数,那么这个 Open 函数中应该
做了某些工作。
分析“int input_open_file(struct inode *inode, struct file *file)”:

在这里插入图片描述
其中有一个“input_handler *”(“输入处理器”或“输入处理句柄”):
在这里插入图片描述
在这里插入图片描述
这里这个“输入处理句柄”结构指向一个“input_table[]”数组。从这个数组里面根据这
个“次设 备号 iminor(inode) >> 5”把打开的文件,根据它的次设备号找到一项。

在这里插入图片描述
接着新的“file_operations”结构"new_fops"等于上面的“input_handler *”指针变量
handler 的成员“ops”(这是一个 file_operations 结构。Input_handler 结构中有这个
file_operations 成员)。
在这里插入图片描述
接着把这个新的 file_operations 结构赋给此函数“input_open_file”的形参“file”的 f_op。
然后再调用这个新的 file_operations 结构“new_fops”的 open(inode,file)函数,如下:
在这里插入图片描述
这样以后要来读按键时,用到的是“struct input_handler *handler”中的“new_fops”.
Input.c 只是一个“中转”作用。最终还会用到“input_table[]”.

(1) 流程如下:input.c
(2) int __init input_init(void): -> err = register_chrdev(INPUT_MAJOR, “input”, &input_fops);
其中“input_fops”结构中只有一个".open"函数:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
问题:怎么读按键?
这里只有一个 Open 函数而没有读函数。则可能是这个 “input_open_file”函数中做了什么
工作。
(3) 分析“input_open_file”函数:
int input_open_file(struct inode *inode, struct file *file): ->input_handler *handler = input_table[iminor(inode) >> 5];
根据传进来的“inode” 这个打开的文件的次设备号(“iminor(inode) >> 5”)得到
一个
“input_handler *handler”。
->new_fops = fops_get(handler->fops):
然后的新的 file_operations 结构体(new_fops)等于这个“input_handler *handler”结构
里面的“file_operations”结构“handler->fops”。
->file->f_op = new_fops:
然后所打开的这个 file 中的 f_op(file_operations)等于这个“new_fops”。
->err = new_fops->open(inode, file):
接着调用这个 new_fops 的“open”函数。
以后 APP 来读的时候,是最终调用“file->f_op”中的 read 函数。最终是要明白“input_handler”是如何定义的,input_table 数组由谁构造。(4) "input_table[]"的构造:“static struct input_handler *input_table[8]”是个静态变量,所以只能用在这个文件里面,所以只 在这个 Input.c 中找到使用了这个数组的地方,如下:int input_register_handler(struct input_handler *handler)这个函数中构造了这个 input_table[]数 组项:
在这里插入图片描述
(5) 这个“input_register_handler()”函数被谁调用过:
搜索源码工程,见到如下:
在这里插入图片描述
有游戏手柄,键盘和鼠标等的源代码调用过它。这些使用就离开了“input.c”这个核心层了。
它们就向上 面的核心层“input.c”注册了“input_register_handler()”.

在这里插入图片描述
打开这个“evdev.c”源码。查看调用“input_register_handler()”的调用:
在这里插入图片描述
只是在这个入口函数“evdev_init()”中调用了"input_register_handler()"。
以上是具体的设备与核心层 input.c 的层次调用关系。

(6) 查看“read”的过程:
在“evdev.c”的入口函数“evdev_init()”中注册了“evdev_handler”这个结构。它的原型如
下:
在这里插入图片描述
它是一个“input_handler”结构体。它的定义如下:
在这里插入图片描述
从上面的"input_handler"结构变量“evdev_handler”的定义可以看到一个“file_operations”
结构“.fops = &evdev_fops”。在这个“evdev_fops”中便有相关的 read,write 等。
在这里插入图片描述
以前自已写驱动时,这个 file_operations 结构体是自已构造的,这里是由系统构造了。
在这里插入图片描述
通过“input_register_handler(&evdev_handler)”注册后,就放到:
在这里插入图片描述
通过传值后,形参“handler”就是“&evdev_handler”,则“handler->minor”就是
“evdev_handler->minor”即“EVDEV_MINOR_BASE”(即次设备号 64)。
就相当于 64 除以 2 的 5 次方,即 64 除以 32 为 2.
则这个“handler”是放在“input_table[2]”这个第二项处。handler 是纯软件的概念。
软件方面(evdev.c/keyboard.c/mousedev.c)是向核心层“input.c”注册“handler” (input_register_handler),这一边代表“软件”;还有另一边另一个层“设备”,是向“核 心 层 input.c”注册“input_register_device”,这一边代表硬件.
在这里插入图片描述
一边的“handler 软件处理者”是否支持另一边的“device”,中间会有一个联系:
在这里插入图片描述
“id_table”表示这个“evdev_handler”能够支持哪些“输入设备”。当我们注册上图中的
“handler”和 “device”时,这两者就会比较(handler 和设备比较),看 handler 是否支持
这个设备。若能支持则,则从 上面的“evdev_handler”结构中知道,应该会调用其中的
“.connect = evdev_connect”

注册输入设备:
分析“int input_register_device(struct input_dev *dev)”所做的事情:看“input_c”中此函数
源码。
注册函数,将输入设备注册到核心层中,注册前需要输入设备需要调用 input_allocate_device
来分配,然后设置设备处理能力.
(1) 放入一个链表:
list_add_tail(&dev->node, &input_dev_list);
input_dev:子系统中用此结构体来描述一个输入设备 .这里是:
将设备加入全局链表中→
然后遍历 input_handler_list 中的每一个 handler,调用 input_attach_handler 进行 attach

(2) 对链表里的每个条目
,“input_handler_list(注册 handler 时加入的链表)”都会调用“input_attach_handler()”函数来
对“硬件设备 device”与“处理方式 handler”进行关联。
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
List_for_each_entry 函数遍历 Input_handler_list(全局链表,连接所有的 input_handler)上的
handler,并调用 Input_attach_handler 来进行输入设备和处理方法的关联。

在这里插入图片描述
从上图可知,不管是先注册加载右边的“handler”还是左边的“device”。最终都会成对的
调用“input_attach_handler()”。看看源码“input_attach_handler()”
在这里插入图片描述
即:
在这里插入图片描述
注册 input_dev 或 input_handler 时,会两两比较左边的 input_dev 和右边的 input_handler,
根据 input_handler 的 id_table 判断这个 input_handler 能否支持这个 input_dev,如果能支
持,则调用 input_handler 的 connect 函数建立"连接。

如何建立连接,可能不同的 handler 都有自已不同的方式。
实例分析“evdev.c”中的“connect”:
在这里插入图片描述
(3) 进入“evdev_connect 函数”分析:
在这里插入图片描述

  1. ,分配了一个“input_handle evdev”结构变量(不是 input_handler 结构)。查看这个 evdev 结构中的成员:
    在这里插入图片描述
    成员中有一个“input_handle handle”结构。
  2. 再对这个“input_handle evdev”进行设置:
    在这里插入图片描述
  3. 最后注册这个 handle:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

如何读数据“按键”
App:read
应用程序来读,最终会导致“handler”里面的新的“.fops”里面的“读函数”被调用。如:
“evdev.c”中的“evdev_handler”结构里面的成员“.fops=&evdev_fops”,在“evdev_fops”
结构中有一个“读”函数“evdev_read”
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以从上面的“wait_event_interruptible()”知道是在“evdev”上休眠,则唤醒也会是在它上面。可以搜索它。

(1) 谁来“唤醒”:
如按下一个按键后,中断处理函数就会被调用。在中断处理函数里面先确定按键值。然后才
来唤醒。
在这里插入图片描述
(2) 谁调用“evdev_event()”:

在这里插入图片描述

总结“输入子系统”

(1) 分为上下两层:
在这里插入图片描述

在这里插入图片描述
其中的“iput_table[]”数组由下面的各个“纯软件”代码构建(如:evdev.c,keyboard.c).
“纯软件(input_handler)”部分和“硬件部分(input_dev)”联系起来,纯软件部分是由
“ input_register_handler ”向上“ input.c ” 核 心 层 注 册 处 理 方 式 ; 硬 件 部 分 是 由
“input_register_device”向上“input.c”核心层注册硬层。这样注册后,会使它们两两比较,
看看其中的某个“handler”是否支持其中的某个“dev”。若是“handler”能支持某个“dev”,
则会接着调用“input_handler”结构下的“.connect”函数,这个函数一般会创建一个 “input_handle”结构(是 handle 而非 handler),并且这个结构“input_handle”会分别放
在两边的“h_list”链表中去。这个结构体中有“.dev”和“.handler”,让它两分别具体指向
右边的“纯软件处理部分”和左右的硬件部分,这样具体的某硬件就和纯软件联系起来了。
可以从任何一边通过“h_list”找到这个“input_handle”结构,再通过成员找到另一端的“.dev”
或“.handler”。
在这里插入图片描述

举例是如何读按键:
最终是应用程序读,最终会导致 handler 中的“read”函数。读的过程中,没有数据可读时
就休眠,有休眠就会唤醒,搜索的结果是“event”函数来唤醒。分析到这里就没有接着去分
析而是猜测是由“硬件”调用的“event”函数,硬件则是指“input_dev”层的设备中断服
务程序调用了“event”函数。通过这个“event”函数可以最终追踪到“纯软件”部分的
“input_handler”结构体中的“.event”成员。

在这里插入图片描述


/* 参考drivers\input\keyboard\gpio_keys.c */

#include <linux/module.h>
#include <linux/version.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/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>

#include <asm/gpio.h>
#include <asm/io.h>
//#include <asm/arch/regs-gpio.h>

struct pin_desc{
	int irq;
	char *name;
	unsigned int pin;
	unsigned int key_val;
};

struct pin_desc pins_desc[4] = {
	{IRQ_EINT0,  "S2", S3C2410_GPF(0),   KEY_L},
	{IRQ_EINT2,  "S3", S3C2410_GPF(2),   KEY_S},
	{IRQ_EINT11, "S4", S3C2410_GPG(3),   KEY_ENTER},
	{IRQ_EINT19, "S5",  S3C2410_GPG(11), KEY_LEFTSHIFT},
};

static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies+HZ/100);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer_function(unsigned long data)
{
	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 : 最后一个参数: 0-松开, 1-按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
		input_sync(buttons_dev);
	}
	else
	{
		/* 按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
		input_sync(buttons_dev);
	}
}

static int buttons_init(void)
{
	int i;
	
	/* 1. 分配一个input_dev结构体 */
	buttons_dev = input_allocate_device();;

	/* 2. 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);
	set_bit(EV_REP, buttons_dev->evbit);
	
	/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
	set_bit(KEY_L, buttons_dev->keybit);
	set_bit(KEY_S, buttons_dev->keybit);
	set_bit(KEY_ENTER, buttons_dev->keybit);
	set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);

	/* 3. 注册 */
	input_register_device(buttons_dev);
	
	/* 4. 硬件相关的操作 */
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer);
	
	for (i = 0; i < 4; i++)
	{
		request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, &pins_desc[i]);
	}
	
	return 0;
}

static void buttons_exit(void)
{
	int i;
	for (i = 0; i < 4; i++)
	{
		free_irq(pins_desc[i].irq, &pins_desc[i]);
	}

	del_timer(&buttons_timer);
	input_unregister_device(buttons_dev);
	input_free_device(buttons_dev);	
}

module_init(buttons_init);

module_exit(buttons_exit);

MODULE_LICENSE("GPL");



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值