Made the Mac implementation robust to disconnects and re-connects while a device has an open handle.

This commit is contained in:
Alan Ott
2010-12-21 00:55:04 -05:00
parent c11c1bc463
commit 02b5007fc4
+134 -27
View File
@@ -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)