输入子系统分析与测试

目录

输入子系统概念介绍

1.1 input子系统的作用

1.2 框架分析

编写程序与测试


输入子系统概念介绍

1.1 input子系统的作用

  • 对于自己写的驱动程序,例如直接打开"/dev/buttons",只是个人知道
  • 缺点:

没办法用在别人写的现成的应用程序上,因为别人的应用程序不会open"/dev/buttons",打开可能是其他现成的设备如/dev/tty,甚至不打开什么设备,例如直接scanf就可以获取按键的输入

  • 想写通用的驱动程序,让其他现成的应用程序无缝的移植到单板上,不需要修改别人的应用程序,使用现成的驱动,内核里面现成的驱动程序把自己的东西融合进去,现成的驱动程序即输入子系统(input子系统),想把我们的代码融合到里面去,就需要把输入子系统的框架弄清楚

1.2 框架分析

  • 在内核代码drivers/input下有input.c为核心层,其次为input_handler层和input_device层
  • 在input.c中调用了 err = register_chrdev(INPUT_MAJOR, "input", &input_fops);而input_fops中只有一个open函数没有read函数
static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};
  • 这样怎么读取按键 ?
static int input_open_file(struct inode *inode, struct file *file)
{
	struct input_handler *handler = input_table[iminor(inode) >> 5];
	const struct file_operations *old_fops, *new_fops = NULL;
	int err;

	/* No load-on-demand here? */
	if (!handler || !(new_fops = fops_get(handler->fops)))
		return -ENODEV;

	/*
	 * That's _really_ odd. Usually NULL ->open means "nothing special",
	 * not "no device". Oh, well...
	 */
	if (!new_fops->open) {
		fops_put(new_fops);
		return -ENODEV;
	}
	old_fops = file->f_op;
	file->f_op = new_fops;

	err = new_fops->open(inode, file);

	if (err) {
		fops_put(file->f_op);
		file->f_op = fops_get(old_fops);
	}
	fops_put(old_fops);
	return err;
}

在 input_open_file函数中,struct input_handler *handler = input_table[iminor(inode) >> 5];根据打开的文件的次设备号得到input_handler

new_fops = fops_get(handler->fops) ,定义了一个构造file_operations结构体从input_handler中得到

file->f_op = new_fops;err = new_fops->open(inode, file);应用程序的open最终会调用到file->f_op->open,因此应用程序的read最终会调用到file->f_op->read

  • input_table数组由谁构造?input_table静态为静态变量,所以只能在这个input.c文件里,由input_register_handler来构造
  • input_register_handler函数又被谁调用?在input_handler层纯软件被调用,例如evdev.c、keyboard.c、mousedev.c等在其入口函数向input.c调用

在input.c中对于input_register_handler,input_table[handler->minor >> 5] = handler例如把evdev.c的handler放入数组,list_add_tail(&handler->node, &input_handler_list)放入链表

list_for_each_entry(dev, &input_dev_list, node)      对于每个input_dev,调用input_attach_handler
    input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev       

int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5])
			return -EBUSY;

		input_table[handler->minor >> 5] = handler;
	}

	list_add_tail(&handler->node, &input_handler_list);

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();
	return 0;
}

 在input.c中对于input_register_device,list_add_tail(&dev->node, &input_dev_list)放入链表,也会对于每个input_dev,调用input_attach_handler

  • 所以无论先注册input_handler或者先注册输入设备都会调用到input_attach_handler,会两两比较input_dev和input_handler
  • 在input_attach_handler中有error = handler->connect(handler, dev, id); 如果input_handler的id_table支持这个input_dev,则调用input_handler的connect函数建立"连接"
  • 连接过程:

1.分配一个input_handle结构体

2.input_handle.dev = input_dev;  // 指向左边的input_dev
   input_handle.handler = input_handler;  // 指向右边的input_handler

3. 注册:
   input_handler->h_list = &input_handle;
   inpu_dev->h_list      = &input_handle;

  • input_register_device中的inpu_dev->h_list指向input_handle,再从input_handle中的handler找到input_handler找到处理者,input_register_handler中的input_handler->h_list也指向input_handle,再从input_handle中的dev找到input_dev找到支持的设备,从而建立连接
  • 读取按键过程

app调用read,例如调用到input_handler层的evdev.c的evdev_read

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
...
// 无数据并且是非阻塞方式打开,则立刻返回
	if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
// 否则休眠
	retval = wait_event_interruptible(evdev->wait,
		client->head != client->tail || !evdev->exist);
...
	return retval;
}

client->head == client->tail,环型缓冲冲区,若为空则返回错误,有读写两个位置,空的时候R==W,满的时候(W+1)%LEN == R, 写:buf[w] = val,w = (w + 1) % LEN,读:val = buf[R], R = (R + 1) % LEN 

若休眠了,谁来唤醒,在evdev.c中的evdev_event函数调用了wake_up_interruptible(&evdev->wait)

evdev_event被谁调用?硬件相关的代码,input_dev层调用,在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数

例如在input_dev层gpio_keys_isr.c中发现利用以下两个函数实现唤醒

input_event(input, type, button->code, !!state);
input_sync(input);

在input_event中,调用加到到链表中每一项handle,判断哪项被open从而调用event来唤醒

list_for_each_entry(handle, &dev->h_list, d_node)
    if (handle->open)
        handle->handler->event(handle, type, code, value);

  • 写符合输入子系统框架的驱动程序

1. 分配一个input_dev结构体 2.设置input_dev结构体 3.注册input_dev结构体 4.硬件相关的代码,如在中断服务程序里上报事件

编写程序与测试

  • 软件方面在input_handler层已经写好了,因此需要完善硬件层input_dev
  • 对于input.c中的file_operations只有一个open函数,open起到一个中转的作用,找到input_handler中的fops来open
  • 用input_allocate_device函数来分配input_dev结构体

struct input_dev {

    void *private;

    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    unsigned long evbit[NBITS(EV_MAX)];      // 表示能产生哪类事件
    unsigned long keybit[NBITS(KEY_MAX)];  // 表示能产生哪些按键
    unsigned long relbit[NBITS(REL_MAX)];    // 表示能产生哪些相对位移事件, x,y,滚轮
    unsigned long absbit[NBITS(ABS_MAX)];  // 表示能产生哪些绝对位移事件, x,y
    unsigned long mscbit[NBITS(MSC_MAX)];
    unsigned long ledbit[NBITS(LED_MAX)];
    unsigned long sndbit[NBITS(SND_MAX)];
    unsigned long ffbit[NBITS(FF_MAX)];
    unsigned long swbit[NBITS(SW_MAX)];

...
};

--------------------------------------------------------

#define EV_SYN            0x00   //同步类
#define EV_KEY            0x01   //按键类
#define EV_REL            0x02   //相对位移类 
#define EV_ABS            0x03   //绝对位移类
#define EV_MSC            0x04
#define EV_SW            0x05
#define EV_LED            0x11
#define EV_SND            0x12
#define EV_REP            0x14
#define EV_FF            0x15
#define EV_PWR            0x16
#define EV_FF_STATUS        0x17
#define EV_MAX            0x1f

  • 设置为按键类事件用set_bit函数,把单板上四个按键用来产生按键类中的L、S、ENTER、LEFYSHIFT事件

set_bit(EV_KEY, buttons_dev->evbit);

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);

  • 用input_register_device注册结构体
  • 在中断服务程序里上报事件 input_event,参数一为input_dev结构体,参数二为按键类事件,参数三为按键类中的事件,参数四为松开还是按下,配合input_sync函数表示上报完毕

DEMO:


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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

struct pin_desc pins_desc[4] = {
	{IRQ_EINT0, "S2", S3C2410_GPF0  , KEY_L},
	{IRQ_EINT2, "S3", S3C2410_GPF2  , KEY_S},
	{IRQ_EINT11,"S4", S3C2410_GPG3  , KEY_ENTER},
	{IRQ_EINT19,"S5", S3C2410_GPG11 , 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_HANDLED;                         
}

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

	if(!pindesc)
		return;

	/* 读取PIN值 */
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	/* 确定按键值 */
	if(pinval){
		/* 松开最后一个参数:1-按下 0-松开 */
		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,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.注册 */
	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, IRQT_BOTHEDGE, 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");
  •  编译加载驱动,其中/dev/event1对应buttons.ko,因为evdev.c中evdev_ids支持所有的输入设备,也就支持我们的按键设备

# ls -l /dev/event*
crw-rw----    1 0        0         13,  64 Jan  1 00:00 /dev/event0
# insmod buttons.ko
input: Unspecified device as /class/input/input1
# ls -l /dev/event*
crw-rw----    1 0        0         13,  64 Jan  1 00:00 /dev/event0
crw-rw----    1 0        0         13,  65 Jan  1 00:00 /dev/event1

  • 在input.c只创建了类,在evdev.c中evdev_connect建立连接才根据类创建设备,因为evdev的EVDEV_MINOR_BASE次设备号从64开始,所以为event1次设备号65
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
...
	sprintf(evdev->name, "event%d", minor);

	evdev_table[minor] = evdev;

	devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),

	cdev = class_device_create(&input_class, &dev->cdev, devt,
				   dev->cdev.dev, evdev->name);
...
	return error;
}

第一种方法:

  • hexdump /dev/event1,打开/dev/event1设备,读出并将数据以十六进制显示,按下回车,0001为类,001c为事件KEY_ENTER,0001表示按下

                      秒             微妙      type  code     value 

0000000 038d 0000 1053 000b 0001 001c  0001 0000
0000010 038d 0000 105f 000b 0000 0000   0000 0000
0000020 038d 0000 310f 000e 0001 001c   0000 0000
0000030 038d 0000 3118 000e 0000 0000  0000 0000

  • 之所以这么显示是因为调用evdev_read其中会将数据返回给用户空间

static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
{
    if (copy_to_user(buffer, event, sizeof(struct input_event)))
        return -EFAULT;

    return 0;
}

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

第二种方法:

个人单板:S3C2440

  • 如果已经启动了QT:可以点开记事本然后按:s2,s3,s4
  • 如果没有启动QT:cat /dev/tty1,按:s2,s3,s4,就可以得到ls,/dev/tty1主设备是4,次设备号1根据复杂的驱动程序tty_io.c来访问keyboard.c,其中的kbd_ids只要输入设备能产生按键类事件,keyboard.c就能够支持,还有EV_SND声音类,则event事件由keyboard.c发生

static const struct input_device_id kbd_ids[] = {
    {
                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
                .evbit = { BIT(EV_KEY) },
        },

    {
                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
                .evbit = { BIT(EV_SND) },
        },

    { },    /* Terminating entry */
};


# cat /dev/tty1
ls

  • exec 0

输入子系统分析与测试_第1张图片

  •  按键重复类事件,使按键按住能不断产生,其中L为shift事件和l事件产生

set_bit(EV_REP, buttons_dev->evbit);

你可能感兴趣的:(Linux驱动)