컴퓨터에는 사용자가 컴퓨터로 값을 전달하고 제어를 위한 도구로 굉장히 다양한 종류의 입력 장치가 있습니다.

예를들어 키보드, 마우스, 터치패드, 터치스크린, 조이스틱 등 여러 입력 장치들이 필요에 따라 존재합니다.

 

  그 종류는 수행하고자 하는 기능에 따라 특수성에 맞춰 점점 늘어나고 다양해지고 있습니다.

늘어나는 장치들을 관리하기 위하여 다양화되는 입력 장치들을 관리하기 위해 커널 영역에서 커널 입력 서브 시스템을 통하여 정리 및 관리 합니다.

  

  커널 입력 서브 시스템을 이용하게 되면 물리적으로 구성이 다르더라도 기능이 유사한 입력 장치는 동일하게 다루며 드라이버를 추가 작성하고 관리하기 편하도록 추상화가 가능합니다.

 

  위와 같이 커널 입력 서브 시스템을 이용함으로써 하드웨어적으로 동작 및 구현 방식이 다르더라도 그 기능이 유사한 장치들은 다 같은 방식으로 처리 및 핸들링 하는 것입니다.

 

  즉, 키보드의 입력 방식이나 통신 방식이 서로 다르더라도 키보드라는 기능이 유사하기에 입력 기능을 동일하게 처리할 수 있다는 의미 입니다.

 

  커널 입력 서브 시스템 및 입력 드라이버는 사용자에게 디바이스를 통하여 입력된 값을 알리기 위한 이벤트 인터페이스를 제공합니다.

 

  입력 장치의 입력 이벤트들을 사용자 영역의 어플리케이션에 전달하기 위해서 /dev/input 하위의 노드나 다른 방식을 이용할 필요없이 입력 관련 인터페이스를 이용하여 사용자에게 입력을 알릴 수 있습니다.

 

  입력 드라이버에서 공통된 부분을 찾아낸 후 단순화하고 일관성있게 만드는 추상 개념을 제공합니다.

입력 하위시스템은 USB로 연결되는 마우스나, 키보드등 다양한 입력장치를 위해 USB HID라는 드라이버를 추상화하고 제공하여 좀 더 단순하고 일관성있게 작성 및 도움이 됩니다.

 

 

 

  위 그림은 입력 장치에 대한 커널 인터페이스 구성 그림입니다. 각각의 입력장치가 USB 형태는 모두 USB HID 인터페이스 드라이버를 통하여 연결되고, 다른 입력 장치들에 대해서는 각각의 통신 방식에 따라 입력 코어를 거쳐 입력 이벤트 드라이버로 전달되며 그 후 전달된 입력들을 입력 이벤트 드라이버에서 이벤트를 발생시킵니다.

 

  커널 내의 입력 드라이버는 입력 장치 드라이버와 입력 이벤트 드라이버로 두가지의 형태로 나누어져 있습니다.

 

    - 입력 장치 드라이버 : 하드웨어 영역의 장치들과 통신하며 해당 입력 장치의 기능을 세부 구현 및 기능 담당
    - 입력 이벤트 드라이버 : 입력 장치 드라이버의 이벤트 수신과 처리를 위하여 사용자 영역과 통신

 

  커널 내부 소스에서도 위와 같이 추상화 되어 분류되어 있음을 확인할 수 있습니다.  

 

 

  입력 디바이스 드라이버를 위한 참조 헤더는 /include/linux/input.h 입니다.

이 헤더에는 입력 디아비스 드라이버들의 추상화 및 이벤트 등의 전달을 위하여 필요한 매크로나 정의, 함수 등을 정의 한 것으로 위 헤더의 정의에 맞춰 드라이버를 설계합니다.

 

struct input_event {
        struct timevale time;
        __u16 type;
        __u16 code;
        __s32 value;

 

 위의 구조체는 input_event 구조체로 입력 이벤트를 발생시키기 위한 구조체 입니다. timeval 구조체의 time이란 변수는 타임스탬프(발생 시간)를 말하며, type은 이벤트의 형식을 말합니다. 절대 자표, 버튼등의 형식이 있으니, input.h를 찾아보시기 바랍니다. code 는 이벤트 코드로 그 이벤트에 해당하는 keycode를 말합니다 예를 들면 키보드의 backspace나 space등 여러 버튼을 구분 짓습니다. value 는 이벤트의 값을 말합니다. 키패드나 키보드 입력이 있을 시에 버튼이 on 이라면 1 off라면 0 등의 값을 나타냅니다.

 

 

  실제 드라이버를 작성하기 위해 필요로 되는 input.h의 기본 함수들을 나열하겠습니다.

//input_dev 구조체를 위한 메모리 할당/해제
     
struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
     
     
     
     
//입력 디바이스 등록/해제
     
void input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);
     
     
     
     
//input_dev 구조체에 추가 정보 설정 (터치스크린에서 필요)
     
static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);



//***이벤트를 이벤트 핸들러 디바이스 드라이버에 전달***
     
//기본 함수
     
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);
     
     
//상대적인 이동 크기 정보 이벤트 전달 (마우스에서 사용) 
     
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
     
//이동된 절대 좌표 이벤트 전달 (터치스크린에서 사용) 
     
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);
     
//하나의 상태에 대하여 여러 이벤트가 동기되어야 한다는 것을 전달
     
static inline void input_sync(struct input_dev *dev);
 
  초기화 과정은 input_dev 구조체에 필요한 정보를 설정하고 이를 이용해서 input_register_device()를 호출하면 됩니다.
초기화 설정에서 각각의 입력 디바이스 드라이버가 이벤트 디바이스 드라이버와 연결되기 위해서는 다음 항목을 설정해 주어야 합니다.
unsigned long evbit[NBITS(EV_MAX)]; // 이벤트 타입
unsigned long keybit[NBITS(KEY_MAX)]; // 사용되는 키 타입
unsigned long absbit[NBITS(ABS_MAX)]; // 사용되는 절대 좌표 값
 
 
예를 들면, 보통 터치스크린 드라이버를 위한 초기화 값은 아래와 같습니다.
 
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); 

evbit은 처리해야할 데이터 타입을 정의하고, EV_KEY는 버튼의 입력을 처리한다는 의미입니다. 또 EV_ABS는 절대 좌표 값을 처리한다는 의미 입니다.

keybit은 사용되는 키 값이 어떤 것이 있는지를 표현하는 것입니다. 

absbit은 이벤트 좌표 값이 형식을 말합니다. ABS_X와 ABS_Y는 각각 절대 좌표 형식의 X와 Y를 처리한다는 것입니다.

 

위에 설정 외에도 name, phys, id 값 등 필요에 따라 설정해주시면 됩니다.

 

실제 터치 동작에 따른 이벤트 전달 및 동작은

Touch Press --> Touch Move --> Touch Release

이 세가지로 구분 됩니다. 따라서 저 이벤트 전달 방식을 코드로 옮기면

input_report_key(input_dev, BTN_TOUCH, 1);
input_report_abs(input_dev, ABS_X, value_x);
input_report_abs(input_dev, ABS_Y, value_y);
input_sync(input_dev);

 

하나의 이벤트가 완료되려면 Press 된 상태와 그 위치 X 와 Y의 좌표값을 모두 전달해야 합니다. 이는 하나의 동작에 세개의 이벤트로 구성되었으나 하나의 이벤트로 알리기 위해 input_sync()를 해서 동시 발생으로 간주하도록 해줍니다. 참고로 input_sync() 함수는 EV_SYN을 전달합니다.

 

Touch Release 시의 코드는 

input_report_key(input_dev, BTN_TOUCH, 0);
input_sync(input_dev);

 

  이와 같이 디바이스 드라이버의 이벤트들이 동기화 되어서 동작하게 하기 위해서 input_register_device()에서는 EV_SYN을 설정하는 코드가 포함되어 있습니다.

 

/device/input/input.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); //이부분
     
     
     
     
}

input_event()는 input_handle_event()를 호출하는데 여기에서 EV_SYN를 체크하여 다음 Handler로 하여 전달할지 안할지 결정해서 동작합니다.

 

 

 

  이제 커널 소스의 Documentation/input/input-programming 의 예제를 보도록 하겠습니다.

 

 

#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>

#include <asm/irq.h>
#include <asm/io.h>

static struct input_dev *button_dev;

static irqreturn_t button_interrupt(int irq, void *dummy)
{
        input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
        input_sync(button_dev);
        return IRQ_HANDLED;
}

static int __init button_init(void)
{
        int error;

        if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
                printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
                return -EBUSY;
        }

        button_dev = input_allocate_device();
        if (!button_dev) {
                printk(KERN_ERR "button.c: Not enough memory\n");
                error = -ENOMEM;
                goto err_free_irq;
        }

        button_dev->evbit[0] = BIT_MASK(EV_KEY);
        button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);

        error = input_register_device(button_dev);
        if (error) {
                printk(KERN_ERR "button.c: Failed to register device\n");
                goto err_free_dev;
        }

        return 0;


 err_free_dev:
        input_free_device(button_dev);
 err_free_irq:
        free_irq(BUTTON_IRQ, button_interrupt);
        return error;
}

static void __exit button_exit(void)
{
        input_unregister_device(button_dev);
        free_irq(BUTTON_IRQ, button_interrupt);
}

module_init(button_init);
module_exit(button_exit);

 

 위와 같이 작성되어 있습니다. 이 소스코드는 하나의 버튼의 입력을 받아 입력 이벤트로 전달하는 코드입니다.

이 코드를 순서도로 본다면, 아래 그림과 같습니다.

 

 

모듈에는 하나의 module_init 및 module_exit를 매크로로 명시한 함수를 호출하게 됩니다. 이는 드라이버 초기화 진입점 및 해제 진입점이 됩니다. 해당 함수의 호출 시점은 드라이버의 형태에 따라 다르게 나타납니다. 만약 드라이버가 Built-in(커널에 포함)이라면, 커널 부팅 중 do_initcalls에 의해서, 적재가능한 모듈 형태라면 모듈이 적재될 때 해당 함수가 불려집니다.

 

 

init 시에는 GPIO 등의 필요하 자원에 따른 속성을 초기화 합니다. 

예제에서는 interrupt 를 이용하였기 때문에 BUTTON_IRQ라는 irq 넘버를 지정하여 그에 대한 interrupt handler 함수를 등록합니다.

 

request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL);

 

필요한 하드웨어 자원을 초기화 했다면, 입력 디바이스 드라이버를 초기화 해주어야 합니다. 

입력 드라이버를 등록하기 위해선 input 디바이스 정보를 가진 자료구조가 필요한데 그것이 input_dev라는 구조체 입니다. 

 

1. input_dev 메모리를 할당

input_allocate_device()를 호출하면 커널 영역의 메모리를 할당하여 input_dev를 만들고 이를 반환합니다.

 

2. 입력 비트필드 설정

input_dev는 input 디바이스에 대한 다양한 정보를 포합하고 잇습니다. 그 중에서 제일 중요한 요소는 어떠한 이벤트를 핸들링하는냐 입니다. evbit는 디바이스가 지원하는 이벤트의 타입을 포함하고 있으며, 대표적으로 EV_KEY, EV_REL, EV_ABS, EV_MSC 등이 있습니다.

 

각각 이벤트 타입마다 추가적인 비트필드가 존재합니다. 키 이벤트일 경우, 어떤 키를 핸들링하는 지에 대한 정보가 필요하고, 절대 혹은 상대 좌표 이벤트일 경우, 어떤 축의 자표를 핸들링할 것인지, 그 최소/최대 값은 얼마인지를 설정해주어야 합니다.

 

예제에는 EV_KEY(버튼) 으로, BTN_0 키를 핸들링 하기에, keybit에 BTN_0을 설정합니다.

 

3. input_dev 등록

input_dev를 다 정의 하였다면, 입력 디바이스 드라이버를 커널에 등록해야 합니다. 

input_register_device(struct input_dev)

 

 

등록이 완료되면 이제 입력 디바이스 드라이버는 인터럽트 기다리며 버튼이 눌릴 때까지 대기 합니다.  인터럽트가 발생하였을 경우에. BUTTON_IRQ에 등록된 인터럽트 핸들러가 호출되어 입력 이벤트를 전달합니다.

 

 

모듈 init 과 유사하게 모듈 해제시에는 입력디바이스를 해제하고, 그에 필요했던 시스템 자원을 해제합니다.

 

 

 

이렇게 입력 디바이스 드라이버에 대한 개념과 예제를 설명하였습니다.

터치스크린이나 키패드 등 여러 입력 장치들에 대해서 입력 디바이스 드라이버를 만드는데 필요한 정보 및 개념이였습니다.

 

 

 

 

 

 

 

 

이미지 및 내용 참고 : 임베디드 개발자를 위한 코드로 읽는 리눅스 디바이스 드라이버, http://sonseungha.tistory.com

+ Recent posts