输入子系统

本帖是学习该帖子后的理解:https://www.cnblogs.com/lifexy/p/7542989.html 

 

若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,当别人使用时会很麻烦。

因此需要使用输入子系统, 使应用程序无需打开多个不同的驱动设备便能实现

 

drivers/input/input.c   

在该文件中可以看到

subsys_initcall(input_init);   //修饰入口函数

其中subsys_initcall和module_init都是宏定义,该宏定义的作用是在链接文件中修饰模块的优先级

#define subsys_initcall(fn) __define_initcall("4",fn,4)

vmlinux.lds是存在于arch/<target>/目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。在vmlinux.lds文件里查找initcall.init就可以看到下面的内容


    __inicall_start = .;
    .initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
    *(.initcall1.init)
    *(.initcall2.init)
    *(.initcall3.init)
    *(.initcall4.init)
    *(.initcall5.init)
    *(.initcall6.init)
    *(.initcall7.init)
    }
    __initcall_end = .;

则说明子系统的入口函数为input_init。找到input_init()的定义:

static int __init input_init(void)
{
       int err;
       err = class_register(&input_class);   //(1)注册类,放在/sys/class
       if (err) {
              printk(KERN_ERR "input: unable to register input_dev class\n");
              return err;
       }
       err = input_proc_init();    //在/proc下面建立相关的文件
       if (err)
              goto fail1;

       err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)注册驱动
       if (err) {
              printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
              goto fail2;
       }

       return 0;

 fail2:     input_proc_exit();

 fail1:     class_unregister(&input_class);

       return err;

}

其中注册了input类,并且注册了字符驱动input。

找到input_fops()

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

当打开输入子系统设备时会调用input_open_file函数

input_open_file()

static int input_open_file(struct inode *inode, struct file *file)
 {
     struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1)
     const struct file_operations *old_fops, *new_fops = NULL;
     int err;

     if (!handler || !(new_fops = fops_get(handler->fops)))  //(2)
          return -ENODEV; 

    if (!new_fops->open) {
           fops_put(new_fops);
           return -ENODEV;
    }

    old_fops = file->f_op;
    file->f_op = new_fops;     //(3)

    err = new_fops->open(inode, file);   //(4)
    if (err) {
          fops_put(file->f_op);
           file->f_op = fops_get(old_fops);
   }

   fops_put(old_fops);

    return err;
}

通过设备的此设备号在input_table数组中寻找相应的input_handler。如果handler有值,说明已经装载了相关的驱动。然后将新的fops替换掉file->f_ops,此时设备子系统的file_operation就是新挂载的input的驱动。

从(4)行可以看到调用了相关驱动函数open()函数。所以整个流程是当打开某输入子系统的设备时会通过次设备号查找到设备相关的input_handler结构体之后,改变file->f_ops,然后在调用其open函数!!!!!

 

通过寻找input_table[]数组查找可以发现函数input_register_handler()

input_register_handler()

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

	retval = mutex_lock_interruptible(&input_mutex);
	if (retval)
		return retval;

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5]) {
			retval = -EBUSY;
			goto out;
		}
         input_table[handler->minor >> 5] = handler;//将handler赋值input_table数组
	}

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

 out:
	mutex_unlock(&input_mutex);
	return retval;
}
EXPORT_SYMBOL(input_register_handler);

该函数一般在一些驱动的init函数中被调用。

以下为时间设备的input_handler。

static struct input_handler evdev_handler = {
       .event =  evdev_event,    
       .connect =      evdev_connect,  //(4)
       .disconnect = evdev_disconnect,
       .fops =           &evdev_fops,    //(1)
       .minor =  EVDEV_MINOR_BASE, //(2)
       .name =         "evdev",
       .id_table =      evdev_ids, //(3)
};

其中id_table表示能够使用该驱动的设备。假如该设备的input_dev->id与该id相同则可以使用。

以上完成了设备类创建、字符设备注册,但是没有创建设备。创建设备需要调用以下函数

input_register_device()

int input_register_device(struct input_dev *dev)   //*dev:要注册的驱动设备
{
 ... ...
       list_add_tail(&dev->node, &input_dev_list);   //(1)放入链表中
 ... ...
       list_for_each_entry(handler, &input_handler_list, node)  //(2)
       input_attach_handler(dev, handler); 
 ... ...
}

该函数将字符设备添加到input_dev_list链表中。

而(2)中该语句没有分号,这是因为这是一个宏定义,将input_handler_list中的input_handler放入handler中,然后input_attach_handler()函数会将设备和驱动进行比较id,看是否支持,若支持则进行连接。同样在input_register_handler()中也会进行id比较

#define list_for_each_entry(pos, head, member)				\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
	     &pos->member != (head); 	\
	     pos = list_entry(pos->member.next, typeof(*pos), member))
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev);  //匹配两者

if (!id)                                     //若不匹配,return退出
return -ENODEV; 

error = handler->connect(handler, dev, id);  //调用input_handler ->connect函数建立连接
... ...

}

如果两者匹配成功就会进入connect函数进行链接

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

	for (minor = 0; minor < EVDEV_MINORS; minor++)
		if (!evdev_table[minor])
			break;

	if (minor == EVDEV_MINORS) {
		pr_err("no more free evdev devices\n");
		return -ENFILE;
	}

	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev)
		return -ENOMEM;

	INIT_LIST_HEAD(&evdev->client_list);
	spin_lock_init(&evdev->client_lock);
	mutex_init(&evdev->mutex);
	init_waitqueue_head(&evdev->wait);

	dev_set_name(&evdev->dev, "event%d", minor);
	evdev->exist = true;
	evdev->minor = minor;

	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;

	evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
	evdev->dev.class = &input_class;
	evdev->dev.parent = &dev->dev;
	evdev->dev.release = evdev_free;
	device_initialize(&evdev->dev);

	error = input_register_handle(&evdev->handle);
	if (error)
		goto err_free_evdev;

	error = evdev_install_chrdev(evdev);
	if (error)
		goto err_unregister_handle;

	error = device_add(&evdev->dev);
	if (error)
		goto err_cleanup_evdev;

	return 0;

 err_cleanup_evdev:
	evdev_cleanup(evdev);
 err_unregister_handle:
	input_unregister_handle(&evdev->handle);
 err_free_evdev:
	put_device(&evdev->dev);
	return error;
}

这里调用了input_register_handle()函数。注意这是input_register_handler(),而不是input_register_handler()函数。

该函数最终形成的效果是:

 

若之前驱动input_dev和处理input_handler已经通过input_handler 的.connect函数建立起了连接,那么就调用evdev_event()的.event事件函数,如下图所示:

该节总节是

1.注册输入子系统,进入input_init():

1)创建主设备号为13的"input"字符设备

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

2.open打开驱动,进入input_open_file():

1)更新设备的file_oprations

file->f_op=fops_get(handler->fops);

2)执行file_oprations->open函数

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

3.注册input_handler,进入input_register_handler():

1)添加到input_table[]处理数组中

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

2)添加到input_handler_list链表中

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

3)判断input_dev的id,是否有支持这个驱动的设备

 list_for_each_entry(dev, &input_dev_list, node)   //遍历查找input_dev_list链表里所有input_dev

 input_attach_handler(dev, handler);             //判断两者id,若两者支持便进行连接。

4.注册input_dev,进入input_register_device():

1)放在input_dev_list链表中

list_add_tail(&dev->node, &input_dev_list);

2)判断input_handler的id,是否有支持这个设备的驱动

list_for_each_entry(handler, &input_handler_list, node)  //遍历查找input_handler_list链表里所有input_handler
input_attach_handler(dev, handler);                      //判断两者id,若两者支持便进行连接。

5.判断input_handlerinput_devid,进入input_attach_handler():

1)匹配两者id,

input_match_device(handler->id_table, dev);        //匹配input_handler和dev的id,不成功退出函数

2)匹配成功调用input_handler ->connect

handler->connect(handler, dev, id);              //建立连接

6.建立input_handlerinput_dev的连接,进入input_handler->connect():

1)创建全局结构体,通过input_handle结构体连接双方

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    //创建两者连接的input_handle全局结构体
list_add_tail(&handle->d_node, &handle->dev->h_list); //连接input_dev->h_list
list_add_tail(&handle->h_node, &handler->h_list);    // 连接input_handle->h_list函数 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值