Skip to content

Commit 4537833

Browse files
authored
Linux Hotplug: Connection-callback implementation for hidraw (#647)
1 parent 60b40d9 commit 4537833

1 file changed

Lines changed: 313 additions & 13 deletions

File tree

linux/hid.c

Lines changed: 313 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <sys/utsname.h>
3636
#include <fcntl.h>
3737
#include <poll.h>
38+
#include <pthread.h>
3839

3940
/* Linux */
4041
#include <linux/hidraw.h>
@@ -68,6 +69,10 @@
6869
#define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len)
6970
#endif
7071

72+
/* The value of the first callback handle to be given upon registration */
73+
/* Can be any arbitrary positive integer */
74+
#define FIRST_HOTPLUG_CALLBACK_HANDLE 1
75+
7176
struct hid_device_ {
7277
int device_handle;
7378
int blocking;
@@ -880,6 +885,96 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
880885
return HID_API_VERSION_STR;
881886
}
882887

888+
static struct hid_hotplug_context {
889+
/* UDEV context that handles the monitor */
890+
struct udev* udev_ctx;
891+
892+
/* UDEV monitor that receives events */
893+
struct udev_monitor* mon;
894+
895+
/* File descriptor for the UDEV monitor that allows to check for new events with select() */
896+
int monitor_fd;
897+
898+
/* Thread for the UDEV monitor */
899+
pthread_t thread;
900+
901+
pthread_mutex_t mutex;
902+
903+
int mutex_ready;
904+
905+
/* HIDAPI unique callback handle counter */
906+
hid_hotplug_callback_handle next_handle;
907+
908+
/* Linked list of the hotplug callbacks */
909+
struct hid_hotplug_callback *hotplug_cbs;
910+
911+
/* Linked list of the device infos (mandatory when the device is disconnected) */
912+
struct hid_device_info *devs;
913+
} hid_hotplug_context = {
914+
.udev_ctx = NULL,
915+
.monitor_fd = -1,
916+
.next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE,
917+
.mutex_ready = 0,
918+
.hotplug_cbs = NULL,
919+
.devs = NULL
920+
};
921+
922+
struct hid_hotplug_callback {
923+
hid_hotplug_callback_handle handle;
924+
unsigned short vendor_id;
925+
unsigned short product_id;
926+
hid_hotplug_event events;
927+
void *user_data;
928+
hid_hotplug_callback_fn callback;
929+
930+
/* Pointer to the next notification */
931+
struct hid_hotplug_callback *next;
932+
};
933+
934+
static void hid_internal_hotplug_cleanup()
935+
{
936+
if (hid_hotplug_context.hotplug_cbs != NULL) {
937+
return;
938+
}
939+
940+
pthread_join(hid_hotplug_context.thread, NULL);
941+
942+
/* Cleanup connected device list */
943+
hid_free_enumeration(hid_hotplug_context.devs);
944+
hid_hotplug_context.devs = NULL;
945+
/* Disarm the udev monitor */
946+
udev_monitor_unref(hid_hotplug_context.mon);
947+
udev_unref(hid_hotplug_context.udev_ctx);
948+
}
949+
950+
static void hid_internal_hotplug_init()
951+
{
952+
if (!hid_hotplug_context.mutex_ready) {
953+
pthread_mutex_init(&hid_hotplug_context.mutex, NULL);
954+
hid_hotplug_context.mutex_ready = 1;
955+
}
956+
}
957+
958+
static void hid_internal_hotplug_exit()
959+
{
960+
if (!hid_hotplug_context.mutex_ready) {
961+
return;
962+
}
963+
964+
pthread_mutex_lock(&hid_hotplug_context.mutex);
965+
struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs;
966+
/* Remove all callbacks from the list */
967+
while (*current) {
968+
struct hid_hotplug_callback* next = (*current)->next;
969+
free(*current);
970+
*current = next;
971+
}
972+
hid_internal_hotplug_cleanup();
973+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
974+
hid_hotplug_context.mutex_ready = 0;
975+
pthread_mutex_destroy(&hid_hotplug_context.mutex);
976+
}
977+
883978
int HID_API_EXPORT hid_init(void)
884979
{
885980
const char *locale;
@@ -895,14 +990,22 @@ int HID_API_EXPORT hid_init(void)
895990
return 0;
896991
}
897992

993+
898994
int HID_API_EXPORT hid_exit(void)
899995
{
900996
/* Free global error message */
901997
register_global_error(NULL);
902998

999+
hid_internal_hotplug_exit();
1000+
9031001
return 0;
9041002
}
9051003

1004+
static int hid_internal_match_device_id(unsigned short vendor_id, unsigned short product_id, unsigned short expected_vendor_id, unsigned short expected_product_id)
1005+
{
1006+
return (expected_vendor_id == 0x0 || vendor_id == expected_vendor_id) && (expected_product_id == 0x0 || product_id == expected_product_id);
1007+
}
1008+
9061009
struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
9071010
{
9081011
struct udev *udev;
@@ -1004,26 +1107,224 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
10041107
}
10051108
}
10061109

1110+
static void hid_internal_invoke_callbacks(struct hid_device_info *info, hid_hotplug_event event)
1111+
{
1112+
struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs;
1113+
while (*current) {
1114+
struct hid_hotplug_callback *callback = *current;
1115+
if ((callback->events & event) && hid_internal_match_device_id(info->vendor_id, info->product_id,
1116+
callback->vendor_id, callback->product_id)) {
1117+
int result = callback->callback(callback->handle, info, event, callback->user_data);
1118+
/* If the result is non-zero, we remove the callback and proceed */
1119+
/* Do not use the deregister call as it locks the mutex, and we are currently in a lock */
1120+
if (result) {
1121+
struct hid_hotplug_callback *callback = *current;
1122+
*current = (*current)->next;
1123+
free(callback);
1124+
continue;
1125+
}
1126+
}
1127+
current = &callback->next;
1128+
}
1129+
}
1130+
1131+
static int match_udev_to_info(struct udev_device* raw_dev, struct hid_device_info *info)
1132+
{
1133+
const char *path = udev_device_get_devnode(raw_dev);
1134+
if (!strcmp(path, info->path)) {
1135+
return 1;
1136+
}
1137+
return 0;
1138+
}
1139+
1140+
static void* hotplug_thread(void* user_data)
1141+
{
1142+
(void) user_data;
1143+
1144+
while (hid_hotplug_context.monitor_fd > 0) {
1145+
fd_set fds;
1146+
struct timeval tv;
1147+
int ret;
1148+
1149+
FD_ZERO(&fds);
1150+
FD_SET(hid_hotplug_context.monitor_fd, &fds);
1151+
/* 5 msec timeout seems reasonable; don't set too low to avoid high CPU usage */
1152+
/* This timeout only affects how much time it takes to stop the thread */
1153+
tv.tv_sec = 0;
1154+
tv.tv_usec = 5000;
1155+
1156+
ret = select(hid_hotplug_context.monitor_fd+1, &fds, NULL, NULL, &tv);
1157+
1158+
/* Check if our file descriptor has received data. */
1159+
if (ret > 0 && FD_ISSET(hid_hotplug_context.monitor_fd, &fds)) {
1160+
1161+
/* Make the call to receive the device.
1162+
select() ensured that this will not block. */
1163+
struct udev_device *raw_dev = udev_monitor_receive_device(hid_hotplug_context.mon);
1164+
if (raw_dev) {
1165+
/* Lock the mutex so callback/device lists don't change elsewhere from here on */
1166+
pthread_mutex_lock(&hid_hotplug_context.mutex);
1167+
1168+
const char* action = udev_device_get_action(raw_dev);
1169+
if (!strcmp(action, "add")) {
1170+
// We create a list of all usages on this UDEV device
1171+
struct hid_device_info *info = create_device_info_for_device(raw_dev);
1172+
struct hid_device_info *info_cur = info;
1173+
while (info_cur) {
1174+
/* For each device, call all matching callbacks */
1175+
/* TODO: possibly make the `next` field NULL to match the behavior on other systems */
1176+
hid_internal_invoke_callbacks(info_cur, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED);
1177+
info_cur = info_cur->next;
1178+
}
1179+
1180+
/* Append all we got to the end of the device list */
1181+
if (info) {
1182+
if (hid_hotplug_context.devs != NULL) {
1183+
struct hid_device_info *last = hid_hotplug_context.devs;
1184+
while (last->next != NULL) {
1185+
last = last->next;
1186+
}
1187+
last->next = info;
1188+
} else {
1189+
hid_hotplug_context.devs = info;
1190+
}
1191+
}
1192+
} else if (!strcmp(action, "remove")) {
1193+
for (struct hid_device_info **current = &hid_hotplug_context.devs; *current;) {
1194+
struct hid_device_info* info = *current;
1195+
if (match_udev_to_info(raw_dev, *current)) {
1196+
/* If the libusb device that's left matches this HID device, we detach it from the list */
1197+
*current = (*current)->next;
1198+
info->next = NULL;
1199+
hid_internal_invoke_callbacks(info, HID_API_HOTPLUG_EVENT_DEVICE_LEFT);
1200+
/* Free every removed device */
1201+
free(info);
1202+
} else {
1203+
current = &info->next;
1204+
}
1205+
}
1206+
}
1207+
udev_device_unref(raw_dev);
1208+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
1209+
}
1210+
}
1211+
}
1212+
return NULL;
1213+
}
1214+
10071215
int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void *user_data, hid_hotplug_callback_handle *callback_handle)
10081216
{
1009-
/* Stub */
1010-
(void)vendor_id;
1011-
(void)product_id;
1012-
(void)events;
1013-
(void)flags;
1014-
(void)callback;
1015-
(void)user_data;
1016-
(void)callback_handle;
1217+
struct hid_hotplug_callback* hotplug_cb;
10171218

1018-
return -1;
1219+
/* Check params */
1220+
if (events == 0
1221+
|| (events & ~(HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT))
1222+
|| (flags & ~(HID_API_HOTPLUG_ENUMERATE))
1223+
|| callback == NULL) {
1224+
return -1;
1225+
}
1226+
1227+
hotplug_cb = (struct hid_hotplug_callback*)calloc(1, sizeof(struct hid_hotplug_callback));
1228+
1229+
if (hotplug_cb == NULL) {
1230+
return -1;
1231+
}
1232+
1233+
/* Fill out the record */
1234+
hotplug_cb->next = NULL;
1235+
hotplug_cb->vendor_id = vendor_id;
1236+
hotplug_cb->product_id = product_id;
1237+
hotplug_cb->events = events;
1238+
hotplug_cb->user_data = user_data;
1239+
hotplug_cb->callback = callback;
1240+
1241+
/* Ensure we are ready to actually use the mutex */
1242+
hid_internal_hotplug_init();
1243+
1244+
/* Lock the mutex to avoid race conditions */
1245+
pthread_mutex_lock(&hid_hotplug_context.mutex);
1246+
1247+
hotplug_cb->handle = hid_hotplug_context.next_handle++;
1248+
1249+
/* handle the unlikely case of handle overflow */
1250+
if (hid_hotplug_context.next_handle < 0)
1251+
{
1252+
hid_hotplug_context.next_handle = 1;
1253+
}
1254+
1255+
/* Return allocated handle */
1256+
if (callback_handle != NULL) {
1257+
*callback_handle = hotplug_cb->handle;
1258+
}
1259+
1260+
/* Append a new callback to the end */
1261+
if (hid_hotplug_context.hotplug_cbs != NULL) {
1262+
struct hid_hotplug_callback *last = hid_hotplug_context.hotplug_cbs;
1263+
while (last->next != NULL) {
1264+
last = last->next;
1265+
}
1266+
last->next = hotplug_cb;
1267+
}
1268+
else {
1269+
// Prepare a UDEV context to run monitoring on
1270+
hid_hotplug_context.udev_ctx = udev_new();
1271+
if(!hid_hotplug_context.udev_ctx)
1272+
{
1273+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
1274+
return -1;
1275+
}
1276+
1277+
hid_hotplug_context.mon = udev_monitor_new_from_netlink(hid_hotplug_context.udev_ctx, "udev");
1278+
udev_monitor_filter_add_match_subsystem_devtype(hid_hotplug_context.mon, "hidraw", NULL);
1279+
udev_monitor_enable_receiving(hid_hotplug_context.mon);
1280+
hid_hotplug_context.monitor_fd = udev_monitor_get_fd(hid_hotplug_context.mon);
1281+
1282+
/* After monitoring is all set up, enumerate all devices */
1283+
hid_hotplug_context.devs = hid_enumerate(0, 0);
1284+
1285+
/* Don't forget to actually register the callback */
1286+
hid_hotplug_context.hotplug_cbs = hotplug_cb;
1287+
1288+
/* Start the thread that will be doing the event scanning */
1289+
pthread_create(&hid_hotplug_context.thread, NULL, &hotplug_thread, NULL);
1290+
}
1291+
1292+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
1293+
1294+
return 0;
10191295
}
10201296

10211297
int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle)
10221298
{
1023-
/* Stub */
1024-
(void)callback_handle;
1299+
if (!hid_hotplug_context.mutex_ready) {
1300+
return -1;
1301+
}
10251302

1026-
return -1;
1303+
pthread_mutex_lock(&hid_hotplug_context.mutex);
1304+
1305+
if (hid_hotplug_context.hotplug_cbs == NULL) {
1306+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
1307+
return -1;
1308+
}
1309+
1310+
int result = -1;
1311+
1312+
/* Remove this notification */
1313+
for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) {
1314+
if ((*current)->handle == callback_handle) {
1315+
struct hid_hotplug_callback *next = (*current)->next;
1316+
free(*current);
1317+
*current = next;
1318+
result = 0;
1319+
break;
1320+
}
1321+
}
1322+
1323+
hid_internal_hotplug_cleanup();
1324+
1325+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
1326+
1327+
return result;
10271328
}
10281329

10291330
hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
@@ -1191,7 +1492,6 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
11911492
return 0; /* Success */
11921493
}
11931494

1194-
11951495
int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
11961496
{
11971497
int res;

0 commit comments

Comments
 (0)