Skip to content

Commit 5dbf5e2

Browse files
k1-801CalcProgrammer1
authored andcommitted
Linux Hotplug: Connection-callback implementation for hidraw (libusb#647)
1 parent 94b300c commit 5dbf5e2

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>
@@ -71,6 +72,10 @@
7172
#define HIDIOCSOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0B, len)
7273
#endif
7374

75+
/* The value of the first callback handle to be given upon registration */
76+
/* Can be any arbitrary positive integer */
77+
#define FIRST_HOTPLUG_CALLBACK_HANDLE 1
78+
7479
struct hid_device_ {
7580
int device_handle;
7681
int blocking;
@@ -896,6 +901,96 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
896901
return HID_API_VERSION_STR;
897902
}
898903

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

1009+
9141010
int HID_API_EXPORT hid_exit(void)
9151011
{
9161012
/* Free global error message */
9171013
register_global_error(NULL);
9181014

1015+
hid_internal_hotplug_exit();
1016+
9191017
return 0;
9201018
}
9211019

1020+
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)
1021+
{
1022+
return (expected_vendor_id == 0x0 || vendor_id == expected_vendor_id) && (expected_product_id == 0x0 || product_id == expected_product_id);
1023+
}
1024+
9221025
struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
9231026
{
9241027
struct udev *udev;
@@ -1020,26 +1123,224 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
10201123
}
10211124
}
10221125

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

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

10371313
int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle)
10381314
{
1039-
/* Stub */
1040-
(void)callback_handle;
1315+
if (!hid_hotplug_context.mutex_ready) {
1316+
return -1;
1317+
}
10411318

1042-
return -1;
1319+
pthread_mutex_lock(&hid_hotplug_context.mutex);
1320+
1321+
if (hid_hotplug_context.hotplug_cbs == NULL) {
1322+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
1323+
return -1;
1324+
}
1325+
1326+
int result = -1;
1327+
1328+
/* Remove this notification */
1329+
for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) {
1330+
if ((*current)->handle == callback_handle) {
1331+
struct hid_hotplug_callback *next = (*current)->next;
1332+
free(*current);
1333+
*current = next;
1334+
result = 0;
1335+
break;
1336+
}
1337+
}
1338+
1339+
hid_internal_hotplug_cleanup();
1340+
1341+
pthread_mutex_unlock(&hid_hotplug_context.mutex);
1342+
1343+
return result;
10431344
}
10441345

10451346
hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
@@ -1222,7 +1523,6 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
12221523
return 0; /* Success */
12231524
}
12241525

1225-
12261526
int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
12271527
{
12281528
int res;

0 commit comments

Comments
 (0)