Mac: Use IOKit IOService:/ paths for devices

Change hid_enumerate() and hid_open_path() to use IOKit IOService:/
paths to uniquely identify devices, instead of creating a path
(done by the make_path() function).

The path passed to hid_open_path() now must be a valid path to an
IOHIDDevice in the IOService plane, like:
"IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/
AppleUSBEHCI/PLAYSTATION(R)3
Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"
This commit is contained in:
Brendan Shanks
2013-04-13 13:20:33 -07:00
committed by Alan Ott
parent 98629a104b
commit 59a076705b
+113 -75
View File
@@ -24,12 +24,14 @@
#include <IOKit/hid/IOHIDManager.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <wchar.h>
#include <locale.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
#include <dlfcn.h>
#include "hidapi.h"
@@ -330,33 +332,61 @@ static wchar_t *dup_wcs(const wchar_t *s)
return ret;
}
static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
/* hidapi_IOHIDDeviceGetService()
*
* Return the io_service_t corresponding to a given IOHIDDeviceRef, either by:
* - on OS X 10.6 and above, calling IOHIDDeviceGetService()
* - on OS X 10.5, extract it from the IOHIDDevice struct
*/
static io_service_t hidapi_IOHIDDeviceGetService(IOHIDDeviceRef device)
{
int res;
unsigned short vid, pid;
char transport[32];
int32_t location;
static void *iokit_framework = NULL;
static io_service_t (*dynamic_IOHIDDeviceGetService)(IOHIDDeviceRef device) = NULL;
buf[0] = '\0';
/* Use dlopen()/dlsym() to get a pointer to IOHIDDeviceGetService() if it exists.
* If any of these steps fail, dynamic_IOHIDDeviceGetService will be left NULL
* and the fallback method will be used.
*/
if (iokit_framework == NULL) {
iokit_framework = dlopen("/System/Library/IOKit.framework/IOKit", RTLD_LAZY);
res = get_string_property_utf8(
device, CFSTR(kIOHIDTransportKey),
transport, sizeof(transport));
if (iokit_framework != NULL)
dynamic_IOHIDDeviceGetService = dlsym(iokit_framework, "IOHIDDeviceGetService");
}
if (!res)
return -1;
if (dynamic_IOHIDDeviceGetService != NULL) {
/* Running on OS X 10.6 and above: IOHIDDeviceGetService() exists */
return dynamic_IOHIDDeviceGetService(device);
}
else
{
/* Running on OS X 10.5: IOHIDDeviceGetService() doesn't exist.
*
* Be naughty and pull the service out of the IOHIDDevice.
* IOHIDDevice is an opaque struct not exposed to applications, but its
* layout is stable through all available versions of OS X.
* Tested and working on OS X 10.5.8 i386, x86_64, and ppc.
*/
struct IOHIDDevice_internal {
/* The first field of the IOHIDDevice struct is a
* CFRuntimeBase (which is a private CF struct).
*
* a, b, and c are the 3 fields that make up a CFRuntimeBase.
* See http://opensource.apple.com/source/CF/CF-476.18/CFRuntime.h
*
* The second field of the IOHIDDevice is the io_service_t we're looking for.
*/
uintptr_t a;
uint8_t b[4];
#if __LP64__
uint32_t c;
#endif
io_service_t service;
};
struct IOHIDDevice_internal *tmp = (struct IOHIDDevice_internal *)device;
location = get_location_id(device);
vid = get_vendor_id(device);
pid = get_product_id(device);
res = snprintf(buf, len, "%s_%04hx_%04hx_%x",
transport, vid, pid, location);
buf[len-1] = '\0';
return res+1;
return tmp->service;
}
}
/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */
@@ -434,7 +464,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
unsigned short dev_pid;
#define BUF_LEN 256
wchar_t buf[BUF_LEN];
char cbuf[BUF_LEN];
IOHIDDeviceRef dev = device_array[i];
@@ -448,7 +477,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
(product_id == 0x0 || product_id == dev_pid)) {
struct hid_device_info *tmp;
size_t len;
io_object_t iokit_dev;
kern_return_t res;
io_string_t path;
/* VID/PID match. Create the record. */
tmp = malloc(sizeof(struct hid_device_info));
@@ -466,8 +497,14 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
/* Fill out the record */
cur_dev->next = NULL;
len = make_path(dev, cbuf, sizeof(cbuf));
cur_dev->path = strdup(cbuf);
/* Fill in the path (IOService plane) */
iokit_dev = hidapi_IOHIDDeviceGetService(dev);
res = IORegistryEntryGetPath(iokit_dev, kIOServicePlane, path);
if (res == KERN_SUCCESS)
cur_dev->path = strdup(path);
else
cur_dev->path = strdup("");
/* Serial Number */
get_serial_number(dev, buf, BUF_LEN);
@@ -681,11 +718,15 @@ static void *read_thread(void *param)
return NULL;
}
/* hid_open_path()
*
* path must be a valid path to an IOHIDDevice in the IOService plane
* Example: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"
*/
hid_device * HID_API_EXPORT hid_open_path(const char *path)
{
int i;
hid_device *dev = NULL;
CFIndex num_devices;
io_registry_entry_t entry = MACH_PORT_NULL;
dev = new_hid_device();
@@ -693,64 +734,61 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path)
if (hid_init() < 0)
return NULL;
/* give the IOHIDManager a chance to update itself */
process_pending_events();
/* Get the IORegistry entry for the given path */
entry = IORegistryEntryFromPath(kIOMasterPortDefault, path);
if (entry == MACH_PORT_NULL) {
/* Path wasn't valid (maybe device was removed?) */
goto return_error;
}
CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);
/* Create an IOHIDDevice for the entry */
dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry);
if (dev->device_handle == NULL) {
/* Error creating the HID device */
goto return_error;
}
num_devices = CFSetGetCount(device_set);
IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));
CFSetGetValues(device_set, (const void **) device_array);
for (i = 0; i < num_devices; i++) {
char cbuf[BUF_LEN];
size_t len;
IOHIDDeviceRef os_dev = device_array[i];
/* Open the IOHIDDevice */
IOReturn ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice);
if (ret == kIOReturnSuccess) {
char str[32];
len = make_path(os_dev, cbuf, sizeof(cbuf));
if (!strcmp(cbuf, path)) {
/* Matched Paths. Open this Device. */
IOReturn ret = IOHIDDeviceOpen(os_dev, kIOHIDOptionsTypeSeizeDevice);
if (ret == kIOReturnSuccess) {
char str[32];
/* Create the buffers for receiving data */
dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle);
dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t));
free(device_array);
CFRetain(os_dev);
CFRelease(device_set);
dev->device_handle = os_dev;
/* Create the Run Loop Mode for this device.
printing the reference seems to work. */
sprintf(str, "HIDAPI_%p", dev->device_handle);
dev->run_loop_mode =
CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
/* Create the buffers for receiving data */
dev->max_input_report_len = (CFIndex) get_max_report_length(os_dev);
dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t));
/* Attach the device to a Run Loop */
IOHIDDeviceRegisterInputReportCallback(
dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
&hid_report_callback, dev);
IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);
/* Create the Run Loop Mode for this device.
printing the reference seems to work. */
sprintf(str, "HIDAPI_%p", os_dev);
dev->run_loop_mode =
CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
/* Start the read thread */
pthread_create(&dev->thread, NULL, read_thread, dev);
/* Attach the device to a Run Loop */
IOHIDDeviceRegisterInputReportCallback(
os_dev, dev->input_report_buf, dev->max_input_report_len,
&hid_report_callback, dev);
IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);
/* Wait here for the read thread to be initialized. */
pthread_barrier_wait(&dev->barrier);
/* Start the read thread */
pthread_create(&dev->thread, NULL, read_thread, dev);
/* Wait here for the read thread to be initialized. */
pthread_barrier_wait(&dev->barrier);
return dev;
}
else {
goto return_error;
}
}
IOObjectRelease(entry);
return dev;
}
else {
goto return_error;
}
return_error:
free(device_array);
CFRelease(device_set);
if (dev->device_handle != NULL)
CFRelease(dev->device_handle);
if (entry != MACH_PORT_NULL)
IOObjectRelease(entry);
free_hid_device(dev);
return NULL;
}