惊雷无声 发表于 2025-3-30 17:56:58

Android7 Input(二)Linux 驱动层输入变乱管理

概述

        在Linux体系中,将键盘,鼠标,触摸屏等这类交互设备交由Linux Input子体系进行管理,Linux Input驱动子体系由于具有精良的和用户空间交互的接口。因此Linux Input驱动子体系,不止于只管理输入类型的设备。也可以将其他类型的设备归纳于Linux Input进行管理, 好比Rockchip 的Android体系平台上,将传感器数据的处理,就归纳到Linux Input进行管理。
        本文主要形貌了输入设备在Linux Input驱动子体系中的管理,Input设备变乱管理框架和产生变乱时如何上报给Linux Input子体系。本文是基于RK3288 Android7的Linux内核进行讲解,该版本使用的是Linux-4.4.y的版本,读者在阅读的时候,请留意,不同版本的内核,部门源码大概与文章涉及到的源码不一样。
Input 驱动框架

Linux input驱动框架,如下所示:
https://i-blog.csdnimg.cn/direct/15fe209a18d440289210a132be55c5b4.png
注: 本文主要形貌侧重点在键盘和触摸这类输入设备,因此,框图中形貌的主要涉及这两类设备的驱动框架;
Input驱动数据布局

在Input驱动子体系中, 输入设备有input_dev数据布局进行管理,焦点成员如下所示:
struct input_dev {
    const char *name;
    ......
    struct input_id id;
    ......
    unsigned long evbit;
    unsigned long keybit;
    unsigned long relbit;
    unsigned long absbit;
   ......
    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 __rcu *grab;
    ......
    struct list_head    h_list;
    struct list_head    node;

}; 主要成员:
name:设备名称
id:输入设备身份标识,好比厂家书息,设备总线类型等信息;
evbit:支持的变乱类型掩码
keybit:按键变乱掩码
relbit:相对坐标变乱掩码
absbit:绝对坐标掩码
grab:变乱Handler(这个反面进行讲解)
h_list:输入设备输入变乱处理handle链表
node: 输入设备连接节点,体系将所有注册的输入设备通过一个全局的链表进行管理;
Input设备的创建和注册

1、Linux Input设备的创建可以使用input_allocate_device和devm_input_allocate_deviced两种类型API,目前保举使用带有devm字样的接口,因为它可以在驱动probe过程中如果发生失败,自动完成内存接纳,防止驱动程序处理不当,导致的物理内存的泄露。devm_input_allocate_deviced函数的实现,最终的焦点部门还是input_allocate_device,如下图所示:
struct input_dev *input_allocate_device(void)
{
    static atomic_t input_no = ATOMIC_INIT(-1);
    struct input_dev *dev;

    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
    if (dev) {
      dev->dev.type = &input_dev_type;
      dev->dev.class = &input_class;
      device_initialize(&dev->dev);
      mutex_init(&dev->mutex);
      spin_lock_init(&dev->event_lock);
      init_timer(&dev->timer);
      INIT_LIST_HEAD(&dev->h_list);
      INIT_LIST_HEAD(&dev->node);

      dev_set_name(&dev->dev, "input%lu",
               (unsigned long)atomic_inc_return(&input_no));

      __module_get(THIS_MODULE);
    }

    return dev;
} input_allocate_device完成主要如下的功能:
a、创建input_dev管理布局,并初始化部门成员变量;
b、设置Input设备的名字;
2、input设备的注册,有input_register_device()接口完成,下面只展示焦点代码:
int input_register_device(struct input_dev *dev)
{
    ......
    /* 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);
    ......
    error = device_add(&dev->dev);
    if (error)
      goto err_free_vals;
    ......
    list_add_tail(&dev->node, &input_dev_list);

    list_for_each_entry(handler, &input_handler_list, node)
      input_attach_handler(dev, handler);

......
    return 0;
......
} input_register_device函数完成如下功能:
1)设置EV_SYN变乱掩码,这是变乱同步掩码,体现当前变乱上报完成标记;
2)将input设备注册到Linux设备驱动管理框架中device_add;
3)将input设备加入到体系全局输入设备管理链表中;
4)为输入设备绑定变乱处理Handler。不同的输入设备可以根据自己的特性,绑定不同的变乱处理Handler;
Input Handler

        在上一个章节,形貌注册input设备时,会为输入设备绑定一个变乱处理handler。在Input驱动子体系中,对输入设备产生的变乱,由input的Handler进行处理。目前在linux-4.4.y内核中,使用input_register_handler() 接口注册变乱处理Handler,因此Input子体系可以灵活开发自己的变乱处理Handler, 可以将输入设备产生的变乱进行灵活处理,input_register_handler函数的焦点实现如下所示:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    const struct input_device_id *id;
    int error;

    id = input_match_device(handler, dev);
    if (!id)
      return -ENODEV;

    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;
}
input_attach_handler完成的主要如下功能:
1) 将体系handler与输入设备dev进行匹配,只有匹配的handler,才能当做input设备的变乱处理器;
2) dev和handler匹配完成,调用handler的connect方法绑定input 设备;
3)input对触摸屏,键盘等这类设备,使用evdev_hander进行变乱处理;
evdev Handler

上一节,我们讲解了Input hander的注册方法,这一章节,我们主要形貌evdev的实现。evdev handler的初始化,主要如下所示:
static struct input_handler evdev_handler = {
    .event      = evdev_event,
    .events   = evdev_events,
    .connect    = evdev_connect,
    .disconnect = evdev_disconnect,
    .legacy_minors= true,
    .minor      = EVDEV_MINOR_BASE,
    .name       = "evdev",
    .id_table   = evdev_ids,
};

static int __init evdev_init(void)
{
    return input_register_handler(&evdev_handler);
   
} 起首,我们讲解connect的处理,因为,他是input设备和handler创建联系的过程。在evdev中connect由evdev_connect实现,焦点代码如下所示:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
    minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
    if (minor < 0) {
      error = minor;
      pr_err("failed to reserve new minor: %d\n", error);
      return error;
    }

    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    if (!evdev) {
      error = -ENOMEM;
      goto err_free_minor;
    }

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

    dev_no = minor;
    /* Normalize device number if it falls into legacy range */
    if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
      dev_no -= EVDEV_MINOR_BASE;
    dev_set_name(&evdev->dev, "event%d", dev_no);

    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, 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;

    cdev_init(&evdev->cdev, &evdev_fops);
    evdev->cdev.kobj.parent = &evdev->dev.kobj;
    error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
    if (error)
      goto err_unregister_handle;

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

    return 0;
} evdev_connect函数完成的功能有:
1) 创建evdev设备,并初始化;
2) 初始化evdev设备的handle,并进行注册。在input驱动框架中,handler和handle要留意区分,这个地方的命名并不太好;
3)注册evdev字符设备,字符设备的路径为/dev/input/event*, 该设备文件与用户空间打交道,重点关注evdev_fops这个方法集合,他对应了用户空间操作输入设备文件操尴尬刁难用的内核空间字符设备的函数实现;
Handle

前面在讲解evdev的connect函数中,我们提到了input handle的注册,handle主要作用就是input dev和input handler创建联系的桥梁,数据布局如下所示:
struct input_handle {
    ......
    struct input_dev *dev;
    struct input_handler *handler;
    struct list_head    d_node;
    struct list_head    h_node;
}; Handle的注册由input_register_handle完成,这里不在具体形貌;
Input变乱上报

本章节,主要形貌常用输入设备产生变乱,Input子体系如何进行变乱的上报。在linux input驱动子体系中:
1)按键变乱使用input_report_key()接口进行上报
2)相对坐标变乱使用input_report_rel()接口进行上报;
3)绝对坐标变乱使用input_report_abs()接口进行上报;
4)变乱同步input_sync()和input_mt_sync(),这两个变乱,可以当做是一个当前变乱上报完成的标记,用户层程序,通过这个变乱,输入设备产生的变乱,是否上报完成;
注: 其他输入类型设备的变乱上报接口,这里不再形貌,感爱好的同学,可以去阅读Linux源码。
Input设备驱动开发

本章节,主要形貌开发一个input设备驱动的大概步骤,以下的形貌基于ft6206这个i2c接口的多点触控芯片,开发步骤如下形貌.
1、创建管理ft6206 Input设备驱动布局体, 将input_dev封装在里面。
struct ft6206_data {
    struct i2c_client *client;
    struct input_dev *input;
    struct gpio_desc *reset_gpio;
    u32 max_x;
    u32 max_y;
}; 2、创建input设备
input = devm_input_allocate_device(dev);
    if (!input)
      return -ENOMEM;

    ft6206->input = input;
    input->name = client->name;
    input->id.bustype = BUS_I2C; 3、设置该触摸设备支持的坐标变乱
input_set_abs_params(input, ABS_MT_POSITION_X, 0,
            ft6206->max_x, 0, 0);
    input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
            ft6206->max_y, 0, 0);

    error = input_mt_init_slots(input, FT6206_MAX_TOUCH_POINTS,
                  INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
    if (error)
      return error; 4、触摸设备被按下,会出发硬件中断,我们注册中断处理函数,吸收硬件触发的变乱
error = devm_request_threaded_irq(dev, client->irq, NULL,
                      ft6206_interrupt, IRQF_ONESHOT,
                      client->name, ft6206);
    if (error) {
      dev_err(dev, "request irq %d failed: %d\n", client->irq, error);
      return error;
    } 5、注册input设备
error = input_register_device(input);
    if (error) {
      dev_err(dev, "failed to register input device: %d\n", error);
      return error;
    } 6、在中断处理函数中,处理硬件触发的变乱,并将该变乱,封装为input变乱,上报给input core处理
static irqreturn_t ft6206_interrupt(int irq, void *dev_id)
{
......
    error = ft6206_read(ft6206->client, FT6206_REG_DEV_MODE, sizeof(buf), &buf);
    if (error) {
      dev_err(dev, "read touchdata failed %d\n", error);
      return IRQ_HANDLED;
    }

    touches = buf.touches & 0xf;
    if (touches > FT6206_MAX_TOUCH_POINTS) {
      dev_dbg(dev,
            "%d touch points reported, only %d are supported\n",
            touches, FT6206_MAX_TOUCH_POINTS);
      touches = FT6206_MAX_TOUCH_POINTS;
    }

    for (i = 0; i < touches; i++) {
      struct ft6206_touchpoint *point = &buf.points;
      u16 x = ((point->xhi & 0xf) << 8) | buf.points.xlo;
      u16 y = ((point->yhi & 0xf) << 8) | buf.points.ylo;
      u8 event = point->event >> 6;
      u8 id = point->id >> 4;
      bool act = (event == FT6206_EVENT_PRESS_DOWN ||
                event == FT6206_EVENT_CONTACT);

      input_mt_slot(input, id);
      input_mt_report_slot_state(input, MT_TOOL_FINGER, act);
      if (!act)
            continue;

      input_report_abs(input, ABS_MT_POSITION_X, x);
      input_report_abs(input, ABS_MT_POSITION_Y, y);
    }

    input_mt_sync_frame(input);
    input_sync(input);

    return IRQ_HANDLED;
} 该中断处理程序完成的功能有:
1、读取硬件上报的触摸坐标;
2、将每一个坐标点封装为一个多点触控inpt变乱(该设备支持多点触控硬件A协议,感爱好的同学,可以研究下硬件多点触控协议);
3、上报多点触控变乱;
4、坐标变乱上报完成,最后上报一个同步变乱,体现本次触摸变乱上报完成;
总结

本文主要形貌了在Linux体系中对于像键盘,触摸这类设备的管理和变乱处理。下一篇讲解Andoid eventHub ,用户空间如何获取Input变乱并进行处理;

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Android7 Input(二)Linux 驱动层输入变乱管理