天天看點

input子系統——架構、驅動、應用程式

一、input子系統簡介

1、Input驅動程式是linux輸入裝置的驅動程式,分成遊戲杆(joystick)、滑鼠(mouse和mice)、事件裝置(event)。其中事件驅動程式是目前通用的驅動程式,可支援鍵盤、滑鼠、觸摸屏等多種輸入裝置。

2、input驅動程式的主裝置号是13、次裝置号的分布如下:

joystick遊戲杆:0~16

mouse滑鼠:

mice滑鼠:

事件裝置: 64~95

3、主要的結構體

Input_device:代表着具體的輸入裝置,他直接從硬體中讀取資料,并以事件的形式轉發

Handler:代表接收某一類事件的上層接口,對應于一類事件裝置檔案

Handle:用于将input_device和handler連接配接起來,對應于某個具體的裝置檔案。

Client:對應于使用者程式對檔案的通路接口,每open一次事件驅動,就建立一個client

Handler:struct input_handler *input_table[8],最多有8中input驅動,比如/dev/input/eventX和/dev/input/mouseX就是兩種常用的input驅動。

Handle:以evdev.c為例,根據次裝置号取值範圍64-95,可以分别生成input/event0、input/event1,一直到input/event31共32個裝置檔案。每個裝置檔案對應一個handle

Client:每個裝置檔案又可以同時對應多個client,當有多個應用程式同時調用裝置檔案時,他們會從不同的client中取資料。

input子系統維護着兩條重要的連結清單:input_dev_list,input_handler_list

二、input子系統架構

input子系統由驅動層drivers,輸入子系統核心層input core,事件處理層event handler組成。

驅動層并不建立檔案節點,他隻負責将采集到的資料通過input.c提供的函數input_event向上一層彙報。而各個事件驅動則分别将他們感興趣的事件資訊提取出來,通過檔案節點傳給使用者空間。

一個輸入事件,通過輸入裝置發給系統如滑鼠移動,鍵盤按鍵按下等通過device driver->input core(handler->event函數)->event handler->user space的順序到達使用者空間傳給應用程式。

一個輸出事件,通過系統發給輸入裝置,通過user space->event handler->input core(dev->event函數)->device driver

1、驅動功能層:負責和底層的硬體裝置打交道,将底層硬體裝置對使用者輸入的響應轉換為标準的輸入事件以後再向上發送給輸入系統核心層

2、Input系統核心層:由driver/input/input.c及相關頭檔案實作,他對下提供了裝置驅動層的接口,對上提供了事件處理層的變成接口。

3、事件處理層将硬體裝置上報的事件分發到使用者空間和核心。

結構圖如下:

input子系統——架構、驅動、應用程式

三、編寫input驅動需要的函數

1)包含頭檔案<linux/input.h>,他是input子系統的接口,提供了必要的定義消息

2)Input_allocate_device()

配置設定了一個Input device的結構,設定他的bit field來告訴input子系統他能産生或者接收什麼事件。

3)input_register_device(struct input_dev *dev)

将dev結構體添加到input_dev_list全局連結清單中去

通過input_attach_handler(struct input_dev *dev, struct input_handler *handler)來查找對應的handler,

input_attach_handler裡面實際調用了input_match_device(const struct input_device_id *id,struct input_dev *dev)

一旦input_attach_handler找到了對應的handler,就執行handler->connect

4)input_report_key(struct input_dev *dev, unsigned int code, int value)

5)input_sync(struct input_dev *dev)

告訴事件的接收者,到此為止為一次完整的消息。比如我們在touch screen上獲得了x、y的值,要使作為一次事件,那麼将input_sync加在report x、y值得後面。

6)其他的事件type,輸出事件處理

其他的事件有:

EV_LED:用作鍵盤的LED燈

EV_SND:用作鍵盤的蜂鳴器

他和鍵盤事件很相似,隻不過鍵盤事件是INPUT_PASS_TO_DEVICE,而輸出事件是INPUT_PASS_TO_HANDLERS,從系統到輸入裝置的驅動程式,如果你的驅動程式要處理這些事件,必須設定evbit中相應位,而且要實作一個回調函數。

struct input_dev *button_dev;

button_dev->event = button_event;這個便是處理輸出事件的回調函數

四、普通按鍵實作input驅動例子

/*
drivers->input core->event handler
function: this file is button driver
date: 20150101
author: lei_wang
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>

static struct input_dev *button_dev;

static irqreturn_t button_intr(int irq, void *dev_id)
{
  int val;
  val = s3c2410_gpio_getpin(S3C2410_GPG(0));
//  printk(KERN_INFO "key value is %d\n", val);

  input_report_key(button_dev, BTN_0, val);
  input_sync(button_dev);
  
  return IRQ_RETVAL(IRQ_HANDLED);
}

static int __init button_init(void)
{
  int ret;
  ret = request_irq(IRQ_EINT8, button_intr, IRQ_TYPE_EDGE_BOTH, "button0", NULL);
  if (ret) {
    printk(KERN_ERR "%s request failed\n", __func__);
    return -ENODEV;
  }

  button_dev = input_allocate_device();
  if (!button_dev) {
    printk(KERN_ERR "button.c: Not enough memory\n");
    free_irq(IRQ_EINT8, NULL);
    return -ENOMEM;
  }

  button_dev->name = "button0";
  button_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY);
  button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);

  ret = input_register_device(button_dev);
  if (ret) {
    printk(KERN_ERR "button.c: Failed to register device\n");
    input_free_device(button_dev);
    free_irq(IRQ_EINT8, NULL);
    return -ENODEV;
  }

  printk(KERN_INFO "button init ok!\n");
  return 0;
}

static void __exit button_exit(void)
{
  input_unregister_device(button_dev);
  input_free_device(button_dev);
  free_irq(IRQ_EINT8, NULL);
  
  printk(KERN_INFO "button exit ok!\n");
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Realsil Luckywang");      

Makefile如下:

obj-m = button.o
KERNELDIR ?=/home/lei/linux-2.6.32.2
modules:
  $(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules
clean:
  rm -rf *.o *.mod.c *.order *.symvers      

Include/linux/bitops.h中定義了

#define BIT(nr) (1UL << (nr))
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
#define BTN_0 0x100
button_dev->evbit[0] = BIT_MASK(EV_KEY);
button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);      

說明:

1)上面的0x100表示BTN_0這個bit在所有的bit中是0x100(bit 256)位,那麼

BIT_WORD(BTN_0)代表bit 256在keybit這個數組的第幾個數組(第8個)

BIT_MASK(BTN_0)代表bit 256在keybit這個數組的第幾個數組裡面的值(第8個數組的bit0)

2)事件類型type——編碼code——值value

evbit是事件數組,evbit這個事件數組裡面可以放很多事件類型,比如key、abs等

事件key裡面又有很多具體編碼BTN_0、BTN_TOUCH等

事件abs裡面也有很多具體編碼ABS_X、ABS_Y等

不同編碼有不同的值

1)input子系統的struct input_dev、struct handler的注冊

2)struct input_dev與struct input_handler怎麼互相比對(類似于device和driver比對)

3)事件處理過程

五、例子對應的應用程式

/*
20150101
just a simple input test code
lei_wang
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <linux/input.h>

int main()
{
  int fd;
  int version;
  int ret;
  struct input_event ev;
  
  fd = open("/dev/input/event1", O_RDONLY);
  if (fd < 0) {
    printf("open file failed\n");
    exit(1);
  }

  ioctl(fd, EVIOCGVERSION, &version);
  printf("evdev driver version is 0x%x: %d.%d.%d\n",
          version, version>>16, (version>>8) & 0xff, version & 0xff);

  while (1) {
    ret = read(fd, &ev, sizeof(struct input_event));
    if (ret < 0) {
      printf("read event error!\n");
      exit(1);
    }
    
    if (ev.type == EV_KEY)
      printf("type %d,code %d, value %d\n", ev.type, ev.code, ev.value);
  }
  
  return 0;
}      

以上隻是一個簡單的應用程式測試。當你按下K1的時候,序列槽終端會有顯示的input dev上報的按鍵的消息。

另外,編寫應用程式的時候如何确定是哪個eventX呢,cat /proc/bus/input/devices,輸出列印消息如下:

input子系統——架構、驅動、應用程式

這裡插入了滑鼠,通過比較VID、PID來找到對應的usb mouse裝置,然後找到對應的mouse0、event1

其實也可以不寫應用程式,直接通過cat /dev/input/mouse0 | hexdump來擷取滑鼠的資料。

繼續閱讀