jz2440 输入子系统分析


本文是基于韦东山视频的学习笔记

汇总点这

输入子系统的分析

输入子系统是什么

之前我们自己写驱动程序时,一般是这5个步骤

  1. 确定主设备号(可以让系统自动取)、
  2. file_operations 结构体、
  3. open、read、write 等函数、
  4. register_chrdev函数注册驱动、
  5. unregister_chrdev卸载驱动。

但是在测试程序中,我们总以自己的方式打开设备文件,如fd = open("/dev/xxx", O_RDWR); ,但事实上应用程序千种多样,并没有约定俗成说是怎么方式打开设备的,比如lcd驱动可能就是scanf()来获取的。

问题就来了,怎么让驱动程序通用起来。

其实应用程序怎么用驱动程序并不用我们考虑,事实上我们只要把自己的驱动程序融入内核里,让内核去用即可,这样可以使我们的程序更通用。

框架

其实内核已经把驱动分离分层了,包括“软件层”和“硬件层”。

软件层input_register_handler向上注册handler,这个handler包括open、read、write 等函数。

硬件层input_register_device向上注册dev,包括硬件的参数。

软件层

input.c里包含了输入子系统的函数,在input_init 函数里有一个register_chrdev函数并注册了结构体input_fops

static int __init input_init(void)
{
...
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
...
}

但是input_fops这个函数里只有input_open_file 这个函数。

static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};

进入input_open_file 这个函数,我们发现这个函数里其实就是调用了new_fops里面的open函数,而new_fops是从一个handler获取的,而handler又是从input_table 获取的。

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;

	if (!new_fops->open) {
		fops_put(new_fops);
		return -ENODEV;
	}
	err = new_fops->open(inode, file);
...
}

input_table 里面的handler是哪里来的呢?其实就是input_register_handler里面做的事

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

其实这个函数大概做了三件事:

  1. handler放进input_table数组
  2. handler放进链表
  3. 调用input_attach_handler,与硬件层进行匹配,看是否支持

硬件层

input.c里还有一个函数,input_register_device函数就是注册硬件层的函数

int input_register_device(struct input_dev *dev)
{
	...
	list_add_tail(&dev->node, &input_dev_list);
	...
	
	list_for_each_entry(handler, &input_handler_list, node)
	input_attach_handler(dev, handler);
	...
}

这个函数也做了两件事:

  1. dev放进链表
  2. 调用input_attach_handler软件层handler进行匹配,看是否支持

连接

input_attach_handler这个函数里

先调用input_match_device进行handler->id_tabledev的匹配,匹配成功会进行connect

handler->id_table这个自然是软件层的,而dev便是硬件层的。

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	if (handler->blacklist && input_match_device(handler->blacklist, dev))
		return -ENODEV;

	id = input_match_device(handler->id_table, dev);
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id);
	if (error && error != -ENODEV)
		printk(KERN_ERR
			"input: failed to attach handler %s to device %s, "
			"error: %d\n",
			handler->name, kobject_name(&dev->cdev.kobj), error);

	return error;
}

怎么连接呢,其实不同的handler都是不一样的,有空回来详细记录。

编写输入子系统

内核已经做好软件层面了,不同的硬件设备对应的硬件层的代码都不一样,就需要我们来编写了。

步骤

  1. 分配一个input_dev结构体
  2. 设置(能产生哪类事件、能产生这类操作里的哪些事件)
  3. 注册
  4. 硬件相关的操作
static int buttons_init(void)
{

	int i, ret;

	/* 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. 注册 */
	ret = input_register_device(buttons_dev);

	/* 4. 硬件相关的操作 */
	for(i=0; i<4; i++)
	{
		ret = request_irq(pins_desc[i].irq,  buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
	}

	return 0;
}

中断函数

int buttons_irq(int irq, void *dev_id)
{	
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;
	
	if (!pindesc)
		return 0;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if  (pinval)
	{
		/* 松开 */
		input_event(buttons_dev, EV_KEY, pindesc->keyval, 1);
		input_sync(buttons_dev);
	}
	else
	{	
		/* 按下 */
		input_event(buttons_dev, EV_KEY, pindesc->keyval, 0);
		input_sync(buttons_dev);

	}
	
return 0;
}

调试

可以用hexdump /dev/event1命令来调试,意思是以16进制的数据来显示/dev/event1

这里调用的是evdev_read函数,里面的evdev_event_to_user函数

数据报出的结构便是struct input_event_compat compat_event;

struct input_event_compat {
	struct compat_timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};

这个结构表示的是:

  1. 32位的sec
  2. 32位的usec
  3. 16位的type
  4. 16位的code
  5. 32位的value
hexdump /dev/event1  (open(/dev/event1), read(), )
           秒        微秒    类  code    value
0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
0000020 0bb2 0000 5815 000e 0001 0026 0000 0000
0000030 0bb2 0000 581f 000e 0000 0000 0000 0000

加入可重复事件后

set_bit(EV_REP, buttons_dev->evbit);

按键按下松开发现,屏幕不停输出对应的字母,我原意是按住按键才不停输出啊,原来是事件的值搞错了,松开是0,按下是1,我搞反了。

input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值