Linux驱动之输入子系统简析

发布者:EuphoricVoyage最新更新时间:2024-08-20 来源: elecfans关键字:Linux  驱动  输入子系统 手机看文章 扫描二维码
随时随地手机看文章

输入子系统由驱动层、输入子系统核心、事件处理层三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。

系统框图

假设打开一个字符设备驱动程序/dev/event0,event代表的是输入子系统的设备文件,当应用程序调用C库的open函数后,open函数会进入系统调用,最后定位到driversinputinput.c文件下(这个文件就是核心层)的。这个函数的功能主要是根据设备的次设备号找到新的fops结构,然后切换到新的fops结构,然后调用它的打开函数。输入子系统的主设备号恒为#define INPUT_MAJOR 13,定义在includelinuxmajor.h中。


static int input_open_file(struct inode *inode, struct file *file)

{

    struct input_handler *handler = input_table[iminor(inode) >> 5];//根据次设备号找到在input_table表中找到handler结构体

    const struct file_operations *old_fops, *new_fops = NULL;

    int err;


    /* No load-on-demand here? */

    if (!handler || !(new_fops = fops_get(handler->fops)))//判断handler结构体是否存在,存在的话将里面的fops变量赋给new_fops

        return -ENODEV;


    /*

     * That's _really_ odd. Usually NULL ->open means 'nothing special',

     * not 'no device'. Oh, well...

     */

    if (!new_fops->open) {

        fops_put(new_fops);

        return -ENODEV;

    }

    old_fops = file->f_op;

    file->f_op = new_fops;//切换f_op变量,以后调用诸如read、write等系统调用时会进入到new_fops的read、write函数


    err = new_fops->open(inode, file);//调用new_fops的open函数


    if (err) {

        fops_put(file->f_op);

        file->f_op = fops_get(old_fops);

    }

    fops_put(old_fops);//释放掉老的fops结构

    return err;

}


接着先来看到input_table表的建立,可以看到它是一个静态变量,在本文件(driversinputinput.c)中搜索它,可以看到它位于input_register_handler函数,这是一个全局的函数,可以供外部的文件调用,这个函数的主要功能是注册一个handler结构体,这个结构体中存在minor这个设备的次设备号,这个结构所在的函数对应的其实就是上述的事件层。


int input_register_handler(struct input_handler *handler)

{

    struct input_dev *dev;


    INIT_LIST_HEAD(&handler->h_list);//初始化handler的h_list结构体,这是一个双向链表


    if (handler->fops != NULL) {

        if (input_table[handler->minor >> 5])//检查是否已经存在这个次设备号的handler结构

            return -EBUSY;


        input_table[handler->minor >> 5] = handler;//将handler结构次设备号放入input_table表

    }


    list_add_tail(&handler->node, &input_handler_list);//将handler结构根据node成员放入input_handler_list链表


    list_for_each_entry(dev, &input_dev_list, node)//根据node这个成员在input_dev_list链表中循环查找dev结构

        input_attach_handler(dev, handler);//对于每一个dev结构调用input_attach_handler函数


    input_wakeup_procfs_readers();//将这个设备信息写入proc文件系统

    return 0;

}


接着搜索input_register_handler,抽取driversinputevdev.c这个文件,可以看到在这个模块的入口函数调用了注册函数


static int __init evdev_init(void)

{

    return input_register_handler(&evdev_handler);

}

接着看到evdev_handler这个结构体,在这个结构体里面找到了evdev_fops这个结构


static struct input_handler evdev_handler = {

    .event =    evdev_event,

    .connect =    evdev_connect,

    .disconnect =    evdev_disconnect,

    .fops =        &evdev_fops,

    .minor =    EVDEV_MINOR_BASE,

    .name =        'evdev',

    .id_table =    evdev_ids,

};


接着看到evdev_fops结构体,可以看到应用层调用的read、write等函数在这里被定义


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

};


知道了事件层对应的位置,那么设备驱动层在哪里呢?接着往下看,回到input_register_handler函数,在里面看到如下语句,这句语句的作用是将事件层与驱动层联系起来。


list_for_each_entry(dev, &input_dev_list, node)//根据node这个成员在input_dev_list链表中循环查找dev结构

        input_attach_handler(dev, handler);//对于每一个dev结构调用input_attach_handler函数

这里可以看到一个新的结构体dev,先看一下dev结构体,它的原型为input_dev,跟抽取driversinputevdev.c这个文件一样,搜索input_dev这个结构体,先列出input_dev这个结构体


struct input_dev {


    void *private;


    const char *name;

    const char *phys;

    const char *uniq;

    struct input_id id;


    unsigned long evbit[NBITS(EV_MAX)];

    unsigned long keybit[NBITS(KEY_MAX)];

    unsigned long relbit[NBITS(REL_MAX)];

    unsigned long absbit[NBITS(ABS_MAX)];

    unsigned long mscbit[NBITS(MSC_MAX)];

    unsigned long ledbit[NBITS(LED_MAX)];

    unsigned long sndbit[NBITS(SND_MAX)];

    unsigned long ffbit[NBITS(FF_MAX)];

    unsigned long swbit[NBITS(SW_MAX)];


    unsigned int keycodemax;

    unsigned int keycodesize;

    void *keycode;

    int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);

    int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);


    struct ff_device *ff;


    unsigned int repeat_key;

    struct timer_list timer;


    int state;


    int sync;


    int abs[ABS_MAX + 1];

    int rep[REP_MAX + 1];


    unsigned long key[NBITS(KEY_MAX)];

    unsigned long led[NBITS(LED_MAX)];

    unsigned long snd[NBITS(SND_MAX)];

    unsigned long sw[NBITS(SW_MAX)];


    int absmax[ABS_MAX + 1];

    int absmin[ABS_MAX + 1];

    int absfuzz[ABS_MAX + 1];

    int absflat[ABS_MAX + 1];


    int (*open)(struct input_dev *dev);

    void (*close)(struct input_dev *dev);

    int (*flush)(struct input_dev *dev, struct file *file);

    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);


    struct input_handle *grab;


    struct mutex mutex;    /* serializes open and close operations */

    unsigned int users;


    struct class_device cdev;

    union {            /* temporarily so while we switching to struct device */

        struct device *parent;

    } dev;


    struct list_head    h_list;

    struct list_head    node;

};


接着看到driversinputtabletkbtab.c这个文件,这个文件代表的就是设备驱动层,简单分析一下,可以看到它也是一个内核的模块,可以动态加载,一旦加载后,它会调用kbtab_init函数,最终会调用到kbtab_probe这个函数,可以看到最终又定位到了input_register_device这个注册设备的函数,它位于核心层,即driversinputinput.c文件下。


static int kbtab_probe(struct usb_interface *intf, const struct usb_device_id *id)

{

    ...

    ...

    input_dev = input_allocate_device();//分配一个input_dev 结构体

    if (!kbtab || !input_dev)

        goto fail1;


    ...

    ...


    input_dev->name = 'KB Gear Tablet';//初始化input_dev 结构体

    input_dev->phys = kbtab->phys;

    usb_to_input_id(dev, &input_dev->id);

    input_dev->dev.parent = &intf->dev;


    input_set_drvdata(input_dev, kbtab);


    input_dev->open = kbtab_open;

    input_dev->close = kbtab_close;


    input_dev->evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_MSC);

    input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);

    input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) | BIT(BTN_TOUCH);

    input_dev->mscbit[0] |= BIT(MSC_SERIAL);

    input_set_abs_params(input_dev, ABS_X, 0, 0x2000, 4, 0);

    input_set_abs_params(input_dev, ABS_Y, 0, 0x1750, 4, 0);

    input_set_abs_params(input_dev, ABS_PRESSURE, 0, 0xff, 0, 0);


    ...

    ...


    error = input_register_device(kbtab->dev);//注册input_dev结构体

    ...

    ...

}


接着看到input_register_device这个函数,它根input_register_handler相对应,前一个注册设备驱动层,后一个注册事件层。列出input_register_device函数,它同样位于driversinputinput.c文件中。


int input_register_device(struct input_dev *dev)

{

    static atomic_t input_no = ATOMIC_INIT(0);

    struct input_handler *handler;

    const char *path;

    int error;


    set_bit(EV_SYN, dev->evbit);//设置同步事件


    /*

     * 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;//设置按键值默认函数


    list_add_tail(&dev->node, &input_dev_list);//将dev->node放入input_dev_list链表


    snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),

         'input%ld', (unsigned long) atomic_inc_return(&input_no) - 1);


    if (!dev->cdev.dev)

        dev->cdev.dev = dev->dev.parent;


    error = class_device_add(&dev->cdev);

    if (error)

        return error;


    path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);

[1] [2] [3]
关键字:Linux  驱动  输入子系统 引用地址:Linux驱动之输入子系统简析

上一篇:Linux驱动之一个简单的输入子系统程序编写
下一篇:Linux驱动之定时器在按键去抖中的应用

推荐阅读最新更新时间:2024-11-11 10:41

MSP430F44X单片机SPI接口驱动C语言程序
#include msp430x44x.h char MST_Data=0X00,SLV_Data=0XFF; void Init(void); void main(void) { unsigned int i; WDTCTL=WDTPW+WDTHOLD; Init(); _EINT(); P3OUT&=~0X020; P3OUT|=0X020; i=50000; do(i--); while(i!=0); while(1) { TXBUF0=MST_Data; LPM0;
[单片机]
LED车灯 手电筒 外置MOS降压恒流驱动IC 12-36V 9V 1A驱动
产品描述 特点 应用领域 应用原理图 AP5125 是一款外围电路简单的 Buck 型平均电 流检测模式的 LED 恒流驱动器,适用于 8-100V 电压 范围的非隔离式大功率恒流 LED 驱动领域。芯片采用 固定频率 140kHz 的 PWM 工作模式, 利用平均电 流检测模式,因此具有优异的负载调整 率特性,高精 度的输出电流特性。 AP5125 芯片集成了高低亮功能,可以通过 MODE 端口 实现高低亮功能切换。在 MODE 引脚悬空或接 地时, 为高亮模式,MODE 引脚接高电平时,为 1/2 电流的低亮模式。 AP5125 芯片内部集成了 VDD 钳位稳压管以及 过温 保护电流等,减小了外围电路元件数量并提高了 系统的可
[嵌入式]
LED车灯 手电筒 外置MOS降压恒流<font color='red'>驱动</font>IC 12-36V 9V 1A<font color='red'>驱动</font>
Linux-2.6.32.2内核在mini2440上的移植(十二)---移植I2C EEPROM驱动
移植环境 1,主机环境:VMare下CentOS 5.5 ,1G内存。 2,集成开发环境:Elipse IDE 3,编译编译环境:arm-linux-gcc v4.4.3,arm-none-linux-gnueabi-gcc v4.5.1。 4,开发板:mini2440,2M nor flash,128M nand flash。 5,u-boot版本:u-boot-2009.08 6,linux 版本:linux-2.6.32.2 7,参考文章: 嵌入式linux应用开发完全手册,韦东山,编著。 Mini2440 之Linux 移植开发实战指南 【1】在内核中配置I2C 驱动 Linux-2.6.32.2 对S2C2440 的I
[单片机]
一种基于LED路灯的PFC开关电源驱动设计方案
LED路灯是低电压、大电流的驱动器件,其发光的强度由流过LED的电流决定,电流过强会引起LED的衰减,电流过弱会影响LED的发光强度,因此LED的驱动需要提供恒流电源,以保证大功率LED使用的安全性,同时达到理想的发光强度。用市电驱动大功率LED需要解决降压、隔离、 PFC (功率因素校正)和恒流问题,还需有比较高的转换效率,有较小的体积,能长时间工作,易散热,低成本,抗电磁干扰,和过温、过流、短路、开路保护等。本方案设计的 PFC 开关电源 性能良好、可靠、经济实惠且效率高,在LED路灯使用过程中取得满意的效果。 系统结构框图 采用隔离变压器、 PFC 控制实现的 开关电源 ,输出恒压恒流的电压,驱动LED路灯。
[电源管理]
一种基于LED路灯的PFC开关电源<font color='red'>驱动</font>设计方案
基于ARM和Linux的路径记忆循迹小车
引言 智能车是近年来发展起来的一门新兴的综合技术,在军事领域得到了广泛应用,而且在生产和生活中的应用也日趋普遍 。 目前智能小车的自主移动方式有两种:循迹和避障。一般的循迹功能可以通过算法使其能精确地在黑线上行驶,但是由于一些室内环境的因素,要实施黑线的布置并不符合实际要求 。一般的避障功能仅仅是通过探测前方障碍物而进行躲避,通过算法可以完成从出发点到定点的行驶 ,但是算法运算量大且放在不同的环境,就需要更改程序,这对实际应用带来很大的不便。 基于这种背景,设计了一种利用超声波测距避障功能以及Linux文件系统的路径记忆循迹智能小车,通过人为地设置障碍物来规划小车的行走路线,并将路线记录入库。小车只需读取库中的数据即可
[单片机]
基于ARM和<font color='red'>Linux</font>的路径记忆循迹小车
LPC11XX驱动1602程序_12MHz
LCD.c文件: #include LPC11XX.h #include gpio.h #include main.h #include Delay.h #include LCD.h /*-----------设置D0-D7为输出口-------------*/ void Set_GPIO_Output(void) { LPC_GPIO2- DIR |= (0x1 0); //设置D0-D7为输出 LPC_GPIO2- DIR |= (0x1 1); LPC_GPIO2- DIR |= (0x1 2); LPC_GPIO2- DIR |= (0x1 3); LPC
[单片机]
09-S3C2440驱动学习(三)嵌入式linux-platform平台总线驱动程序及分离分层构建驱动框架
简介Platform: 内核中有IIC总线、PCI总线、串口总线、SPI总线、can总线、单总线等,一些设备可以挂载在这些总线上,然后通过总线的match进行设备和驱动的匹配。但是有些设备并不属于这些总线,于是引入了虚拟总线,也就是Platform总线,对于的设备叫做Platform设备,对于的驱动叫做Platform驱动。 平台设备框图: Platform总线: struct bus_type platform_bus_type = { .name = platform , .dev_attrs = platform_dev_attrs, .match = platform_match,
[单片机]
09-S3C2440<font color='red'>驱动</font>学习(三)嵌入式<font color='red'>linux</font>-platform平台总线<font color='red'>驱动</font>程序及分离分层构建<font color='red'>驱动</font>框架
商用车电驱动系统发展趋势
目录 一、国内新能源汽车发展概况 二、纯电动商用车电机系统现状及发展趋势 三、新能源商用车电控系统现状及发展趋势 四、插电式混合动力商用车电驱动系统现状 五、总结与建议 一.国内新能源汽车发展概况 2020年8月,统筹推进疫情防控和经济社会发展工作取得积极成效,企业加快实现复工复产复市,稳岗就业扎实推进,同时伴随中央及地方政府一系列利好政策的拉动,消费信心得到提升,部分消费者被抑制的需求也加快释放,汽车市场逐步恢复。 1.1 国内新能源汽车市场销量 2020年7月新能源汽车产销分别完成10万辆和9.8万辆,同比累计分别增长15.6%和19.3%。 1-7月,新能源汽车产销分别完成49.6万辆
[汽车电子]
商用车电<font color='red'>驱动</font>系统发展趋势
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved