前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linxu 输入子系统分析

Linxu 输入子系统分析

作者头像
DragonKingZhu
发布2022-05-08 15:45:19
3140
发布2022-05-08 15:45:19
举报
代码语言:javascript
复制
/*输入子系统分析 input.c*/

/*1. 为什么需要输入子系统? 

	由于我们平时用的输入设备比较杂乱,比较多。 比如: 鼠标,键盘, 触摸屏等。
	当我们写驱动的时候都需要注册字符设备文件或者混杂设备文件。所以出现了一种机制。
	这种机制就是把各种输入设备定义为input_device。 把处理这种设备的函数定义为input_handler。
	这个样以来,程序员只需要操作input_device,而input_handler则是已经为各种设备做好的处理函数了。
	这样的好处是对所有的设备进行一个统一和抽象处理。形成一个统一的接口。方便驱动的编写等。
	这种机制就是输入子系统。

*/

/*分析: drivers/input/input.c*/

static int __init input_init(void)
{
	int err;

	/*注册input类*/
	err = class_register(&input_class);
	if (err) {
		pr_err("unable to register input_dev class\n");
		return err;
	}

	err = input_proc_init();
	if (err)
		goto fail1;

	/*注册字符设备。主设备号13。 file_operations定义为input_fops*/
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
	if (err) {
		pr_err("unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}

	return 0;
}

/*而input_fops中只有一个open函数, 那如何read, write设备呢?*/
static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};

/*先进入open函数看看,都干了什么? */
static int input_open_file(struct inode *inode, struct file *file)
{
	struct input_handler *handler;
	const struct file_operations *old_fops, *new_fops = NULL;
	
	/*从input_table中根据次设备号找到handler, 获得handler中的fops, 赋值给new_fops*/
	handler = input_table[iminor(inode) >> 5];
	if (handler)
		new_fops = fops_get(handler->fops);
		
	/*将new_fops给file->f_op*/
	old_fops = file->f_op;
	file->f_op = new_fops;

  /*调用new_fops中的open函数。 也就是说input_open_file只是一个中转的过程。到头来还是调用设备注册时的fops中的open函数*/
	err = new_fops->open(inode, file);
	if (err) {
		fops_put(file->f_op);
		file->f_op = fops_get(old_fops);
	}
}

/* 有一个问题? 那input_table是从那里赋值的?发现input_table是从 input_register_handle中得到的 */
int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;
	int retval;

	INIT_LIST_HEAD(&handler->h_list);
	
	/*将传进来的hanler放入input_table中*/
	input_table[handler->minor >> 5] = handler;

  /*放入input_handler_list链表*/
	list_add_tail(&handler->node, &input_handler_list);

  /*遍历input_dev_list链表, 对于每个Input_dev结构, 调用input_attach_handler根据input_handler的id_table判断是否支持input_dev*/
	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	return retval;
}

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	int error;

	/*根据input_handler中的id_table判断是否支持input_dev。 如果支持则调用input_handler中的connect函数*/
	id = input_match_device(handler, dev);
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id);

	return error;
}

/*还有一个问题?那是谁调用input_register_handler函数的?*/

/*搜索工程,发现是各式各样的输入设备。 比如键盘, 鼠标, 游戏句柄等。 也就是说当调用input_register_handler函数时, 
 参数handler就是处理这类设备的操作集合。
*/

/*那么问题来了? 当我想实现一个按键操作LED灯的驱动程序时, 我应该如何实现? */

int input_register_device(struct input_dev *dev)
{
	/*将input_dev放入input_dev_list链表中*/
	list_add_tail(&dev->node, &input_dev_list);

  /*遍历input_handler_list链表,对于每个input_handler结构,调用input_attach_handler看是否匹配。
    如果匹配则调用input_handler中的connect函数建立"连接"
   */
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);
}

/*
那么, 如何建立连接?
*/      

/*分析: evdev.c 一个特殊的handler例子*/
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{
	
	//1. 分配一个evdev结构	
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

  //2. 初始化evdev->handle中的dev
  //   初始化evdev->handle中的hanler。 这样以来,input_handle就成handler与dev之间连接的桥梁了。
	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;

}

//3. 注册input_handle
int input_register_handle(struct input_handle *handle)
{
	//input_handler->h_list = input_handle
	//input_dev->h_list = input_handle
	list_add_tail_rcu(&handle->d_node, &dev->h_list);
	list_add_tail_rcu(&handle->h_node, &handler->h_list);
}

/*
小结:当注册Input_dev时,会去input_handler的链表中, 从前到后,依次的根据input_handler中的id_table判断是否支持这个刚注册的input_dev。
      如果支持,则调用input_handler中的connect函数来建立连接。 所谓的建立连接,也就是建立一个input_handle结构。 这样的话handle中的dev指向input_dev
      handle中的handler指向Input_handler。 然后将input_handle放入input_dev的handle链表中,input_handle放如input_dev的链表中。
      当需要操作input_dev支持的Input_handler时, 就会去input_dev的handle链表中找到input_handle,然后同过input_handle中的handler找到input_handler。
      
      同理: 当注册input_handler时,会去input_dev的链表中, (省略, 和上面的操作几乎一样)
*/



/*那还有几个问题? 当应用程序read时, 我们的驱动程序应该干些什么?*/

//我们上面分析了,new_fops是从input_register_handler传进来的handler中的fops。 行,我们假设我们用的evdev这个事件处理handler

/*分析evdev_read函数 */
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
	//如果循环缓冲区的头等于循环缓冲区的尾部,或者是不阻塞的。 则立刻返回
	if (client->packet_head == client->tail && evdev->exist &&
	    (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
		
	/*不过不是上述的情况,则立马休眠*/
	retval = wait_event_interruptible(evdev->wait,
		client->packet_head != client->tail || !evdev->exist);
}

/*很明显,当read的时候,没有数据就立马休眠, 那当有数据时, 谁来唤醒呢? */

/*毫无疑问,当数据来时,硬件先知道的。 当数据到达时, 就会触发中断。*/

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	//当事件发生时, 则调用input_event上报事件,则会调用input_handler中的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. 硬件相关的代码,一般都在中断子程序中上报事件。
*/
本文参与?腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-01-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com