Ugly workaround for lack of multiple HID device support on OSX. 3.5.1
authorMichael McMaster <michael@codesrc.com>
Thu, 17 Jul 2014 13:57:31 +0000 (23:57 +1000)
committerMichael McMaster <michael@codesrc.com>
Thu, 17 Jul 2014 13:57:31 +0000 (23:57 +1000)
software/bootloaderhost/Makefile
software/bootloaderhost/SCSI2SD_HID.cc
software/bootloaderhost/hidapi-mac/hid.c [new file with mode: 0644]
software/scsi2sd-config/Makefile
software/scsi2sd-config/scsi2sd-config.cc
software/scsi2sd-debug/Makefile

index 83a482f..0273813 100644 (file)
@@ -28,10 +28,10 @@ ifeq ($(TARGET),Linux)
 endif
 ifeq ($(TARGET),Darwin)
        # Should match OSX
-       VPATH += hidapi/mac
+       VPATH += hidapi-mac
        LDFLAGS += -framework IOKit -framework CoreFoundation
        CFLAGS += -mmacosx-version-min=10.7
-       CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=10.7
+       CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=10.7 -std=c++0x
        CC=clang
        CXX=clang++
        BUILD=build/mac
index c5f2889..535f102 100644 (file)
@@ -32,18 +32,57 @@ HID::HID(hid_device_info* hidInfo) :
        myFirmwareVersion(0),
        mySDCapacity(0)
 {
-       while (hidInfo)
+       // hidInfo->interface_number not supported on mac, and interfaces
+       // are enumerated in a random order. :-(
+       // We rely on the watchdog value of the debug interface changing on each
+       // read to differentiate the interfaces.
+       while (hidInfo && !(myConfigHandle && myDebugHandle))
        {
                if (hidInfo->interface_number == CONFIG_INTERFACE)
                {
                        myConfigHandle = hid_open_path(hidInfo->path);
+                       hidInfo = hidInfo->next;
                }
                else if (hidInfo->interface_number == DEBUG_INTERFACE)
                {
                        myDebugHandle = hid_open_path(hidInfo->path);
                        readDebugData();
+                       hidInfo = hidInfo->next;
+               }
+               else if (hidInfo->interface_number == -1)
+               {
+                       // hidInfo->interface_number not supported on mac, and
+                       // interfaces are enumerated in a random order. :-(
+                       // We rely on the watchdog value of the debug interface
+                       // changing on each read to differentiate the interfaces.
+                       hid_device* dev = hid_open_path(hidInfo->path);
+                       if (!dev)
+                       {
+                               hidInfo = hidInfo->next;
+                               continue;
+                       }
+
+                       uint8_t buf[HID_PACKET_SIZE];
+                       int watchVal = -1;
+                       int configIntFound = 1;
+                       for (int i = 0; i < 4; ++i)
+                       {
+                               buf[0] = 0; // report id
+                               hid_read(dev, buf, HID_PACKET_SIZE);
+                               if (watchVal == -1) watchVal = buf[25];
+                               configIntFound = configIntFound && (buf[25] == watchVal);
+                       }
+
+                       if (configIntFound)
+                       {
+                               myConfigHandle = dev;
+                       }
+                       else
+                       {
+                               myDebugHandle = dev;
+                               readDebugData();
+                       }
                }
-               hidInfo = hidInfo->next;
        }
 }
 
diff --git a/software/bootloaderhost/hidapi-mac/hid.c b/software/bootloaderhost/hidapi-mac/hid.c
new file mode 100644 (file)
index 0000000..153101f
--- /dev/null
@@ -0,0 +1,1119 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 2010-07-03
+
+ Copyright 2010, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+        http://github.com/signal11/hidapi .
+********************************************************/
+
+/* See Apple Technical Note TN2187 for details on IOHidManager. */
+
+#include <IOKit/hid/IOHIDManager.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <wchar.h>
+#include <locale.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "hidapi.h"
+
+/* Barrier implementation because Mac OSX doesn't have pthread_barrier.
+   It also doesn't have clock_gettime(). So much for POSIX and SUSv2.
+   This implementation came from Brent Priddy and was posted on
+   StackOverflow. It is used with his permission. */
+typedef int pthread_barrierattr_t;
+typedef struct pthread_barrier {
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    int count;
+    int trip_count;
+} pthread_barrier_t;
+
+static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
+{
+       if(count == 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if(pthread_mutex_init(&barrier->mutex, 0) < 0) {
+               return -1;
+       }
+       if(pthread_cond_init(&barrier->cond, 0) < 0) {
+               pthread_mutex_destroy(&barrier->mutex);
+               return -1;
+       }
+       barrier->trip_count = count;
+       barrier->count = 0;
+
+       return 0;
+}
+
+static int pthread_barrier_destroy(pthread_barrier_t *barrier)
+{
+       pthread_cond_destroy(&barrier->cond);
+       pthread_mutex_destroy(&barrier->mutex);
+       return 0;
+}
+
+static int pthread_barrier_wait(pthread_barrier_t *barrier)
+{
+       pthread_mutex_lock(&barrier->mutex);
+       ++(barrier->count);
+       if(barrier->count >= barrier->trip_count)
+       {
+               barrier->count = 0;
+               pthread_cond_broadcast(&barrier->cond);
+               pthread_mutex_unlock(&barrier->mutex);
+               return 1;
+       }
+       else
+       {
+               pthread_cond_wait(&barrier->cond, &(barrier->mutex));
+               pthread_mutex_unlock(&barrier->mutex);
+               return 0;
+       }
+}
+
+static int return_data(hid_device *dev, unsigned char *data, size_t length);
+
+/* Linked List of input reports received from the device. */
+struct input_report {
+       uint8_t *data;
+       size_t len;
+       struct input_report *next;
+};
+
+struct hid_device_ {
+       IOHIDDeviceRef device_handle;
+       int blocking;
+       int uses_numbered_reports;
+       int disconnected;
+       CFStringRef run_loop_mode;
+       CFRunLoopRef run_loop;
+       CFRunLoopSourceRef source;
+       uint8_t *input_report_buf;
+       CFIndex max_input_report_len;
+       struct input_report *input_reports;
+
+       pthread_t thread;
+       pthread_mutex_t mutex; /* Protects input_reports */
+       pthread_cond_t condition;
+       pthread_barrier_t barrier; /* Ensures correct startup sequence */
+       pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */
+       int shutdown_thread;
+};
+
+static hid_device *new_hid_device(void)
+{
+       hid_device *dev = calloc(1, sizeof(hid_device));
+       dev->device_handle = NULL;
+       dev->blocking = 1;
+       dev->uses_numbered_reports = 0;
+       dev->disconnected = 0;
+       dev->run_loop_mode = NULL;
+       dev->run_loop = NULL;
+       dev->source = NULL;
+       dev->input_report_buf = NULL;
+       dev->input_reports = NULL;
+       dev->shutdown_thread = 0;
+
+       /* Thread objects */
+       pthread_mutex_init(&dev->mutex, NULL);
+       pthread_cond_init(&dev->condition, NULL);
+       pthread_barrier_init(&dev->barrier, NULL, 2);
+       pthread_barrier_init(&dev->shutdown_barrier, NULL, 2);
+
+       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);
+       if (dev->source)
+               CFRelease(dev->source);
+       free(dev->input_report_buf);
+
+       /* Clean up the thread objects */
+       pthread_barrier_destroy(&dev->shutdown_barrier);
+       pthread_barrier_destroy(&dev->barrier);
+       pthread_cond_destroy(&dev->condition);
+       pthread_mutex_destroy(&dev->mutex);
+
+       /* Free the structure itself. */
+       free(dev);
+}
+
+static IOHIDManagerRef hid_mgr = 0x0;
+
+
+#if 0
+static void register_error(hid_device *device, const char *op)
+{
+
+}
+#endif
+
+
+static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key)
+{
+       CFTypeRef ref;
+       int32_t value;
+
+       ref = IOHIDDeviceGetProperty(device, key);
+       if (ref) {
+               if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
+                       CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value);
+                       return value;
+               }
+       }
+       return 0;
+}
+
+static unsigned short get_vendor_id(IOHIDDeviceRef device)
+{
+       return get_int_property(device, CFSTR(kIOHIDVendorIDKey));
+}
+
+static unsigned short get_product_id(IOHIDDeviceRef device)
+{
+       return get_int_property(device, CFSTR(kIOHIDProductIDKey));
+}
+
+static int32_t get_location_id(IOHIDDeviceRef device)
+{
+       return get_int_property(device, CFSTR(kIOHIDLocationIDKey));
+}
+
+static int32_t get_max_report_length(IOHIDDeviceRef device)
+{
+       return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey));
+}
+
+static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len)
+{
+       CFStringRef str;
+
+       if (!len)
+               return 0;
+
+       str = IOHIDDeviceGetProperty(device, prop);
+
+       buf[0] = 0;
+
+       if (str) {
+               CFIndex str_len = CFStringGetLength(str);
+               CFRange range;
+               CFIndex used_buf_len;
+               CFIndex chars_copied;
+
+               len --;
+
+               range.location = 0;
+               range.length = ((size_t)str_len > len)? len: (size_t)str_len;
+               chars_copied = CFStringGetBytes(str,
+                       range,
+                       kCFStringEncodingUTF32LE,
+                       (char)'?',
+                       FALSE,
+                       (UInt8*)buf,
+                       len * sizeof(wchar_t),
+                       &used_buf_len);
+
+               if (chars_copied == len)
+                       buf[len] = 0; /* len is decremented above */
+               else
+                       buf[chars_copied] = 0;
+
+               return 0;
+       }
+       else
+               return -1;
+
+}
+
+static int get_string_property_utf8(IOHIDDeviceRef device, CFStringRef prop, char *buf, size_t len)
+{
+       CFStringRef str;
+       if (!len)
+               return 0;
+
+       str = IOHIDDeviceGetProperty(device, prop);
+
+       buf[0] = 0;
+
+       if (str) {
+               len--;
+
+               CFIndex str_len = CFStringGetLength(str);
+               CFRange range;
+               range.location = 0;
+               range.length = str_len;
+               CFIndex used_buf_len;
+               CFIndex chars_copied;
+               chars_copied = CFStringGetBytes(str,
+                       range,
+                       kCFStringEncodingUTF8,
+                       (char)'?',
+                       FALSE,
+                       (UInt8*)buf,
+                       len,
+                       &used_buf_len);
+
+               if (used_buf_len == len)
+                       buf[len] = 0; /* len is decremented above */
+               else
+                       buf[used_buf_len] = 0;
+
+               return used_buf_len;
+       }
+       else
+               return 0;
+}
+
+
+static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+       return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len);
+}
+
+static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+       return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len);
+}
+
+static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+       return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len);
+}
+
+
+/* Implementation of wcsdup() for Mac. */
+static wchar_t *dup_wcs(const wchar_t *s)
+{
+       size_t len = wcslen(s);
+       wchar_t *ret = malloc((len+1)*sizeof(wchar_t));
+       wcscpy(ret, s);
+
+       return ret;
+}
+
+
+static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
+{
+       int res;
+       unsigned short vid, pid;
+       char transport[32];
+       int32_t location;
+
+       buf[0] = '\0';
+
+       res = get_string_property_utf8(
+               device, CFSTR(kIOHIDTransportKey),
+               transport, sizeof(transport));
+
+       if (!res)
+               return -1;
+
+       location = get_location_id(device);
+       vid = get_vendor_id(device);
+       pid = get_product_id(device);
+
+       int interval = get_int_property(device, CFSTR(kIOHIDReportIntervalKey));
+
+       res = snprintf(buf, len, "%s_%04hx_%04hx_%x_%d",
+                       transport, vid, pid, location, interval);
+
+
+       buf[len-1] = '\0';
+       return res+1;
+}
+
+/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */
+static int init_hid_manager(void)
+{
+       /* Initialize all the HID Manager Objects */
+       hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+       if (hid_mgr) {
+               IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
+               IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+               return 0;
+       }
+
+       return -1;
+}
+
+/* Initialize the IOHIDManager if necessary. This is the public function, and
+   it is safe to call this function repeatedly. Return 0 for success and -1
+   for failure. */
+int HID_API_EXPORT hid_init(void)
+{
+       if (!hid_mgr) {
+               return init_hid_manager();
+       }
+
+       /* Already initialized. */
+       return 0;
+}
+
+int HID_API_EXPORT hid_exit(void)
+{
+       if (hid_mgr) {
+               /* Close the HID manager. */
+               IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone);
+               CFRelease(hid_mgr);
+               hid_mgr = NULL;
+       }
+
+       return 0;
+}
+
+static void process_pending_events(void) {
+       SInt32 res;
+       do {
+               res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE);
+       } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut);
+}
+
+struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+{
+       struct hid_device_info *root = NULL; /* return object */
+       struct hid_device_info *cur_dev = NULL;
+       CFIndex num_devices;
+       int i;
+
+       /* Set up the HID Manager if it hasn't been done */
+       if (hid_init() < 0)
+               return NULL;
+
+       /* give the IOHIDManager a chance to update itself */
+       process_pending_events();
+
+       /* Get a list of the Devices */
+       IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
+       CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);
+
+       /* Convert the list into a C array so we can iterate easily. */
+       num_devices = CFSetGetCount(device_set);
+       IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));
+       CFSetGetValues(device_set, (const void **) device_array);
+
+       /* Iterate over each device, making an entry for it. */
+       for (i = 0; i < num_devices; i++) {
+               unsigned short dev_vid;
+               unsigned short dev_pid;
+               #define BUF_LEN 256
+               wchar_t buf[BUF_LEN];
+               char cbuf[BUF_LEN];
+
+               IOHIDDeviceRef dev = device_array[i];
+
+        if (!dev) {
+            continue;
+        }
+               dev_vid = get_vendor_id(dev);
+               dev_pid = get_product_id(dev);
+
+               /* Check the VID/PID against the arguments */
+               if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
+                   (product_id == 0x0 || product_id == dev_pid)) {
+                       struct hid_device_info *tmp;
+                       size_t len;
+
+                       /* VID/PID match. Create the record. */
+                       tmp = malloc(sizeof(struct hid_device_info));
+                       if (cur_dev) {
+                               cur_dev->next = tmp;
+                       }
+                       else {
+                               root = tmp;
+                       }
+                       cur_dev = tmp;
+
+                       /* Get the Usage Page and Usage for this device. */
+                       cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey));
+                       cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey));
+
+                       /* Fill out the record */
+                       cur_dev->next = NULL;
+                       len = make_path(dev, cbuf, sizeof(cbuf));
+                       cur_dev->path = strdup(cbuf);
+
+                       /* Serial Number */
+                       get_serial_number(dev, buf, BUF_LEN);
+                       cur_dev->serial_number = dup_wcs(buf);
+
+                       /* Manufacturer and Product strings */
+                       get_manufacturer_string(dev, buf, BUF_LEN);
+                       cur_dev->manufacturer_string = dup_wcs(buf);
+                       get_product_string(dev, buf, BUF_LEN);
+                       cur_dev->product_string = dup_wcs(buf);
+
+                       /* VID/PID */
+                       cur_dev->vendor_id = dev_vid;
+                       cur_dev->product_id = dev_pid;
+
+                       /* Release Number */
+                       cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));
+
+                       /* Interface Number (Unsupported on Mac)*/
+                       cur_dev->interface_number = -1;
+               }
+       }
+
+       free(device_array);
+       CFRelease(device_set);
+
+       return root;
+}
+
+void  HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
+{
+       /* This function is identical to the Linux version. Platform independent. */
+       struct hid_device_info *d = devs;
+       while (d) {
+               struct hid_device_info *next = d->next;
+               free(d->path);
+               free(d->serial_number);
+               free(d->manufacturer_string);
+               free(d->product_string);
+               free(d);
+               d = next;
+       }
+}
+
+hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
+{
+       /* This function is identical to the Linux version. Platform independent. */
+       struct hid_device_info *devs, *cur_dev;
+       const char *path_to_open = NULL;
+       hid_device * handle = NULL;
+
+       devs = hid_enumerate(vendor_id, product_id);
+       cur_dev = devs;
+       while (cur_dev) {
+               if (cur_dev->vendor_id == vendor_id &&
+                   cur_dev->product_id == product_id) {
+                       if (serial_number) {
+                               if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
+                                       path_to_open = cur_dev->path;
+                                       break;
+                               }
+                       }
+                       else {
+                               path_to_open = cur_dev->path;
+                               break;
+                       }
+               }
+               cur_dev = cur_dev->next;
+       }
+
+       if (path_to_open) {
+               /* Open the device */
+               handle = hid_open_path(path_to_open);
+       }
+
+       hid_free_enumeration(devs);
+
+       return handle;
+}
+
+static void hid_device_removal_callback(void *context, IOReturn result,
+                                        void *sender)
+{
+       /* Stop the Run Loop for this device. */
+       hid_device *d = context;
+
+       d->disconnected = 1;
+       CFRunLoopStop(d->run_loop);
+}
+
+/* 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(). */
+static void hid_report_callback(void *context, IOReturn result, void *sender,
+                         IOHIDReportType report_type, uint32_t report_id,
+                         uint8_t *report, CFIndex report_length)
+{
+       struct input_report *rpt;
+       hid_device *dev = context;
+
+       /* Make a new Input Report object */
+       rpt = calloc(1, sizeof(struct input_report));
+       rpt->data = calloc(1, report_length);
+       memcpy(rpt->data, report, report_length);
+       rpt->len = report_length;
+       rpt->next = NULL;
+
+       /* Lock this section */
+       pthread_mutex_lock(&dev->mutex);
+
+       /* Attach the new report object to the end of the list. */
+       if (dev->input_reports == NULL) {
+               /* The list is empty. Put it at the root. */
+               dev->input_reports = rpt;
+       }
+       else {
+               /* Find the end of the list and attach. */
+               struct input_report *cur = dev->input_reports;
+               int num_queued = 0;
+               while (cur->next != NULL) {
+                       cur = cur->next;
+                       num_queued++;
+               }
+               cur->next = rpt;
+
+               /* Pop one off if we've reached 30 in the queue. This
+                  way we don't grow forever if the user never reads
+                  anything from the device. */
+               if (num_queued > 30) {
+                       return_data(dev, NULL, 0);
+               }
+       }
+
+       /* Signal a waiting thread that there is data. */
+       pthread_cond_signal(&dev->condition);
+
+       /* Unlock */
+       pthread_mutex_unlock(&dev->mutex);
+
+}
+
+/* This gets called when the read_thred's run loop gets signaled by
+   hid_close(), and serves to stop the read_thread's run loop. */
+static void perform_signal_callback(void *context)
+{
+       hid_device *dev = context;
+       CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/
+}
+
+static void *read_thread(void *param)
+{
+       hid_device *dev = param;
+       SInt32 code;
+
+       /* Move the device's run loop to this thread. */
+       IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode);
+
+       /* Create the RunLoopSource which is used to signal the
+          event loop to stop when hid_close() is called. */
+       CFRunLoopSourceContext ctx;
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.version = 0;
+       ctx.info = dev;
+       ctx.perform = &perform_signal_callback;
+       dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx);
+       CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode);
+
+       /* Store off the Run Loop so it can be stopped from hid_close()
+          and on device disconnection. */
+       dev->run_loop = CFRunLoopGetCurrent();
+
+       /* Notify the main thread that the read thread is up and running. */
+       pthread_barrier_wait(&dev->barrier);
+
+       /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input
+          reports into the hid_report_callback(). */
+       while (!dev->shutdown_thread && !dev->disconnected) {
+               code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE);
+               /* Return if the device has been disconnected */
+               if (code == kCFRunLoopRunFinished) {
+                       dev->disconnected = 1;
+                       break;
+               }
+
+
+               /* Break if The Run Loop returns Finished or Stopped. */
+               if (code != kCFRunLoopRunTimedOut &&
+                   code != kCFRunLoopRunHandledSource) {
+                       /* There was some kind of error. Setting
+                          shutdown seems to make sense, but
+                          there may be something else more appropriate */
+                       dev->shutdown_thread = 1;
+                       break;
+               }
+       }
+
+       /* Now that the read thread is stopping, Wake any threads which are
+          waiting on data (in hid_read_timeout()). Do this under a mutex to
+          make sure that a thread which is about to go to sleep waiting on
+          the condition acutally will go to sleep before the condition is
+          signaled. */
+       pthread_mutex_lock(&dev->mutex);
+       pthread_cond_broadcast(&dev->condition);
+       pthread_mutex_unlock(&dev->mutex);
+
+       /* Wait here until hid_close() is called and makes it past
+          the call to CFRunLoopWakeUp(). This thread still needs to
+          be valid when that function is called on the other thread. */
+       pthread_barrier_wait(&dev->shutdown_barrier);
+
+       return NULL;
+}
+
+hid_device * HID_API_EXPORT hid_open_path(const char *path)
+{
+       int i;
+       hid_device *dev = NULL;
+       CFIndex num_devices;
+
+       dev = new_hid_device();
+
+       /* Set up the HID Manager if it hasn't been done */
+       if (hid_init() < 0)
+               return NULL;
+
+       /* give the IOHIDManager a chance to update itself */
+       process_pending_events();
+
+       CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);
+
+       num_devices = CFSetGetCount(device_set);
+       IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));
+       CFSetGetValues(device_set, (const void **) device_array);
+
+       i = rand() % num_devices;
+       for (int j = 0; j < num_devices; j++) {
+               ++i;
+               if (i >= num_devices) i = 0;
+
+               char cbuf[BUF_LEN];
+               size_t len;
+               IOHIDDeviceRef os_dev = device_array[i];
+
+               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];
+
+                               free(device_array);
+                               CFRetain(os_dev);
+                               CFRelease(device_set);
+                               dev->device_handle = os_dev;
+
+                               /* 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));
+
+                               /* 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);
+
+                               /* 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);
+
+                               /* 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;
+                       }
+               }
+       }
+
+return_error:
+       free(device_array);
+       CFRelease(device_set);
+       free_hid_device(dev);
+       return NULL;
+}
+
+static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length)
+{
+       const unsigned char *data_to_send;
+       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. */
+               data_to_send = data+1;
+               length_to_send = length-1;
+       }
+       else {
+               /* Using numbered Reports.
+                  Send the Report Number */
+               data_to_send = data;
+               length_to_send = length;
+       }
+
+       if (!dev->disconnected) {
+               res = IOHIDDeviceSetReport(dev->device_handle,
+                                          type,
+                                          data[0], /* Report ID*/
+                                          data_to_send, length_to_send);
+
+               if (res == kIOReturnSuccess) {
+                       return length;
+               }
+               else
+                       return -1;
+       }
+
+       return -1;
+}
+
+int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
+{
+       return set_report(dev, kIOHIDReportTypeOutput, data, length);
+}
+
+/* Helper function, so that this isn't duplicated in hid_read(). */
+static int return_data(hid_device *dev, unsigned char *data, size_t length)
+{
+       /* Copy the data out of the linked list item (rpt) into the
+          return buffer (data), and delete the liked list item. */
+       struct input_report *rpt = dev->input_reports;
+       size_t len = (length < rpt->len)? length: rpt->len;
+       memcpy(data, rpt->data, len);
+       dev->input_reports = rpt->next;
+       free(rpt->data);
+       free(rpt);
+       return len;
+}
+
+static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+       while (!dev->input_reports) {
+               int res = pthread_cond_wait(cond, mutex);
+               if (res != 0)
+                       return res;
+
+               /* A res of 0 means we may have been signaled or it may
+                  be a spurious wakeup. Check to see that there's acutally
+                  data in the queue before returning, and if not, go back
+                  to sleep. See the pthread_cond_timedwait() man page for
+                  details. */
+
+               if (dev->shutdown_thread || dev->disconnected)
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
+{
+       while (!dev->input_reports) {
+               int res = pthread_cond_timedwait(cond, mutex, abstime);
+               if (res != 0)
+                       return res;
+
+               /* A res of 0 means we may have been signaled or it may
+                  be a spurious wakeup. Check to see that there's acutally
+                  data in the queue before returning, and if not, go back
+                  to sleep. See the pthread_cond_timedwait() man page for
+                  details. */
+
+               if (dev->shutdown_thread || dev->disconnected)
+                       return -1;
+       }
+
+       return 0;
+
+}
+
+int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
+{
+       int bytes_read = -1;
+
+       /* Lock the access to the report list. */
+       pthread_mutex_lock(&dev->mutex);
+
+       /* There's an input report queued up. Return it. */
+       if (dev->input_reports) {
+               /* Return the first one */
+               bytes_read = return_data(dev, data, length);
+               goto ret;
+       }
+
+       /* Return if the device has been disconnected. */
+       if (dev->disconnected) {
+               bytes_read = -1;
+               goto ret;
+       }
+
+       if (dev->shutdown_thread) {
+               /* This means the device has been closed (or there
+                  has been an error. An error code of -1 should
+                  be returned. */
+               bytes_read = -1;
+               goto ret;
+       }
+
+       /* There is no data. Go to sleep and wait for data. */
+
+       if (milliseconds == -1) {
+               /* Blocking */
+               int res;
+               res = cond_wait(dev, &dev->condition, &dev->mutex);
+               if (res == 0)
+                       bytes_read = return_data(dev, data, length);
+               else {
+                       /* There was an error, or a device disconnection. */
+                       bytes_read = -1;
+               }
+       }
+       else if (milliseconds > 0) {
+               /* Non-blocking, but called with timeout. */
+               int res;
+               struct timespec ts;
+               struct timeval tv;
+               gettimeofday(&tv, NULL);
+               TIMEVAL_TO_TIMESPEC(&tv, &ts);
+               ts.tv_sec += milliseconds / 1000;
+               ts.tv_nsec += (milliseconds % 1000) * 1000000;
+               if (ts.tv_nsec >= 1000000000L) {
+                       ts.tv_sec++;
+                       ts.tv_nsec -= 1000000000L;
+               }
+
+               res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts);
+               if (res == 0)
+                       bytes_read = return_data(dev, data, length);
+               else if (res == ETIMEDOUT)
+                       bytes_read = 0;
+               else
+                       bytes_read = -1;
+       }
+       else {
+               /* Purely non-blocking */
+               bytes_read = 0;
+       }
+
+ret:
+       /* Unlock */
+       pthread_mutex_unlock(&dev->mutex);
+       return bytes_read;
+}
+
+int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
+{
+       return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
+}
+
+int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
+{
+       /* All Nonblocking operation is handled by the library. */
+       dev->blocking = !nonblock;
+
+       return 0;
+}
+
+int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
+{
+       return set_report(dev, kIOHIDReportTypeFeature, data, length);
+}
+
+int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
+{
+       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 */
+                                  data, &len);
+       if (res == kIOReturnSuccess)
+               return len;
+       else
+               return -1;
+}
+
+
+void HID_API_EXPORT hid_close(hid_device *dev)
+{
+       if (!dev)
+               return;
+
+       /* Disconnect the report callback before close. */
+       if (!dev->disconnected) {
+               IOHIDDeviceRegisterInputReportCallback(
+                       dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
+                       NULL, dev);
+               IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev);
+               IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode);
+               IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
+       }
+
+       /* Cause read_thread() to stop. */
+       dev->shutdown_thread = 1;
+
+       /* Wake up the run thread's event loop so that the thread can exit. */
+       CFRunLoopSourceSignal(dev->source);
+       CFRunLoopWakeUp(dev->run_loop);
+
+       /* Notify the read thread that it can shut down now. */
+       pthread_barrier_wait(&dev->shutdown_barrier);
+
+       /* Wait for read_thread() to end. */
+       pthread_join(dev->thread, NULL);
+
+       /* 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, kIOHIDOptionsTypeSeizeDevice);
+       }
+
+       /* Clear out the queue of received reports. */
+       pthread_mutex_lock(&dev->mutex);
+       while (dev->input_reports) {
+               return_data(dev, NULL, 0);
+       }
+       pthread_mutex_unlock(&dev->mutex);
+       CFRelease(dev->device_handle);
+
+       free_hid_device(dev);
+}
+
+int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+       return get_manufacturer_string(dev->device_handle, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+       return get_product_string(dev->device_handle, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+       return get_serial_number(dev->device_handle, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
+{
+       /* TODO: */
+
+       return 0;
+}
+
+
+HID_API_EXPORT const wchar_t * HID_API_CALL  hid_error(hid_device *dev)
+{
+       /* TODO: */
+
+       return NULL;
+}
+
+
+
+
+
+
+
+#if 0
+static int32_t get_usage(IOHIDDeviceRef device)
+{
+       int32_t res;
+       res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey));
+       if (!res)
+               res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey));
+       return res;
+}
+
+static int32_t get_usage_page(IOHIDDeviceRef device)
+{
+       int32_t res;
+       res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey));
+       if (!res)
+               res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey));
+       return res;
+}
+
+static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+       return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len);
+}
+
+
+int main(void)
+{
+       IOHIDManagerRef mgr;
+       int i;
+
+       mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+       IOHIDManagerSetDeviceMatching(mgr, NULL);
+       IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone);
+
+       CFSetRef device_set = IOHIDManagerCopyDevices(mgr);
+
+       CFIndex 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++) {
+               IOHIDDeviceRef dev = device_array[i];
+               printf("Device: %p\n", dev);
+               printf("  %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev));
+
+               wchar_t serial[256], buf[256];
+               char cbuf[256];
+               get_serial_number(dev, serial, 256);
+
+
+               printf("  Serial: %ls\n", serial);
+               printf("  Loc: %ld\n", get_location_id(dev));
+               get_transport(dev, buf, 256);
+               printf("  Trans: %ls\n", buf);
+               make_path(dev, cbuf, 256);
+               printf("  Path: %s\n", cbuf);
+
+       }
+
+       return 0;
+}
+#endif
index 9433699..11a8d85 100644 (file)
@@ -28,10 +28,10 @@ ifeq ($(TARGET),Linux)
 endif
 ifeq ($(TARGET),Darwin)
        # Should match OSX
-       VPATH += ../bootloaderhost/hidapi/mac
+       VPATH += ../bootloaderhost/hidapi-mac
        LDFLAGS += -framework IOKit -framework CoreFoundation
        CFLAGS += -mmacosx-version-min=10.7
-       CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=10.7
+       CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=10.7 -std=c++0x
        CC=clang
        CXX=clang++
        BUILD=build/mac
index eca98c6..bbd685c 100644 (file)
@@ -113,14 +113,18 @@ struct __attribute__((packed)) ConfigPacket
 
 static void printConfig(ConfigPacket* packet)
 {
-       printf("SCSI ID:\t\t\t%d\n", packet->scsiId);
-       printf("Vendor:\t\t\t\t\"%.*s\"\n", 8, packet->vendor);
-       printf("Product ID:\t\t\t\"%.*s\"\n", 16, packet->prodId);
-       printf("Revision:\t\t\t\"%.*s\"\n", 4, packet->revision);
-       printf("\n");
-       printf("Parity Checking:\t\t%s\n", packet->enableParity ? "enabled" : "disabled");
-       printf("Unit Attention Condition:\t%s\n", packet->enableUnitAttention ? "enabled" : "disabled");
-       printf("Bytes per sector:\t\t%d\n", packet->bytesPerSector);
+       std::cout <<
+               "SCSI ID:\t\t\t" << packet->scsiId << "\n" <<
+               "Vendor:\t\t\t\t\"" << std::string(packet->vendor, 8) << "\"\n" <<
+               "Product ID:\t\t\t\"" << std::string(packet->prodId, 16) << "\"\n" <<
+               "Revision:\t\t\t\"" << std::string(packet->revision, 4) << "\"\n" <<
+               "\n" <<
+               "Parity Checking:\t\t" <<
+                       (packet->enableParity ? "enabled" : "disabled") << "\n" <<
+               "Unit Attention Condition:\t" <<
+                       (packet->enableUnitAttention ? "enabled" : "disabled") << "\n" <<
+               "Bytes per sector:\t\t" << packet->bytesPerSector << std::endl;
+
        if (packet->maxSectors)
        {
                char sizeBuf[64];
@@ -142,11 +146,12 @@ static void printConfig(ConfigPacket* packet)
                        sprintf(sizeBuf, "%" PRIu64 " bytes", maxBytes);
                }
 
-               printf("Maximum Size:\t\t\t%s (%d sectors)\n", sizeBuf, packet->maxSectors);
+               std::cout <<"Maximum Size:\t\t\t" << sizeBuf <<
+                       " (" << packet->maxSectors << " sectors)" << std::endl;
        }
        else
        {
-               printf("Maximum Size:\t\t\tUnlimited\n");
+               std::cout << "Maximum Size:\t\t\tUnlimited" << std::endl;
        }
 }
 
@@ -311,7 +316,7 @@ int main(int argc, char* argv[])
                {
                        int64_t maxSectors = -1;
                        if (sscanf(optarg, "%" PRId64, &maxSectors) == 1 &&
-                               maxSectors >= 0 && maxSectors <= UINT32_MAX)
+                               maxSectors >= 0 && maxSectors <= 0xffffffff)
                        {
                                packet.maxSectors = maxSectors;
                        }
@@ -362,6 +367,7 @@ int main(int argc, char* argv[])
                        break;
 
                case '?':
+                       doWrite = 0;
                        usage();
                }
        }
index c68e105..5690879 100644 (file)
@@ -28,10 +28,10 @@ ifeq ($(TARGET),Linux)
 endif
 ifeq ($(TARGET),Darwin)
        # Should match OSX
-       VPATH += ../bootloaderhost/hidapi/mac
+       VPATH += ../bootloaderhost/hidapi-mac
        LDFLAGS += -framework IOKit -framework CoreFoundation
        CFLAGS += -mmacosx-version-min=10.7
-       CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=10.7
+       CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=10.7 -std=c++0x
        CC=clang
        CXX=clang++
        BUILD=build/mac