Skip to content

Commit e9166a0

Browse files
committed
Support input device hot-plugging and removal via udev
udev (userspace /dev) is a device manager for the Linux kernel. By monitoring "add" and "remove" events from udev, we can dynamically update the event device file descriptors table to enable device hot-plugging and removal. Close #61
1 parent b75d5ca commit e9166a0

File tree

2 files changed

+113
-8
lines changed

2 files changed

+113
-8
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ endif
106106

107107
ifeq ($(CONFIG_BACKEND_FBDEV), y)
108108
BACKEND = fbdev
109+
TARGET_LIBS += -ludev
109110
libtwin.a_files-y += backend/fbdev.c
110111
libtwin.a_files-y += backend/linux_input.c
111112
endif

backend/linux_input.c

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
#include <fcntl.h>
8+
#include <libudev.h>
89
#include <linux/input.h>
910
#include <poll.h>
1011
#include <pthread.h>
@@ -18,6 +19,10 @@
1819

1920
#define EVDEV_CNT_MAX 32
2021
#define EVDEV_NAME_SIZE_MAX 50
22+
#define EVDEV_IDX_START 1
23+
24+
#define UDEV_RESERVED_CNT 1
25+
#define UDEV_EVDEV_FD_CNT (EVDEV_CNT_MAX + EVDEV_IDX_START)
2126

2227
typedef struct {
2328
twin_screen_t *screen;
@@ -101,9 +106,73 @@ static void twin_linux_input_events(struct input_event *ev,
101106
}
102107
}
103108

104-
static void *twin_linux_evdev_thread(void *arg)
109+
static int twin_linux_udev_init(struct udev **udev, struct udev_monitor **mon)
105110
{
106-
twin_linux_input_t *tm = arg;
111+
/* Create udev object */
112+
*udev = udev_new();
113+
if (!*udev) {
114+
log_error("Failed to create udev object");
115+
return -1;
116+
}
117+
118+
/* Create a monitor for kernel events */
119+
*mon = udev_monitor_new_from_netlink(*udev, "udev");
120+
if (!*mon) {
121+
log_error("Failed to create udev monitor");
122+
udev_unref(*udev);
123+
return -1;
124+
}
125+
126+
/* Filter for input subsystem */
127+
udev_monitor_filter_add_match_subsystem_devtype(*mon, "input", NULL);
128+
udev_monitor_enable_receiving(*mon);
129+
130+
/* File descriptor for the monitor */
131+
return udev_monitor_get_fd(*mon);
132+
}
133+
134+
static bool twin_linux_udev_update(struct udev_monitor *mon)
135+
{
136+
struct udev_device *dev = NULL;
137+
138+
/* Get the device that caused the event */
139+
dev = udev_monitor_receive_device(mon);
140+
if (dev) {
141+
const char *action = udev_device_get_action(dev);
142+
const char *dev_node = udev_device_get_devnode(dev);
143+
144+
if (action && dev_node) {
145+
const char *keyboard =
146+
udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");
147+
const char *mouse =
148+
udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
149+
150+
/* Ensure udev event is for mouse or keyboard */
151+
if (!keyboard && !mouse) {
152+
udev_device_unref(dev);
153+
return false;
154+
}
155+
156+
/* Capture only add and remove events */
157+
if (!strcmp(action, "add") || !strcmp(action, "remove")) {
158+
log_info("udev: %s: %s", action, dev_node);
159+
udev_device_unref(dev);
160+
return true;
161+
}
162+
}
163+
}
164+
165+
/* No event is caputured */
166+
return false;
167+
}
168+
169+
static void twin_linux_edev_open(struct pollfd *pfds)
170+
{
171+
/* Reset evdev fd table */
172+
for (int i = 0; i < evdev_cnt; i++) {
173+
close(evdev_fd[i]);
174+
}
175+
evdev_cnt = 0;
107176

108177
/* Open all event devices */
109178
char evdev_name[EVDEV_NAME_SIZE_MAX] = {0};
@@ -116,22 +185,53 @@ static void *twin_linux_evdev_thread(void *arg)
116185
}
117186
}
118187

119-
/* Initialize pollfd array */
120-
struct pollfd pfds[EVDEV_CNT_MAX];
121-
for (int i = 0; i < evdev_cnt; i++) {
122-
pfds[i].fd = evdev_fd[i];
188+
/* Initialize evdev poll file descriptors */
189+
for (int i = EVDEV_IDX_START; i < evdev_cnt + UDEV_RESERVED_CNT; i++) {
190+
pfds[i].fd = evdev_fd[i - 1];
123191
pfds[i].events = POLLIN;
124192
}
193+
}
194+
195+
static void *twin_linux_evdev_thread(void *arg)
196+
{
197+
twin_linux_input_t *tm = arg;
198+
199+
struct udev *udev = NULL;
200+
struct udev_monitor *mon = NULL;
201+
202+
/* Open Linux udev (user space device manager) */
203+
int udev_fd = twin_linux_udev_init(&udev, &mon);
204+
if (udev_fd < 0) {
205+
exit(2);
206+
}
207+
208+
/* Place the udev fd into the poll fds */
209+
struct pollfd pfds[UDEV_EVDEV_FD_CNT];
210+
pfds[0].fd = udev_fd;
211+
pfds[0].events = POLLIN;
212+
213+
/* Open event devices */
214+
twin_linux_edev_open(pfds);
125215

126216
/* Event polling */
127217
struct input_event ev;
128218
while (1) {
129219
/* Wait until any event is available */
130-
if (poll(pfds, evdev_cnt, -1) <= 0)
220+
if (poll(pfds, evdev_cnt + UDEV_RESERVED_CNT, -1) <= 0)
131221
continue;
132222

133223
/* Iterate through all file descriptors */
134-
for (int i = 0; i < evdev_cnt; i++) {
224+
for (int i = 0; i < evdev_cnt + UDEV_RESERVED_CNT; i++) {
225+
if (i == 0) {
226+
/* Check udev event */
227+
if (twin_linux_udev_update(mon)) {
228+
/* Re-open event devices */
229+
twin_linux_edev_open(pfds);
230+
break;
231+
}
232+
continue;
233+
}
234+
135235
/* Try reading events */
136236
ssize_t n = read(pfds[i].fd, &ev, sizeof(ev));
137237
if (n == sizeof(ev)) {
@@ -141,6 +241,10 @@ static void *twin_linux_evdev_thread(void *arg)
141241
}
142242
}
143243

244+
/* Clean up */
245+
udev_monitor_unref(mon);
246+
udev_unref(udev);
247+
144248
return NULL;
145249
}
146250

0 commit comments

Comments
 (0)