이 글은 http://www.kernel.org/doc/Documentation/input/input-programming.txt을 바탕으로 작성되었습니다. 

  1. 간단한 예제

    간단한 입력 디바이스 드라이버 예제를 만들어 보자.  디바이스는 BTN_0이라는 한 가지 버튼만을 가지고 있고 버튼은 BUTTON_PORT로 정의된 i/o 포트에서 접근할 수 있다고 가정하자. 버튼을 누르거나 뗄 때, BUTTON_IRQ가 발생한다. 이러한 디바이스에 대한 드라이버는 다음과 같다. 



    #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); 




  2.  예제 설명 
     
    일반적으로 리눅스 드라이버는 모듈 프로그래밍으로 구현된다. 구현된 드라이버 모듈은 커널에 컴파일 시 포함되거나, 커널 부팅 후 관리자에 의해 로드된다.

    위 예제를 도식화 하면 다음과 같다. 



    여기서 커널이 어떻게 드라이버 모듈을 로드/해제 하는지는 차후 알아보기로 하고 위 도식에 따른 각각의 동작에 대한 설명에 집중하기로 한다. 


    모듈 로드 

    모듈이 로드되면 커널은 module_init 매크로로 명시한 함수를 호출하게 되는 데, 이는 드라이버 초기화 진입점이 된다. 
    해당 함수의 호출 시점은 드라이버의 형태에 따라 다르다. 만약 드라이버가 빌트인이라면, 커널 부팅 중 do_initcalls()에서, 적재가능한 모듈 형태라면 모듈이 적재될 때 해당 함수가 불려지게 될 것이다. 

    모듈 당 단 한 개의 module_init 함수가 존재하게 된다. 

    참고 : http://lxr.free-electrons.com/source/include/linux/init.h#L259

    module_init(button_init)

    module_init 매크로에 의해 button_init 함수가 호출된다. 
    module_init에 의해 불려진 함수에서 하는 일은 크게 2가지이다. 
    드라이버 구동에 필요한 자원 할당과 드라이버를 커널에 등록하는 일이다.

    필요한 자원은 드라이버의 속성에 따라 다르다. i2c로 통신하는 디바이스는 i2c 관련 자원을 확보하고 ADC값을 다루는 디바이스는 ADC와 관련 자원을 확보해야할 것이며 인터럽트를 사용하는 디바이스는 디바이스에 대한 IRQ를 확보하고 인터럽트 핸들러를 등록해야 할 것이다.  
      예제는 interrupt driven 방식이므로 BUTTON_IRQ라는 irq 넘버에 대한 인터럽트 핸들러를 등록해야 한다.

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

    필요한 자원을 확보했으면, 이제 드라이버를 등록해야 한다.
    input 드라이버를 등록하기 위해선 input 디바이스 정보를 가진 자료구조가 필요한데 그것이 input_dev이다.
    input 드라이버의 등록은 input_dev에 대한 조작 과정이며, input_dev에 대한 메모리 할당, input_dev 비트필드 설정, 드라이버 등록 순으로 이루어진다. 




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

    더보기


    * input 비트필드 설정


      input_dev는 input 디바이스에 대한 다양한 정보를 포함하고 있다. 그러나 그 중에서 가장 중요한 것은 어떤 이벤트를 핸들링하느냐이다.  evbit는 디바이스가 지원하는 이벤트의 타입을 포함하고 있으며, 대표적으로 EV_KEY(일반적인 키 혹은 버튼), EV_REL(상대적 좌표), EV_ABS(절대 좌표) 등의 이벤트를 선언할 수 있다. 
     

      그리고 각 이벤트타입마다 추가적인 비트필드가 존재한다. 키 이벤트일 경우, 어떤 키를 핸들링하는 지에 대한 정보가 필요하고, 절대 혹은 상대 좌표 이벤트일 경우, 어떤 축의 좌표를 핸들링할 것인지, 그 최소/최대값은 얼마인지 등에 대한 정보가 필요하기 때문이다. 자세한 사항은 http://lxr.free-electrons.com/source/include/linux/input.h#L1219를 참고하라. 

    예제에서는 버튼을 사용하고 있으므로 EV_KEY를 가지게 되며, BTN_0 키를 핸들링하므로 keybit에 BTN_0을 설정해준다. 

    * input_dev 등록 
        이제 input_dev 자료구조에는 동작에 필요한 모든 정보를 가지고 있으므로 이를 커널에 등록해야 한다. 이는 input_register_device(struct input_dev)를 호출함으로써 이루어진다. 


    드라이버 동작 

    드라이버가 동작하는 방식에는 크게 interrupt driven 방식과 polling 방식이 있다. 예제에서는 interrupt driven 방식으로 되어 있으므로 이 글에서는 interrupt driven 방식만을 다루기로 한다. 

    더보기


      BUTTON_IRQ가 발생하면, 커널은 BUTTON_IRQ에 대해 등록된 인터럽트 핸들러인 button_interrupt()를 호출한다. 

     

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

    매번의 인터럽트마다 button_interrupt는 BUTTON_0의 버튼 상태(PRESS/RELEASE)를 체크하여 커널에 보고해야 한다.
     input_report_key()함수를 통해 BUTTON_0의 상태를 보고할 수 있다.

    그러나 만약, 동시에 여러 개의 입력이 발생할 경우에는 어떻게 처리해야 할까? 가령 마우스와 같이 동시에 X,Y 좌표를 전달해야 할 때에, 드라이버는 커널에 X,Y 좌표 변화가 동시에 발생하였다는 것을 알려야 한다. 이렇게 복수 개의 입력이 하나의 이벤트로 발생하였다는 것을 알리기 위해 input_sync()를 호출한다.

     

    모듈 해제

    모듈 로드 시와 유사하게 모듈 해제 시, 커널은 module_exit 매크로로 명시한 함수를 호출하게 되며 
    이는 드라이버 종료 진입점이 된다. 예제에서는 button_exit 함수가 호출된다.

    해당 함수의 호출 여부는 드라이버의 형태에 따라 다르다. 드라이버가 모듈 형태일 경우, 사용자가 rmmod로 모듈을 해제할 시, cleanup_module()함수에 의해 불려지지만 드라이버가 빌트인 형태라면, 모듈을 해지하는 경우가 없기에 해당 함수는 불려지지 않게 된다. 

    참고 : http://lxr.free-electrons.com/source/include/linux/init.h#L268

    module_exit(button_exit)

    module_exit 매크로에 의해 button_exit 함수가 호출하게 된다. 

    module_exit 매크로로 명시된 함수는 드라이버의 구동 중에 사용한 자원을 반환해야 한다. 
    예제에서는 IRQ를 사용하였으므로 IRQ에 대한 자원을 해지해야 한다. 

    그리고 커널에 등록한 드라이버를 해지하고 메모리를 반환해야 한다. 경우에 따라 두 가지 함수가 쓰이는  데 다음과 같다. 
       * input_unregister_device() - input_register_device()에 의해 등록되었을 경우, 드라이버를 해지하고 메모리를 반환                                               한다.
       * input_free_device() -  input_register_device()에 등록되지 않는 input_dev에 대한 메모리를 반환한다. 주로 초기화                                       도중 에러가 생겼을 때 메모리 반환을 위해 쓰인다. 
      

Posted by 라판