Chapter 45: Input subsystem¶
What: the input subsystem, the kernel framework that turns “a GPIO went low” or “an I²C read returned a touch coordinate” into a standardised event stream consumed by
evdev, X11, Wayland, framebuffer toolkits, and command-line tools. We’ll build agpio-keysderivative, the canonical “GPIO as keyboard key” driver, and walk every byte from the IRQ handler toevtestreading/dev/input/eventN. MCU bridge: Think of an IRQ like an EXTI/NVIC interrupt path, except Linux splits the hard interrupt from deferred work and must share lines across drivers. MCU bridge: Think of Linux GPIO like the same pin set/reset block you used on STM32, but accessed through a kernel subsystem that owns numbering, direction, interrupts, and user-space exposure. IRQ: interrupt request, the signal path that tells the CPU or interrupt controller that hardware needs service. GPIO: General-Purpose Input/Output, a pin controlled as a digital input, output, or interrupt source.Why: every input device on a Linux box, keyboard, mouse, touchscreen, joystick, IR remote, goes through the input subsystem. Once you understand
input_register_deviceandinput_event, every input driver in the kernel looks familiar. The framework handles event multiplexing, queueing, sysfs/evdevintegration, autorepeat, and userspace device-node creation, your driver just callsinput_report_key()andinput_sync(). sysfs: a kernel-generated filesystem under /sys that exposes devices, drivers, and attributes.Focus: type, code, value, the three-element tuple that describes every input event. Once that triple makes sense,
EV_KEY+KEY_ENTER+1means “Enter was pressed”, the rest of the input subsystem (abs axes, relative motion, multi-touch slots) is just different combinations of type/code/value.
45.1 The picture¶
When you press a key on a USB keyboard:
physical key down
│
USB-HID interrupt-in transfer reports scan-code 0x28 (Enter)
│
usbhid driver: hid_input_report → input_report_key(dev, KEY_ENTER, 1)
│
input core: queue an event {EV_KEY, KEY_ENTER, 1} on every evdev handler
│
/dev/input/event3 becomes readable
│
user-space: read() returns 24 bytes — a struct input_event
│
X11 / Wayland / your application: "Enter was pressed"
The driver’s only job is to call input_report_*() and input_sync(). The core handles queueing, multiplexing, and user-space delivery. Your driver feeds events into the type/code/value protocol. The input core delivers them to user-space. You never talk to user-space directly.
45.2 Event types and codes¶
Lab vs production: Do not burn fuses, enroll production keys, or sign release images while following the lab. Use throwaway keys and back up the unsigned image plus the key directory before testing irreversible security flows.
#include <linux/input.h>, defines hundreds of EV_*, KEY_*, BTN_*, ABS_*, REL_*, SW_*, LED_*, SND_*, MSC_* constants.
Common event types:
Type |
Meaning |
Typical codes |
|---|---|---|
|
Synchronization, end-of-event-group marker |
|
|
Key/button pressed/released |
|
|
Relative axis (mouse motion, scroll wheel) |
|
|
Absolute axis (touchscreen, joystick, IMU) |
|
|
Miscellaneous (scancode, raw value pass-through) |
|
|
Switch (lid open/closed, headphone jack, dock) |
|
|
LED state output (driver consumes from userspace) |
|
|
Sound output (PC speaker beep) |
|
Each event has a value appropriate to its type:
EV_KEYvalue:0= released,1= pressed,2= autorepeat.EV_RELvalue: signed delta (+1, -3, etc.).EV_ABSvalue: absolute position, in whatever range the driver declared.EV_SYNvalue: usually 0.
A coherent group of events ends with EV_SYN/SYN_REPORT. The input core delivers all events between two SYN_REPORTs atomically to userspace, important when a single touch update sends multiple coordinates that must arrive together.
45.3 The mainline gpio-keys driver¶
Before we write our own, let’s notice that the kernel already has gpio-keys, the in-tree driver that exposes any number of DT-described GPIOs as keyboard keys. For real production use, just use it. The DT looks like this:
gpio_keys {
compatible = "gpio-keys";
key-enter {
label = "Enter";
linux,code = <KEY_ENTER>;
gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
debounce-interval = <50>;
wakeup-source;
};
key-up {
label = "Up";
linux,code = <KEY_UP>;
gpios = <&gpio4 14 GPIO_ACTIVE_LOW>;
debounce-interval = <50>;
};
};
Set CONFIG_KEYBOARD_GPIO=y in the kernel config, boot. Pressing the GPIO19 button now generates real KEY_ENTER events on /dev/input/eventN.
For learning purposes, though, we’ll write our own version so we know what the framework is doing.
45.5 Auto-repeat, debounce, and key mapping¶
The input core provides autorepeat for free: register EV_REP capability and the core will generate repeat events (value=2) while a key is held. gpio-keys does this automatically. Our minimal driver doesn’t bother.
Debounce is software (or hardware). gpio-keys uses a timer (debounce-interval ms), on the falling edge, schedule a delayed work. Only report the event if the GPIO is still low after the delay. Implementing this in our driver is an exercise. The pattern: cancel the previous delayed work, schedule a new one for the debounce interval, only input_report_key from the work handler.
Key mapping. The KEY_* codes are logical, KEY_ENTER is the same value regardless of whether the user’s keymap is US QWERTY or Dvorak. The user-space keymap translates KEY_* to characters. For embedded devices with only a few buttons, you pick meaningful KEY_* codes:
KEY_VOLUMEUP/KEY_VOLUMEDOWNfor media buttons.KEY_POWERfor the power button (the kernel and systemd both recognise this).KEY_HOME,KEY_BACK,KEY_MENUfor navigation.KEY_WAKEUPfor a wakeup-from-suspend button.
The full list is in include/uapi/linux/input-event-codes.h. Pick a code that matches the role of the button. User-space will know what to do with it.
45.6 Absolute axes, touchscreens and joysticks¶
For a touchscreen or joystick, you have coordinates, not key presses. Declare EV_ABS capabilities and report values:
input_set_abs_params(input, ABS_X, 0, 4095, 0, 0);
input_set_abs_params(input, ABS_Y, 0, 4095, 0, 0);
input_set_abs_params(input, ABS_PRESSURE, 0, 255, 0, 0);
/* In the IRQ/work handler when a sample arrives: */
input_report_abs(input, ABS_X, x_coord);
input_report_abs(input, ABS_Y, y_coord);
input_report_abs(input, ABS_PRESSURE, pressure);
input_report_key(input, BTN_TOUCH, 1);
input_sync(input);
input_set_abs_params(dev, code, min, max, fuzz, flat), min/max is the valid range, fuzz is the noise floor (changes ≤ fuzz are suppressed), flat is the center deadzone for joysticks.
For multi-touch, the protocol is more involved, the MT-B (slot-based) protocol uses ABS_MT_SLOT + per-finger ABS_MT_POSITION_X/Y. We’ll cover that in Ch 55G (GT911 multi-touch driver).
45.7 Polled vs interrupt-driven¶
Some input devices don’t have IRQs, accelerometers configured for continuous mode, for instance, or a button on a slow I²C expander where IRQ wiring isn’t practical. The input_polled_dev framework (now subsumed into the regular input_dev via input_setup_polling) polls a device at a fixed rate:
input_setup_polling(input, my_poll_callback);
input_set_poll_interval(input, 20); /* 20 ms = 50 Hz */
input_register_device(input);
The core calls my_poll_callback(input) every 20 ms. Inside, sample the hardware, input_report_*, input_sync. No IRQ wiring needed.
45.8 User-space, evdev¶
/dev/input/event* is the chardev that streams struct input_event records to user-space:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
Read one record per event. The most common tool is evtest (interactive) for debugging. For production, applications either use libevdev (a thin wrapper) or higher-level libraries:
libinput: used by Wayland and modern X11. Handles gesture recognition, palm rejection, tap-to-click, etc.libevdev: minimal wrapper for reading raw events./dev/input/eventNdirectly: fine for embedded with one app.
For a quick test from shell:
$ sudo evtest /dev/input/event2 # interactive
$ sudo cat /dev/input/event2 | hexdump -C
45.9 Lab¶
Build and load the button-input driver. Verify
evtestshowsKEY_ENTERevents.Add a second button with
KEY_VOLUMEUP. Update DT to use twobutton-gpios, modify driver to allocate oneinput_devwith both keys (or two devices, the kernel allows both).Add debounce. Schedule a
delayed_workfrom the IRQ handler with a 30 ms delay. Only report the event from the work-handler.Compare to
gpio-keys. Drop your driver, configure DT withcompatible = "gpio-keys"andlinux,code = <KEY_ENTER>;. Confirm identical behavior. Look atdrivers/input/keyboard/gpio_keys.c, note how much more it handles (autorepeat, wakeup, runtime PM).Touchscreen simulator. Adapt your driver to emit
ABS_X/ABS_Y/BTN_TOUCHevents with random values once per second. Verifyevtestshows touch events. This is the foundation for a real touchscreen driver (Ch 55G).Power-button integration. Reconfigure your button to emit
KEY_POWER. With systemd, a long press should trigger a graceful shutdown.
45.10 Pitfalls¶
Forgetting
input_sync. Events are buffered untilSYN_REPORT. Without it, user-space never sees them. After any group ofinput_report_*calls, callinput_sync.Reporting an unsupported event. If you
input_report_key(input, KEY_ENTER, 1)but didn’tinput_set_capability(..., EV_KEY, KEY_ENTER), the event is silently dropped. Always declare capabilities first.Not using
devm_input_allocate_device. Forgettinginput_free_devicein error paths leaks the device.devm_handles it.Calling
input_register_devicebefore setting capabilities. Capabilities must be set before register. Order: alloc → set_capability → register.Mixing
input_allocate_devicewith separateinput_register_device. Both can fail, at different points. Use standardgotocleanup, or just usedevm_input_allocate_deviceto avoid the problem.Confusing absolute and relative axes. Mice use
EV_REL(delta motion). Touchscreens useEV_ABS(absolute position). Mixing them gives weird user-space behavior.Multi-touch with single-touch protocol. Don’t try to emit
ABS_Xfor multiple fingers, that’s not how it works. Use the MT-B slot protocol (Ch 55G).Repeating events that haven’t actually changed. The core does not dedupe. Every
input_report_key(..., 1)followed byinput_syncis one event. Polling a held button without state-tracking spams the queue.IRQ flag
IRQF_TRIGGER_FALLINGwithoutIRQF_TRIGGER_RISING: you only get press events, not release. For a press/release-capable button, request both edges (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING).
45.11 Going deeper¶
Documentation/input/: the input subsystem’s full documentation. Readinput.rst,event-codes.rst,multi-touch-protocol.rst.include/uapi/linux/input-event-codes.h: the canonical list ofEV_*,KEY_*,ABS_*, etc.drivers/input/keyboard/gpio_keys.c: the in-tree gpio-keys driver. Read it. ~600 lines and covers debounce, wakeup, autorepeat, runtime PM, polling, every feature you’d add to a production button driver.drivers/input/evdev.c: the evdev “handler” that exposes input events as/dev/input/eventN.libevdevsource (freedesktop.org), for high-level user-space input access.Documentation/input/input.rst: overview of the input architecture, including the handler/handle/dev triangle.
Next chapter: Chapter 46: I²C drivers. With GPIO and input behind us, we move to a real bus: the i.MX6ULL’s I²C controllers, the
i2c_client/i2c_drivermodel, and how a single I²C bus accommodates a half-dozen sensors at different addresses.