在linux内核中 已做好各类驱动的框架,驱动程序也属于内核的一部分,我们可以在原有的驱动上修改,来匹配我们自已的硬件,也可以自已编写符合内核驱动框架的驱动程序。出于学习的目的,便于更好的理解各类驱动的框架和编程思想,先分析内核自带的驱动框架和流程,再自已编写符合内核框架的驱动程序。下面开始,从输入子系统开始学习分析,后面一步一步涉及各类驱动。

一、输入子系统

  从 drivers/input/input.c 这个文件开始分析,分析驱动程序的时候,先从其入口函数开始,因为每当加载一个驱动程序的时候就是调用这个驱动的入口函数,后面我们就假设对一个驱动的读写操作,一步一步展开深入,分析其流程,从而得到框架,当我们自已写驱动时就可以参照这个框架一步步编写程序,实现功能。

 1 /* 定义一个 file_operations 结构体 并设置 */
2 static const struct file_operations input_fops = {
3 .owner = THIS_MODULE,
4 .open = input_open_file,
5 .llseek = noop_llseek,
6 };
7
8 static int __init input_init(void)
9 {
10 int err;
11 /* 注册一个 input_class 类 */
12 err = class_register(&input_class);
13 if (err) {
14 pr_err("unable to register input_dev class\n");
15 return err;
16 }
17
18 err = input_proc_init();
19 if (err)
20 goto fail1;
21 /* 注册一个字符设备 参数:INPUT_MAJOR-主设备号 "input"-名字 input_fops-file_operations 结构体 */
22 err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
23 if (err) {
24 pr_err("unable to register char major %d", INPUT_MAJOR);
25 goto fail2;
26 }
27
28 return 0;
29
30 fail2: input_proc_exit();
31 fail1: class_unregister(&input_class);
32 return err;
33 }

  A:假设我们的应用程序打开这个设备,那么就会调用这个设备的open函数,上面注册设备时,传入了一个 file_operations 结构体,他里面有一个 open = input_open_file 进入这个函数来分析

static int input_open_file(struct inode *inode, struct file *file)
{
/* 定义一个 input_handler 结构体指针 */
struct input_handler *handler;
/* 定义两个file_operations 结构体指针 */
const struct file_operations *old_fops, *new_fops = NULL;
int err;
/* 互斥锁 上锁 */
err = mutex_lock_interruptible(&input_mutex);
if (err)
return err;
/* 设置 handler 指向 以打开设备的次设备号右移5位的值为下标
* 取出 input_table【下标】 数组的一项 赋给 handler
*/
/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5];
/* 判断这项是否存在 1-存在 0-不存在 */
if (handler)
/* 把 handler 里的 file_operations 结构体 赋给 new_fops */
new_fops = fops_get(handler->fops);
/* 互斥锁 解锁 */
mutex_unlock(&input_mutex); /*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops || !new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
/* 保存原来的 file_operations 并把新的new_fops 赋给file->f_op */
old_fops = file->f_op;
file->f_op = new_fops;
/* 调用新的fops里的open函数
* 即执行的是 handler->fops->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);
out:
return err;
}

到这里,我们就知道了,APP执行open时,最终会调用到 handler->fops->open 函数,那么,handler->fops->open 函数是谁呢?这个函数从那里来的?谁创建设置这个函数?

在上面的代码里有一条 handler = input_table[iminor(inode) >> 5]; 从 input_table 数组中得到,那这个数组又是谁构造和填充设置的呢?这个数组是一个静态数组,所以在本文件中搜索一下应该可以知道结果。

static struct input_handler *input_table[8];
int input_register_handler(struct input_handler *handler)
{
/* 定义一个 input_dev 结构体指针 */
struct input_dev *dev;
int retval;
/* 互斥锁 上锁 */
retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;
/* 初始化链表头 */
INIT_LIST_HEAD(&handler->h_list);
/* 判断 handler->fops 是否存在 判断 input_table[handler->minor >> 5]
* 数组里这项是否存在 存在就返回忙错误 跳到 out处
*/
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
/* 把这个函数的参数 handler 赋给这一项 即设置数组这一项,open 里要用到 */
input_table[handler->minor >> 5] = handler;
}
/* 把这个结构体加入 input_handler_list 链表 */
list_add_tail(&handler->node, &input_handler_list);
/* 遍历 input_dev_list 链表 对这个链表的每一个节点都执行 input_attach_handler(dev, handler); */
list_for_each_entry(dev, &input_dev_list, node)
/* 这个函数是比较dev和handler里的id 是否匹配 */
input_attach_handler(dev, handler); input_wakeup_procfs_readers(); out:
mutex_unlock(&input_mutex);
return retval;
}

搜索得到在 int input_register_handler(struct input_handler *handler) 函数里构造设置了,这里传入的参数 struct input_handler *handler 我们不清楚是那来的,再搜索那里调用了 input_register_handler 把参数搞清楚

得到很多文件调用了这个函数,我们选一个简单的来分析 就以 Evdev.c 为例来分析一下,了解一个 传入参数是什么内容。进入 evdev.c 分析。

Apm-power.c (drivers\input): return input_register_handler(&apmpower_handler);
Evbug.c (drivers\input): return input_register_handler(&evbug_handler);
Evdev.c (drivers\input): return input_register_handler(&evdev_handler);
Input.c (drivers\input): * input_register_handler - register a new input handler
Input.c (drivers\input):int input_register_handler(struct input_handler *handler)
Input.c (drivers\input):EXPORT_SYMBOL(input_register_handler);
Input.c (net\rfkill): return input_register_handler(&rfkill_handler);
Input.h (include\linux):int __must_check input_register_handler(struct input_handler *);
Joydev.c (drivers\input): return input_register_handler(&joydev_handler);
Keyboard.c (drivers\tty\vt): error = input_register_handler(&kbd_handler);
Kgdboc.c (drivers\tty\serial): if (input_register_handler(&kgdboc_reset_handler) == 0)
Mac_hid.c (drivers\macintosh): err = input_register_handler(&mac_hid_emumouse_handler);
Mousedev.c (drivers\input): error = input_register_handler(&mousedev_handler);
Sysrq.c (drivers\tty): error = input_register_handler(&sysrq_handler);

static struct input_handler evdev_handler = {
.event = evdev_event, /* 事件处理函数 */
.connect = evdev_connect, /* 连接函数 设备层和 软件处理层连接 */
.disconnect = evdev_disconnect, /* 清除连接 */
.fops = &evdev_fops, /* file_operations 结构体 */
.minor = EVDEV_MINOR_BASE, /* 次设备号 */
.name = "evdev", /* 名字 */
.id_table = evdev_ids, /* ID 用于和设备层匹配 */
}; static int __init evdev_init(void)
{
/* 注册一个 input_register_handler */
return input_register_handler(&evdev_handler);
}

我们可以看到,是在入口函数里调用了 input_register_handler 这个函数,这个参数就是 input_handler 结构体,可以看到这个结构体里很多函数,file_operations 结构体 等等,从这里我们基本就可以确定在前的所需要用到的一些值和函数了。下面简单列出一些相对重要的参数。

/* input_table[handler->minor >> 5] = handler;
* handler->minor >> 5 = 64 >> 5 = 2
* handler = evdev_handler
* input_table[2] = evdev_handler
*/ /* err = new_fops->open(inode, file);
* new_fops->open = evdev_fops->evdev_open(inode, file);
*/ static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};

由此得知,当app应用层调用操作时,最终会调用到 input_handler 结构体里的操作函数 事实上如果跟着分析下去还有很多东西,如果现在跟得太深,不太好理解,所以暂时对于handler层 先分析到这里,后面的内容和另一个层 设备层是一起的,所以下面转过头先分析设备层,等到设备层分析到和handler层相接的地方,在往下分析,由此可以便于加深理解,为什么我知道有个设备层呢?“我也是跟别人学的” 但是有一点可以看出来,上面分析的过程中,出现过一个东西在 input_register_handler 这个函里有一段处理

    /* 把这个结构体加入 input_handler_list 链表 */
list_add_tail(&handler->node, &input_handler_list);
/* 遍历 input_dev_list 链表 对这个链表的每一个节点都执行 input_attach_handler(dev, handler); */
list_for_each_entry(dev, &input_dev_list, node)
/* 这个函数是比较dev和handler里的id 是否匹配 */
input_attach_handler(dev, handler);

这里提到一个链表 input_dev_list 这就是设备层的链表,我们搜索一下,看在那里可以找到相关的信息,搜索到一些内容,我们依次进去看一下,看在那里有这个链表的处理 最终我搜索到一条这样的处理

list_add_tail(&dev->node, &input_dev_list); 这条处理就把dev结构体加入input_dev_list这个链表,在 int input_register_device(struct input_dev *dev) 函数里调用,看名字就是注册一个输入设备,参数是input_dev 结构体

和我们前面看到的注册类同,因此我们再搜索一下谁调用了这个函数 结果是 一大堆设备调用了这个函数,由此我猜测,调用这个函数的就是设备层关于硬件相关的驱动,你问我怎么知道,因为我是天才!!!开个玩笑,我要是天才就不用学了。。。当然我也是跟别人学的。所谓的别人就是“韦东山大牛” 这不是广告,纯属个人觉得,他的东西确实好,好像跑偏了。回到正题。刚说到搜索到很多调用,我们选一个简单点的来分析一下,设备层这边的框架,现在分析的东西,就是我们自已写驱动所需要编写的内容,了解了这层的框架和逻辑思维后,按照框架就可以写出符合内核的驱动程序了,同上面一样,找一个简单的例子分析,我里我们选择 gpio_key.c 这只是一个例子,他没有具体的硬件操作。下面开始

在 static int __devinit gpio_keys_probe(struct platform_device *pdev) 这个函数中调用了 error = input_register_device(input); 和上面一样,注册一个input_dev 设备,关于 gpio_keys_probe 是和平台设备相关,我们先不管他我们写驱动时也可以不管他,直接注册设备即可,在这里了解一下 注册前需要做那些事,看代码

static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
const struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct gpio_keys_drvdata *ddata;
struct device *dev = &pdev->dev;
struct gpio_keys_platform_data alt_pdata;
struct input_dev *input;
int i, error;
int wakeup = 0; if (!pdata) {
error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);
if (error)
return error;
pdata = &alt_pdata;
} ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data),
GFP_KERNEL);
/* 分配一个 inpu_dev 结构体 前面有定义的 struct input_dev *input; */
input = input_allocate_device();
if (!ddata || !input) {
dev_err(dev, "failed to allocate state\n");
error = -ENOMEM;
goto fail1;
} ddata->input = input;
ddata->n_buttons = pdata->nbuttons;
ddata->enable = pdata->enable;
ddata->disable = pdata->disable;
mutex_init(&ddata->disable_lock); platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata);
/* 这里有一堆设置 input_dev 结构体的 我们自已编写的时候用到什么函数就实现什么函数这里是例子 */
input->name = pdata->name ? : pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->open = gpio_keys_open; /* open 函数 */
input->close = gpio_keys_close; input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100; /* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit); for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i]; error = gpio_keys_setup_key(pdev, input, bdata, button);
if (error)
goto fail2; if (button->wakeup)
wakeup = 1;
} error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
goto fail2;
}
/* 注册 input_dev */
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto fail3;
} /* get current state of buttons that are connected to GPIOs */
for (i = 0; i < pdata->nbuttons; i++) {
struct gpio_button_data *bdata = &ddata->data[i];
if (gpio_is_valid(bdata->button->gpio))
gpio_keys_gpio_report_event(bdata);
}
input_sync(input); device_init_wakeup(&pdev->dev, wakeup); return 0; fail3:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
fail2:
while (--i >= 0)
gpio_remove_key(&ddata->data[i]); platform_set_drvdata(pdev, NULL);
fail1:
input_free_device(input);
kfree(ddata);
/* If we have no platform_data, we allocated buttons dynamically. */
if (!pdev->dev.platform_data)
kfree(pdata->buttons); return error;
}

我们看到,在注册前,分配了一个input_dev 结构体 并设置他。然后调用注册函数,我们到注册函数里看一下做了些什么鬼事情?

int input_register_device(struct input_dev *dev)
{
/* 这是什么鬼,不认识 不管他 */
static atomic_t input_no = ATOMIC_INIT(0);
/* 定义一个 input_handler 结构体 */
struct input_handler *handler;
const char *path;
int error; /* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit); /* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit); /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
input_cleanse_bitmasks(dev); if (!dev->hint_events_per_packet)
dev->hint_events_per_packet =
input_estimate_events_per_packet(dev); /*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
init_timer(&dev->timer); /* 应该是初始化定时器 */
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
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; dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1); error = device_add(&dev->dev);
if (error)
return error; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%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_dev 加入 input_dev_list 链表 */
list_add_tail(&dev->node, &input_dev_list);
/* 遍历 input_handler_list 链表 对每一项都执行input_attach_handler 函数 */
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return 0;
}

我们看到了在后面同样是加入了链表,同样也遍历链表,执行相同的函数,在dve层是遍历 input_headler_list链表 在handler层是遍历 input_dev_list链表 嗯,我主场我做主,我找你,你主场你做主,你找我,谁是你,谁是我,你高兴就好,爱谁谁。两个层都调用了相同的函数 input_attach_handler 所以我们很有必要去看一下他想干啥,

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/* 找好基友函数 对比ID 是否相同,相同的话返回值 id=1 */
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
/* 上面必须返回1才会执行这条 即调用handr层的connect函数 */
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error); return error;
}

里面还有一个函数我们再进去看看

static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
int i; for (id = handler->id_table; id->flags || id->driver_info; 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; 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); if (!handler->match || handler->match(handler, dev))
return id;
} return NULL;
}

就是看一下 handler->id_tabe 支不支持 dev,支持就返回1 我们看到 handler->id_tabe  里说了,匹配所有设备,然后调用handler->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_headle 结构体 注意看名称,少了个 r

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

error = input_register_handle(&evdev->handle); 进去看个函数

int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error; /*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error; /*
* Filters go to the head of the list, normal handlers
* to the tail.
*/
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list); mutex_unlock(&dev->mutex); /*
* Since we are supposed to be called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
list_add_tail_rcu(&handle->h_node, &handler->h_list); if (handler->start)
handler->start(handle); return 0;
}

一个指向 handler 一个指向dev 所以无论从那边进入都可以找到对方,到此双方就建立“基友”的连接关系了。

  B:假设app读操作,通过上面的分析我们知道最终会调用到对应的handler里的fops里的read函数,我们来看一下他怎么读的

static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval = 0; if (count < input_event_size())
return -EINVAL; if (!(file->f_flags & O_NONBLOCK)) {
retval = wait_event_interruptible(evdev->wait,/* 休眠 谁来唤醒 搜索evdev->wait */
client->packet_head != client->tail ||
!evdev->exist);
if (retval)
return retval;
} if (!evdev->exist)
return -ENODEV; while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) { if (input_event_to_user(buffer + retval, &event))
return -EFAULT; retval += input_event_size();
} if (retval == 0 && (file->f_flags & O_NONBLOCK))
return -EAGAIN; return retval;
}

在读函数里休眠,那谁来唤醒他呢,他在那个队列里休眠,我们就搜索这个队列,看在那里有唤醒操作 在static void evdev_event(struct input_handle *handle)函数里调用 wake_up_interruptible(&evdev->wait); 来唤醒,谁又调用了evdev_event 函数呢 别人猜得很准,说是从设备层那边会调用到这个函数,用例子来说明,当有按键按下时,进入设备层中断处理函数 在里面会调用inpu_event,分析这个函数可以知道如何操作

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
const struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned long flags; BUG_ON(irq != bdata->irq); spin_lock_irqsave(&bdata->lock, flags); if (!bdata->key_pressed) {
input_event(input, EV_KEY, button->code, 1);
input_sync(input); if (!bdata->timer_debounce) {
input_event(input, EV_KEY, button->code, 0);
input_sync(input);
goto out;
} bdata->key_pressed = true;
} if (bdata->timer_debounce)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(bdata->timer_debounce));
out:
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}

这里就简单列出调用关系,就不做进一步细化分析了

inpu_event

  input_handle_event

    input_pass_event

      handler->event(handle, type, code, value);  //这里最终就是调用 evdev_event 唤醒 evdev_read函数唤醒后调用 input_event_to_user 把数据给app

到这里框架就分析完毕了,那么 我们自已如何编写一个符合内核这套input 输入子系统的驱动呢,我们主要关心设备层

1:分配一个input_dev 结构体

2:设置

3:注册

4:硬件机关的操作 (例:当按键按下中,产生中断,在中断服务函数里上报事件即可)

input_subsys 输入子系统框架分析的更多相关文章

  1. Linux输入子系统框架分析(1)

    在Linux下的输入设备键盘.触摸屏.鼠标等都能够用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层.事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备 ...

  2. linux input输入子系统应用分析

    输入设备(如按键.键盘.触摸屏.鼠标等)是典型的字符设备,其一般的工作机理是底层在按键.触摸等动作发送时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI.I2 C或外部存储器总线读取 ...

  3. 【驱动】input子系统全面分析

    初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputC ...

  4. INPUT输入子系统——按键

    一.什么是input输入子系统? 1.1. Linux系统支持的输入设备繁多,例如键盘.鼠标.触摸屏.手柄或者是一些输入设备像体感输入等等,Linux系统是如何管理如此之多的不同类型.不同原理.不同的 ...

  5. 7.Linux 输入子系统分析

    为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons .... ...

  6. Linux输入子系统详解

    input输入子系统框架  linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(Input ...

  7. linux输入子系统之按键驱动

    上一节中,我们讲解了Linux  input子系统的框架,到内核源码里详细分析了输入子系统的分离分层的框架等. 上一节文章链接:http://blog.csdn.net/lwj103862095/ar ...

  8. linux输入子系统概念介绍

    在此文章之前,我们讲解的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥.非阻塞.定时器去抖动. 上一节文章链接:http://blo ...

  9. 8、linux下输入子系统

    input_sync(button_dev);    /*通知接收者,一个报告发送完毕*/ 参考:http://www.51hei.com/bbs/dpj-27652-1.html  很详细说明 in ...

  10. 12.Linux之输入子系统分析(详解)

    版权声明:本文为博主原创文章,转载请标注出处:   在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时 ...

随机推荐

  1. 【C++复习】5.7 多文件结构与编译预处理命令

    1.C++项目结构 C++程序的一般组织架构 类声明文件(.h文件) 类实现文件(.cpp文件) 类的使用文件(main()所在的.cpp文件) 用工程组合各文件 2.编译链接 编译链接过程 3.外部 ...

  2. 常用的Linux命令与它们的功能

    概要 filename 文件名 dir 文件夹名 string 字符串 username 用户名 groupname 组名 regex 正则表达式 path 路径 partition 分区名 port ...

  3. Oracle 会话锁死

    需要管理员用户下执行(sys/sysdba) --先查锁 select * from v$lock where lmode > 0 and type in ('TM','TX'); --查用户名 ...

  4. 整合log4j

    引入依赖 <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId& ...

  5. chrome浏览器启用es6语法支持,初次体验浏览器端模块化加载

    医院项目,记录下,花了一个晚上的时间. 注意:最新版本的chrome浏览器已支持module语法,需要在web服务器环境下运行! 注:chrome76版本及以上不再需要设置了,浏览器已默认支持,至于从 ...

  6. react-router 路由 入门必看

    如果您已经入门reactjs,请绕道~ 这篇博客只适合初学者,初学reactjs的时候,如果你不会webpack,相信很多人都会被官方的例子绕的晕头转向. ES6的例子也会搞死一批入门者.之前一直用的 ...

  7. spring boot读取本地文件

    File file = ResourceUtils.getFile("classpath:face/1112.txt"); InputStream inputStream = ne ...

  8. Docker学习——Dockerfile 指令详解(五)

    我们已经介绍了 FROM (指定基础镜像) , RUN(执行命令) ,还提及了 COPY , ADD ,其实 Dockerfile 功能很强大,它提供了十多个指令.下面我们继续讲解其他的指令. COP ...

  9. 打车起步价8元(3KM以内) 超过3KM,超出的每公里1.2元 超过5KM,超出的每公里1.5元 请在键盘上接收公里数,得出总价。

    import java.util.Scanner; public class Taxi { public static void main(String []agrs){ Scanner s = ne ...

  10. web端测试的测试点和注意事项【转载】

    文章来源:作者:simplesally 出处:https://www.cnblogs.com/simple1025/   [转载] 工作中接触了不同类型的web端系统,内容不同,需求不同,测试关注点也 ...