网站地图    收藏    合作   

主页 > 系统 > linux系统 >

Linux输入子系统框架原理解析

来源:自学PHP网    时间:2020-09-17 11:25 作者:小飞侠 阅读:

[导读] Linux输入子系统框架原理解析...

今天带来Linux输入子系统框架原理解析教程详解

input输入子系统框架

linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。

一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。

【注意】keyboard.c不会在/dev/input下产生节点,而是作为ttyn终端(不包括串口终端)的输入。

驱动层

对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。

输入子系统核心层

对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。

事件处理层

对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。

/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。

输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。

作为输入设备的驱动开发者,需要做以下几步:

  • 在驱动加载模块中,设置你的input设备支持的事件类型
  • 注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
  • 将输入设备注册到输入子系统中

///////////////////////////////////////////////////////////////////分割线/////////////////////////////////////////////////////////////////////////////////

输入核心提供了底层输入设备驱动程序所需的API,如分配/释放一个输入设备:

struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);

/**
 * input_allocate_device - allocate memory for new input device
 *
 * Returns prepared struct input_dev or NULL.
 *
 * NOTE: Use input_free_device() to free devices that have not been
 * registered; input_unregister_device() should be used for already
 * registered devices.
 */
struct input_dev *input_allocate_device(void)
{
  struct input_dev *dev;
     /*分配一个input_dev结构体,并初始化为0*/ 
  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);/*初始化device结构*/ 
    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);/*模块引用技术加1*/ 
  }

  return dev;
}

注册/注销输入设备用的接口如下:

int __must_check input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);

/**
 * input_register_device - register device with input core
 * @dev: device to be registered
 *
 * 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.
 * If function fails the device must be freed with input_free_device().
 * Once device has been successfully registered it can be unregistered
 * with input_unregister_device(); input_free_device() should not be
 * called in this case.
 */
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;
  //设置 input_dev 所支持的事件类型,由 evbit 成员来表示。具体类型在后面归纳。
  /* 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);

   //初始化 timer 定时器,用来处理重复点击按键。(去抖)
  /*
   * 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);
    //如果 rep[REP_DELAY] 和 [REP_PERIOD] 没有设值,则赋默认值。为了去抖。
  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;//设置指定位置键值
    //设置 input_dev 中 device 的名字为 inputN
    //将如 input0 input1 input2 出现在 sysfs 文件系统中
  dev_set_name(&dev->dev, "input%ld",
       (unsigned long) atomic_inc_return(&input_no) - 1);
    //将 input->dev 包含的 device 结构注册到 Linux 设备模型中。
  error = device_add(&dev->dev);
  if (error)
    return error;
  //打印设备的路径并输出调试信息
  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_dev 加入 input_dev_list 链表中(这个链表中包含有所有 input 设备)
  list_add_tail(&dev->node, &input_dev_list);

  list_for_each_entry(handler, &input_handler_list, node)
   //调用 input_attatch_handler()函数匹配 handler 和 input_dev。
    //这个函数很重要,在后面单独分析。
    input_attach_handler(dev, handler);

  input_wakeup_procfs_readers();

  mutex_unlock(&input_mutex);

  return 0;
}

而对于所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event

/*
 * The event structure itself
 */

struct input_event {
  struct timeval time; //<输入事件发生的时间
  __u16 type;     //<输入事件的类型
  __u16 code;     //<在输入事件类型下的编码
  __s32 value;     //

输入事件的类型--input_event.type

/*
 * Event types
 */

#define EV_SYN      0x00 //< 同步事件
#define EV_KEY      0x01 //< 按键事件
#define EV_REL      0x02 //<相对坐标(如:鼠标移动,报告相对最后一次位置的偏移) 
#define EV_ABS      0x03 //< 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置) 
#define EV_MSC      0x04 //< 其它 
#define EV_SW       0x05 //<开关 
#define EV_LED      0x11 //<按键/设备灯 
#define EV_SND      0x12 //<声音/警报 
#define EV_REP      0x14 //<重复 
#define EV_FF       0x15 //<力反馈 
#define EV_PWR      0x16 //<电源 
#define EV_FF_STATUS   0x17 //<力反馈状态 
#define EV_MAX      0x1f //< 事件类型最大个数和提供位掩码支持 
#define EV_CNT      (EV_MAX+1)

Linux输入子系统提供了设备驱动层上报输入事件的函数

报告输入事件用的接口如下:

/* 报告指定type、code的输入事件 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/* 报告键值 */
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_KEY, code, !!value);
}
/* 报告相对坐标 */
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_REL, code, value);
}
/* 报告绝对坐标 */
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_ABS, code, value);
}
...

当提交输入设备产生的输入事件之后,需要调用下面的函数来通知输入子系统,以处理设备产生的完整事件:

void input_sync(struct input_dev *dev);

【例子】驱动实现――报告结束input_sync()同步用于告诉input core子系统报告结束,触摸屏设备驱动中,一次点击的整个报告过程如下:

input_reprot_abs(input_dev,ABS_X,x); //x坐标
input_reprot_abs(input_dev,ABS_Y,y); // y坐标
input_reprot_abs(input_dev,ABS_PRESSURE,1);
input_sync(input_dev);//同步结束

【例子】按键中断程序

//按键初始化
static int __init button_init(void)
{//申请中断
  if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))
    return CEBUSY;
  set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件
  set_bit(BTN_0,button_dev.keybit); //支持设备两个键
  set_bit(BTN_1,button_dev.keybit); //
  input_register_device(&button_dev);//注册input设备
}
/*在按键中断中报告事件*/
Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{
  input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//读取寄存器BUTTON_PORT0的值
  input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
  input_sync(&button_dev);
}

【小结】input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告(这里在linux中是通过中断来实现的)。

Event Handler层解析

Input输入子系统数据结构关系图


以上就是关于Linux输入子系统框架原理解析全部内容,感谢大家支持自学php网。

自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

京ICP备14009008号-1@版权所有www.zixuephp.com

网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

添加评论