Add hotplug support to the Darwin backend.
authorNathan Hjelm <hjelmn@me.com>
Thu, 29 Nov 2012 23:13:00 +0000 (16:13 -0700)
committerHans de Goede <hdegoede@redhat.com>
Wed, 15 May 2013 17:54:31 +0000 (19:54 +0200)
Hotplug events are handled by the async event thread. This thread listens for
two events: kIOTerminatedNotification, and kIOFirstMatchNotification. If
either of these events fires the thread will iterate through the appropriate
iterator and will either enumerate or call usbi_disconnect_device depending
on the event.

While adding hotplug support it was discovered that when reading from the IO
registry we need to use kCFNumberSInt32Type for the device location. Using
kCFNumberLongType would cause locations to be sign-extended and not match
what is stored for the session id. This is now fixed.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
libusb/os/darwin_usb.c
libusb/os/darwin_usb.h
libusb/version_nano.h

index d9012c7..7cd1bea 100644 (file)
@@ -20,7 +20,6 @@
 
 #include "config.h"
 #include <ctype.h>
-#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <pthread.h>
 #include "darwin_usb.h"
 
 /* async event thread */
-static pthread_mutex_t libusb_darwin_at_mutex;
-static pthread_cond_t  libusb_darwin_at_cond;
+static pthread_mutex_t libusb_darwin_at_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  libusb_darwin_at_cond = PTHREAD_COND_INITIALIZER;
 
 static clock_serv_t clock_realtime;
 static clock_serv_t clock_monotonic;
 
-static CFRunLoopRef libusb_darwin_acfl = NULL; /* async cf loop */
+static CFRunLoopRef libusb_darwin_acfl = NULL; /* event cf loop */
 static volatile int32_t initCount = 0;
 
 /* async event thread */
@@ -68,7 +67,11 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, int
 static int darwin_reset_device(struct libusb_device_handle *dev_handle);
 static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0);
 
-#ifdef ENABLE_LOGGING
+static int darwin_scan_devices(struct libusb_context *ctx);
+static int process_new_device (struct libusb_context *ctx, usb_device_t **device, UInt32 locationID,
+                               UInt32 parent_location, UInt8 port);
+
+#if defined(ENABLE_LOGGING)
 static const char *darwin_error_str (int result) {
   switch (result) {
   case kIOReturnSuccess:
@@ -285,19 +288,36 @@ static kern_return_t darwin_get_device (uint32_t dev_location, usb_device_t ***d
   return kIOReturnSuccess;
 }
 
+static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) {
+  struct libusb_context *ctx;
+  usb_device_t **device;
+  UInt32 location, parent_location;
+  UInt8 port;
+
+  usbi_mutex_lock(&active_contexts_lock);
+
+  while ((device = usb_get_next_device (add_devices, &location, &port, &parent_location))) {
+    /* add this device to each active context's device list */
+    list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) {
+      process_new_device (ctx, device, location, parent_location, port);
+    }
+
+    /* release extra reference */
+    (*device)->Release (device);
+  }
+
+  usbi_mutex_unlock(&active_contexts_lock);
+}
+
 static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) {
+  struct libusb_device *dev = NULL;
   struct libusb_context *ctx;
-  struct libusb_device_handle *handle;
   struct darwin_device_priv *dpriv;
-  struct darwin_device_handle_priv *priv;
 
   io_service_t device;
-  UInt32 location;
   bool locationValid;
+  UInt32 location;
   CFTypeRef locationCF;
-  UInt32 message;
-
-  usbi_dbg ("a device has been detached");
 
   while ((device = IOIteratorNext (rem_devices)) != 0) {
     /* get the location from the i/o registry */
@@ -319,23 +339,17 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) {
     usbi_mutex_lock(&active_contexts_lock);
 
     list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) {
-      usbi_mutex_lock(&ctx->open_devs_lock);
-
       usbi_dbg ("libusb/darwin.c darwin_devices_detached: notifying context %p of device disconnect", ctx);
 
-      list_for_each_entry(handle, &ctx->open_devs, list, struct libusb_device_handle) {
-        dpriv = (struct darwin_device_priv *)handle->dev->os_priv;
-
-        /* the device may have been opened several times. write to each handle's event descriptor */
-        if (dpriv->location == location  && handle->os_priv) {
-          priv  = (struct darwin_device_handle_priv *)handle->os_priv;
-
-          message = MESSAGE_DEVICE_GONE;
-          write (priv->fds[1], &message, sizeof (message));
-        }
+      dev = usbi_get_device_by_session_id(ctx, location);
+      if (!dev) {
+        continue;
       }
+      dpriv = (struct darwin_device_priv *) dev->os_priv;
 
-      usbi_mutex_unlock(&ctx->open_devs_lock);
+      /* signal the core that this device has been disconnected. the core will tear down this device
+         when the reference count reaches 0 */
+      usbi_disconnect_device(dev);
     }
 
     usbi_mutex_unlock(&active_contexts_lock);
@@ -349,7 +363,7 @@ static void darwin_clear_iterator (io_iterator_t iter) {
     IOObjectRelease (device);
 }
 
-static void *event_thread_main (void *arg0) {
+static void *darwin_event_thread_main (void *arg0) {
   IOReturn kresult;
   struct libusb_context *ctx = (struct libusb_context *)arg0;
   CFRunLoopRef runloop;
@@ -357,7 +371,7 @@ static void *event_thread_main (void *arg0) {
   /* Set this thread's name, so it can be seen in the debugger
      and crash reports. */
 #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
-  pthread_setname_np ("org.libusb.device-detach");
+  pthread_setname_np ("org.libusb.device-hotplug");
 #endif
 
   /* Tell the Objective-C garbage collector about this thread.
@@ -368,10 +382,11 @@ static void *event_thread_main (void *arg0) {
   objc_registerThreadWithCollector();
 #endif
 
-  /* hotplug (device removal) source */
+  /* hotplug (device arrival/removal) sources */
   CFRunLoopSourceRef     libusb_notification_cfsource;
   io_notification_port_t libusb_notification_port;
   io_iterator_t          libusb_rem_device_iterator;
+  io_iterator_t          libusb_add_device_iterator;
 
   usbi_dbg ("creating hotplug event source");
 
@@ -395,12 +410,25 @@ static void *event_thread_main (void *arg0) {
     pthread_exit (NULL);
   }
 
+  /* create notifications for attached devices */
+  kresult = IOServiceAddMatchingNotification(libusb_notification_port, kIOFirstMatchNotification,
+                                              IOServiceMatching(kIOUSBDeviceClassName),
+                                              (IOServiceMatchingCallback)darwin_devices_attached,
+                                              (void *)ctx, &libusb_add_device_iterator);
+
+  if (kresult != kIOReturnSuccess) {
+    usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult));
+
+    pthread_exit (NULL);
+  }
+
   /* arm notifiers */
   darwin_clear_iterator (libusb_rem_device_iterator);
+  darwin_clear_iterator (libusb_add_device_iterator);
 
   usbi_dbg ("thread ready to receive events");
 
-  /* signal the main thread that the async runloop has been created. */
+  /* signal the main thread that the hotplug runloop has been created. */
   pthread_mutex_lock (&libusb_darwin_at_mutex);
   libusb_darwin_acfl = runloop;
   pthread_cond_signal (&libusb_darwin_at_cond);
@@ -413,7 +441,10 @@ static void *event_thread_main (void *arg0) {
 
   /* delete notification port */
   IONotificationPortDestroy (libusb_notification_port);
+
+  /* delete iterators */
   IOObjectRelease (libusb_rem_device_iterator);
+  IOObjectRelease (libusb_add_device_iterator);
 
   CFRelease (runloop);
 
@@ -424,6 +455,12 @@ static void *event_thread_main (void *arg0) {
 
 static int darwin_init(struct libusb_context *ctx) {
   host_name_port_t host_self;
+  int rc;
+
+  rc = darwin_scan_devices (ctx);
+  if (LIBUSB_SUCCESS != rc) {
+    return rc;
+  }
 
   if (OSAtomicIncrement32Barrier(&initCount) == 1) {
     /* create the clocks that will be used */
@@ -433,10 +470,7 @@ static int darwin_init(struct libusb_context *ctx) {
     host_get_clock_service(host_self, SYSTEM_CLOCK, &clock_monotonic);
     mach_port_deallocate(mach_task_self(), host_self);
 
-    pthread_mutex_init (&libusb_darwin_at_mutex, NULL);
-    pthread_cond_init (&libusb_darwin_at_cond, NULL);
-
-    pthread_create (&libusb_darwin_at, NULL, event_thread_main, (void *)ctx);
+    pthread_create (&libusb_darwin_at, NULL, darwin_event_thread_main, (void *)ctx);
 
     pthread_mutex_lock (&libusb_darwin_at_mutex);
     while (!libusb_darwin_acfl)
@@ -452,7 +486,7 @@ static void darwin_exit (void) {
     mach_port_deallocate(mach_task_self(), clock_realtime);
     mach_port_deallocate(mach_task_self(), clock_monotonic);
 
-    /* stop the async runloop and wait for the thread to terminate. */
+    /* stop the event runloop and wait for the thread to terminate. */
     CFRunLoopStop (libusb_darwin_acfl);
     pthread_join (libusb_darwin_at, NULL);
   }
@@ -739,30 +773,26 @@ static int darwin_cache_device_descriptor (struct libusb_context *ctx, struct li
 }
 
 static int process_new_device (struct libusb_context *ctx, usb_device_t **device, UInt32 locationID,
-                               UInt32 parent_location, UInt8 port, struct discovered_devs **_discdevs,
-                               struct libusb_device **last_dev) {
+                               UInt32 parent_location, UInt8 port) {
   struct darwin_device_priv *priv;
   struct libusb_device *dev, *parent = NULL;
-  struct discovered_devs *discdevs;
-  UInt16                address;
-  UInt8                 devSpeed;
-  int ret = 0, need_unref = 0;
+  UInt8 devSpeed;
+  UInt16 address;
+  int ret = 0;
 
   do {
-    dev = usbi_get_device_by_session_id(ctx, locationID);
-    if (!dev) {
-      usbi_dbg ("allocating new device for location 0x%08x", locationID);
-      dev = usbi_alloc_device(ctx, locationID);
-      need_unref = 1;
-    } else
-      usbi_dbg ("using existing device for location 0x%08x", locationID);
+    usbi_dbg ("allocating new device for location 0x%08x", locationID);
 
+    dev = usbi_alloc_device(ctx, locationID);
     if (!dev) {
-      ret = LIBUSB_ERROR_NO_MEM;
-      break;
+      return LIBUSB_ERROR_NO_MEM;
     }
 
     priv = (struct darwin_device_priv *)dev->os_priv;
+    priv->device = device;
+
+    /* increment the device's reference count (it is decremented in darwin_destroy_device) */
+    (*device)->AddRef (device);
 
     (*device)->GetDeviceAddress (device, (USBDeviceAddress *)&address);
 
@@ -777,6 +807,7 @@ static int process_new_device (struct libusb_context *ctx, usb_device_t **device
 
     /* the device iterator provides devices in increasing order of location. given this property
      * we can use the last device to find the parent. */
+/* TODO: FIX TOPOLOGY!
     for (parent = *last_dev ; parent ; parent = parent->parent_dev) {
       struct darwin_device_priv *parent_priv = (struct darwin_device_priv *) parent->os_priv;
 
@@ -788,6 +819,7 @@ static int process_new_device (struct libusb_context *ctx, usb_device_t **device
     dev->parent_dev = parent;
 
     dev->port_number    = port;
+*/
     dev->bus_number     = locationID >> 24;
     dev->device_address = address;
 
@@ -813,42 +845,36 @@ static int process_new_device (struct libusb_context *ctx, usb_device_t **device
     if (ret < 0)
       break;
 
-    /* append the device to the list of discovered devices */
-    discdevs = discovered_devs_append(*_discdevs, dev);
-    if (!discdevs) {
-      ret = LIBUSB_ERROR_NO_MEM;
-      break;
-    }
-
-    *_discdevs = discdevs;
-    *last_dev = dev;
-
     usbi_dbg ("found device with address %d port = %d parent = %p at %p", dev->device_address,
-              dev->port_number, priv->sys_path, (void *) parent);
+              dev->port_number, (void *) parent, priv->sys_path);
   } while (0);
 
-  if (need_unref)
-    libusb_unref_device(dev);
+  if (0 == ret) {
+    usbi_connect_device (dev);
+  } else {
+    libusb_unref_device (dev);
+  }
 
   return ret;
 }
 
-static int darwin_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs) {
+static int darwin_scan_devices(struct libusb_context *ctx) {
   io_iterator_t        deviceIterator;
   usb_device_t         **device;
   kern_return_t        kresult;
   UInt32               location, parent_location;
   UInt8                port;
-  struct libusb_device *last_dev = NULL;
 
   kresult = usb_setup_device_iterator (&deviceIterator, 0);
   if (kresult != kIOReturnSuccess)
     return darwin_to_libusb (kresult);
 
   while ((device = usb_get_next_device (deviceIterator, &location, &port, &parent_location)) != NULL) {
-    (void) process_new_device (ctx, device, location, parent_location, port, _discdevs, &last_dev);
+    (void) process_new_device (ctx, device, location, parent_location, port);
 
-    (*(device))->Release(device);
+    /* process_new_device added a reference so we need to release the one
+       from QueryInterface */
+    (*device)->Release (device);
   }
 
   IOObjectRelease(deviceIterator);
@@ -908,6 +934,9 @@ static int darwin_open (struct libusb_device_handle *dev_handle) {
       /* add the cfSource to the aync run loop */
       CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes);
     }
+
+    /* add the cfSource to the aync run loop */
+    CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes);
   }
 
   /* device opened successfully */
@@ -1015,9 +1044,10 @@ static int darwin_set_configuration(struct libusb_device_handle *dev_handle, int
 
 static int darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, io_service_t *usbInterfacep) {
   IOUSBFindInterfaceRequest request;
-  uint8_t                   current_interface;
   kern_return_t             kresult;
   io_iterator_t             interface_iterator;
+  CFTypeRef                 bInterfaceNumberCF;
+  int                       bInterfaceNumber;
 
   *usbInterfacep = IO_OBJECT_NULL;
 
@@ -1031,10 +1061,21 @@ static int darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, io_s
   if (kresult)
     return kresult;
 
-  for ( current_interface = 0 ; current_interface <= ifc ; current_interface++ ) {
-    *usbInterfacep = IOIteratorNext(interface_iterator);
-    if (current_interface != ifc)
-      (void) IOObjectRelease (*usbInterfacep);
+  while ((*usbInterfacep = IOIteratorNext(interface_iterator))) {
+    /* find the interface number */
+    bInterfaceNumberCF = IORegistryEntryCreateCFProperty (*usbInterfacep, CFSTR("bInterfaceNumber"),
+                                                          kCFAllocatorDefault, 0);
+    if (!bInterfaceNumberCF) {
+      continue;
+    }
+
+    CFNumberGetValue(bInterfaceNumberCF, kCFNumberIntType, &bInterfaceNumber);
+
+    if ((uint8_t) bInterfaceNumber == ifc) {
+      break;
+    }
+
+    (void) IOObjectRelease (*usbInterfacep);
   }
 
   /* done with the interface iterator */
@@ -1329,7 +1370,14 @@ static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle,
 }
 
 static void darwin_destroy_device(struct libusb_device *dev) {
-  (void)dev;
+  struct darwin_device_priv *dpriv = (struct darwin_device_priv *) dev->os_priv;
+
+  if (dpriv->device) {
+    /* it is an internal error if the reference count of a device is < 0 after release */
+    assert(0 <= (*(dpriv->device))->Release(dpriv->device));
+
+    dpriv->device = NULL;
+  }
 }
 
 static int submit_bulk_transfer(struct usbi_transfer *itransfer) {
@@ -1729,12 +1777,8 @@ static int op_handle_events(struct libusb_context *ctx, struct pollfd *fds, POLL
       ret = read (hpriv->fds[0], &message, sizeof (message));
       if (ret < (ssize_t)sizeof (message))
         continue;
-    } else
+    } else {
       /* could not poll the device-- response is to delete the device (this seems a little heavy-handed) */
-      message = MESSAGE_DEVICE_GONE;
-
-    switch (message) {
-    case MESSAGE_DEVICE_GONE:
       /* remove the device's async port from the runloop */
       if (hpriv->cfSource) {
         if (libusb_darwin_acfl)
@@ -1747,7 +1791,9 @@ static int op_handle_events(struct libusb_context *ctx, struct pollfd *fds, POLL
       usbi_handle_disconnect(handle);
 
       /* done with this device */
-      continue;
+    }
+
+    switch (message) {
     case MESSAGE_ASYNC_IO_COMPLETE:
       read (hpriv->fds[0], &itransfer, sizeof (itransfer));
       read (hpriv->fds[0], &kresult, sizeof (IOReturn));
@@ -1795,7 +1841,7 @@ const struct usbi_os_backend darwin_backend = {
         .caps = 0,
         .init = darwin_init,
         .exit = darwin_exit,
-        .get_device_list = darwin_get_device_list,
+        .get_device_list = NULL, /* not needed */
         .get_device_descriptor = darwin_get_device_descriptor,
         .get_active_config_descriptor = darwin_get_active_config_descriptor,
         .get_config_descriptor = darwin_get_config_descriptor,
index 33760ba..94f13fa 100644 (file)
@@ -166,10 +166,7 @@ struct darwin_transfer_priv {
 };
 
 enum {
-  MESSAGE_DEVICE_GONE,
   MESSAGE_ASYNC_IO_COMPLETE
 };
 
-
-
 #endif
index eae7c6a..108ba52 100644 (file)
@@ -1 +1 @@
-#define LIBUSB_NANO 10652
+#define LIBUSB_NANO 10653