From 02b5007fc48d64a302c1ef150dbf74a648ac5397 Mon Sep 17 00:00:00 2001 From: Alan Ott Date: Tue, 21 Dec 2010 00:55:04 -0500 Subject: [PATCH] Made the Mac implementation robust to disconnects and re-connects while a device has an open handle. --- mac/hid.c | 161 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 134 insertions(+), 27 deletions(-) diff --git a/mac/hid.c b/mac/hid.c index 810be5e..2762f2e 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -40,12 +40,19 @@ struct hid_device_ { IOHIDDeviceRef device_handle; int blocking; int uses_numbered_reports; + int disconnected; CFStringRef run_loop_mode; uint8_t *input_report_buf; struct input_report *input_reports; pthread_mutex_t mutex; + + hid_device *next; }; +/* Static list of all the devices open. This way when a device gets + disconnected, its hid_device structure can be marked as disconnected + from hid_device_removal_callback(). */ +static hid_device *device_list = NULL; static hid_device *new_hid_device(void) { @@ -53,11 +60,75 @@ static hid_device *new_hid_device(void) dev->device_handle = NULL; dev->blocking = 1; dev->uses_numbered_reports = 0; + dev->disconnected = 0; + dev->run_loop_mode = NULL; + dev->input_report_buf = NULL; dev->input_reports = NULL; + dev->next = NULL; + + pthread_mutex_init(&dev->mutex, NULL); + + /* Add the new record to the device_list. */ + if (!device_list) + device_list = dev; + else { + hid_device *d = device_list; + while (d) { + if (!d->next) { + d->next = dev; + break; + } + d = d->next; + } + } return dev; } +static void free_hid_device(hid_device *dev) +{ + if (!dev) + return; + + /* Delete any input reports still left over. */ + struct input_report *rpt = dev->input_reports; + while (rpt) { + struct input_report *next = rpt->next; + free(rpt->data); + free(rpt); + rpt = next; + } + + /* Free the string and the report buffer. The check for NULL + is necessary here as CFRelease() doesn't handle NULL like + free() and others do. */ + if (dev->run_loop_mode) + CFRelease(dev->run_loop_mode); + free(dev->input_report_buf); + + pthread_mutex_destroy(&dev->mutex); + + /* Remove it from the device list. */ + hid_device *d = device_list; + if (d == dev) { + device_list = d->next; + } + else { + while (d) { + if (d->next == dev) { + d->next = d->next->next; + break; + } + + d = d->next; + } + } + + /* Free the structure itself. */ + free(dev); + +} + static IOHIDManagerRef hid_mgr = 0x0; @@ -346,6 +417,19 @@ hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short pr return handle; } +static void hid_device_removal_callback(void *context, IOReturn result, + void *sender, IOHIDDeviceRef dev_ref) +{ + hid_device *d = device_list; + while (d) { + if (d->device_handle == dev_ref) { + d->disconnected = 1; + } + + d = d->next; + } +} + /* The Run Loop calls this function for each input report received. This function puts the data into a linked list to be picked up by hid_read(). */ @@ -430,8 +514,8 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) IOHIDDeviceRegisterInputReportCallback( os_dev, dev->input_report_buf, max_input_report_len, &hid_report_callback, dev); + IOHIDManagerRegisterDeviceRemovalCallback(hid_mgr, hid_device_removal_callback, NULL); - pthread_mutex_init(&dev->mutex, NULL); return dev; } @@ -444,7 +528,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) return_error: free(device_array); CFRelease(device_set); - free(dev); + free_hid_device(dev); return NULL; } @@ -454,6 +538,10 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char size_t length_to_send; IOReturn res; + /* Return if the device has been disconnected. */ + if (dev->disconnected) + return -1; + if (data[0] == 0x0) { /* Not using numbered Reports. Don't send the report number. */ @@ -467,17 +555,20 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char length_to_send = length; } - res = IOHIDDeviceSetReport(dev->device_handle, - type, - data[0], /* Report ID*/ - data_to_send, length_to_send); + if (!dev->disconnected) { + res = IOHIDDeviceSetReport(dev->device_handle, + type, + data[0], /* Report ID*/ + data_to_send, length_to_send); - if (res == kIOReturnSuccess) { - return length; + if (res == kIOReturnSuccess) { + return length; + } + else + return -1; } - else - return -1; - + + return -1; } int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) @@ -512,6 +603,12 @@ int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) ret_val = return_data(dev, data, length); goto ret; } + + /* Return if the device has been disconnected. */ + if (dev->disconnected) { + ret_val = -1; + goto ret; + } /* There are no input reports queued up. Need to get some from the OS. */ @@ -527,6 +624,14 @@ int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) while (1) { code = CFRunLoopRunInMode(dev->run_loop_mode, 1000, TRUE); + /* Return if the device has been disconnected */ + if (code == kCFRunLoopRunFinished) { + dev->disconnected = 1; + ret_val = -1; + goto ret; + } + + /* Return if some data showed up. */ if (dev->input_reports) break; @@ -543,6 +648,7 @@ int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) goto ret; } else { + dev->disconnected = 1; ret_val = -1; /* An error occured (maybe CTRL-C?). */ goto ret; } @@ -551,6 +657,13 @@ int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) /* Non-blocking. See if the OS has any reports to give. */ SInt32 code; code = CFRunLoopRunInMode(dev->run_loop_mode, 0, TRUE); + if (code == kCFRunLoopRunFinished) { + /* The run loop is finished, indicating an error + or the device had been disconnected. */ + dev->disconnected = 1; + ret_val = -1; + goto ret; + } if (dev->input_reports) { /* Return the first one */ ret_val = return_data(dev, data, length); @@ -586,6 +699,10 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, CFIndex len = length; IOReturn res; + /* Return if the device has been unplugged. */ + if (dev->disconnected) + return -1; + res = IOHIDDeviceGetReport(dev->device_handle, kIOHIDReportTypeFeature, data[0], /* Report ID */ @@ -602,24 +719,14 @@ void HID_API_EXPORT hid_close(hid_device *dev) if (!dev) return; - /* Close the OS handle to the device. */ - IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeNone); - - /* Delete any input reports still left over. */ - struct input_report *rpt = dev->input_reports; - while (rpt) { - struct input_report *next = rpt->next; - free(rpt->data); - free(rpt); - rpt = next; + /* Close the OS handle to the device, but only if it's not + been unplugged. If it's been unplugged, then calling + IOHIDDeviceClose() will crash. */ + if (!dev->disconnected) { + IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeNone); } - /* Free the string and the report buffer. */ - CFRelease(dev->run_loop_mode); - free(dev->input_report_buf); - pthread_mutex_destroy(&dev->mutex); - - free(dev); + free_hid_device(dev); } int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)