With the previous patches (and a little more as I found out the cygwin thread ID setup was broken) having been pushed, let's get down to something a bit more substantial.

Given the recent request, please find attached a proposal for the addon of the following topology calls to the API:
* libusb_get_port_number
* libusb_get_parent
* libusb_get_port_path

But first of all, I'm going to clarify my position when it comes to topology, which I see as the logical consequence of trying to provide a _generic_ USB library to our users. Feel free to snip the next 3 paragraphs if you're only interested in the technical aspects.

One of the most user-visible aspects of USB is the use of ports on chained hubs. That ports are fully interchangeable and that hubs don't provide much of any immediately identifiable information doesn't detract from the fact that, when end-users look at anything USB, they very much see a device plugged into one specific port (usually of many) of a specific hub. It's only natural then that they would expect an USB application, and by extension, its underlying USB library, to be able to report what they see, in one way or another.

Thus, as part of the overall topology, I have to very much disagree with anyone who wants to pretend that providing a port number or hub location has little use. Instead, I see plenty of scenarios where it can such as: - because of cable length or placement, someone wants to reserve a specific port for easy access and prevent others from blocking it (eg. could happen in a lab experiment that runs measurements using an USB device) - likewise, someone may want to identify a known flaky port on a hub and alert users about it (I'm not that far from experiencing such a situation on my good old LCD USB hub, from ever using the most accessible ports) - someone created a hub and associated devices that, they want to be plugged in a specific way (eg: if each device and hub is also an electronic work of art and intended to be placed at a specific position) - someone wants to detect and spread power usage between hubs. It's also possible that one could have a hubs where different ports have different power supplying capabilities. I also provided the example when we first discussed topology where one may have a hub that may report power usage for each port. - accessibility (eg. visually impaired users may want to get some voice notification of which port of which hub they plugged a device in, or someone wants to display a representation of the USB tree as a means to help non tech-savvy users better visualize their environment)

Finally, I'd like to remind everyone that we are purveyors of a generic library. As long as we don't have clear and precise information on an user's specific intended usage, it is really not our damn job to question what people would like to do with it or assume that they are planning to use it "wrong". Instead, our job is to ensure that our library can be used in every imaginable way, including the ones we may not necessarily approve of (that is, as long as they don't go against the USB specs or against the overall user experience, if it's a feature request).

OK, back to the implementation. As you can see, there are 3 calls, where 2 (get_parent and one of port_number/port_path) or possibly even 1 (port_path), could do. This is due to the fact that we couldn't resolve diverging opinions during the last topology discussion. Alan was of the opinion that port_path was the best approach while I see port_number + get_parent as better, especially as most of the topology requests we seem to get are for a port number alone. However, I don't really identify much of any dramatic consequences with keeping these 3 calls, if it leaves everyone and especially our users happy. Once you have get_parent + port_number port_path is a no brainer, so maintaining an extra call is no big deal.

Finally, on the triple boot machine I tested, apart from the bus number which will be difficult to harmonize, the Windows, OS X (courtesy of Nathan) and Linux (courtesy of Alan) implementations all return the same topological information, which is nice. As expected, there's no *BSD implementation as anything *BSD isn't really a priority compared to the long list of task we have to churn through, but if anyone wants to have a crack at it, go ahead.

Regards,

/Pete

>From 35a35825f55565cd64274b94577f550b69ca6c3e Mon Sep 17 00:00:00 2001
From: Pete Batard <p...@akeo.ie>
Date: Thu, 10 May 2012 20:01:10 +0100
Subject: [PATCH] All: Add parent and port topology calls

* Adds libusb_get_port_number, libusb_get_parent and libusb_get_port_path
* Linux implementation provided by Alan Stern, OS X by Nathan Hjelm
---
 examples/xusb.c         |    4 ---
 libusb/core.c           |   56 ++++++++++++++++++++++++++++++++++++++
 libusb/libusb.h         |    3 ++
 libusb/libusbi.h        |    2 +
 libusb/os/darwin_usb.c  |   69 ++++++++++++++++++++++++++++++++++++++++-------
 libusb/os/linux_usbfs.c |   69 +++++++++++++++++++++++++++++++++++++++++++++++
 libusb/os/windows_usb.c |    2 +
 7 files changed, 192 insertions(+), 15 deletions(-)

diff --git a/examples/xusb.c b/examples/xusb.c
index 2c0409f..5bf63f8 100644
--- a/examples/xusb.c
+++ b/examples/xusb.c
@@ -576,9 +576,7 @@ static int test_device(uint16_t vid, uint16_t pid)
 {
        libusb_device_handle *handle;
        libusb_device *dev;
-#ifdef HAS_GETPORTPATH
        uint8_t bus, port_path[8];
-#endif
        struct libusb_config_descriptor *conf_desc;
        const struct libusb_endpoint_descriptor *endpoint;
        int i, j, k, r;
@@ -603,7 +601,6 @@ static int test_device(uint16_t vid, uint16_t pid)
        }
 
        dev = libusb_get_device(handle);
-#ifdef HAS_GETPORTPATH
        bus = libusb_get_bus_number(dev);
        r = libusb_get_port_path(NULL, dev, port_path, sizeof(port_path));
        if (r > 0) {
@@ -613,7 +610,6 @@ static int test_device(uint16_t vid, uint16_t pid)
                }
                printf("\n");
        }
-#endif
        r = libusb_get_device_speed(dev);
        if ((r<0) || (r>4)) r=0;
        printf("speed: %s\n", speed_name[r]);
diff --git a/libusb/core.c b/libusb/core.c
index 2c3de76..f02065e 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -660,6 +660,62 @@ uint8_t API_EXPORTED libusb_get_bus_number(libusb_device 
*dev)
 }
 
 /** \ingroup dev
+ * Get the number of the port that a device is connected to
+ * \param dev a device
+ * \returns the port number (0 if not available)
+ */
+uint8_t API_EXPORTED libusb_get_port_number(libusb_device *dev)
+{
+       return dev->port_number;
+}
+
+/** \ingroup dev
+ * Get the list of all port numbers from root for the specified device
+ * \param dev a device
+ * \param path the array that should contain the port numbers
+ * \param path_len the maximum length of the array
+ * \returns the number of elements filled
+ * \returns LIBUSB_ERROR_OVERFLOW if the array is too small
+ */
+int API_EXPORTED libusb_get_port_path(libusb_context *ctx, libusb_device *dev, 
uint8_t* path, uint8_t path_len)
+{
+       int i = path_len;
+       ssize_t r;
+       struct libusb_device **devs;
+
+       /* The device needs to be open, else the parents may have been 
destroyed */
+       r = libusb_get_device_list(ctx, &devs);
+       if (r < 0)
+               return (int)r;
+
+       while(dev) {
+               // HCDs can be listed as devices and would have port #0
+               // TODO: see how the other backends want to implement HCDs as 
parents
+               if (dev->port_number == 0)
+                       break;
+               if (--i<0) {
+                       return LIBUSB_ERROR_OVERFLOW;
+               }
+               path[i] = dev->port_number;
+               dev = dev->parent_dev;
+       }
+       libusb_free_device_list(devs, 1);
+       memmove(path, &path[i], path_len-i);
+       return path_len-i;
+}
+
+/** \ingroup dev
+ * Get the the parent from the specified device
+ * \param dev a device
+ * \returns the device parent or NULL if not available
+ */
+DEFAULT_VISIBILITY
+libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev)
+{
+       return dev->parent_dev;
+}
+
+/** \ingroup dev
  * Get the address of the device on the bus it is connected to.
  * \param dev a device
  * \returns the device address
diff --git a/libusb/libusb.h b/libusb/libusb.h
index 3e91275..6748b43 100644
--- a/libusb/libusb.h
+++ b/libusb/libusb.h
@@ -976,6 +976,9 @@ int LIBUSB_CALL 
libusb_get_config_descriptor_by_value(libusb_device *dev,
 void LIBUSB_CALL libusb_free_config_descriptor(
        struct libusb_config_descriptor *config);
 uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev);
+uint8_t LIBUSB_CALL libusb_get_port_number(libusb_device *dev);
+libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev);
+int LIBUSB_CALL libusb_get_port_path(libusb_context *ctx, libusb_device *dev, 
uint8_t* path, uint8_t path_length);
 uint8_t LIBUSB_CALL libusb_get_device_address(libusb_device *dev);
 int LIBUSB_CALL libusb_get_device_speed(libusb_device *dev);
 int LIBUSB_CALL libusb_get_max_packet_size(libusb_device *dev,
diff --git a/libusb/libusbi.h b/libusb/libusbi.h
index b8abab5..e04d4c2 100644
--- a/libusb/libusbi.h
+++ b/libusb/libusbi.h
@@ -293,6 +293,8 @@ struct libusb_device {
        struct libusb_context *ctx;
 
        uint8_t bus_number;
+       uint8_t port_number;
+       struct libusb_device* parent_dev;
        uint8_t device_address;
        uint8_t num_configurations;
        enum libusb_speed speed;
diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c
index e6e514e..4554b5f 100644
--- a/libusb/os/darwin_usb.c
+++ b/libusb/os/darwin_usb.c
@@ -185,11 +185,26 @@ static int usb_setup_device_iterator (io_iterator_t 
*deviceIterator, long locati
   return IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, 
deviceIterator);
 }
 
-static usb_device_t **usb_get_next_device (io_iterator_t deviceIterator, 
UInt32 *locationp) {
+int get_ioregistry_value_number (io_service_t service, CFStringRef property, 
CFNumberType type, void *p) {
+  CFTypeRef cfNumber = IORegistryEntryCreateCFProperty (service, property, 
kCFAllocatorDefault, 0);
+  int ret = 0;
+
+  if (cfNumber) {
+    if (CFGetTypeID(cfNumber) == CFNumberGetTypeID()) {
+      ret = CFNumberGetValue(cfNumber, type, p);
+    }
+
+    CFRelease (cfNumber);
+  }
+
+  return ret;
+}
+
+static usb_device_t **usb_get_next_device (io_iterator_t deviceIterator, 
UInt32 *locationp, UInt8 *portp, UInt32 *parent_locationp) {
   io_cf_plugin_ref_t *plugInInterface = NULL;
   usb_device_t **device;
-  io_service_t usbDevice;
-  long result;
+  io_service_t usbDevice, parent;
+  kern_return_t result;
   SInt32 score;
 
   if (!IOIteratorIsValid (deviceIterator))
@@ -203,6 +218,22 @@ static usb_device_t **usb_get_next_device (io_iterator_t 
deviceIterator, UInt32
 
     /* we are done with the usb_device_t */
     (void)IOObjectRelease(usbDevice);
+
+    if (portp) {
+      *portp = 0;
+      (void) get_ioregistry_value_number (usbDevice, CFSTR("PortNum"), 
kCFNumberSInt8Type, portp);
+    }
+
+    if (parent_locationp) {
+      *parent_locationp = 0;
+
+      result = IORegistryEntryGetParentEntry (usbDevice, kIOUSBPlane, &parent);
+
+      if (kIOReturnSuccess == result) {
+       (void) get_ioregistry_value_number (parent, CFSTR("locationID"), 
kCFNumberLongType, parent_locationp);
+      }
+    }
+
     if (kIOReturnSuccess == result && plugInInterface)
       break;
 
@@ -235,7 +266,7 @@ static kern_return_t darwin_get_device (uint32_t 
dev_location, usb_device_t ***d
     return kresult;
 
   /* This port of libusb uses locations to keep track of devices. */
-  while ((*darwin_device = usb_get_next_device (deviceIterator, &location)) != 
NULL) {
+  while ((*darwin_device = usb_get_next_device (deviceIterator, &location, 
NULL, NULL)) != NULL) {
     if (location == dev_location)
       break;
 
@@ -691,9 +722,11 @@ static int darwin_cache_device_descriptor (struct 
libusb_context *ctx, struct li
   return 0;
 }
 
-static int process_new_device (struct libusb_context *ctx, usb_device_t 
**device, UInt32 locationID, struct discovered_devs **_discdevs) {
+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 darwin_device_priv *priv;
-  struct libusb_device *dev;
+  static struct libusb_device *last_dev = NULL;
+  struct libusb_device *dev, *parent = NULL;
   struct discovered_devs *discdevs;
   UInt16                address;
   UInt8                 devSpeed;
@@ -726,6 +759,19 @@ static int process_new_device (struct libusb_context *ctx, 
usb_device_t **device
     if (ret < 0)
       break;
 
+    /* the device iterator provides devices in increasing order of location. 
given this property
+     * we can use the last device to find the parent. */
+    for (parent = last_dev ; parent ; parent = parent->parent_dev) {
+      struct darwin_device_priv *parent_priv = (struct darwin_device_priv *) 
parent->os_priv;
+
+      if (parent_priv->location == parent_location) {
+       break;
+      }
+    }
+
+    dev->parent_dev = parent;
+
+    dev->port_number    = port;
     dev->bus_number     = locationID >> 24;
     dev->device_address = address;
 
@@ -756,8 +802,10 @@ static int process_new_device (struct libusb_context *ctx, 
usb_device_t **device
     }
 
     *_discdevs = discdevs;
+    last_dev = dev;
 
-    usbi_info (ctx, "found device with address %d at %s", dev->device_address, 
priv->sys_path);
+    usbi_info (ctx, "found device with address %d port = %d parent = %p at 
%p", dev->device_address,
+              dev->port_number, priv->sys_path, (void *) parent);
   } while (0);
 
   if (need_unref)
@@ -770,14 +818,15 @@ static int darwin_get_device_list(struct libusb_context 
*ctx, struct discovered_
   io_iterator_t        deviceIterator;
   usb_device_t         **device;
   kern_return_t        kresult;
-  UInt32               location;
+  UInt32               location, parent_location;
+  UInt8                port;
 
   kresult = usb_setup_device_iterator (&deviceIterator, 0);
   if (kresult != kIOReturnSuccess)
     return darwin_to_libusb (kresult);
 
-  while ((device = usb_get_next_device (deviceIterator, &location)) != NULL) {
-    (void) process_new_device (ctx, device, location, _discdevs);
+  while ((device = usb_get_next_device (deviceIterator, &location, &port, 
&parent_location)) != NULL) {
+    (void) process_new_device (ctx, device, location, parent_location, port, 
_discdevs);
 
     (*(device))->Release(device);
   }
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c
index a6114ca..a9e4ca0 100644
--- a/libusb/os/linux_usbfs.c
+++ b/libusb/os/linux_usbfs.c
@@ -1157,6 +1157,74 @@ static int sysfs_scan_device(struct libusb_context *ctx,
                devname);
 }
 
+static void sysfs_analyze_topology(struct discovered_devs *discdevs)
+{
+       struct linux_device_priv *priv;
+       int i, j;
+       struct libusb_device *dev1, *dev2;
+       const char *sysfs_dir1, *sysfs_dir2;
+       const char *p;
+       int n, boundary_char;
+
+       /* Fill in the port_number and parent_dev fields for each device */
+
+       for (i = 0; i < discdevs->len; ++i) {
+               dev1 = discdevs->devices[i];
+               priv = _device_priv(dev1);
+               if (!priv)
+                       continue;
+               sysfs_dir1 = priv->sysfs_dir;
+
+               /* Root hubs have sysfs_dir names of the form "usbB",
+                * where B is the bus number.  All other devices have
+                * sysfs_dir names of the form "B-P[.P ...]", where the
+                * P values are port numbers leading from the root hub
+                * to the device.
+                */
+
+               /* Root hubs don't have parents or port numbers */
+               if (sysfs_dir1[0] == 'u')
+                       continue;
+
+               /* The rightmost component is the device's port number */
+               p = strrchr(sysfs_dir1, '.');
+               if (!p) {
+                       p = strchr(sysfs_dir1, '-');
+                       if (!p)
+                               continue;       /* Should never happen */
+               }
+               dev1->port_number = atoi(p + 1);
+
+               /* Search for the parent device */
+               boundary_char = *p;
+               n = p - sysfs_dir1;
+               for (j = 0; j < discdevs->len; ++j) {
+                       dev2 = discdevs->devices[j];
+                       priv = _device_priv(dev2);
+                       if (!priv)
+                               continue;
+                       sysfs_dir2 = priv->sysfs_dir;
+
+                       if (boundary_char == '-') {
+                               /* The parent's name must begin with 'usb';
+                                * skip past that part of sysfs_dir2.
+                                */
+                               if (sysfs_dir2[0] != 'u')
+                                       continue;
+                               sysfs_dir2 += 3;
+                       }
+
+                       /* The remainder of the parent's name must be equal to
+                        * the first n bytes of sysfs_dir1.
+                        */
+                       if (memcmp(sysfs_dir1, sysfs_dir2, n) == 0 && 
!sysfs_dir2[n]) {
+                               dev1->parent_dev = dev2;
+                               break;
+                       }
+               }
+       }
+}
+
 static int sysfs_get_device_list(struct libusb_context *ctx,
        struct discovered_devs **_discdevs)
 {
@@ -1189,6 +1257,7 @@ static int sysfs_get_device_list(struct libusb_context 
*ctx,
        if (!r)
                *_discdevs = discdevs;
        closedir(devices);
+       sysfs_analyze_topology(discdevs);
        return r;
 }
 
diff --git a/libusb/os/windows_usb.c b/libusb/os/windows_usb.c
index f29f84c..5c1ce7e 100644
--- a/libusb/os/windows_usb.c
+++ b/libusb/os/windows_usb.c
@@ -1001,8 +1001,10 @@ static int init_device(struct libusb_device* dev, struct 
libusb_device* parent_d
        }
        dev->bus_number = parent_dev->bus_number;
        priv->port = port_number;
+       dev->port_number = port_number;
        priv->depth = parent_priv->depth + 1;
        priv->parent_dev = parent_dev;
+       dev->parent_dev = parent_dev;
 
        // If the device address is already set, we can stop here
        if (dev->device_address != 0) {
-- 
1.7.9.msysgit.0

------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and 
threat landscape has changed and how IT managers can respond. Discussions 
will include endpoint security, mobile security and the latest in malware 
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/
_______________________________________________
libusbx-devel mailing list
libusbx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/libusbx-devel

Reply via email to