Linux Input Event Driver

주의

내공 부족으로 정확하지 않는 내용이 다수 있을 수 있음.
Subsystem이라고 부를 정도로 정말 거대하다.

0. The Input Subsystem

출처 : Essential Linux Device Drivers

hw -> Transfer Layer{spi, i2c, ….} -> input driver -> i2c core api -> input event driver -> Evdev Interface -> application

내가 코드 보며 이해한 바로는 이런데 실제로는 어떨련지 모르겠다.

굳이 input driver와 input event driver를 나누지 않고 하나로 합쳐서 작성된 것도 있다.


1. The Evdev Interface

include/linux/input.h
input subsystem의 자료형 및 정의가 있다

struct input_event {
  struct timeval    time;    //timestamp
  __u16                type;
  __u16                code;
  __s32                value;    //event value
}

이 구조체 형태로 /dev/input/ 디렉토리에 이벤트 값이 전달 된다.
주의 : 한 회에 한 종류 값만 전달 된다.

무슨 의미인고 하니 read로 읽을 때 이벤트 당 1개씩 값이 전달 된다는 이야기

예를 들어 (x, y) 좌표에 (12, 34) 클릭이 발생한 경우
data#1 - 클릭 발생
type = EV_KEY,
code = BTN_TOUCH,
value = 1

data#2 - x 좌표
type = EV_ABS,
code = ABS_X,
value = 12

data#3 - y 좌표
type = EV_ABS,
code = ABS_Y,
value = 34

data#4 - 이벤트 끝
type = EV_SYN, //event1 finished
code = SYN_REPORT,
value = 0

EV_SYN으로 하나의 이벤트 전송 종료를 알린다.

이벤트 리포팅을 위해

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
...
static inline void input_sync(struct input_dev *dev);

여러 이벤트 종류에 따라 각기 다양하게 함수들이 있다.


사용 예시
drivers/input/joystick/a3d.c

static void a3d_read(struct a3d *a3d, unsigned char *data)
{
  ...
  input_report_key(dev, BTN_RIGHT, data[2] & 1);
  input_report_key(dev, BTN_LEFT, data[3] & 2);
  ...
  input_report_abs(dev, ABS_X, ....);
  ...
  input_sync(dev);
}

data를 얻어와서 event 를 evdev로 보내는 모습

이 드라이버의 경우 data는 별도의 input driver에게 얻는다.

struct input_dev {
  ...
  struct input_id    id;
  unsigned long evbit[...];
  unsigned long keybit[...];
  ...
  int (*open)(struct input_dev *dev);
  void (*close)(struct input_dev *dev);
  ...
}

엄청나게 거대하므로 각각 역할은 커널소스의 주석을 직접 확인하기 바란다.


2. Example Source

drivers/input/keyboard/pxa27x_keypad.c

이 코드가 가장 보기 쉬웠어요

module_platform_driver
-> probe
-> request_mem_region, ioremap
-> input_allocate_device, input_dev 설정, request_irq
-> input_register_device

irq_handler - mmio read, input_event, input_report_key, input_sync
대략 이런 흐름인 것 같다.


3. Linux input event read Application code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <sys/time.h>
#include <string.h>

int main()
{
    int fd, ret;
    int x, y;
    const char* evdPath = "/dev/input/event3";
    struct input_event iev[3];

    fd = open(evdPath, O_RDONLY);

    if(fd < 0) {
        perror("error");
        return -1;
    }

    while(1) {
        ret = read(fd, iev, sizeof(struct input_event)*3);
        if(ret < 0) {
            perror("error");
            break;
        }

        if(iev[0].type == EV_REL && iev[0].code == REL_X)
            x = iev[0].value;
        if(idev[1].type == EV_REL && iev[1].code == REL_Y)
            y = iev[1].value;

        printf("x:%d, y:%d\n", x, y);
        printf("%hu, %hu, %d\n", iev[0].type, iev[0].code, iev[0].value);
        printf("%hu, %hu, %d\n", iev[1].type, iev[1].code, iev[1].value);
        printf("%hu, %hu, %d\n", iev[2].type, iev[2].code, iev[2].value);
    }

    close(fd);
    return 0;
}

RUN

4, 3
2, 0, 4
2, 1, 3
0, 0, 0
...

별 설명할 꺼리가 없다……
다만 왜 event3로 생성이 되는지 그건 잘 모르겠다.


4. virtual input event driver code

이름은 거창한데 사실 별거 없다
hw 에서 인터럽트 혹은 데이터를 안 받았는데 그냥 데이터 들어왔다고 evdev에게 이벤트를 알리는 것이다.

const char                 *pdev_name = "ietest";
struct platform_device    *pdev;
struct ev_test {
      struct input_dev    *input_dev;
      struct task_struct    *task;
};

int read_data_from_sensor(void)
{
  static int roll = 0;
  return roll++%2 ? 3 : 4;
}

void set_input_device_property(struct input_dev *dev)
{
  set_bit(EV_REL, dev->evbit);
  set_bit(REL_X, dev->relbit);
  set_bit(REL_Y, dev->relbit);
}

void send_event_msg_to_evdev(struct input_dev *dev)
{
  input_report_rel(dev, REL_X, read_data_from_sensor());
  input_report_rel(dev, REL_Y, read_data_from_sensor());
  input_sync(dev);
}

static int hw_fake_int_handler(void *arg)
{
  struct ev_test *event_ctrl = (struct ev_test*) arg;
  struct input_dev *idev = event_ctrl->input_dev;

  while(!kthread_sould_stop())        //check stop signal
  {
    send_event_msg_to_evdev(idev);
    msleep_interruptible(300);
  }

  return 0;
}

/* REMOVED ERROR CHECKING ROUTINE */
static int inputevent_test_init(void)
{
  int ret;
  struct ev_test        *event_ctrl;
  struct task_struct    *task;
  struct input_dev        *idev;

  /* platform or i2c or spi or serio or etc ... */
  pdev = platform_device_register_simple(pdev_name, -1, NULL, 0);

  event_ctrl = kzalloc((sizeof(struct ev_test)), GFP_KERNEL);

  idev = input_allocate_device();
  set_input_device_property(idev);
  input_register_device(idev);

  task = kthread_run(hw_fake_int_handler, event_ctrl, 
                          "ieint_%s_#%d", pdev_name, 1);

  event_ctrl->input_dev = idev;
  event_ctrl->task = task;
  dev_set_drvdata(&pdev->dev, event_ctrl);

  return 0;
}

static void inputevent_test_exit(void)
{
  struct ev_test    *event_ctrl = (struct ev_test*)dev_get_drvdata(&pdev->dev);
  kthread_stop(event_ctrl->task);

  input_unregister_device(pdev);  
}

module_init(inputevent_test_init);
module_exit(inputevent_test_exit);

이 코드에서는 pdev를 가상으로 만들었지만 실제로는 여러 통신 방식을 통해 probe 되고 거기에 interrupt handler를 등록해서 input event 를 보내는 식으로 처리하는 것 같다
인터럽트 핸들러 대신 가상으로 커널 쓰레드를 만들어서 계속 좌표를 보내도록 개발했다


기타 github에 올려본 예제

https://github.com/chocokeki/input_event_driver

Posted by 쵸코케키

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.3
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

최근에 올라온 글

최근에 달린 댓글

글 보관함