WDS2期第13课 2 输入子系统 1分配一个input_dev结构体 2设置 3注册 4硬件相关操作 重复事件修改定时器超时时间 改标准输入到指定设备exec <

之前不用input_subsystem的步骤

自己写驱动程序
用户需要open,read,write
驱动也要完成对应的drv_open, drv_read, drv_write

  • 主设备号
  • 构造fops
    .read
    .open
    .poll
    .fasync
  • register_chrdev(major, name, fops)
  • 入口
  • 出口
  • LICENSE

以前在一个文件里把所有写完,在输入子系统里被拆分为几个文件,我们需要做的是:

  1. 分配input_dev
  2. 设置
  3. 注册
  4. 硬件相关操作

在输入子系统下编程

主要参考例子,drivers\input\keyboard\gpio_keys.c

上报事件,看drivers\input\input.c中的input_event函数的定义,最后几行,input_event会从input_dev的h_list中找到input_hanldle找到handler调用event函数,

/**
 * input_event() - report new input event
 * @dev: device that generated the event
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * This function should be used by drivers implementing various input devices
 * See also input_inject_event()
 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    // input_event会从input_dev的h_list中找到input_hanldle
    //  找到handler调用event函数  
	...
	else
		list_for_each_entry(handle, &dev->h_list, d_node)
			if (handle->open)
				handle->handler->event(handle, type, code, value);
}

有了input subsystem,我们只需要写硬件相关的代码,其他read,write,poll等已经被写好了,在drivers\input\evdev.c里面,

static const struct file_operations evdev_fops = {
	.owner =	THIS_MODULE,
	.read =		evdev_read,
	.write =	evdev_write,
	.poll =		evdev_poll,
	.open =		evdev_open,
	.release =	evdev_release,
	.unlocked_ioctl = evdev_ioctl,

1. 编译make

2. 拷贝到网络文件系统cp xxx.ko /mnt/

3. 查看原来的事件,ls /dev/event*

4. 插入模块inmod buttons.ko

5. 再查看事件,ls /dev/event*

看到新的event主设备号为13,次设备号为65,

下面解释次设备号65:

input_register_device函数会从右边handler一个个取出与左边input_dev比较,比较id_table,在drivers\input\evdev.c中看到evdev支持所有的输入设备/* Matches all devices */
当然也支持按键类输入设备,于是会调用connect函数,在connect函数里,
```c
drivers\input\evdev.c – id_table

static const struct input_device_id evdev_ids[] = {
	{ .driver_info = 1 },	/* Matches all devices */
	{ },			/* Terminating zero entry */
};

MODULE_DEVICE_TABLE(input, evdev_ids);

static struct input_handler evdev_handler = {
	.event =	evdev_event,
	.connect =	evdev_connect,
	.disconnect =	evdev_disconnect,
	.fops =		&evdev_fops,
	.minor =	EVDEV_MINOR_BASE,
	.name =		"evdev",
	.id_table =	evdev_ids,
};

drivers\input\evdev.c -- connect
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	// #define EVDEV_MINOR_BASE	64
	devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
	
	// 在class下面创建设备,这样就文件系统中mdev就会自动创建设备节点
	cdev = class_device_create(&input_class, &dev->cdev, devt,
				   dev->cdev.dev, evdev->name);
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200414210158602.png)

drivers\input\input.c中,创建了class,注册了字符设备,但并没有在class下面创建设备(在class下面创建设备,文件系统的mdev就会自动创建设备节点),所以创建设备应该是在connnet中完成,
(之前自己写代码。创建class后,下面就直接创建设备,输入子系统把他们分开了,在connectinput_init分别完成)
```c
drivers\input\input.c

static int __init input_init(void)
{
	// 创建class
	err = class_register(&input_class);
	// 注册字符设备
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
}

drivers\input\evdev.c -- connect
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	// #define EVDEV_MINOR_BASE	64
	devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
	
	// 在class下面创建设备,这样就文件系统中mdev就会自动创建设备节点,次设备号
	cdev = class_device_create(&input_class, &dev->cdev, devt,
				   dev->cdev.dev, evdev->name);
```

6. 打开终端,等待按键输入,cat /dev/tty1,按键S2 S3 S4

出错,调试

调试方法一:hexdump /dev/event1 (打开 /dev/event1设备,读取设备[对应文件],以16进制显示)

在这里插入图片描述
打开哪个对应的文件,分析,在drivers\input\input.cinput_init需要用input_fops注册字符设备,找到 input_fops.read = input_open_file,打开,次设备号为65,右移5就是除以32等于2也就是input_table[2],这个东西在input_register_handler(&evdev_handler);被赋值handler也就是evdev_handler

drivers\input\input.c
static int input_open_file(struct inode *inode, struct file *file)
{
	struct input_handler *handler = input_table[iminor(inode) >> 5];

drivers\input\input.c
int input_register_handler(struct input_handler *handler)
{
	input_table[handler->minor >> 5] = handler;

怎么读,evdev_read,读环形缓冲区数据 头不等于尾,然后将读到的数据拷贝到用户空间evdev_event_to_user,查看数据格式,就是前面那个hexdump /dev/event1命令打开的内容了,就是input_event 它了,

include\linux\input.h
static const struct file_operations evdev_fops = {
	.owner =	THIS_MODULE,
	.read =		evdev_read,
	.write =	evdev_write,
	.poll =		evdev_poll,
	.open =		evdev_open,
	.release =	evdev_release,
	.unlocked_ioctl = evdev_ioctl,
	
include\linux\input.h
struct input_event {
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};
include\linux\time.h
struct timeval {
	time_t		tv_sec;		/* seconds */      4个字节
	suseconds_t	tv_usec;	/* microseconds */ 4个字节
};

现在就可以具体解析那个文件16进制内容,4 4 2 2 4字节,左边低字节右边高字节,
如 类 0001是EV_KEY的值0x01,0000是EV_SYN的值0x00;
code 0026是KEY_L的值38;
value 0001 0000是按下 1,0000 0000是松开 0;
第二行和第四行表示上报的同步类事件。

#define EV_SYN			0x00
#define EV_KEY			0x01
#define EV_REL			0x02

在这里插入图片描述

调试方法二:如果没有启动QT,cat /dev/tty1,按键S2 S3 S4就是输入ls enter

如果没有启动QT,cat /dev/tty1,按键S2 S3 S4就是输入ls enter

或者 exec 0</dev/tty1将标准输入改成tty1

查看需要的、依赖的文件有没有被编译进内核,这里检查keyboard.c
ls drivers/char/keyboard.存在对应.o就是被编译进去了。
在这里插入图片描述

调试方法三:如果启动了QT,打开记事本,新建文件,然后按S2 S3 S4,

如果启动了QT,打开记事本,新建文件,然后按S2 S3 S4,可以在文件中看到ls enter。
杀不掉QT,就inittab,vi /etc/inittab ,修改那个rcSvi /etc/init.d/rcS,注释掉最后一行。
在这里插入图片描述
exec 0</dev/tty1将标准输入改成tty1,以前从串口得到输入现在改为按键输入。

分析连续键值 (重复事件修改定时器超时时间)

按着按键不松开并没有显示多个值(连续键值),需要产生重复类事件就可以了,实际是通过修改定时器的值实现,

    // 2.1能产生哪类事件,按键类 重复类(修改定时器超时事件)
    set_bit(EV_KEY, _buttons_dev->evbit);
    set_bit(EV_REP, _buttons_dev->evbit);

分析,按键的重复事件,在input_event中,如果能产生重复类事件就修改定时器超时时间,当然也需要时间处理函数就需要init_timer跟着找,

drivers\input\input.c
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	case EV_KEY:  ...
		if (value == 2)
			break;

		change_bit(code, dev->key);
		// 如果能产生重复类事件就修改定时器超时时间
		if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
			dev->repeat_key = code; // 记录需要重复的按键值
			mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
		}

		break;

drivers\input\input.c
int input_register_device(struct input_dev *dev)
{
	...
	init_timer(&dev->timer);
		dev->timer.function = input_repeat_key;

drivers\input\input.c
static void input_repeat_key(unsigned long data)
{
	// 上报时间 记录的那个重复键值,重复事件2,0松开1按下
	input_event(dev, EV_KEY, dev->repeat_key, 2);
	input_sync(dev);

	if (dev->rep[REP_PERIOD])
		mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD]));
}

根据接受的数据显示或删除的程序是shell,进程号770,查看它打开了哪些文件,ls -l /proc/770/fd,标准输入0输出1错误2都是串口, exec 0</dev/tty1将标准输入改成tty1
在这里插入图片描述

源码

#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> // S3C2410_GPFx

#include <asm/gpio.h>

// 引脚描述结构体 中断号、名称、管脚、键值
static struct pin_desc {
    int irq;
    char *name;
    unsigned int pin;
    unsigned int key_val;
};
// 定义一个引脚描述数组,并初始化,对应于4个按键的信息
#define NUMS_KEYS   4
struct pin_desc pins_desc[NUMS_KEYS] = {
    {IRQ_EINT0,  "EINT0_S2",  S3C2410_GPF0,  KEY_L},
    {IRQ_EINT2,  "EINT2_S3",  S3C2410_GPF2,  KEY_S},
    {IRQ_EINT11, "EINT11_S4", S3C2410_GPG3,  KEY_ENTER},
    {IRQ_EINT19, "EINT19_S5", S3C2410_GPG11, KEY_LEFTSHIFT}
}

// 定义一个输入设备结构
static struct input_dev *_buttons_dev;
// 定义一个定时器
static struct timer_list buttons_timer;
// 发生中断后,参数的在irq和timer_function之间传递
static struct pin_desc *irq_pd; // 在中断中的引脚描述pin_desc

// ISR 发生按键中断之后会调用这个函数, irq就是对应的中断号
// 按键中断发生后进入irq,修改定时器溢出值,修改按键的struct
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    irq_pd = (struct pin_desc *)dev_id;
    // jiffies是全局变量,每隔10ms系统产生一个时钟中断, 10ms
    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 pin_status;
    if (!pindesc)
        return;
    // 获取该引脚状态
    pin_status = s3c2410_gpio_getpin(pindesc->pin);
    // 上报事件
    // 之前的代码是 确定按键值,唤醒用户程序或者发信号,
    // 在input subsystem中,现在只需要上报事件就ok
    // input_event会从input_dev的h_list中找到input_hanldle
    //  找到handler调用event函数 在drivers\input\input.c中input_event的最后几行    
    if (pin_status) { // 松开
        // 哪个设备、事件类型、哪个事件、值(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_inSubSys_init(void)
{
    int i=0, error;
    /*1. 分配一个input_dev结构体 */
    _buttons_dev = input_allocate_device();
    if (!_buttons_dev)
        return -ENOMEM;
    /*2. 设置 */
	// unsigned long evbit[NBITS(EV_MAX)];  // 表示能产生哪类事件
	// unsigned long keybit[NBITS(KEY_MAX)];// 表示能产生哪些事件
	// unsigned long relbit[NBITS(REL_MAX)];// 表示能产生哪些相对位移事件 (x y 滚轮)
  
    // #define EV_SYN			0x00 // 同步类事件
    // #define EV_KEY			0x01 // 按键类事件
    // #define EV_REL			0x02 // 相对位移类事件 鼠标滑动
    // #define EV_ABS			0x03 // 绝对位移类事件 点击屏幕
    // 2.1能产生哪类事件,按键类 重复类(修改定时器超时事件)
    set_bit(EV_KEY, _buttons_dev->evbit);
    set_bit(EV_REP, _buttons_dev->evbit); // 只需要这一句就可以实现按下按键时重复按键值
    // 2.2能产生这类事件里的哪些事件 (哪个按键呢 l s ENTER LEFTSHIFT)
    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. 注册 */ 
    // 加入到某个管理输入子系统设备的链表,然后依次与右边的hanlder的id比较,
    // 看是否支持此设备,支持则调用connect函数,建立input_handle, 
    // dev指向左,handler指向右边,再把input_handle放入两边的.h_list
    error = input_register_device(_buttons_dev);
    if (error){
		printk(KERN_ERR "Unable to register _buttons_dev input device.\n");
        goto fail_input_register_device;
    }
    /*4. 硬件相关操作 */
    // 初始化定时器、定时器函数、start a timer,用于防抖动
    init_timer(&buttons_timer);
    buttons_timer.function = buttons_timer_function;
    add_timer(&buttons_timer);
    // 注册中断函数
    for (i=0; i<NUMS_KEYS; ++i){
        // 应该需要判断返回值
        request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, \
                    pins_desc[i].name, &pins_desc[i]);
		if (error) {
			printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d.\n",
				pins_desc[i].irq, error);
			goto fail_request_irq;        
        }

    return 0;

// 倒序释放
fail_request_irq:
    // 对应于request_irq,注意i
    for (int i=i-1; i>=0; --i)     
        free_irq(pins_desc[i].irq, &pins_desc[i]);
    // 对应于add_timer
    del_timer(&buttons_timer);

fail_input_register_device:
    // 对应于input_register_device
    input_unregister_device(_buttons_dev);
    // input_allocate_device
    input_free_device(_buttons_dev);

    return error;
}

static void buttons_inSubSys_exit(void)
{
    int i;
    // 对应于request_irq
    for (i=NUMS_KEYS-1; i>=; --i) {
        free_irq(pins_desc[i].irq, &pins_desc[i]);
    }    
    // 对应于add_timer
    del_timer(&buttons_timer);
    // 对应于input_register_device
    input_unregister_device(_buttons_dev);
    // input_allocate_device
    input_free_device(_buttons_dev);
}

module_init(buttons_inSubSys_init);
module_exit(buttons_inSubSys_exit);
MODULE_LICENSE("GPL");

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值