Input 서브시스템

Linux Input 서브시스템을 이벤트 라우팅과 사용자 경험 품질 관점에서 심층 분석합니다. input_dev/input_handler/evdev 계층의 역할, 키보드·마우스·터치·센서 이벤트 타입 해석, 멀티터치 슬롯 프로토콜, debounce·repeat·calibration 처리, IRQ 기반 이벤트 수집과 지연 처리 분리, power key/wakeup 같은 전원 연계 동작, udev/libinput으로 전달되는 사용자 공간 경로, evtest/libinput-debug-events를 활용한 문제 분석까지 입력 장치 드라이버 실전에 필요한 핵심을 다룹니다.

전제 조건: 디바이스 드라이버Workqueue 문서를 먼저 읽으세요. 입출력 인터페이스 드라이버는 데이터 경로와 제어 경로를 동시에 다루므로 큐/버퍼/비동기 처리 경계를 먼저 구분해야 합니다.
일상 비유: 이 주제는 콜센터 접수와 처리 라인 분리와 비슷합니다. 요청 접수와 실제 처리를 분리해 병목을 줄이듯이, 드라이버도 IRQ·큐·작업 스레드를 역할별로 나눠야 안정적입니다.

핵심 요약

  • 초기화 순서 — 탐색, 바인딩, 자원 등록 순서를 점검합니다.
  • 제어/데이터 분리 — 빠른 경로와 설정 경로를 분리 설계합니다.
  • IRQ/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
  • 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
  • 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.

단계별 이해

  1. 장치 수명주기 확인
    probe부터 remove까지 흐름을 점검합니다.
  2. 비동기 경로 설계
    IRQ, 워크큐, 타이머 역할을 분리합니다.
  3. 자원 정합성 검증
    DMA/클록/전원 참조를 교차 확인합니다.
  4. 현장 조건 테스트
    연결 끊김/복구/부하 상황을 재현합니다.
관련 페이지: 기본 디바이스 드라이버 모델은 디바이스 드라이버, 버스 프레임워크는 I2C/SPI/GPIO 페이지를 참고하세요.

Input 서브시스템 (키보드, 마우스, 터치)

리눅스 Input 서브시스템(drivers/input/)은 키보드, 마우스, 터치스크린, 조이스틱, 리모컨 등 모든 종류의 입력 장치를 통합 관리하는 프레임워크입니다. 하드웨어별 드라이버가 이벤트를 생성하면, Input Core가 이를 적절한 핸들러(evdev, kbd, mousedev 등)로 라우팅하여 유저 공간에 전달합니다.

Input 서브시스템 아키텍처 User Space libinput / X11 evtest / evemu SDL / Qt Input systemd-logind console (VT) /dev/input/eventN, mouseN, jsN Event Handlers (input_handler) evdev kbd mousedev joydev rfkill-input input-leds input_handle (연결) Input Core (drivers/input/input.c) 디바이스 등록 · 이벤트 라우팅 · handler 매칭 · input_event 전파 Input Device Drivers (input_dev) atkbd hid-generic gpio-keys goodix_ts i2c-hid xpad custom Hardware PS/2 (i8042) USB HID I2C 터치패널 SPI 디지타이저 GPIO 버튼 Bluetooth HID serio (PS/2) · USB · I2C · SPI · GPIO · Bluetooth (hidp)

Input 서브시스템은 3계층으로 구성됩니다:

핵심 데이터 구조

/* === struct input_dev — Input 디바이스를 나타내는 핵심 구조체 ===
 * include/linux/input.h
 * 하드웨어 드라이버가 할당·등록하며, 지원하는 이벤트 유형과 코드를 비트마스크로 선언 */
struct input_dev {
    const char *name;           /* 사람이 읽을 수 있는 디바이스 이름 */
    const char *phys;           /* 물리적 경로 (예: "usb-0000:00:1d.0-1/input0") */
    const char *uniq;           /* 고유 식별자 (시리얼 번호 등) */
    struct input_id id;          /* bustype, vendor, product, version */

    /* 이벤트 능력(capability) 비트마스크 */
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];    /* 지원 이벤트 유형 */
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];  /* 지원 키 코드 */
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];  /* 상대축 코드 */
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];  /* 절대축 코드 */
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];  /* 기타 이벤트 */
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];  /* LED 상태 */
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    /* 스위치 상태 */
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    /* Force Feedback */

    /* 절대축 파라미터 (min, max, fuzz, flat, resolution) */
    struct input_absinfo *absinfo;

    /* 콜백 함수 */
    int (*open)(struct input_dev *dev);    /* 첫 핸들러 연결 시 */
    void (*close)(struct input_dev *dev);  /* 마지막 핸들러 해제 시 */
    int (*event)(struct input_dev *dev,
                 unsigned int type,
                 unsigned int code, int value); /* LED/FF 출력 이벤트 */

    struct device dev;            /* 내장 device 구조체 */
    struct list_head h_list;     /* 연결된 input_handle 리스트 */
    struct list_head node;       /* 전역 input_dev 리스트 */
};
/* === struct input_handler — 이벤트 핸들러 (evdev, kbd, mousedev 등) ===
 * Input Core에 등록되어 매칭되는 디바이스의 이벤트를 처리 */
struct input_handler {
    void (*event)(struct input_handle *handle,
                  unsigned int type,
                  unsigned int code, int value);
    void (*events)(struct input_handle *handle,
                   const struct input_value *vals,
                   unsigned int count);         /* 배치 이벤트 (성능 최적화) */
    bool (*filter)(struct input_handle *handle,
                   unsigned int type,
                   unsigned int code, int value); /* 이벤트 필터링 */
    bool (*match)(struct input_handler *handler,
                  struct input_dev *dev);        /* 추가 매칭 조건 */
    int  (*connect)(struct input_handler *handler,
                    struct input_dev *dev,
                    const struct input_device_id *id);
    void (*disconnect)(struct input_handle *handle);

    const char *name;
    const struct input_device_id *id_table;   /* 매칭 테이블 */
    struct list_head h_list;                   /* 연결된 handle 리스트 */
    struct list_head node;                     /* 전역 handler 리스트 */
};
/* === struct input_handle — input_dev와 input_handler 연결 ===
 * 하나의 디바이스는 여러 핸들러에 연결 가능 (예: evdev + kbd 동시) */
struct input_handle {
    void *private;                     /* 핸들러별 개인 데이터 */
    int open;                           /* 열린 파일 디스크립터 수 */
    const char *name;
    struct input_dev *dev;              /* 연결된 디바이스 */
    struct input_handler *handler;      /* 연결된 핸들러 */
    struct list_head d_node;            /* dev->h_list 노드 */
    struct list_head h_node;            /* handler->h_list 노드 */
};

/* === struct input_event — 유저 공간에 전달되는 이벤트 구조 ===
 * /dev/input/eventN에서 read() 시 이 구조체 배열로 전달됨 */
struct input_event {
    struct timeval time;   /* 이벤트 발생 시각 (또는 input_event_usec) */
    __u16 type;             /* EV_KEY, EV_REL, EV_ABS ... */
    __u16 code;             /* KEY_A, REL_X, ABS_MT_POSITION_X ... */
    __s32 value;            /* 키: 1(press)/0(release)/2(repeat), 축: 좌표값 */
};

이벤트 유형 상세

이벤트 유형상수용도주요 코드 예시
동기화EV_SYN0x00이벤트 그룹 경계 표시SYN_REPORT, SYN_MT_REPORT, SYN_DROPPED
키/버튼EV_KEY0x01키보드, 마우스 버튼, 게임패드KEY_A, BTN_LEFT, BTN_TOUCH
상대 이동EV_REL0x02마우스 이동, 스크롤 휠REL_X, REL_Y, REL_WHEEL
절대 위치EV_ABS0x03터치스크린, 태블릿, 조이스틱ABS_X, ABS_MT_POSITION_X, ABS_MT_SLOT
기타EV_MSC0x04스캔코드 등 잡다한 이벤트MSC_SCAN, MSC_TIMESTAMP
스위치EV_SW0x05덮개(lid), 헤드폰 잭, 태블릿 모드SW_LID, SW_HEADPHONE_INSERT
LEDEV_LED0x11키보드 LED 제어LED_CAPSL, LED_NUML, LED_SCROLLL
사운드EV_SND0x12비프, 클릭 사운드SND_BELL, SND_TONE
반복EV_REP0x14키 반복 파라미터REP_DELAY, REP_PERIOD
Force FeedbackEV_FF0x15진동/햅틱/포스 피드백FF_RUMBLE, FF_CONSTANT, FF_PERIODIC
전원EV_PWR0x16전원 버튼 이벤트(예약됨)
FF 상태EV_FF_STATUS0x17FF 효과 재생 상태FF_STATUS_STOPPED, FF_STATUS_PLAYING
EV_SYN과 이벤트 패킷: 입력 이벤트는 패킷 단위로 전달됩니다. 하나의 동작(예: 마우스 대각선 이동)은 EV_REL/REL_X, EV_REL/REL_Y 이벤트 후 EV_SYN/SYN_REPORT로 묶입니다. 유저 공간은 SYN_REPORT를 받을 때까지 버퍼링해야 합니다. SYN_DROPPED를 수신하면 이벤트 누락이 발생한 것이므로 디바이스 상태를 다시 동기화해야 합니다.

Input 디바이스 등록

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

struct my_kbd_data {
    struct input_dev *idev;
    int irq;
};

/* 인터럽트 핸들러 — 하드웨어에서 키 이벤트 수신 */
static irqreturn_t my_kbd_irq(int irq, void *dev_id)
{
    struct my_kbd_data *data = dev_id;
    u8 scancode;

    /* 하드웨어에서 스캔코드 읽기 (실제로는 MMIO/I2C/SPI 등) */
    scancode = read_hw_scancode();

    /* 이벤트 보고 — Input Core가 연결된 모든 handler에 전파 */
    input_report_key(data->idev, scancode, 1);  /* 키 누름 */
    input_sync(data->idev);                      /* SYN_REPORT */

    /* 키 해제 (간단한 예제 — 실제로는 별도 인터럽트) */
    input_report_key(data->idev, scancode, 0);  /* 키 해제 */
    input_sync(data->idev);

    return IRQ_HANDLED;
}

static int my_kbd_probe(struct platform_device *pdev)
{
    struct my_kbd_data *data;
    struct input_dev *idev;
    int ret, i;

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    /* === 1. devm_input_allocate_device — managed 할당 ===
     * 드라이버 해제 시 자동으로 input_free_device() 호출됨
     * input_allocate_device()와 달리 수동 free 불필요 */
    idev = devm_input_allocate_device(&pdev->dev);
    if (!idev)
        return -ENOMEM;

    /* === 2. 디바이스 식별 정보 설정 === */
    idev->name = "My Custom Keyboard";
    idev->phys = "my-kbd/input0";
    idev->id.bustype = BUS_HOST;       /* BUS_USB, BUS_I2C, BUS_SPI 등 */
    idev->id.vendor  = 0x1234;
    idev->id.product = 0x5678;
    idev->id.version = 0x0100;

    /* === 3. 이벤트 능력(capability) 선언 ===
     * 이 디바이스가 생성할 수 있는 이벤트 유형과 코드를 선언
     * Input Core가 이 정보로 적절한 handler를 매칭 */
    input_set_capability(idev, EV_KEY, KEY_A);     /* EV_KEY + KEY_A 동시 설정 */
    input_set_capability(idev, EV_KEY, KEY_B);

    /* 또는 set_bit로 개별 설정 */
    set_bit(EV_KEY, idev->evbit);
    for (i = KEY_ESC; i <= KEY_MICMUTE; i++)
        set_bit(i, idev->keybit);

    /* 키 반복(autorepeat) 자동 지원 */
    set_bit(EV_REP, idev->evbit);

    /* === 4. open/close 콜백 (선택) ===
     * 유저 공간에서 디바이스 열기/닫기 시 호출
     * 전력 절약: 열린 핸들러가 없으면 하드웨어 비활성화 */
    idev->open  = my_kbd_open;
    idev->close = my_kbd_close;

    /* === 5. 인터럽트 등록 === */
    data->irq = platform_get_irq(pdev, 0);
    if (data->irq < 0)
        return data->irq;

    ret = devm_request_irq(&pdev->dev, data->irq,
                           my_kbd_irq, IRQF_TRIGGER_FALLING,
                           "my-kbd", data);
    if (ret)
        return ret;

    data->idev = idev;
    platform_set_drvdata(pdev, data);

    /* === 6. 디바이스 등록 ===
     * Input Core에 디바이스 등록 → 매칭되는 handler와 자동 연결
     * 등록 후에는 capability 변경 금지 */
    ret = input_register_device(idev);
    if (ret)
        return ret;  /* devm이므로 idev는 자동 해제 */

    dev_info(&pdev->dev, "keyboard registered\\n");
    return 0;
}
input_set_capability() vs set_bit(): input_set_capability(dev, EV_KEY, KEY_A)set_bit(EV_KEY, dev->evbit)set_bit(KEY_A, dev->keybit)를 한 번에 수행합니다. 이벤트 유형(evbit)을 빠뜨리는 실수를 방지하므로 가능하면 input_set_capability()를 사용하세요.

이벤트 보고 API

함수이벤트 유형설명
input_report_key(dev, code, value)EV_KEY키/버튼 누름(1), 해제(0), 반복(2)
input_report_rel(dev, code, value)EV_REL상대 이동 (마우스 X/Y, 스크롤)
input_report_abs(dev, code, value)EV_ABS절대 좌표 (터치, 조이스틱)
input_report_switch(dev, code, value)EV_SW스위치 상태 (lid, 잭 등)
input_event(dev, type, code, value)모든 유형범용 이벤트 보고 (위 함수들의 기반)
input_sync(dev)EV_SYNSYN_REPORT — 이벤트 패킷 완료 표시
input_mt_sync(dev)EV_SYNSYN_MT_REPORT — MT Protocol A 슬롯 구분
/* 마우스 이동 + 클릭 보고 예제 */
input_report_rel(idev, REL_X, dx);
input_report_rel(idev, REL_Y, dy);
input_report_rel(idev, REL_WHEEL, wheel);
input_report_key(idev, BTN_LEFT, left_pressed);
input_sync(idev);   /* 하나의 패킷으로 원자적 전달 */

/* 절대 좌표 + 압력 보고 예제 (태블릿) */
input_report_abs(idev, ABS_X, x);
input_report_abs(idev, ABS_Y, y);
input_report_abs(idev, ABS_PRESSURE, pressure);
input_report_key(idev, BTN_TOUCH, pressure > 0);
input_sync(idev);

절대축 파라미터 (ABS)

/* input_set_abs_params(dev, axis, min, max, fuzz, flat)
 *
 * min/max  : 축의 유효 범위
 * fuzz     : 노이즈 필터링 — |new - old| < fuzz이면 무시 (jitter 제거)
 * flat     : 데드존 — |value| < flat이면 0으로 처리 (조이스틱 중앙)
 * resolution: input_abs_set_res()로 별도 설정 (units/mm 등)
 */

/* 터치스크린: 1920x1080 해상도, 노이즈 필터링 4픽셀 */
input_set_abs_params(idev, ABS_X, 0, 1920, 4, 0);
input_set_abs_params(idev, ABS_Y, 0, 1080, 4, 0);
input_set_abs_params(idev, ABS_PRESSURE, 0, 255, 0, 0);

/* resolution 설정 — libinput이 물리적 크기 계산에 사용 */
input_abs_set_res(idev, ABS_X, 40);   /* 40 units/mm */
input_abs_set_res(idev, ABS_Y, 40);

/* 조이스틱: -32768 ~ 32767, 데드존 4096 */
input_set_abs_params(idev, ABS_X, -32768, 32767, 16, 4096);
input_set_abs_params(idev, ABS_Y, -32768, 32767, 16, 4096);

멀티터치 프로토콜

리눅스 커널은 멀티터치를 위한 두 가지 프로토콜을 정의합니다 (Documentation/input/multi-touch-protocol.rst):

항목Protocol A (Deprecated)Protocol B (현재 표준)
슬롯 관리커널이 관리하지 않음커널이 슬롯 할당·추적
터치 구분SYN_MT_REPORT로 분리ABS_MT_SLOT + ABS_MT_TRACKING_ID
대역폭모든 터치를 매번 전송변경된 슬롯만 전송 (효율적)
유저 공간ID 추적을 직접 해야 함슬롯 기반으로 자연스러운 추적
사용처레거시 터치 컨트롤러최신 터치스크린 (goodix, atmel, etc.)
/* === Multitouch Protocol B — 슬롯 기반 (권장) ===
 * input_mt_init_slots()로 슬롯 수를 미리 선언
 * 각 슬롯에 tracking_id를 할당하여 터치 추적 */

#include <linux/input/mt.h>

static int ts_probe(struct i2c_client *client)
{
    struct input_dev *idev;
    int ret;

    idev = devm_input_allocate_device(&client->dev);

    /* 절대 좌표 설정 */
    input_set_abs_params(idev, ABS_MT_POSITION_X, 0, 1920, 0, 0);
    input_set_abs_params(idev, ABS_MT_POSITION_Y, 0, 1080, 0, 0);
    input_set_abs_params(idev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
    input_set_abs_params(idev, ABS_MT_PRESSURE, 0, 255, 0, 0);

    /* 최대 10개 동시 터치, DIRECT = 터치스크린 (POINTER = 터치패드) */
    ret = input_mt_init_slots(idev, 10, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
    if (ret)
        return ret;

    input_set_capability(idev, EV_KEY, BTN_TOUCH);

    return input_register_device(idev);
}

/* 터치 이벤트 보고 (인터럽트 핸들러 또는 threaded irq에서) */
static void ts_report_touches(struct input_dev *idev,
                              struct touch_point *tp, int count)
{
    int i;

    for (i = 0; i < count; i++) {
        /* 슬롯 선택 — 같은 tracking_id를 같은 슬롯에 유지 */
        input_mt_slot(idev, tp[i].slot);

        /* tracking_id 할당: 양수=활성 터치, -1=터치 해제 */
        input_mt_report_slot_state(idev, MT_TOOL_FINGER, tp[i].active);

        if (tp[i].active) {
            input_report_abs(idev, ABS_MT_POSITION_X, tp[i].x);
            input_report_abs(idev, ABS_MT_POSITION_Y, tp[i].y);
            input_report_abs(idev, ABS_MT_TOUCH_MAJOR, tp[i].width);
            input_report_abs(idev, ABS_MT_PRESSURE, tp[i].pressure);
        }
    }

    /* INPUT_MT_DROP_UNUSED: 보고되지 않은 슬롯 자동 해제 */
    input_mt_sync_frame(idev);

    /* 단일 터치 이벤트도 함께 생성 (Protocol B가 자동 처리) */
    input_sync(idev);
}
input_mt_sync_frame() 필수: Protocol B에서 INPUT_MT_DROP_UNUSED 플래그를 사용하면 input_mt_sync_frame()을 반드시 호출해야 합니다. 이 함수가 현재 프레임에서 보고되지 않은 슬롯을 자동으로 비활성화합니다. 이를 빠뜨리면 "유령 터치(ghost touch)"가 발생합니다.
MT 축 코드설명일반적인 범위
ABS_MT_SLOT현재 슬롯 인덱스0 ~ (max_slots - 1)
ABS_MT_TRACKING_ID터치 추적 ID (-1 = 해제)자동 할당
ABS_MT_POSITION_X/Y터치 중심 좌표디바이스 해상도
ABS_MT_TOUCH_MAJOR터치 영역 장축 길이0 ~ 255
ABS_MT_TOUCH_MINOR터치 영역 단축 길이0 ~ 255
ABS_MT_WIDTH_MAJOR접근 도구(손가락) 너비0 ~ 255
ABS_MT_PRESSURE터치 압력0 ~ 255
ABS_MT_ORIENTATION터치 타원 방향-90 ~ 90
ABS_MT_TOOL_TYPEMT_TOOL_FINGER / MT_TOOL_PEN도구 유형
ABS_MT_DISTANCE표면과의 거리 (호버링)0 = 접촉

폴링 Input 디바이스

인터럽트를 지원하지 않는 하드웨어의 경우, input_setup_polling()을 사용하여 커널이 주기적으로 하드웨어를 폴링합니다. 이전의 input_polled_dev 구조체는 deprecated되었으며, v5.12부터 통합 API로 대체되었습니다.

/* === 폴링 Input 디바이스 예제 (v5.12+) === */
static void my_sensor_poll(struct input_dev *idev)
{
    struct my_sensor *sensor = input_get_drvdata(idev);
    int x, y;

    /* 하드웨어에서 현재 값 읽기 */
    x = read_sensor_x(sensor);
    y = read_sensor_y(sensor);

    input_report_abs(idev, ABS_X, x);
    input_report_abs(idev, ABS_Y, y);
    input_sync(idev);
}

static int my_sensor_probe(struct i2c_client *client)
{
    struct input_dev *idev;
    struct my_sensor *sensor;

    sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
    idev = devm_input_allocate_device(&client->dev);

    idev->name = "My Analog Sensor";
    input_set_abs_params(idev, ABS_X, 0, 4095, 2, 0);
    input_set_abs_params(idev, ABS_Y, 0, 4095, 2, 0);

    input_set_drvdata(idev, sensor);

    /* 폴링 설정: 콜백 + 주기 */
    input_setup_polling(idev, my_sensor_poll);
    input_set_poll_interval(idev, 20);       /* 20ms = 50Hz */
    input_set_min_poll_interval(idev, 10);   /* 최소 10ms */
    input_set_max_poll_interval(idev, 100);  /* 최대 100ms */

    /* 유저 공간에서 poll_interval 조정 가능:
     * /sys/class/input/inputN/device/poll_interval */

    return input_register_device(idev);
}

Device Tree 바인딩

커널은 gpio-keys, gpio-keys-polled, matrix-keypad 등 범용 Input 드라이버를 제공합니다. Device Tree만으로 입력 장치를 정의할 수 있어 별도 드라이버 작성이 불필요합니다.

/* === gpio-keys: GPIO 기반 버튼 (인터럽트 지원) === */
gpio-keys {
    compatible = "gpio-keys";

    power-button {
        label = "Power Button";
        gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
        linux,code = <KEY_POWER>;        /* input-event-codes.h 참조 */
        wakeup-source;                    /* 이 버튼으로 시스템 깨우기 */
        debounce-interval = <20>;        /* ms 단위 디바운스 */
    };

    volume-up {
        label = "Volume Up";
        gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
        linux,code = <KEY_VOLUMEUP>;
        autorepeat;                       /* 키 반복 활성화 */
    };

    lid-switch {
        label = "Lid Switch";
        gpios = <&gpio2 3 GPIO_ACTIVE_LOW>;
        linux,input-type = <EV_SW>;      /* 스위치 이벤트 */
        linux,code = <SW_LID>;
    };
};

/* === gpio-keys-polled: 인터럽트 없는 GPIO 폴링 === */
gpio-keys-polled {
    compatible = "gpio-keys-polled";
    poll-interval = <100>;              /* 100ms 간격 폴링 */

    button-0 {
        label = "Reset";
        gpios = <&gpio3 8 GPIO_ACTIVE_LOW>;
        linux,code = <KEY_RESTART>;
    };
};

/* === matrix-keypad: 행/열 매트릭스 키패드 === */
matrix-keypad {
    compatible = "gpio-matrix-keypad";
    row-gpios = <&gpio1 0 0>, <&gpio1 1 0>, <&gpio1 2 0>;
    col-gpios = <&gpio1 3 0>, <&gpio1 4 0>, <&gpio1 5 0>;
    debounce-delay-ms = <5>;
    col-scan-delay-us = <2>;

    linux,keymap = <
        MATRIX_KEY(0, 0, KEY_1)
        MATRIX_KEY(0, 1, KEY_2)
        MATRIX_KEY(0, 2, KEY_3)
        MATRIX_KEY(1, 0, KEY_4)
        MATRIX_KEY(1, 1, KEY_5)
        MATRIX_KEY(1, 2, KEY_6)
        MATRIX_KEY(2, 0, KEY_7)
        MATRIX_KEY(2, 1, KEY_8)
        MATRIX_KEY(2, 2, KEY_9)
    >;
};

내장 Event Handler

핸들러디바이스 노드매칭 조건역할
evdev/dev/input/eventN모든 input_dev범용 이벤트 인터페이스 — libinput, X11, Wayland에서 사용. struct input_event 배열을 read()로 전달
kbd(내부)EV_KEY 디바이스콘솔(VT) 키보드 처리 — scancode→keycode→keysym 변환, 콘솔 스위칭(Alt+Fn), SysRq
mousedev/dev/input/mouseN, /dev/input/miceEV_REL 또는 EV_ABS + BTN레거시 PS/2 마우스 프로토콜 에뮬레이션 (ImPS/2). /dev/input/mice는 모든 마우스의 통합 노드
joydev/dev/input/jsNBTN_JOYSTICK/GAMEPAD 등레거시 조이스틱 API (현재는 evdev 사용 권장)
input-leds(내부)EV_LED 디바이스Input LED를 LED 클래스 디바이스로 연결 — LED 서브시스템 trigger 사용 가능
rfkill-input(내부)KEY_RFKILL 등무선 킬 스위치 이벤트 → rfkill 서브시스템 연동

evdev 유저 공간 인터페이스

evdev는 가장 중요한 핸들러로, /dev/input/eventN 캐릭터 디바이스를 통해 모든 이벤트를 있는 그대로(raw) 유저 공간에 전달합니다.

/* === 유저 공간에서 evdev 이벤트 읽기 === */
#include <linux/input.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    int fd = open("/dev/input/event0", O_RDONLY);
    struct input_event ev;
    char name[256];

    /* 디바이스 이름 조회 */
    ioctl(fd, EVIOCGNAME(sizeof(name)), name);
    printf("Device: %s\\n", name);

    /* 이벤트 루프 */
    while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
        if (ev.type == EV_KEY)
            printf("Key %d %s\\n", ev.code,
                   ev.value ? "pressed" : "released");
    }
    close(fd);
}
ioctl방향설명
EVIOCGNAMER디바이스 이름 문자열
EVIOCGIDRstruct input_id (bus, vendor, product, version)
EVIOCGPHYSR물리적 경로 문자열
EVIOCGUNIQR고유 식별자 문자열
EVIOCGPROPR디바이스 프로퍼티 비트마스크 (INPUT_PROP_*)
EVIOCGBIT(type, size)R지원하는 이벤트 유형/코드 비트마스크
EVIOCGABS(axis)Rstruct input_absinfo (min, max, fuzz, flat, res)
EVIOCSABS(axis)W절대축 파라미터 변경 (캘리브레이션)
EVIOCGKEYR현재 키 상태 비트마스크 (눌린 키 조회)
EVIOCGLEDR현재 LED 상태 비트마스크
EVIOCGSWR현재 스위치 상태 비트마스크
EVIOCGRABW디바이스 독점(grab) — 다른 프로세스/핸들러 차단
EVIOCREVOKEWfd의 접근 권한 철회 (logind 세션 전환 시)
EVIOCSFFWForce Feedback 효과 업로드
EVIOCRMFFWForce Feedback 효과 삭제
EVIOCGEFFECTSR동시 FF 효과 수
EVIOCGRAB — 디바이스 독점: ioctl(fd, EVIOCGRAB, 1)을 호출하면 해당 fd만 이벤트를 수신합니다. 다른 모든 evdev 클라이언트와 kbd/mousedev 핸들러가 이벤트를 받지 못합니다. 게임, 스크린 잠금, 키 매핑 도구에서 사용되며, EVIOCGRAB, 0으로 해제합니다.

Force Feedback (FF)

Force Feedback은 게임패드 진동, 스티어링 휠 저항, 햅틱 피드백 등을 지원합니다. 커널은 FF 효과를 디바이스에 업로드하고 재생/정지를 제어하는 프레임워크를 제공합니다.

/* === 커널 드라이버: FF 지원 등록 === */
#include <linux/input.h>

static int my_ff_upload(struct input_dev *dev,
                        struct ff_effect *effect,
                        struct ff_effect *old)
{
    /* 효과를 하드웨어에 프로그래밍 */
    write_ff_to_hw(effect);
    return 0;
}

static int my_ff_playback(struct input_dev *dev,
                          int effect_id, int value)
{
    /* value: 1=재생, 0=정지 */
    if (value)
        start_hw_effect(effect_id);
    else
        stop_hw_effect(effect_id);
    return 0;
}

static int setup_ff(struct input_dev *idev)
{
    input_set_capability(idev, EV_FF, FF_RUMBLE);
    input_set_capability(idev, EV_FF, FF_PERIODIC);
    input_set_capability(idev, EV_FF, FF_CONSTANT);

    /* 최대 동시 효과 수, 업로드/재생 콜백 */
    return input_ff_create(idev, 16);
    /* input_ff_create() 이후 콜백 설정 */
    idev->ff->upload = my_ff_upload;
    idev->ff->playback = my_ff_playback;
}

/* === 간편 rumble API (많은 게임패드에서 사용) ===
 * upload/playback 콜백 없이 드라이버가 직접 모터 제어 */
static int my_play_effect(struct input_dev *dev,
                          void *data,
                          struct ff_effect *effect)
{
    u16 strong = effect->u.rumble.strong_magnitude;
    u16 weak   = effect->u.rumble.weak_magnitude;

    /* 모터 강도 설정 */
    set_motor_speed(data, strong, weak);
    return 0;
}

/* input_ff_create_memless()로 간편 등록 */
input_ff_create_memless(idev, priv, my_play_effect);

Input 프로퍼티 (INPUT_PROP_*)

Input 프로퍼티는 디바이스의 물리적 특성을 유저 공간에 알려줍니다. input_set_capability()가 아닌 set_bit()으로 dev->propbit에 설정합니다.

프로퍼티설명사용처
INPUT_PROP_DIRECT화면에 직접 맵핑되는 입력 (터치스크린)좌표 변환 없이 화면 좌표로 사용
INPUT_PROP_POINTER간접 포인팅 (터치패드, 트랙볼)가속 커브 적용, 커서 제어
INPUT_PROP_SEMI_MT불완전한 멀티터치 (바운딩 박스만 제공)저가 터치패드
INPUT_PROP_TOPBUTTONPAD상단에 소프트 버튼 영역 (클릭패드)ThinkPad 등 트랙포인트 버튼
INPUT_PROP_BUTTONPAD패드 전체가 버튼 (클릭패드)MacBook, 최신 노트북 터치패드
INPUT_PROP_ACCELEROMETER가속도 센서 (포인팅 아님)화면 회전, 게임 기울기
/* 터치스크린: DIRECT 프로퍼티 설정 */
set_bit(INPUT_PROP_DIRECT, idev->propbit);

/* 터치패드: POINTER + BUTTONPAD */
set_bit(INPUT_PROP_POINTER, idev->propbit);
set_bit(INPUT_PROP_BUTTONPAD, idev->propbit);

키코드 매핑과 스캔코드

Input 서브시스템은 하드웨어 스캔코드(scancode)를 리눅스 키코드(keycode)로 변환하는 2단계 매핑을 지원합니다. 키코드 테이블은 런타임에 변경 가능하여 유저 공간에서 키 재매핑이 가능합니다.

/* 커널 드라이버에서 키코드 테이블 설정 */
static const unsigned short my_keymap[] = {
    [0x01] = KEY_ESC,
    [0x02] = KEY_1,
    [0x03] = KEY_2,
    /* ... */
};

idev->keycode     = my_keymap;
idev->keycodesize = sizeof(my_keymap[0]);
idev->keycodemax  = ARRAY_SIZE(my_keymap);

/* 선택: 커스텀 getkeycode/setkeycode 콜백
 * 기본 구현은 keycode[] 배열을 인덱스로 접근
 * 희소(sparse) 매핑이 필요하면 커스텀 콜백 구현 */
idev->getkeycode = my_getkeycode;
idev->setkeycode = my_setkeycode;
# 유저 공간에서 키 재매핑 (evdev ioctl)
# EVIOCSKEYCODE — scancode → keycode 변경

# udevadm hwdb 기반 자동 매핑 (권장)
# /etc/udev/hwdb.d/70-keyboard.hwdb:
evdev:input:b0003v046DpC52B*
 KEYBOARD_KEY_70039=esc        # CapsLock → Escape
 KEYBOARD_KEY_70029=capslock   # Escape → CapsLock

# hwdb 업데이트 적용
sudo systemd-hwdb update
sudo udevadm trigger

디버깅 도구

# === /proc/bus/input/devices — 등록된 모든 Input 디바이스 === 
cat /proc/bus/input/devices
# I: Bus=0011 Vendor=0001 Product=0001 Version=ab54
# N: Name="AT Translated Set 2 keyboard"
# P: Phys=isa0060/serio0/input0
# S: Sysfs=/devices/platform/i8042/serio0/input/input0
# U: Uniq=
# H: Handlers=sysrq kbd leds event0
# B: PROP=0
# B: EV=120013
# B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
# B: MSC=10
# B: LED=7

# === /proc/bus/input/handlers — 등록된 핸들러 목록 ===
cat /proc/bus/input/handlers
# N: Number=0 Name=rfkill
# N: Number=1 Name=kbd
# N: Number=2 Name=mousedev Minor=32
# N: Number=3 Name=evdev Minor=64
# N: Number=4 Name=joydev Minor=0

# === sysfs Input 디바이스 정보 ===
ls /sys/class/input/input0/
# capabilities/  device/  event0/  id/  inhibited  name  phys  properties  uevent  uniq

# 디바이스 활성/비활성 (v5.14+)
echo 1 > /sys/class/input/input0/inhibited   # 일시 비활성
echo 0 > /sys/class/input/input0/inhibited   # 재활성

# === evtest — 실시간 이벤트 모니터링 (가장 유용한 도구) ===
sudo evtest /dev/input/event0
# Event: time 1234567890.123456, type 1 (EV_KEY), code 30 (KEY_A), value 1
# Event: time 1234567890.123456, type 0 (EV_SYN), code 0 (SYN_REPORT), value 0
# Event: time 1234567890.234567, type 1 (EV_KEY), code 30 (KEY_A), value 0
# Event: time 1234567890.234567, type 0 (EV_SYN), code 0 (SYN_REPORT), value 0

# === libinput debug-events — 고수준 이벤트 분석 ===
sudo libinput debug-events
# -event2   KEYBOARD_KEY     +3.24s  KEY_A (30) pressed
# -event2   KEYBOARD_KEY     +3.38s  KEY_A (30) released
# -event5   POINTER_MOTION   +5.12s  12.00/ -3.00

# === evemu — 이벤트 녹화/재생 (재현 테스트용) ===
sudo evemu-record /dev/input/event0 > recording.txt   # 녹화
sudo evemu-play /dev/input/event0 < recording.txt     # 재생

# === 커널 디버그: input 이벤트 추적 ===
echo 1 > /sys/module/evdev/parameters/debug            # evdev 디버그 (if available)

# ftrace로 Input 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/input/input_event/enable
cat /sys/kernel/debug/tracing/trace_pipe
# irq/18-i8042-18 input_event: dev=input0, type=1, code=30, value=1
Input 디바이스 시뮬레이션 (uinput): /dev/uinput을 통해 유저 공간에서 가상 입력 디바이스를 생성할 수 있습니다. 자동화 테스트, 원격 입력, 매크로 도구에 활용됩니다.
/* === uinput: 유저 공간에서 가상 Input 디바이스 생성 === */
#include <linux/uinput.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int create_virtual_kbd(void)
{
    int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
    struct uinput_setup usetup;

    /* 이벤트 유형 활성화 */
    ioctl(fd, UI_SET_EVBIT, EV_KEY);
    ioctl(fd, UI_SET_KEYBIT, KEY_A);
    ioctl(fd, UI_SET_KEYBIT, KEY_B);

    /* 디바이스 정보 설정 */
    memset(&usetup, 0, sizeof(usetup));
    usetup.id.bustype = BUS_USB;
    usetup.id.vendor  = 0x1234;
    usetup.id.product = 0x5678;
    strcpy(usetup.name, "Virtual Keyboard");

    ioctl(fd, UI_DEV_SETUP, &usetup);
    ioctl(fd, UI_DEV_CREATE);

    /* 키 이벤트 주입 */
    struct input_event ev = { .type = EV_KEY, .code = KEY_A, .value = 1 };
    write(fd, &ev, sizeof(ev));
    ev.type = EV_SYN; ev.code = SYN_REPORT; ev.value = 0;
    write(fd, &ev, sizeof(ev));

    /* 해제 */
    ioctl(fd, UI_DEV_DESTROY);
    close(fd);
    return 0;
}

이벤트 흐름 요약

Hardware IRQ 발생 또는 폴링 Device Driver input_report_*() input_sync() Input Core input_handle_event() handler에 전파 evdev Handler evdev_event() client 버퍼에 저장 User Space read() / poll() /dev/input/eventN input_report_*() → input_handle_event() → handler->event() → evdev client buffer → read() fuzz 필터링은 Input Core에서, 타임스탬프는 input_handle_event()에서 부여
주요 소스 파일:
  • drivers/input/input.c — Input Core: 디바이스/핸들러 등록, 이벤트 라우팅
  • drivers/input/evdev.c — evdev 핸들러: /dev/input/eventN 캐릭터 디바이스
  • drivers/input/mousedev.c — 레거시 PS/2 마우스 에뮬레이션
  • drivers/input/joydev.c — 레거시 조이스틱 인터페이스
  • drivers/input/keyboard/ — 키보드 드라이버 (atkbd, gpio_keys, ...)
  • drivers/input/mouse/ — 마우스/터치패드 드라이버 (psmouse, elantech, ...)
  • drivers/input/touchscreen/ — 터치스크린 드라이버 (goodix, atmel_mxt, ...)
  • drivers/input/misc/ — 기타 (uinput, pwm-beeper, ...)
  • include/linux/input.h — 핵심 헤더 (struct input_dev, 이벤트 보고 API)
  • include/uapi/linux/input-event-codes.h — 모든 이벤트 유형/코드 상수 정의

HID 서브시스템 연동

현대 입력 장치 대부분은 HID(Human Interface Device) 프로토콜을 사용합니다. USB 키보드/마우스는 물론, Bluetooth 게임패드, I2C 터치패드까지 HID 레포트 디스크립터를 통해 자신의 기능을 기술합니다. HID 서브시스템(drivers/hid/)은 이 디스크립터를 파싱하여 Input 서브시스템에 자동으로 이벤트 디바이스를 생성합니다.

User Space libinput / evtest → /dev/input/eventN → /dev/hidrawN Input Subsystem (input_dev) HID Core (drivers/hid/hid-core.c) Report Descriptor 파싱 · Usage → Input 이벤트 변환 · hidraw 인터페이스 HID Drivers hid-generic hid-logitech hid-apple hid-playstation hid-nintendo hid-multitouch HID Transport usbhid i2c-hid hidp (BT) uhid (user) intel-ish-hid Hardware USB HID 장치 · I2C-HID 터치패드 · Bluetooth HID 게임패드 · ISH 센서 허브
HID Transport커널 모듈버스사용 장치
usbhiddrivers/hid/usbhid/USBUSB 키보드, 마우스, 게임패드
i2c-hiddrivers/hid/i2c-hid/I2C노트북 터치패드/터치스크린 (ACPI/OF)
hidpnet/bluetooth/hidp/BluetoothBT 키보드, DualShock, Joy-Con
uhiddrivers/hid/uhid.cUser space/dev/uhid로 가상 HID 디바이스 생성
intel-ish-hiddrivers/hid/intel-ish-hid/ISHIntel Sensor Hub (가속도/자이로)
amd-sfhdrivers/hid/amd-sfh-hid/SFHAMD Sensor Fusion Hub
/* HID 드라이버 구조 (장치별 커스터마이징) */
static struct hid_driver my_hid_driver = {
    .name          = "my-hid",
    .id_table      = my_hid_devices,

    /* Report Descriptor 수정 (quirk 적용) */
    .report_fixup  = my_report_fixup,

    /* 특정 이벤트 가공/변환 */
    .event         = my_event,

    /* raw 데이터 직접 처리 */
    .raw_event     = my_raw_event,

    /* Input 디바이스 설정 커스터마이징 */
    .input_configured = my_input_configured,
    .input_mapping    = my_input_mapping,

    .probe         = my_probe,
    .remove        = my_remove,
};
module_hid_driver(my_hid_driver);

/* HID Report Descriptor → Input 이벤트 매핑 과정:
 *
 * 1. HID Transport가 raw report descriptor 수신
 * 2. hid-core가 파싱 → hid_field, hid_usage 생성
 * 3. hid_usage.hid (HID Usage Page:Usage) → Linux 이벤트 변환:
 *    - Usage Page 0x01 (Generic Desktop):
 *      Usage 0x30 (X) → EV_ABS/ABS_X 또는 EV_REL/REL_X
 *    - Usage Page 0x07 (Keyboard):
 *      Usage 0x04 (a) → EV_KEY/KEY_A
 *    - Usage Page 0x09 (Button):
 *      Usage 0x01 → EV_KEY/BTN_LEFT
 *    - Usage Page 0x0D (Digitizer):
 *      Usage 0x30 (Tip Pressure) → EV_ABS/ABS_PRESSURE
 *
 * 4. hid-generic은 표준 매핑, hid-* 드라이버는 quirk 적용
 * 5. input_dev 등록 → evdev handler 연결
 */
# HID 디버깅
# Report Descriptor 덤프
cat /sys/kernel/debug/hid/0003:046D:C52B.0001/rdesc

# hidraw로 raw 데이터 접근
cat /dev/hidraw0 | xxd | head -20

# HID 이벤트 추적
echo 1 > /sys/kernel/debug/hid/0003:046D:C52B.0001/events

# udev로 HID 드라이버 바인딩 확인
udevadm info /dev/input/event5 | grep HID

PS/2와 serio 서브시스템

PS/2(Personal System/2)는 IBM이 1987년에 도입한 키보드/마우스 인터페이스입니다. 물리적으로는 거의 사라졌지만, 가상화 환경(QEMU, VMware)과 BIOS 에뮬레이션에서 여전히 활발히 사용됩니다. 커널의 serio 서브시스템이 PS/2 물리 계층을, atkbd/psmouse가 프로토콜 계층을 담당합니다.

키보드 경로 Input Core → evdev + kbd handler atkbd (AT Keyboard) scancode → keycode 변환 serio (PS/2 포트 0) serio_interrupt() 콜백 i8042 컨트롤러 IRQ 1 (키보드) 마우스 경로 Input Core → evdev + mousedev psmouse (PS/2 Mouse) 프로토콜 감지 (ImPS/2, Synaptics...) serio (PS/2 포트 1, AUX) serio_interrupt() 콜백 i8042 컨트롤러 IRQ 12 (마우스) i8042 (Intel 8042 / KBC) I/O 0x60, 0x64 · ACPI 'PNP0303' / 'PNP0F13'
프로토콜드라이버특징
AT Set 2atkbd표준 AT 키보드 스캔코드 세트 2, 3바이트 확장키
PS/2 Mousepsmouse (bare)3바이트 패킷 (X, Y, 버튼)
ImPS/2psmouse4바이트 패킷 (IntelliMouse, 스크롤 추가)
Synapticspsmouse + synaptics터치패드 절대좌표, 멀티핑거, 클릭패드
ALPSpsmouse + alpsALPS 터치패드 (v3~v8 프로토콜)
Elantechpsmouse + elantechElantech 터치패드 (v1~v4)
TrackPointpsmouse + trackpointIBM/Lenovo 트랙포인트 (감도/속도 sysfs 조정)
/* serio 드라이버 등록 — PS/2 프로토콜 드라이버 */
static struct serio_driver my_serio_drv = {
    .driver = {
        .name = "my-serio-dev",
    },
    .description = "My PS/2 Device",
    .id_table    = my_serio_ids,

    /* PS/2 포트 바인딩 시 호출 */
    .connect     = my_connect,
    .disconnect  = my_disconnect,

    /* 바이트 수신 인터럽트 콜백
     * i8042 ISR → serio_interrupt() → 이 콜백 */
    .interrupt   = my_interrupt,
};

/* PS/2 바이트 수신 콜백 */
static irqreturn_t my_interrupt(struct serio *serio,
                                unsigned char data,
                                unsigned int flags)
{
    struct my_dev *dev = serio_get_drvdata(serio);

    /* 패킷 조립 (PS/2는 바이트 단위 인터럽트) */
    dev->packet[dev->idx++] = data;

    if (dev->idx >= PACKET_SIZE) {
        my_process_packet(dev);
        dev->idx = 0;
    }

    return IRQ_HANDLED;
}

/* PS/2 명령 전송 (포트에 바이트 쓰기) */
serio_write(serio, PS2_CMD_RESET);      /* 0xFF */
ps2_command(&dev->ps2dev, param, cmd);  /* 응답 대기 포함 */

Input Core 매칭 알고리즘

Input 디바이스가 등록되면 Input Core는 모든 등록된 핸들러와 매칭을 시도합니다. 반대로 핸들러가 등록되면 모든 기존 디바이스와 매칭합니다. 매칭 기준은 input_device_id 테이블의 플래그 비트마스크입니다.

input_attach_handler() 매칭 흐름 input_register_device(dev) 모든 handler의 id_table 순회 id->flags 비트 검사 불일치 다음 id_table 일치 handler->match()? 거부 매칭 실패 수락 handler->connect() → input_handle 생성 INPUT_DEVICE_ID_MATCH_BUS INPUT_DEVICE_ID_MATCH_VENDOR INPUT_DEVICE_ID_MATCH_EVBIT INPUT_DEVICE_ID_MATCH_KEYBIT ...
/* Input Core 매칭 로직 (drivers/input/input.c) */
static bool input_match_device(
    const struct input_device_id *id,
    const struct input_dev *dev)
{
    /* 1. bustype/vendor/product/version 매칭 (flags로 선택) */
    if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
        if (id->bustype != dev->id.bustype) return false;
    if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
        if (id->vendor != dev->id.vendor) return false;
    /* ... product, version 동일 패턴 ... */

    /* 2. 이벤트 능력(capability) 매칭
     * id_table에 설정된 비트가 dev에도 있어야 함
     * (dev는 추가 비트가 있어도 OK → 부분 집합 매칭) */
    if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX)) return false;
    if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX)) return false;
    if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX)) return false;
    if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX)) return false;
    /* ... mscbit, ledbit, sndbit, ffbit, swbit ... */

    return true;
}

/* evdev의 id_table — 모든 디바이스에 매칭 */
static const struct input_device_id evdev_ids[] = {
    { .driver_info = 1 },  /* flags=0 → 모든 조건 무시 → 전부 매칭 */
    { },
};

/* joydev의 id_table — 조이스틱/게임패드만 매칭 */
static const struct input_device_id joydev_ids[] = {
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_ABSBIT,
        .evbit = { BIT_MASK(EV_ABS) },
        .absbit = { BIT_MASK(ABS_X) },
    },
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_KEYBIT,
        .evbit = { BIT_MASK(EV_KEY) },
        .keybit = { [BIT_WORD(BTN_JOYSTICK)] = BIT_MASK(BTN_JOYSTICK) },
    },
    { },
};

이벤트 필터링 — Fuzz, SYN_DROPPED 복구

Input Core는 두 가지 중요한 필터링 메커니즘을 제공합니다: 절대축 fuzz 필터(노이즈 제거)와 SYN_DROPPED(이벤트 유실 알림). 이 두 메커니즘을 이해하지 못하면 "떨리는 커서"나 "비동기 상태" 문제를 진단할 수 없습니다.

/* === Fuzz 필터링 (drivers/input/input.c) ===
 *
 * input_abs_set_params()에서 설정한 fuzz 값으로
 * 연속 이벤트에서 노이즈를 제거합니다.
 *
 * 알고리즘 (Hysteresis 기반):
 * - new_value가 old_value ± fuzz 범위 안이면 → 이벤트 무시
 * - 범위 밖이면 → old_value 방향으로 fuzz만큼 보정한 값 보고
 *
 * 예: fuzz=4, old=100, new=103 → 무시 (|103-100|=3 < 4)
 *     fuzz=4, old=100, new=108 → 보고값 = 108-4 = 104
 */

static int input_defuzz_abs_event(int value, int old_val, int fuzz)
{
    if (fuzz) {
        if (value > old_val - fuzz && value < old_val + fuzz)
            return old_val;  /* fuzz 범위 내 → 무시 */

        if (value > old_val + fuzz)
            return value - fuzz;
        if (value < old_val - fuzz)
            return value + fuzz;
    }
    return value;
}

/* === SYN_DROPPED 처리 ===
 *
 * evdev client 버퍼(원형 큐)가 가득 찬 경우:
 * 1. 새 이벤트가 버려짐
 * 2. 다음 SYN_REPORT 시점에 SYN_DROPPED 이벤트 삽입
 * 3. 유저 공간이 SYN_DROPPED을 감지하면 상태 재동기화 필요
 *
 * 유저 공간 복구 절차:
 * 1. SYN_DROPPED 수신
 * 2. 다음 SYN_REPORT까지 모든 이벤트 버림
 * 3. EVIOCGKEY, EVIOCGABS 등으로 현재 상태 조회
 * 4. 내부 상태를 조회 결과로 갱신
 */
/* 유저 공간 SYN_DROPPED 복구 예제 */
struct input_event ev;
bool dropped = false;

while (read(fd, &ev, sizeof(ev)) > 0) {
    if (ev.type == EV_SYN && ev.code == SYN_DROPPED) {
        dropped = true;
        continue;
    }

    if (ev.type == EV_SYN && ev.code == SYN_REPORT) {
        if (dropped) {
            /* 상태 재동기화 */
            sync_device_state(fd);
            dropped = false;
            continue;
        }
        process_event_frame();
        continue;
    }

    if (!dropped)
        accumulate_event(&ev);
}

void sync_device_state(int fd)
{
    /* 키 상태 재조회 */
    unsigned char keys[KEY_CNT / 8 + 1];
    ioctl(fd, EVIOCGKEY(sizeof(keys)), keys);

    /* 절대축 상태 재조회 */
    struct input_absinfo absinfo;
    ioctl(fd, EVIOCGABS(ABS_X), &absinfo);
    /* 내부 상태 갱신 */
}

libinput과 사용자 공간 입력 스택

libinput은 Wayland 컴포지터와 X.Org에서 사용하는 입력 처리 라이브러리입니다. evdev의 raw 이벤트를 받아 포인터 가속, 제스처 인식, 탭-투-클릭, 스크롤 에뮬레이션 등 고수준 처리를 수행합니다.

Wayland 경로 GTK/Qt Application Wayland Compositor libinput 가속, 제스처, 탭, 스크롤 /dev/input/eventN (evdev) Input Core (커널) Hardware Driver X11 경로 X11 Application X Server (Xorg) xf86-input- libinput xf86-input- evdev (레거시) /dev/input/eventN (evdev) Input Core (커널) Hardware Driver
# libinput 디버깅 및 설정

# 디바이스 목록 및 기능 확인
sudo libinput list-devices

# 실시간 고수준 이벤트 모니터링
sudo libinput debug-events --verbose
# -event5  POINTER_MOTION      +0.456s  12.34/  -5.67 ( +2.00/ -1.00)
# -event5  POINTER_BUTTON      +1.234s  BTN_LEFT pressed
# -event8  GESTURE_SWIPE_BEGIN +2.000s  3 fingers

# 포인터 가속 프로파일 확인
sudo libinput debug-events --show-keycodes

# libinput quirks 확인 (장치별 보정 데이터)
sudo libinput quirks list /dev/input/event5
# AttrPressureRange=10:8
# ModelTabletModeNoSuspend=1

# xinput로 X11 Input 속성 조정
xinput list                                     # 디바이스 목록
xinput list-props "SynPS/2 Synaptics TouchPad"  # 속성 확인
xinput set-prop 12 "libinput Tapping Enabled" 1 # 탭-투-클릭 활성화
xinput set-prop 12 "libinput Natural Scrolling Enabled" 1

# Wayland에서 libinput 설정 (sway/GNOME)
# /etc/libinput/local-overrides.quirks
[Touchpad Override]
MatchName=*Touchpad*
AttrPressureRange=10:8
libinput 기능설명관련 evdev 이벤트
포인터 가속속도에 따라 비선형 가속 커브 적용EV_REL/REL_X,Y
탭-투-클릭터치패드 탭 → 마우스 클릭 변환EV_KEY/BTN_LEFT
두 손가락 스크롤2핑거 드래그 → 스크롤 이벤트EV_ABS/ABS_MT_*
3/4핑거 제스처스와이프, 핀치 인식멀티터치 슬롯
Palm detection손바닥 터치 무시터치 크기/압력 분석
Disable-while-typing키보드 입력 중 터치패드 비활성화키보드 + 터치패드 상관관계
Middle button emulation좌+우 동시 클릭 → 중간 버튼BTN_LEFT + BTN_RIGHT
Clickfinger1/2/3 손가락 클릭 → 좌/우/중간멀티터치 + BTN_TOOL_*

터치패드 드라이버 상세

노트북 터치패드는 PS/2(Synaptics, ALPS, Elantech), I2C-HID, USB-HID 세 가지 경로로 연결됩니다. 최신 노트북 대부분은 I2C-HID를 사용하지만, PS/2 터치패드 프로토콜은 역사적으로 매우 복잡하며 여전히 활발히 유지보수됩니다.

제조사PS/2 드라이버I2C-HID프로토콜 버전특징
Synapticspsmouse (synaptics.c)hid-rmiPS/2 v7+, RMI4가장 널리 사용, clickpad, SMBus 모드
ALPSpsmouse (alps.c)hid-alpsv3~v8, U1복잡한 바이트 패킷, trackstick 통합
Elantechpsmouse (elantech.c)hid-multitouchv1~v4세밀한 멀티터치, 합리적 가격
Cypresspsmouse (cypress_ps2.c)hid-multitouch-일부 Acer/HP 제품
Apple-applespi / hid-appleSPI/USBForce Touch, 고해상도 터치
Microsoft-hid-multitouchPTP (Precision)Windows Precision Touchpad 표준
/* I2C-HID 터치패드의 Report Descriptor가 파싱되면:
 *
 * hid-multitouch.c가 MT 컬렉션을 인식:
 * 1. Usage Page: Digitizer (0x0D)
 * 2. Usage: Touch Screen (0x04) 또는 Touch Pad (0x05)
 * 3. Contact Count Maximum → input_mt_init_slots()
 * 4. 각 Contact:
 *    - Tip Switch → BTN_TOUCH
 *    - Contact ID → ABS_MT_TRACKING_ID (슬롯 할당)
 *    - X/Y → ABS_MT_POSITION_X/Y
 *    - Width/Height → ABS_MT_TOUCH_MAJOR/MINOR
 *    - Pressure → ABS_MT_PRESSURE
 *
 * Windows Precision Touchpad (PTP)는 표준화된 HID descriptor를
 * 사용하므로 hid-multitouch.c 하나로 거의 모든 최신 터치패드 지원
 */

/* Synaptics PS/2 고급 기능 sysfs 제어 */
/* /sys/devices/.../serio1/input/input5/ */
# 터치패드 연결 경로 확인
udevadm info /dev/input/event5 | grep -E 'ID_INPUT|DEVPATH'
# ID_INPUT_TOUCHPAD=1
# DEVPATH=.../i2c-ELAN0001:00/.../input5

# Synaptics PS/2 정보
cat /sys/bus/serio/devices/serio1/firmware_id
cat /sys/bus/serio/devices/serio1/protocol

# 터치패드가 I2C-HID인지 PS/2인지 확인
sudo libinput list-devices | grep -A5 -i touchpad
# Kernel: /dev/input/event5
# Group:  8
# Seat:   seat0, default
# Capabilities: pointer gesture

Input 전원 관리

Input 서브시스템의 전원 관리는 open/close 콜백wakeup-source 두 메커니즘을 중심으로 동작합니다. evdev 클라이언트가 디바이스 파일을 열면 dev->open()이 호출되고, 모든 클라이언트가 닫으면 dev->close()가 호출되어 하드웨어를 비활성화할 수 있습니다.

Input 디바이스 전원 상태 전환 IDLE (비활성) IRQ 비활성, 클럭 off 첫 evdev open() → dev->open() ACTIVE (활성) IRQ 활성, 이벤트 수집 중 마지막 close() → dev->close() IDLE (비활성) 자원 해제 INHIBITED sysfs inhibited=1 → 이벤트 차단 echo 1 > inhibited System Suspend input_dev_suspend() → 상태 저장 System Resume input_dev_resume() → 상태 복원, EV_KEY replay wakeup
/* open/close 콜백으로 전력 절약 */
static int my_input_open(struct input_dev *dev)
{
    struct my_data *data = input_get_drvdata(dev);

    /* 하드웨어 활성화 */
    pm_runtime_get_sync(&data->client->dev);
    enable_irq(data->irq);
    my_hw_enable(data);

    return 0;
}

static void my_input_close(struct input_dev *dev)
{
    struct my_data *data = input_get_drvdata(dev);

    /* 하드웨어 비활성화 */
    my_hw_disable(data);
    disable_irq(data->irq);
    pm_runtime_put(&data->client->dev);
}

/* wakeup-source 설정 — 전원 버튼 등 */
static int my_probe(...)
{
    /* Device Tree에서 wakeup-source 속성이 있으면 */
    if (device_property_read_bool(dev, "wakeup-source"))
        device_init_wakeup(dev, true);
}

/* suspend 시 wakeup IRQ 설정 */
static int my_suspend(struct device *dev)
{
    if (device_may_wakeup(dev))
        enable_irq_wake(data->irq);
    else
        my_hw_disable(data);  /* wakeup 불필요 시 완전 비활성화 */

    return 0;
}

이벤트 타임스탬프와 입력 지연

입력 이벤트의 타임스탬프는 input_handle_event()에서 ktime_get()으로 부여됩니다(CLOCK_MONOTONIC 기준). 이 시점은 하드웨어에서 이벤트가 실제 발생한 시점보다 항상 늦으므로, 입력 지연(input latency)의 구성 요소를 이해해야 합니다.

지연 구간원인전형적 값최적화 방법
하드웨어 스캔디바이스 스캔 주기 (폴링 레이트)1~8ms (125~1000Hz)고속 폴링 디바이스 사용
버스 전송USB 마이크로프레임, I2C 클럭1~8ms (USB), <1ms (I2C)USB 1000Hz 폴링, I2C 400kHz+
IRQ → ISR인터럽트 지연, ISR 실행1~50usthreaded IRQ 사용 시 증가
Input Core이벤트 라우팅, 타임스탬프 부여<10us-
evdev 버퍼원형 큐 저장, wake_up<10us-
스케줄링user space 프로세스 깨우기1~100usRT 스케줄러, CPU affinity
libinput 처리가속, 제스처, 필터링50~500us-
컴포지터Wayland/X11 프레임 동기화0~16.7ms (vsync)VRR, 고주사율 디스플레이
GPU 렌더링프레임 렌더링 → 디스플레이 출력0~16.7ms-
# 입력 지연 측정

# evhz — USB 마우스 폴링 레이트 측정
sudo evhz /dev/input/event5
# Average: 1000.2 Hz (1.0ms)

# MSC_TIMESTAMP — 하드웨어 타임스탬프 (일부 디바이스)
sudo evtest /dev/input/event5
# Event: ... type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 12345

# evdev 타임스탬프 기반 지연 분석
sudo evtest /dev/input/event0 | awk '/^Event/{print $3}'

# USB 마우스 폴링 레이트 변경 (고속)
# /etc/modprobe.d/usbhid.conf
options usbhid mousepoll=1  # 1ms = 1000Hz (기본 8ms = 125Hz)

# ftrace로 IRQ-to-userspace 지연 측정
echo 1 > /sys/kernel/debug/tracing/events/input/input_event/enable
echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
cat /sys/kernel/debug/tracing/trace_pipe

게임 컨트롤러

최신 리눅스 커널은 주요 게임 컨트롤러를 모두 지원합니다. HID 드라이버가 컨트롤러의 고유 기능(모션 센서, 터치패드, LED, 오디오)까지 활용할 수 있도록 장치별 드라이버를 제공합니다.

컨트롤러연결커널 드라이버특수 기능
Xbox (USB)USBxpad진동(rumble), LED, 무선 어댑터
Xbox (BT)Bluetoothhid-microsoftBLE 연결, 진동
DualShock 4USB/BThid-playstation터치패드, 자이로, LED bar, 오디오
DualSenseUSB/BThid-playstation적응형 트리거, 햅틱, LED, 마이크
Joy-ConBluetoothhid-nintendoIMU, HD rumble, NFC, IR 카메라
Switch ProUSB/BThid-nintendoIMU, HD rumble, NFC
Steam ControllerUSB/무선hid-steam터치패드(2), 자이로, 햅틱
8BitDoUSB/BThid-generic모드별 다른 HID descriptor
Google StadiaUSBhid-google-stadia-ff진동
/* DualSense 컨트롤러 구조 (hid-playstation.c 참고)
 *
 * Input devices created:
 * 1. 게임패드: 버튼(16+), 아날로그 스틱(L3/R3), 트리거(L2/R2)
 * 2. 모션 센서: 가속도계 3축 + 자이로 3축 (별도 input_dev)
 * 3. 터치패드: 멀티터치 2점 (Protocol B, INPUT_PROP_POINTER)
 *
 * 출력 기능 (write report):
 * - LED 색상 (RGB)
 * - 적응형 트리거 효과 (저항, 진동, 다단계)
 * - 햅틱 피드백 (FF_RUMBLE)
 * - 플레이어 LED
 * - 마이크 뮤트 LED
 *
 * 전원 관리:
 * - Bluetooth: 유휴 시 저전력 모드
 * - USB: 충전 상태 보고 (power_supply 연동)
 */
# 게임 컨트롤러 디버깅
jstest /dev/input/js0           # 레거시 조이스틱 API 테스트
evtest /dev/input/event10       # evdev 이벤트 확인

# FF (진동) 테스트
fftest /dev/input/event10

# SDL2 게임패드 매핑 확인
SDL_GAMECONTROLLERCONFIG="..." sdl2-jstest

# 컨트롤러별 sysfs LED 제어 (DualSense)
echo 255 0 0 > /sys/class/leds/playstation::dualsense-1/color
echo 1 > /sys/class/leds/playstation::dualsense-1/brightness

Input LED 서브시스템 연동

input-leds 핸들러는 Input 디바이스의 LED(Caps Lock, Num Lock, Scroll Lock 등)를 LED 클래스 디바이스로 노출합니다. 이를 통해 LED 서브시스템의 trigger 메커니즘을 활용하여 LED 동작을 커스터마이징할 수 있습니다.

# 키보드 LED 확인
ls /sys/class/leds/ | grep input
# input0::capslock
# input0::numlock
# input0::scrolllock

# LED 상태 확인/변경
cat /sys/class/leds/input0::capslock/brightness
echo 1 > /sys/class/leds/input0::capslock/brightness

# LED trigger로 활용 (디스크 활동 표시 등)
echo disk-activity > /sys/class/leds/input0::scrolllock/trigger
/* 커널 드라이버에서 LED 지원 */

/* 1. capability 선언 */
set_bit(EV_LED, idev->evbit);
set_bit(LED_CAPSL, idev->ledbit);
set_bit(LED_NUML, idev->ledbit);

/* 2. event 콜백 — 유저 공간에서 LED 제어 시 호출 */
static int my_event(struct input_dev *dev,
                    unsigned int type,
                    unsigned int code, int value)
{
    if (type != EV_LED)
        return -1;

    switch (code) {
    case LED_CAPSL:
        set_hw_led(LED_CAPSLOCK_BIT, value);
        break;
    case LED_NUML:
        set_hw_led(LED_NUMLOCK_BIT, value);
        break;
    }
    return 0;
}
idev->event = my_event;

Input 보안 — 세션 관리와 접근 제어

입력 장치에 대한 접근 제어는 시스템 보안의 핵심입니다. 키보드 입력을 스니핑하면 비밀번호를 탈취할 수 있고, 가상 입력을 주입하면 임의의 명령을 실행할 수 있습니다.

메커니즘설명사용처
EVIOCGRAB디바이스 독점 — 다른 클라이언트 차단게임, 스크린잠금, 키 매핑 도구
EVIOCREVOKEfd 접근 권한 철회 (재open 불가)systemd-logind 세션 전환
logind 세션활성 세션만 입력 장치 접근 허용멀티시트, VT 전환
/dev/input 퍼미션udev 규칙으로 그룹 input 설정일반 사용자 접근 제어
uinput 제한/dev/uinput 접근에 root 또는 특정 그룹 필요가상 입력 주입 방지
sysfs inhibited디바이스를 일시적으로 비활성화태블릿 모드, 덮개 닫힘
/* systemd-logind의 EVIOCREVOKE 사용 패턴:
 *
 * 1. 사용자 로그인 → logind가 세션에 evdev fd 할당
 * 2. VT 전환 (Ctrl+Alt+F2) → 이전 세션의 모든 evdev fd에 REVOKE
 *    ioctl(fd, EVIOCREVOKE, 0);
 * 3. revoke된 fd는 read() → -ENODEV
 * 4. 새 세션 활성화 → 새로운 fd 할당
 *
 * 이를 통해 비활성 세션이 키보드를 읽는 것을 방지
 */

/* EVIOCGRAB vs EVIOCREVOKE:
 * GRAB: fd 소유자가 독점. 해제(ungrab) 가능.
 *       다른 evdev 클라이언트만 차단 (kbd handler는 차단 안됨 주의)
 * REVOKE: 외부에서 fd 무효화. 복구 불가.
 *         logind 같은 세션 관리자가 사용
 */

흔한 실수와 트러블슈팅

실수 1: evbit 설정 없이 이벤트 보고

set_bit(EV_KEY, dev->evbit) 없이 input_report_key()를 호출하면 이벤트가 조용히 무시됩니다. input_set_capability()를 사용하면 evbit을 자동으로 설정하므로 이 실수를 방지할 수 있습니다.

실수 2: input_sync() 누락

input_sync()(SYN_REPORT)를 빠뜨리면 이벤트 패킷이 닫히지 않아 evdev 클라이언트에서 이벤트가 보이지 않습니다. 하나의 하드웨어 이벤트 처리 후 반드시 input_sync()를 호출하세요.

실수 3: input_mt_sync_frame() 누락 (Protocol B)

INPUT_MT_DROP_UNUSED 플래그와 함께 input_mt_init_slots()을 사용했다면, 매 프레임마다 input_mt_sync_frame()을 호출해야 합니다. 빠뜨리면 이전에 활성이던 슬롯이 해제되지 않아 "유령 터치"가 발생합니다.

실수 4: 등록 후 capability 변경

input_register_device() 이후에 set_bit()으로 새 capability를 추가하면 이미 연결된 핸들러가 새 이벤트를 인식하지 못합니다. 모든 capability는 등록 전에 설정하세요.

실수 5: IRQ 컨텍스트에서 I2C 읽기

I2C 터치스크린 드라이버에서 hard IRQ 핸들러 안에 i2c_transfer()를 넣으면 sleep 불가 컨텍스트에서 sleep 발생 → BUG. devm_request_threaded_irq()로 threaded IRQ를 사용하세요.

devm_request_threaded_irq(&client->dev, client->irq,
    NULL, my_ts_irq,  /* hard_irq=NULL, thread_fn만 사용 */
    IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
    "my-touch", data);
실수 6: 절대축 resolution 미설정

터치패드에서 input_abs_set_res()를 설정하지 않으면 libinput이 물리적 크기를 계산할 수 없어 포인터 가속이 부정확해집니다. 터치스크린은 INPUT_PROP_DIRECT이므로 영향이 적지만, 터치패드(INPUT_PROP_POINTER)에서는 필수입니다.

실전 실습

실습 환경: 물리 장치가 없어도 uinput으로 가상 입력 장치를 만들거나, QEMU에서 PS/2 키보드를 사용할 수 있습니다.

Lab 1: evtest로 이벤트 분석

# 1. 디바이스 목록 확인
cat /proc/bus/input/devices

# 2. 키보드 이벤트 모니터링
sudo evtest /dev/input/event0
# 키를 누르고 떼면서 EV_KEY, SYN_REPORT 관찰
# value: 1=press, 0=release, 2=repeat

# 3. 마우스 이동 관찰 (EV_REL)
sudo evtest /dev/input/event3
# REL_X, REL_Y 값 변화 관찰

# 4. capability 비트 해석
# B: EV=120013 → 비트 0(SYN)+1(KEY)+4(MSC)+17(LED)+20(REP)
python3 -c "print(bin(0x120013))"
# 0b100100000000000010011

Lab 2: uinput으로 가상 마우스 만들기

#include <linux/uinput.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
    int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
    struct uinput_setup setup;

    /* 이벤트 유형 설정 */
    ioctl(fd, UI_SET_EVBIT, EV_REL);
    ioctl(fd, UI_SET_RELBIT, REL_X);
    ioctl(fd, UI_SET_RELBIT, REL_Y);
    ioctl(fd, UI_SET_EVBIT, EV_KEY);
    ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
    ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);

    memset(&setup, 0, sizeof(setup));
    setup.id.bustype = BUS_USB;
    strcpy(setup.name, "Virtual Mouse");
    ioctl(fd, UI_DEV_SETUP, &setup);
    ioctl(fd, UI_DEV_CREATE);

    sleep(1);  /* udev 처리 대기 */

    /* 마우스를 원 그리며 이동 */
    for (int i = 0; i < 360; i++) {
        struct input_event ev[3];
        memset(ev, 0, sizeof(ev));

        ev[0].type = EV_REL; ev[0].code = REL_X;
        ev[0].value = (int)(5 * cos(i * M_PI / 180));
        ev[1].type = EV_REL; ev[1].code = REL_Y;
        ev[1].value = (int)(5 * sin(i * M_PI / 180));
        ev[2].type = EV_SYN; ev[2].code = SYN_REPORT;

        write(fd, ev, sizeof(ev));
        usleep(10000);  /* 10ms 간격 */
    }

    ioctl(fd, UI_DEV_DESTROY);
    close(fd);
    return 0;
}

Lab 3: evemu로 이벤트 녹화/재생

# 이벤트 녹화 (키보드 타이핑 10초)
sudo timeout 10 evemu-record /dev/input/event0 > /tmp/typing.evemu

# 녹화 내용 확인
head -30 /tmp/typing.evemu
# N: AT Translated Set 2 keyboard
# I: 0011 0001 0001 ab54
# P: 00 00 00 00 00 00 00 00 ...
# B: 00 ...
# E: 0.000001 0001 0030 1    ← EV_KEY KEY_A press
# E: 0.000001 0000 0000 0    ← SYN_REPORT

# 재생 (가상 디바이스 생성 → 이벤트 주입)
sudo evemu-play /dev/input/event0 < /tmp/typing.evemu
# 녹화된 타이핑이 재현됨

Lab 4: libinput quirks 디버깅

# 터치패드 문제 진단

# 1. libinput 디버그 이벤트
sudo libinput debug-events --verbose --device /dev/input/event5
# 제스처 인식, palm detection, 압력 임계값 관찰

# 2. quirks 확인
sudo libinput quirks list /dev/input/event5

# 3. 커스텀 quirk 추가 (/etc/libinput/local-overrides.quirks)
cat > /etc/libinput/local-overrides.quirks << 'EOF'
[My Touchpad Fix]
MatchName=*ELAN*Touchpad*
AttrPressureRange=10:8
AttrPalmPressureThreshold=200
EOF

# 4. udev 규칙으로 Input 속성 확인
udevadm info --export-db | grep -B5 'ID_INPUT_TOUCHPAD=1'

Lab 5: GPIO 키 디버깅 (임베디드)

# Device Tree 적용 확인
cat /proc/device-tree/gpio-keys/power-button/linux,code
# 116 (KEY_POWER)

# GPIO 상태 확인
cat /sys/kernel/debug/gpio | grep -A2 'gpio-keys'

# 이벤트 발생 확인
sudo evtest /dev/input/event1
# 버튼 누르면: EV_KEY KEY_POWER value 1

# wakeup source 상태 확인
cat /sys/devices/.../gpio-keys/power/wakeup
# enabled
cat /sys/devices/.../gpio-keys/power/wakeup_count
# 3 (3회 wakeup 발생)

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.