在分析完TS的代码之后,我尝试改了个TS驱动,结果发现驱动并没有调用probe函数,带着这个疑问,我们继续来分析input.c文件,这个文件和TS驱动的关联是INPUT设备的注册。
同样的,为了方便理解,我们打乱了程序的顺序。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
我们先分析tsdev.c用到的函数
//注册句柄(8种输入类型之一),init初始化的时候调用
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) {
//如果handler挂上了fops操作函数,则执行这段
//次版本号,只用高三位。如果已经挂上了handler则出错,否则挂上handler。
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;
}
//将handler挂上list链表
list_add_tail(&handler->node, &input_handler_list);
//遍历list设备链表,取出节点中每一个设备
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
//唤醒读等待队列input_devices_poll_wait
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}
EXPORT_SYMBOL(input_register_handler);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
注册本应该和卸载函数在一起的,因为在注册函数中用到了input_attach_handler,为了方便阅读,这里就将相关联的函数给提前分析了
static int input_attach_handler(
struct input_dev *dev, //设备
struct input_handler *handler) //句柄
{
const struct input_device_id *id; //这个结构体里保存的是事件的报告数组
int error;
//这个函数我们在SI中关联了一下,发现有两个函数会调用,一个是input_register_handler,另外一个是input_register_device。
//至少在TS的input_register_handler中,blacklist是没有设置的,所以这一段可以暂时忽略
if (handler->blacklist
&& input_match_device(handler->blacklist, dev))
return -ENODEV;
//继续关联进这个函数(为了方便阅读,这个函数的实现跟在本函数下,请先跳读)
//实际上就是找出id_table中和dev中设置完全一样的ID。
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV; //匹配失败
//调用handler的connect函数
error = handler->connect(handler, dev, id);
return error;
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//这个函数只是在input_attach_handler时才被调用
//这里第二次出现了这个结构体,所以我们先看一下在tsdev文件中的一个定义:
static const struct input_device_id tsdev_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT
| INPUT_DEVICE_ID_MATCH_KEYBIT
| INPUT_DEVICE_ID_MATCH_RELBIT,
.evbit = { BIT(EV_KEY) | BIT(EV_REL) }, //按键,相对坐标
.keybit = { [BTN_LEFT/32] = BIT(BTN_LEFT) }, //左键
.relbit = { BIT(REL_X) | BIT(REL_Y) }, //相对坐标
} /* A mouse like device, at least one button, two relative axes */
如果id->flags定义的类型匹配成功。或者是id->flags没有定义,就会进入到MATCH_BIT的匹配项了.从MATCH_BIT宏的定义可以看出。只有当iput device和input handler的id成员在evbit, keybit,… swbit项相同才会匹配成功。而且匹配的顺序是从evbit, keybit到swbit.只要有一项不同,就会循环到id中的下一项进行比较.
static const struct input_device_id *input_match_device(
const struct input_device_id *id, //要匹配的ID组
struct input_dev *dev) //设备
{
int i;
//flags | driver_info存在,说明该id有效
for (; id->flags || id->driver_info; id++) {
//flag要件检查机器总线,如果bustype不匹配,下一个ID检查
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
//生产商
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
//产品ID
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
//版本号
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
return id;
}
return NULL;
}
//--------------------------
将MATCH_BIT宏展开:
#define MATCH_BIT(bit, max) \
for (i = 0; i < BITS_TO_LONGS(max); i++) \
if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \
break; \
if (i != BITS_TO_LONGS(max)) \
continue;
我们将其中一项代入来看:
for(i=0; i<BIT_TO_LONGS(EV_MAX); i++) //计算max占几个双字
{
//这样一代入就很清楚了,比较id中指定的数组和dev是否一致,不一致就break
if( (id->evbit[i] & dev->evbit[i]) != id->evbit[i] )
break;
}
if(i != BITS_TO_LONGS(max))
continue; //如果在上面的比较中是中途被打断的,则说明没有匹配,直接进行下一个id循环
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
卸载句柄时调用
void input_unregister_handler(struct input_handler *handler)
{
struct input_handle *handle, *next;
mutex_lock(&input_mutex);
//遍历该类设备的句柄列表,将挂在该列表上的设备全部删除(调用disconnect)
list_for_each_entry_safe(handle, next, &handler->h_list, h_node)
handler->disconnect(handle);
WARN_ON(!list_empty(&handler->h_list));
//将本handler从input设备上断开
list_del_init(&handler->node);
//将input_table挂载的点挂空
if (handler->fops != NULL)
input_table[handler->minor >> 5] = NULL;
//唤醒等待队列
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
}
EXPORT_SYMBOL(input_unregister_handler);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//设备注册
//顺便COPY一段注解:
* This function registers device with input core. The device must be
* allocated with input_allocate_device() and all it's capabilities
* set up before registering.
//这个函数实际操作如下:
//1、保证dev的参数rep[],getkeycode,setkeycode有挂载
//2、指定设备的父设备
//3、device_add(&dev->dev)添加设备
//4、将dev->node关联上input_dev_list
//5、input_attach_handler本设备属于哪一类handler
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0); //这个宏没有代码,就是返回x
struct input_handler *handler;
const char *path;
int error;
__set_bit(EV_SYN, dev->evbit); //设备都要带上SYN事件
init_timer(&dev->timer); //初始化定时器
//如果这两个参数没有设置,则进行初始化(主要是处理按键按住不放的)
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev; //将dev作为参数传入TMR回调函数中
dev->timer.function = input_repeat_key; //设置TMR的回调函数
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
//如果获取/设置按键代码函数没有指定,则关联上默认的函数
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
//指定设备的父设备
if (dev->cdev.dev)
dev->dev.parent = dev->cdev.dev;
//将设备添加进设备链表中
error = device_add(&dev->dev);
if (error)
return error;
//获取路径,貌似只是为了打印,打印完后就把path给kfree掉了
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
//等待互斥锁
error = mutex_lock_interruptible(&input_mutex);
if (error) {
device_del(&dev->dev);
return error;
}
//将本INPUT设备链接进input_dev_list链表(这里才是注册的关键吧)
//在handler注册的时候,会从input_dev_list链表中提取每一个设备来attach
list_add_tail(&dev->node, &input_dev_list);
//从input_handler_list中找到已经注册的handler,然后进行attach检测,看本设备是否属于该类(检测的方法其实就是比较各种ID标记的设置是否一致)
//如果已经注册的handler都不是本设备的宿主,则当新handler注册的时候也会遍历input_dev_list链表来attach设备。
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
//唤醒等待队列
input_wakeup_procfs_readers();
//释放互斥锁
mutex_unlock(&input_mutex);
return 0;
}
EXPORT_SYMBOL(input_register_device);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//卸载函数:
//1、对device下所有的handle进行disconnect
//2、删除TIMER
//3、将dev->node从链表中断开。(input_dev_list)
void input_unregister_device(struct input_dev *dev)
{
struct input_handle *handle, *next;
input_disconnect_device(dev);
mutex_lock(&input_mutex);
//从dev的h_list链表中提取每一个handle进行disconnect
list_for_each_entry_safe(handle, next, &dev->h_list, d_node)
handle->handler->disconnect(handle);
WARN_ON(!list_empty(&dev->h_list));
//同步的删除timer
del_timer_sync(&dev->timer);
//将dev->node从链表中断开
list_del_init(&dev->node);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
device_unregister(&dev->dev);
}
EXPORT_SYMBOL(input_unregister_device);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
static void input_disconnect_device(struct input_dev *dev)
{
struct input_handle *handle;
int code;
mutex_lock(&dev->mutex);
dev->going_away = 1; //表示设备已经断开
mutex_unlock(&dev->mutex);
spin_lock_irq(&dev->event_lock);
//支持按键事件,将所有还没有释放的按键释放
if (is_event_supported(EV_KEY, dev->evbit, EV_MAX)) {
for (code = 0; code <= KEY_MAX; code++) {
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
test_bit(code, dev->key)) {
input_pass_event(dev, EV_KEY, code, 0);
}
}
input_pass_event(dev, EV_SYN, SYN_REPORT, 1);
}
//将dev上的所有handle的open计数都清0
list_for_each_entry(handle, &dev->h_list, d_node)
handle->open = 0;
spin_unlock_irq(&dev->event_lock);
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
顺便,我们把剩下两个和device有关的函数也一起看下
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
这个函数很简单,根据type类型 __set_bit(code, dev->xxxbit);
最后__set_bit(type, dev->evbit);
//释放设备
void input_free_device(struct input_dev *dev)
{
if (dev)
input_put_device(dev);
}
EXPORT_SYMBOL(input_free_device);
//申请一个输入设备,并做一些初始化工作
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev;
//申请一块内存
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
if (dev) { //申请成功,设置一些初始值
dev->dev.type = &input_dev_type;
//static struct device_type input_dev_type = {
// .groups = input_dev_attr_groups,
// .release = input_dev_release,
// .uevent = input_dev_uevent,
//};
dev->dev.class = &input_class;
//struct class input_class = {
// .name = "input", //定义了所属类的名字——input设备类
//};
device_initialize(&dev->dev); //初始化设备
mutex_init(&dev->mutex); //初始化互斥锁
spin_lock_init(&dev->event_lock); //初始化自旋锁
INIT_LIST_HEAD(&dev->h_list);
INIT_LIST_HEAD(&dev->node); //初始化链表
__module_get(THIS_MODULE); //空函数
}
return dev;
}
EXPORT_SYMBOL(input_allocate_device);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
我们再看看TIMRE的中断函数:
这里的参数data为dev,在register中设置的
这个函数的作用是处理长按键的(间隔指定时间重发按键)
static void input_repeat_key(unsigned long data)
{
struct input_dev *dev = (void *) data;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags); //自旋锁关中断
//如果dev不支持按键,则什么也不做
if (test_bit(dev->repeat_key, dev->key) &&
is_event_supported(dev->repeat_key, dev->keybit, KEY_MAX)) {
input_pass_event(dev, EV_KEY, dev->repeat_key, 2);
//同步操作,使报告有效
if (dev->sync) {
input_pass_event(dev, EV_SYN, SYN_REPORT, 1);
}
//设置下一次进TIMR的时间
if (dev->rep[REP_PERIOD])
mod_timer(&dev->timer, jiffies +
msecs_to_jiffies(dev->rep[REP_PERIOD]));
}
//解锁
spin_unlock_irqrestore(&dev->event_lock, flags);
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
继续看进去:input_pass_event
其实这个函数的实质还是调用具体handler的event函数,将参数直接透传下去
如果dev没有设备grab上,即handle为空,则在dev的h_list链表上找所有open的设备来透传参数。
static void input_pass_event(
struct input_dev *dev, //设备
unsigned int type, //事件
unsigned int code, //代码
int value) //值
{
struct input_handle *handle;
rcu_read_lock(); //读锁
handle = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
rcu_read_unlock();
}
下面这个图是从网上找到的,比较直观的说明了这个函数
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//注册和卸载handle,主要的操作是关联上d_node和h_node
//在具体handler的connect函数时调用
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
//互斥锁
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
//d_node,这里应该是dev node,即设备节点的缩写,所以是挂在dev上
list_add_tail_rcu(&handle->d_node, &dev->h_list);
mutex_unlock(&dev->mutex);
synchronize_rcu();
//同样的,h_node应该就是handler的缩写了
//在handler的unregister函数中,正是遍历handler->h_list来断开设备的
list_add_tail(&handle->h_node, &handler->h_list);
//调用start函数
if (handler->start)
handler->start(handle);
return 0;
}
EXPORT_SYMBOL(input_register_handle);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//同样的,unregister就是register的反过程,其实也就做了两件事,断开handle的h_node和d_node链表
void input_unregister_handle(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
list_del_init(&handle->h_node);
mutex_lock(&dev->mutex);
list_del_rcu(&handle->d_node);
mutex_unlock(&dev->mutex);
synchronize_rcu();
}
EXPORT_SYMBOL(input_unregister_handle);
分析完以上三个注册函数之后,我们可以大概理清一个顺序:
Input设备中一功有N大类,每一类关联着handler,然后有N个设备device,他们按类型关联到相关的handler之中,再然后是handle,他们关联在某一个device下。
这个时候,我们再来看下面这个图,应该就不难理解了:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define INPUT_DEVICES 256
static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list); //两个链表,一个是设备,一个是句柄
static DEFINE_MUTEX(input_mutex); //互斥锁
static struct input_handler *input_table[8]; //输入表,用于保存每一类的handler
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
subsys_initcall(input_init); //入口是定义在.initcall 4 .init段中,也就会先于模块执行(module_init是编译在initcall 6段中,具体可以查看include\linux\init.h文件)
module_exit(input_exit);
先看一下操作函数
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file, //只定义了OPEN接口
};
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //注册输入类
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}
err = input_proc_init(); //初始化代码,这个函数我们后面再看
if (err)
goto fail1;
//注册字符型驱动(INPUT_MAJOR是在内核里定义的主设备号)
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
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;
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
static void __exit input_exit(void)
{
input_proc_exit(); //卸载函数,完成一些销毁动作
unregister_chrdev(INPUT_MAJOR, "input"); //卸载设备
class_unregister(&input_class); //卸载类
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
我们现在来看看初始化函数中调用的input_proc_init函数执行了什么功能
在看这个函数之前,我们先看看这一段的变量申明:
static struct proc_dir_entry *proc_bus_input_dir; //这个结构体是和文件系统有关了
static DECLARE_WAIT_QUEUE_HEAD(input_devices_poll_wait); //等待队列
static int input_devices_state;
static int __init input_proc_init(void)
{
struct proc_dir_entry *entry;
//建立一个文件夹(实际路径是在\proc\bus\input)
proc_bus_input_dir = proc_mkdir("input", proc_bus);
if (!proc_bus_input_dir)
return -ENOMEM;
proc_bus_input_dir->owner = THIS_MODULE;
//在该文件夹下建立devices文件
entry = create_proc_entry("devices", 0, proc_bus_input_dir);
if (!entry)
goto fail1;
entry->owner = THIS_MODULE;
entry->proc_fops = &input_devices_fileops; //关联上文件的操作函数
//创建handlers文件
entry = create_proc_entry("handlers", 0, proc_bus_input_dir);
if (!entry)
goto fail2;
entry->owner = THIS_MODULE;
entry->proc_fops = &input_handlers_fileops;
return 0;
//删除之前创建的文件和文件夹
fail2: remove_proc_entry("devices", proc_bus_input_dir);
fail1: remove_proc_entry("input", proc_bus);
return -ENOMEM;
}
//我们再来看看关联的文件操作:
static const struct file_operations input_devices_fileops = {
.owner = THIS_MODULE,
.open = input_proc_devices_open,
.poll = input_proc_devices_poll,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static const struct file_operations input_handlers_fileops = {
.owner = THIS_MODULE,
.open = input_proc_handlers_open,
//这几个seq操作,其实都在fs\Seq_file.c中实现的。
//这个操作要说清楚就有得扯了,还是先放放吧,我们只知道他会调用input_handlers_seq_ops中的那四个函数操作。
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
初始化的代码看完了,input_proc_exit其实也该猜到了,肯定是删除文件和文件夹
static void input_proc_exit(void)
{
remove_proc_entry("devices", proc_bus_input_dir);
remove_proc_entry("handlers", proc_bus_input_dir);
remove_proc_entry("input", proc_bus);
}
这部分代码就先在这里打住,主要是seq的操作很晕人,我们先看看input注册时挂上的fops
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
设备的打开函数小结如下:
1、根据inode的次版本号获取对应的handler
2、获取handler的fops操作函数,并执行其中的open函数
static int input_open_file(struct inode *inode, struct file *file)
{
//根据inode的次设备号获得handler句柄
//原理在handler的注册函数中有代码说明(handler注册的时候,次版本的高3位表示handler属于INPUT的哪一类,然后按类将handler存入对应的input_table中)
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
int err;
//如果handler没有注册 或 handler没有关联fops,出错
if (!handler || !(new_fops = fops_get(handler->fops)))
return -ENODEV;
//如果handler的fops中无open函数,出错
if (!new_fops->open) {
fops_put(new_fops); //在fops_get中引入了计数,所以这里要put
return -ENODEV;
}
//文件的f_op映射到handler的fops上
old_fops = file->f_op;
file->f_op = new_fops;
//调用handler->open函数
err = new_fops->open(inode, file);
if (err) { //出错的处理
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops); //正确,减小old_fops的引用
return err;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
我们再来看看input文件中其他一些对外的函数
输入事件,这个函数的一个调用是在input_report_xxx宏中(input.h文件)。
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
void input_event(
struct input_dev *dev, //设备
unsigned int type, //事件
unsigned int code, //代码(如键值)
int value) //值(如按下,放开)
{
unsigned long flags;
//判断这个事件是否被dev所支持
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags); //自旋锁
//这个函数和report无关,这里先忽略
add_input_randomness(type, code, value);
//这个函数的实现见下,具体作用是根据type来决定最后调用哪个函数来处理event
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
EXPORT_SYMBOL(input_event);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
跟踪进input_handle_event
#define INPUT_IGNORE_EVENT 0
#define INPUT_PASS_TO_HANDLERS 1
#define INPUT_PASS_TO_DEVICE 2
#define INPUT_PASS_TO_ALL
(INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
这个函数的作用是根据传入type分别处理,处理的实质只是设置好disposition,switch完了之后将根据这个寄存器决定是调用dev->event(dev, type, code, value)还是input_pass_event(dev, type, code, value);
static void input_handle_event(
struct input_dev *dev,
unsigned int type,
unsigned int code,
int value)
{
int disposition = INPUT_IGNORE_EVENT; //0
//根据事件类型做不同的处理
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
if (!dev->sync) {
dev->sync = 1;
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
}
break;
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) {
if (value != 2) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
}
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) {
__change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX)) {
value = input_defuzz_abs_event(value,
dev->abs[code], dev->absfuzz[code]);
if (dev->abs[code] != value) {
dev->abs[code] = value;
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
case EV_REL:
if (is_event_supported(code, dev->relbit, REL_MAX) && value)
disposition = INPUT_PASS_TO_HANDLERS;
break;
case EV_MSC:
if (is_event_supported(code, dev->mscbit, MSC_MAX))
disposition = INPUT_PASS_TO_ALL;
break;
case EV_LED:
if (is_event_supported(code, dev->ledbit, LED_MAX) &&
!!test_bit(code, dev->led) != value) {
__change_bit(code, dev->led);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_SND:
if (is_event_supported(code, dev->sndbit, SND_MAX)) {
if (!!test_bit(code, dev->snd) != !!value)
__change_bit(code, dev->snd);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_REP:
if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_FF:
if (value >= 0)
disposition = INPUT_PASS_TO_ALL;
break;
case EV_PWR:
disposition = INPUT_PASS_TO_ALL;
break;
}
//如果不是SYN事件,dev的sync设置为0
if (type != EV_SYN)
dev->sync = 0;
//描述为PASS给设备 且 设备的event函数有设置,将参数透传给设备的event处理
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
//描述为PASS给handler,则调用input_pass_event
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//这个函数其实和上一个函数input_event很相似,只是这个函数多一个grab的比较
void input_inject_event(
struct input_handle *handle,
unsigned int type,
unsigned int code,
int value)
{
struct input_dev *dev = handle->dev;
struct input_handle *grab;
unsigned long flags;
//检查事件类型是否被支持
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
rcu_read_lock();
grab = rcu_dereference(dev->grab);
//handle不存在 或 handle为指定handle
//如果handle不存在,则说明没有设置过grab机制,那么就不理会他
//如果handle存在,则需要比较以下当前的handle是否是指定的handle
if (!grab || grab == handle)
input_handle_event(dev, type, code, value);
rcu_read_unlock();
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
EXPORT_SYMBOL(input_inject_event);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//绑定handle到dev中
int input_grab_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int retval;
//申请自旋锁
retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval;
//设备的grab已经设置,出错
if (dev->grab) {
retval = -EBUSY;
goto out;
}
//绑定handle
rcu_assign_pointer(dev->grab, handle);
synchronize_rcu();
out:
mutex_unlock(&dev->mutex);
return retval;
}
EXPORT_SYMBOL(input_grab_device);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//释放指定的handle,主要是将handle与dev->grab断开关联
void input_release_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
mutex_lock(&dev->mutex);
__input_release_device(handle);
mutex_unlock(&dev->mutex);
}
EXPORT_SYMBOL(input_release_device);
static void __input_release_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
//如果没有绑定handle,则什么也不做
if (dev->grab == handle) {
rcu_assign_pointer(dev->grab, NULL); //将grab挂空
synchronize_rcu();
//取出挂载在dev上的全部handle,并执行handle对应的handler->start()
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open && handle->handler->start)
handle->handler->start(handle);
}
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
这个函数的核心就是dev->open()
int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int retval;
retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval;
//如果他关联的宿主dev已经disconnect,则错误返回
if (dev->going_away) {
retval = -ENODEV;
goto out;
}
handle->open++; //打开计数加1
//调用dev的open函数
if (!dev->users++ && dev->open)
retval = dev->open(dev);
//打开失败
if (retval) {
dev->users--;
if (!--handle->open) {
synchronize_rcu();
}
}
out:
mutex_unlock(&dev->mutex);
return retval;
}
EXPORT_SYMBOL(input_open_device);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
同样的,把一些可以忽略的代码去掉之后,这个函数的核心就是dev->flush()
int input_flush_device(struct input_handle *handle, struct file *file)
{
struct input_dev *dev = handle->dev;
int retval;
retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval;
if (dev->flush)
retval = dev->flush(dev, file);
mutex_unlock(&dev->mutex);
return retval;
}
EXPORT_SYMBOL(input_flush_device);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//这个函数的核心就是执行了两个操作:
1、 通过release函数断开dev->grab的关联
2、如果dev->users的计数为0了,就调用dev->close(该计数每open操作时加1)
void input_close_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
mutex_lock(&dev->mutex);
__input_release_device(handle); //先release设备(断开dev->grab的关联)
//引用计数减1,如果为0就调用close
if (!--dev->users && dev->close)
dev->close(dev);
if (!--handle->open) {
synchronize_rcu();
}
mutex_unlock(&dev->mutex);
}
EXPORT_SYMBOL(input_close_device);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
有对外接口的函数分析完了,我们再来从头清一下零散的函数
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//这个函数我们之前见了很多了,检查事件是否被支持
static inline int is_event_supported(
unsigned int code,
unsigned long *bm,
unsigned int max)
{
//code小于max 且 bm的指定位code为1
return code <= max && test_bit(code, bm);
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
这个函数在函数input_handle_event中,检测EV_ABS时调用
这个函数是比较前后两次采样到的位置值的差值,然后根据比较这个差值决定返回值(或者说校正后的值)
static int input_defuzz_abs_event(
int value, //当前值
int old_val, //前一次的值
int fuzz) //差值依据
{
if (fuzz) {
if (value > old_val - fuzz / 2 && value < old_val + fuzz / 2)
return old_val;
if (value > old_val - fuzz && value < old_val + fuzz)
return (old_val * 3 + value) / 4;
if (value > old_val - fuzz * 2 && value < old_val + fuzz * 2)
return (old_val + value) / 2;
}
return value;
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
这个函数在input_handle_event的KV_KEY事件中被调用,当按键有效时调用这个函数,不用代码就知道这个函数干什么用了:打开TMR,检测重复按键。
static void input_start_autorepeat(struct input_dev *dev, int code)
{
if (test_bit(EV_REP, dev->evbit) &&
dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] &&
dev->timer.data) {
dev->repeat_key = code;
//打开TMR
mod_timer(&dev->timer,
jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
}
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//默认的获取KEY代码的函数
static int input_default_getkeycode(
struct input_dev *dev,
int scancode,
int *keycode)
{
//代码大小为0,错误
//keycodesize表示按键代码占用几个字节
if (!dev->keycodesize)
return -EINVAL;
//扫描的代码小于0 或 大于最大值,错误
if (scancode < 0 || scancode >= dev->keycodemax)
return -EINVAL;
//根据扫描码scancode获取键盘代码
//这个函数的实质就是根据scancode查keycode数组
*keycode = input_fetch_keycode(dev, scancode);
return 0;
}
//根据scancode查keycode数组
static int input_fetch_keycode(struct input_dev *dev, int scancode)
{
switch (dev->keycodesize) {
case 1:
return ((u8 *)dev->keycode)[scancode];
case 2:
return ((u16 *)dev->keycode)[scancode];
default:
return ((u32 *)dev->keycode)[scancode];
}
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
static int input_default_setkeycode(
struct input_dev *dev, //设备
int scancode, //扫描码
int keycode) //键值
{
int old_keycode;
int i;
//前面删掉了一些不难读懂的检查代码
//如果要设置的键值keycode类型大于keycodesize 且 超出的部分有数值,那么出错
if (dev->keycodesize < sizeof(keycode) && (keycode >> (dev->keycodesize * 8)))
return -EINVAL;
//将新值keycode替换进keycode数组中
switch (dev->keycodesize) {
case 1: {
u8 *k = (u8 *)dev->keycode;
old_keycode = k[scancode];
k[scancode] = keycode;
break;
}
case 2: {
u16 *k = (u16 *)dev->keycode;
old_keycode = k[scancode];
k[scancode] = keycode;
break;
}
default: {
u32 *k = (u32 *)dev->keycode;
old_keycode = k[scancode];
k[scancode] = keycode;
break;
}
}
//修改支持的键值
clear_bit(old_keycode, dev->keybit);
set_bit(keycode, dev->keybit);
//发现keycode数组中有其他地方还有设置为old_keycode,则恢复该键的支持
//我想这个地方应该是检查是否替换成功了,如果替换失败就返回原来的支持
for (i = 0; i < dev->keycodemax; i++) {
if (input_fetch_keycode(dev, i) == old_keycode) {
set_bit(old_keycode, dev->keybit);
break; /* Setting the bit twice is useless, so break */
}
}
return 0;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
下面的,就是input设备alloc时候有初始化一个结构体,这些函数就是对这个结构体的说明了
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
格式化一些信息进buf,返回buf中信息的长度。
static int input_print_modalias_bits(
char *buf, //保存显示信息的buf
int size, //name的大小
char name, //名字字符
unsigned long *bm, //位图指针
unsigned int min_bit, //开始检查的最小位
unsigned int max_bit) //开始检查的最大位
{
int len = 0, i;
//将name格式化进buf,并返回长度
len += snprintf(buf, max(size, 0), "%c", name);
for (i = min_bit; i < max_bit; i++)
if (bm[BIT_WORD(i)] & BIT_MASK(i)) //指定位为1,if成立,追加信息
len += snprintf(buf + len, max(size - len, 0), "%X,", i);
//返回buf长度
return len;
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
格式化指定设备的相关信息,返回buf长度
static int input_print_modalias(
char *buf, //buf
int size, //buf操作的总大小
struct input_dev *id, //要显示的设备
int add_cr) //是否加入换行
{
int len;
//格式化 总线类型,厂家,产品,版本信息
len = snprintf(buf, max(size, 0),
"input:b%04Xv%04Xp%04Xe%04X-",
id->id.bustype, id->id.vendor,
id->id.product, id->id.version);
//格式化设备的各个属性信息
len += input_print_modalias_bits(buf + len, size - len,
'e', id->evbit, 0, EV_MAX);
len += input_print_modalias_bits(buf + len, size - len,
'k', id->keybit, KEY_MIN_INTERESTING, KEY_MAX);
<….省略了类似的代码….>
//加入换行字符
if (add_cr)
len += snprintf(buf + len, max(size - len, 0), "\n");
return len;
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
static ssize_t input_dev_show_modalias(
struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct input_dev *id = to_input_dev(dev);
ssize_t len;
len = input_print_modalias(buf, PAGE_SIZE, id, 1);
return min_t(int, len, PAGE_SIZE);
}
//绕了一个大圈子,上面那三个格式化显示的函数其实最后都是为了给这个宏提供显示函数用的,而这个宏和前面三个INPUT_DEV_STRING_ATTR_SHOW的区别就是少了一个显示函数的定义,不过这里他自己已经有显示函数了,所以就直接调用了DEVICE_ATTR来定义结构体modalias了。
static DEVICE_ATTR(modalias, S_IRUGO, input_dev_show_modalias, NULL);
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//看上去似乎这个宏作用不大,只是定义了一个函数:input_dev_show_XXX,这个函数的功能只是按格式打印一个字符串进buf。然后真正有用的,则是后面DEVICE_ATTR宏的调用。
#define INPUT_DEV_STRING_ATTR_SHOW(name) \
static ssize_t input_dev_show_##name(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct input_dev *input_dev = to_input_dev(dev); \
\
return scnprintf(buf, PAGE_SIZE, "%s\n", \
input_dev->name ? input_dev->name : ""); \
} \
static DEVICE_ATTR(name, S_IRUGO, input_dev_show_##name, NULL)
先把这个宏给挖一下:
#define DEVICE_ATTR(_name,_mode,_show,_store) \
struct device_attribute dev_attr_##_name = __ATTR(_name,_mode,_show,_store)
#define __ATTR(_name,_mode,_show,_store) { \
.attr = { .name = __stringify(_name),
.mode = _mode }, \
.show = _show, \
.store = _store, \
}
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
struct attribute {
const char *name;
struct module *owner;
mode_t mode;
};
我们把其中一个代入到宏中看看:
先是定义了一个函数:
input_dev_show_name()
然后调用宏
static DEVICE_ATTR(name, S_IRUGO, input_dev_show_name, NULL)
再展开:
struct device_attribute dev_attr_name =
{
.attr =
{
.name = __stringify(name),
.mode = S_IRUGO
},
.show = input_dev_show_name,
.store = NULL,
}
最后回到正文:
INPUT_DEV_STRING_ATTR_SHOW(name);
INPUT_DEV_STRING_ATTR_SHOW(phys);
INPUT_DEV_STRING_ATTR_SHOW(uniq);
再加上刚刚分析的这个
static DEVICE_ATTR(modalias, S_IRUGO, input_dev_show_modalias, NULL);
//这回就很容易找到这个结构体中成员的来历了。
static struct attribute *input_dev_attrs[] = {
&dev_attr_name.attr,
&dev_attr_phys.attr,
&dev_attr_uniq.attr,
&dev_attr_modalias.attr,
NULL
};
//这一组的显示是显示name的
static struct attribute_group input_dev_attr_group = {
.attrs = input_dev_attrs,
};
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
这个宏和INPUT_DEV_STRING_ATTR_SHOW宏类似,只是后者显示的是input_dev->name,而它是显示input_dev->id.name
#define INPUT_DEV_ID_ATTR(name) \
static ssize_t input_dev_show_id_##name(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct input_dev *input_dev = to_input_dev(dev); \
return scnprintf(buf, PAGE_SIZE, "%04x\n", input_dev->id.name); \
} \
static DEVICE_ATTR(name, S_IRUGO, input_dev_show_id_##name, NULL)
//所以,这里的调用也就不需要多解释了
INPUT_DEV_ID_ATTR(bustype);
INPUT_DEV_ID_ATTR(vendor);
INPUT_DEV_ID_ATTR(product);
INPUT_DEV_ID_ATTR(version);
static struct attribute *input_dev_id_attrs[] = {
&dev_attr_bustype.attr,
&dev_attr_vendor.attr,
&dev_attr_product.attr,
&dev_attr_version.attr,
NULL
};
//这一组的显示是显示id的name的
static struct attribute_group input_dev_id_attr_group = {
.name = "id",
.attrs = input_dev_id_attrs,
};
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
看这个函数的函数名,应该是打印位图
之前input_dev_show_modalias是打印第几位为1,这里则是直接打印出bitmap的值
static int input_print_bitmap(
char *buf,
int buf_size,
unsigned long *bitmap,
int max,
int add_cr)
{
int i;
int len = 0;
//从最高位开始,找到第一个不为0的成员
for (i = BITS_TO_LONGS(max) - 1; i > 0; i--)
if (bitmap[i])
break;
//打印bitmap[i]的值
for (; i >= 0; i--)
len += snprintf(buf + len, max(buf_size - len, 0),
"%lx%s", bitmap[i], i > 0 ? " " : "");
if (add_cr)
len += snprintf(buf + len, max(buf_size - len, 0), "\n");
return len;
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
不出所料的,第三组打印函数出现了
#define INPUT_DEV_CAP_ATTR(ev, bm) \
static ssize_t input_dev_show_cap_##bm(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct input_dev *input_dev = to_input_dev(dev); \
int len = input_print_bitmap(buf, PAGE_SIZE, \
input_dev->bm##bit, ev##_MAX, 1); \
return min_t(int, len, PAGE_SIZE); \
} \
static DEVICE_ATTR(bm, S_IRUGO, input_dev_show_cap_##bm, NULL)
INPUT_DEV_CAP_ATTR(EV, ev);
INPUT_DEV_CAP_ATTR(KEY, key);
INPUT_DEV_CAP_ATTR(REL, rel);
INPUT_DEV_CAP_ATTR(ABS, abs);
INPUT_DEV_CAP_ATTR(MSC, msc);
INPUT_DEV_CAP_ATTR(LED, led);
INPUT_DEV_CAP_ATTR(SND, snd);
INPUT_DEV_CAP_ATTR(FF, ff);
INPUT_DEV_CAP_ATTR(SW, sw);
static struct attribute *input_dev_caps_attrs[] = {
&dev_attr_ev.attr,
&dev_attr_key.attr,
&dev_attr_rel.attr,
&dev_attr_abs.attr,
&dev_attr_msc.attr,
&dev_attr_led.attr,
&dev_attr_snd.attr,
&dev_attr_ff.attr,
&dev_attr_sw.attr,
NULL
};
static struct attribute_group input_dev_caps_attr_group = {
.name = "capabilities",
.attrs = input_dev_caps_attrs,
};
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
最后,是对上面三组数据的总结
static struct attribute_group *input_dev_attr_groups[] = {
&input_dev_attr_group,
&input_dev_id_attr_group,
&input_dev_caps_attr_group,
NULL
};
这个结构体就是input_allocate_device()中,dev->dev.type = &input_dev_type;的一员了
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
这个函数直接就是input_dev_type结构体的一员
static void input_dev_release(struct device *device)
{
struct input_dev *dev = to_input_dev(device);
input_ff_destroy(dev); //这个函数和input无关,忽略
kfree(dev); //释放掉device所占用的结构体
module_put(THIS_MODULE); //模块使用者计数减1
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
最后一个成员函数input_dev_uevent中关联有kobject的操作,这一块又是一个大的知识点,所以先放放了。
至此,本文件中除了ff和kobject相关的代码外,其他的代码都已经分析完了。