]> sigrok.org Git - libsigrok.git/blobdiff - src/usb.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / usb.c
index d66f188085c94638efea1283b4dd900d4fb9a130..feafe9d2a2ae0aefbb4389638f9e7676a643ba9f 100644 (file)
--- a/src/usb.c
+++ b/src/usb.c
@@ -19,6 +19,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <config.h>
 #include <stdlib.h>
 #include <memory.h>
 #include <glib.h>
@@ -27,7 +28,7 @@
 #include "libsigrok-internal.h"
 
 /* SR_CONF_CONN takes one of these: */
-#define CONN_USB_VIDPID  "^([0-9a-z]{4})\\.([0-9a-z]{4})$"
+#define CONN_USB_VIDPID  "^([0-9a-fA-F]{4})\\.([0-9a-fA-F]{4})$"
 #define CONN_USB_BUSADDR "^(\\d+)\\.(\\d+)$"
 
 #define LOG_PREFIX "usb"
@@ -37,7 +38,6 @@ typedef int libusb_os_handle;
 #endif
 
 /** Custom GLib event source for libusb I/O.
- * @internal
  */
 struct usb_source {
        GSource base;
@@ -133,16 +133,12 @@ static gboolean usb_source_dispatch(GSource *source,
                pollfd = g_ptr_array_index(usource->pollfds, i);
                revents |= pollfd->revents;
        }
-       if (revents != 0)
-               sr_spew("%s: revents 0x%.2X", __func__, revents);
-       else
-               sr_spew("%s: timed out", __func__);
 
        if (!callback) {
                sr_err("Callback not set, cannot dispatch event.");
                return G_SOURCE_REMOVE;
        }
-       keep = (*(sr_receive_data_callback)callback)(-1, revents, user_data);
+       keep = (*SR_RECEIVE_DATA_CALLBACK(callback))(-1, revents, user_data);
 
        if (G_LIKELY(keep) && G_LIKELY(!g_source_is_destroyed(source))) {
                if (usource->timeout_us >= 0)
@@ -183,8 +179,11 @@ static LIBUSB_CALL void usb_pollfd_added(libusb_os_handle fd,
 
        usource = user_data;
 
+       if (G_UNLIKELY(g_source_is_destroyed(&usource->base)))
+               return;
+
        pollfd = g_slice_new(GPollFD);
-#ifdef G_OS_WIN32
+#ifdef _WIN32
        events = G_IO_IN;
 #endif
        pollfd->fd = (gintptr)fd;
@@ -192,7 +191,7 @@ static LIBUSB_CALL void usb_pollfd_added(libusb_os_handle fd,
        pollfd->revents = 0;
 
        g_ptr_array_add(usource->pollfds, pollfd);
-       g_source_add_poll((GSource *)usource, pollfd);
+       g_source_add_poll(&usource->base, pollfd);
 }
 
 /** Callback invoked when a libusb FD should be removed from the poll set.
@@ -205,13 +204,16 @@ static LIBUSB_CALL void usb_pollfd_removed(libusb_os_handle fd, void *user_data)
 
        usource = user_data;
 
+       if (G_UNLIKELY(g_source_is_destroyed(&usource->base)))
+               return;
+
        /* It's likely that the removed poll FD is at the end.
         */
        for (i = usource->pollfds->len; G_LIKELY(i > 0); i--) {
                pollfd = g_ptr_array_index(usource->pollfds, i - 1);
 
                if ((libusb_os_handle)pollfd->fd == fd) {
-                       g_source_remove_poll((GSource *)usource, pollfd);
+                       g_source_remove_poll(&usource->base, pollfd);
                        g_ptr_array_remove_index_fast(usource->pollfds, i - 1);
                        return;
                }
@@ -295,70 +297,116 @@ static GSource *usb_source_new(struct sr_session *session,
 }
 
 /**
- * Find USB devices according to a connection string.
+ * Extract VID:PID or bus.addr from a connection string.
  *
- * @param usb_ctx libusb context to use while scanning.
- * @param conn Connection string specifying the device(s) to match. This
- * can be of the form "<bus>.<address>", or "<vendorid>.<productid>".
+ * @param[in] conn Connection string.
+ * @param[out] vid Pointer to extracted vendor ID. Can be #NULL.
+ * @param[out] pid Pointer to extracted product ID. Can be #NULL.
+ * @param[out] bus Pointer to extracted bus number. Can be #NULL.
+ * @param[out] addr Pointer to extracted device number. Can be #NULL.
  *
- * @return A GSList of struct sr_usb_dev_inst, with bus and address fields
- * matching the device that matched the connection string. The GSList and
- * its contents must be freed by the caller.
+ * @return SR_OK when parsing succeeded, SR_ERR* otherwise.
+ *
+ * @private
+ *
+ * The routine fills in the result variables, and returns the scan success
+ * in the return code. Callers can specify #NULL for variable references
+ * if they are not interested in specific aspects of the USB address.
  */
-SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn)
+SR_PRIV int sr_usb_split_conn(const char *conn,
+       uint16_t *vid, uint16_t *pid, uint8_t *bus, uint8_t *addr)
 {
-       struct sr_usb_dev_inst *usb;
-       struct libusb_device **devlist;
-       struct libusb_device_descriptor des;
-       GSList *devices;
+       gboolean valid;
        GRegex *reg;
        GMatchInfo *match;
-       int vid, pid, bus, addr, b, a, ret, i;
        char *mstr;
+       uint32_t num;
+
+       if (vid) *vid = 0;
+       if (pid) *pid = 0;
+       if (bus) *bus = 0;
+       if (addr) *addr = 0;
 
-       vid = pid = bus = addr = 0;
+       valid = TRUE;
        reg = g_regex_new(CONN_USB_VIDPID, 0, 0, NULL);
        if (g_regex_match(reg, conn, 0, &match)) {
-               if ((mstr = g_match_info_fetch(match, 1)))
-                       vid = strtoul(mstr, NULL, 16);
+               /* Found a VID:PID style pattern. */
+               if ((mstr = g_match_info_fetch(match, 1))) {
+                       num = strtoul(mstr, NULL, 16);
+                       if (num > 0xffff)
+                               valid = FALSE;
+                       if (vid)
+                               *vid = num & 0xffff;
+               }
                g_free(mstr);
 
-               if ((mstr = g_match_info_fetch(match, 2)))
-                       pid = strtoul(mstr, NULL, 16);
+               if ((mstr = g_match_info_fetch(match, 2))) {
+                       num = strtoul(mstr, NULL, 16);
+                       if (num > 0xffff)
+                               valid = FALSE;
+                       if (pid)
+                               *pid = num & 0xffff;
+               }
                g_free(mstr);
-               sr_dbg("Trying to find USB device with VID:PID = %04x:%04x.",
-                      vid, pid);
        } else {
                g_match_info_unref(match);
                g_regex_unref(reg);
                reg = g_regex_new(CONN_USB_BUSADDR, 0, 0, NULL);
                if (g_regex_match(reg, conn, 0, &match)) {
-                       if ((mstr = g_match_info_fetch(match, 1)))
-                               bus = strtoul(mstr, NULL, 10);
+                       /* Found a bus.address style pattern. */
+                       if ((mstr = g_match_info_fetch(match, 1))) {
+                               num = strtoul(mstr, NULL, 10);
+                               if (num > 255)
+                                       valid = FALSE;
+                               if (bus)
+                                       *bus = num & 0xff;
+                       }
                        g_free(mstr);
 
-                       if ((mstr = g_match_info_fetch(match, 2)))
-                               addr = strtoul(mstr, NULL, 10);
+                       if ((mstr = g_match_info_fetch(match, 2))) {
+                               num = strtoul(mstr, NULL, 10);
+                               if (num > 127)
+                                       valid = FALSE;
+                               if (addr)
+                                       *addr = num & 0x7f;
+                       }
                        g_free(mstr);
-                       sr_dbg("Trying to find USB device with bus.address = "
-                              "%d.%d.", bus, addr);
                }
        }
        g_match_info_unref(match);
        g_regex_unref(reg);
 
-       if (vid + pid + bus + addr == 0) {
-               sr_err("Neither VID:PID nor bus.address was specified.");
-               return NULL;
-       }
+       return valid ? SR_OK : SR_ERR_ARG;
+}
 
-       if (bus > 64) {
-               sr_err("Invalid bus specified: %d.", bus);
+/**
+ * Find USB devices according to a connection string.
+ *
+ * @param usb_ctx libusb context to use while scanning.
+ * @param conn Connection string specifying the device(s) to match. This
+ * can be of the form "<bus>.<address>", or "<vendorid>.<productid>".
+ *
+ * @return A GSList of struct sr_usb_dev_inst, with bus and address fields
+ * matching the device that matched the connection string. The GSList and
+ * its contents must be freed by the caller.
+ */
+SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn)
+{
+       struct sr_usb_dev_inst *usb;
+       struct libusb_device **devlist;
+       struct libusb_device_descriptor des;
+       GSList *devices;
+       uint16_t vid, pid;
+       uint8_t bus, addr;
+       int b, a, ret, i;
+
+       ret = sr_usb_split_conn(conn, &vid, &pid, &bus, &addr);
+       if (ret != SR_OK) {
+               sr_err("Invalid input, or neither VID:PID nor bus.address specified.");
                return NULL;
        }
-
-       if (addr > 127) {
-               sr_err("Invalid address specified: %d.", addr);
+       if (!(vid && pid) && !(bus && addr)) {
+               sr_err("Could neither determine VID:PID nor bus.address numbers.");
                return NULL;
        }
 
@@ -372,25 +420,24 @@ SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn)
                        continue;
                }
 
-               if (vid + pid && (des.idVendor != vid || des.idProduct != pid))
+               if (vid && pid && (des.idVendor != vid || des.idProduct != pid))
                        continue;
 
                b = libusb_get_bus_number(devlist[i]);
                a = libusb_get_device_address(devlist[i]);
-               if (bus + addr && (b != bus || a != addr))
+               if (bus && addr && (b != bus || a != addr))
                        continue;
 
                sr_dbg("Found USB device (VID:PID = %04x:%04x, bus.address = "
                       "%d.%d).", des.idVendor, des.idProduct, b, a);
 
-               usb = sr_usb_dev_inst_new(libusb_get_bus_number(devlist[i]),
-                               libusb_get_device_address(devlist[i]), NULL);
+               usb = sr_usb_dev_inst_new(b, a, NULL);
                devices = g_slist_append(devices, usb);
        }
        libusb_free_device_list(devlist, 1);
 
-       sr_dbg("Found %d device(s).", g_slist_length(devices));
-       
+       /* No log message for #devices found (caller will log that). */
+
        return devices;
 }
 
@@ -456,7 +503,7 @@ SR_PRIV int usb_source_add(struct sr_session *session, struct sr_context *ctx,
        if (!source)
                return SR_ERR;
 
-       g_source_set_callback(source, (GSourceFunc)cb, cb_data, NULL);
+       g_source_set_callback(source, G_SOURCE_FUNC(cb), cb_data, NULL);
 
        ret = sr_session_source_add_internal(session, ctx->libusb_ctx, source);
        g_source_unref(source);
@@ -477,19 +524,20 @@ SR_PRIV int usb_get_port_path(libusb_device *dev, char *path, int path_len)
 /*
  * FreeBSD requires that devices prior to calling libusb_get_port_numbers()
  * have been opened with libusb_open().
+ * This apparently also applies to some Mac OS X versions.
  */
-#ifdef __FreeBSD__
+#if defined(__FreeBSD__) || defined(__APPLE__)
        struct libusb_device_handle *devh;
        if (libusb_open(dev, &devh) != 0)
                return SR_ERR;
 #endif
        n = libusb_get_port_numbers(dev, port_numbers, sizeof(port_numbers));
-#ifdef __FreeBSD__
+#if defined(__FreeBSD__) || defined(__APPLE__)
        libusb_close(devh);
 #endif
 
-/* Workaround FreeBSD libusb_get_port_numbers() returning 0. */
-#ifdef __FreeBSD__
+/* Workaround FreeBSD / Mac OS X libusb_get_port_numbers() returning 0. */
+#if defined(__FreeBSD__) || defined(__APPLE__)
        if (n == 0) {
                port_numbers[0] = libusb_get_device_address(dev);
                n = 1;
@@ -506,3 +554,47 @@ SR_PRIV int usb_get_port_path(libusb_device *dev, char *path, int path_len)
 
        return SR_OK;
 }
+
+/**
+ * Check the USB configuration to determine if this device has a given
+ * manufacturer and product string.
+ *
+ * @return TRUE if the device's configuration profile strings
+ *         configuration, FALSE otherwise.
+ */
+SR_PRIV gboolean usb_match_manuf_prod(libusb_device *dev,
+               const char *manufacturer, const char *product)
+{
+       struct libusb_device_descriptor des;
+       struct libusb_device_handle *hdl;
+       gboolean ret;
+       unsigned char strdesc[64];
+
+       hdl = NULL;
+       ret = FALSE;
+       while (!ret) {
+               /* Assume the FW has not been loaded, unless proven wrong. */
+               libusb_get_device_descriptor(dev, &des);
+
+               if (libusb_open(dev, &hdl) != 0)
+                       break;
+
+               if (libusb_get_string_descriptor_ascii(hdl,
+                               des.iManufacturer, strdesc, sizeof(strdesc)) < 0)
+                       break;
+               if (strcmp((const char *)strdesc, manufacturer))
+                       break;
+
+               if (libusb_get_string_descriptor_ascii(hdl,
+                               des.iProduct, strdesc, sizeof(strdesc)) < 0)
+                       break;
+               if (strcmp((const char *)strdesc, product))
+                       break;
+
+               ret = TRUE;
+       }
+       if (hdl)
+               libusb_close(hdl);
+
+       return ret;
+}