Logo Search packages:      
Sourcecode: libgphoto2 version File versions  Download package

libusb.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gphoto2-port-usb.c
 *
 * Copyright © 2001 Lutz Müller <lutz@users.sf.net>
 * Copyright © 1999-2000 Johannes Erdfelt <johannes@erdfelt.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include "config.h"
#include <gphoto2/gphoto2-port-library.h>

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/param.h>
#include <dirent.h>
#include <string.h>

#include <usb.h>

#include <gphoto2/gphoto2-port.h>
#include <gphoto2/gphoto2-port-result.h>
#include <gphoto2/gphoto2-port-log.h>

#ifdef ENABLE_NLS
#  include <libintl.h>
#  undef _
#  define _(String) dgettext (GETTEXT_PACKAGE, String)
#  ifdef gettext_noop
#    define N_(String) gettext_noop (String)
#  else
#    define N_(String) (String)
#  endif
#else
#  define textdomain(String) (String)
#  define gettext(String) (String)
#  define dgettext(Domain,Message) (Message)
#  define dcgettext(Domain,Message,Type) (Message)
#  define bindtextdomain(Domain,Directory) (Domain)
#  define _(String) (String)
#  define N_(String) (String)
#endif

#define CHECK(result) {int r=(result); if (r<0) return (r);}

struct _GPPortPrivateLibrary {
      void *dh;
      struct usb_device *d;

      int config;
      int interface;
      int altsetting;
};

GPPortType
gp_port_library_type (void)
{
      return (GP_PORT_USB);
}

int
gp_port_library_list (GPPortInfoList *list)
{
      GPPortInfo info;
      struct usb_bus *bus;
      struct usb_device *dev;
      int nrofdevices = 0, i, i1, i2, unknownint;

      /* default port first */
      info.type = GP_PORT_USB;
      strcpy (info.name, "Universal Serial Bus");
      strcpy (info.path, "usb:");
      CHECK (gp_port_info_list_append (list, info));

      /* generic matcher. This will catch passed XXX,YYY entries for instance. */
      memset (info.name, 0, sizeof(info.name));
      strcpy (info.path, "^usb:");
      CHECK (gp_port_info_list_append (list, info));

      usb_init ();
      usb_find_busses ();
      usb_find_devices ();

      strcpy (info.name, "Universal Serial Bus");

      bus = usb_get_busses();

      /* Look and enumerate all USB ports. */
      while (bus) {
            for (dev = bus->devices; dev; dev = dev->next) {
                  /* Devices which are definitely not cameras. */
                  if (  (dev->descriptor.bDeviceClass == USB_CLASS_HUB)       ||
                        (dev->descriptor.bDeviceClass == USB_CLASS_HID)       ||
                        (dev->descriptor.bDeviceClass == USB_CLASS_PRINTER)   ||
                        (dev->descriptor.bDeviceClass == USB_CLASS_COMM)
                  )
                        continue;
                  /* excepts HUBs, usually the interfaces have the classes, not
                   * the device */
                  unknownint = 0;
                  for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
                        if (!dev->config) {
                              unknownint++;
                              continue;
                        }
                        for (i1 = 0; i1 < dev->config[i].bNumInterfaces; i1++)
                              for (i2 = 0; i2 < dev->config[i].interface[i1].num_altsetting; i2++) {
                                    struct usb_interface_descriptor *intf = &dev->config[i].interface[i1].altsetting[i2]; 
                                    if (  (intf->bInterfaceClass == USB_CLASS_HID)  ||
                                          (intf->bInterfaceClass == USB_CLASS_PRINTER)    ||
                                          (intf->bInterfaceClass == USB_CLASS_COMM))
                                          continue;
                                    unknownint++;
                              }
                  }
                  /* when we find only hids, printer or comm ifaces  ... skip this */
                  if (!unknownint)
                        continue;
                  /* Note: We do not skip USB storage. Some devices can support both,
                   * and the Ricoh erronously reports it.
                   */ 
                  nrofdevices++;
            }
            bus = bus->next;
      }

      /* If we already added usb:, and have 0 or 1 devices we have nothing to do.
       * This should be the standard use case.
       */
      if (nrofdevices <= 1) 
            return (GP_OK);

      /* Redo the same bus/device walk, but now add the ports with usb:x,y notation,
       * so we can address all USB devices.
       */
      bus = usb_get_busses();
      while (bus) {
            for (dev = bus->devices; dev; dev = dev->next) {
                  char *s;
                  /* Devices which are definitely not cameras. */
                  if (  (dev->descriptor.bDeviceClass == USB_CLASS_HUB)       ||
                        (dev->descriptor.bDeviceClass == USB_CLASS_HID)       ||
                        (dev->descriptor.bDeviceClass == USB_CLASS_PRINTER)   ||
                        (dev->descriptor.bDeviceClass == USB_CLASS_COMM)
                  )
                        continue;
                  /* excepts HUBs, usually the interfaces have the classes, not
                   * the device */
                  unknownint = 0;
                  for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
                        if (!dev->config) {
                              unknownint++;
                              continue;
                        }
                        for (i1 = 0; i1 < dev->config[i].bNumInterfaces; i1++)
                              for (i2 = 0; i2 < dev->config[i].interface[i1].num_altsetting; i2++) {
                                    struct usb_interface_descriptor *intf = &dev->config[i].interface[i1].altsetting[i2]; 
                                    if (  (intf->bInterfaceClass == USB_CLASS_HID)  ||
                                          (intf->bInterfaceClass == USB_CLASS_PRINTER)    ||
                                          (intf->bInterfaceClass == USB_CLASS_COMM))
                                          continue;
                                    unknownint++;
                              }
                  }
                  /* when we find only hids, printer or comm ifaces  ... skip this */
                  if (!unknownint)
                        continue;
                  /* Note: We do not skip USB storage. Some devices can support both,
                   * and the Ricoh erronously reports it.
                   */ 
                  sprintf (info.path, "usb:%s,%s", bus->dirname, dev->filename);
                  /* On MacOS X we might get usb:006,002-04a9-3139-00-00. */
                  s = strchr(info.path, '-');if (s) *s='\0';
                  CHECK (gp_port_info_list_append (list, info));
            }
            bus = bus->next;
      }
      return (GP_OK);
}

static int gp_port_usb_init (GPPort *port)
{
      port->pl = malloc (sizeof (GPPortPrivateLibrary));
      if (!port->pl)
            return (GP_ERROR_NO_MEMORY);
      memset (port->pl, 0, sizeof (GPPortPrivateLibrary));

      port->pl->config = port->pl->interface = port->pl->altsetting = -1;

      usb_init ();
      usb_find_busses ();
      usb_find_devices ();

      return (GP_OK);
}

static int
gp_port_usb_exit (GPPort *port)
{
      if (port->pl) {
            free (port->pl);
            port->pl = NULL;
      }

      return (GP_OK);
}

static int
gp_port_usb_open (GPPort *port)
{
      int ret;
      char name[64];

      gp_log (GP_LOG_DEBUG,"libusb","gp_port_usb_open()");
      if (!port || !port->pl->d)
            return GP_ERROR_BAD_PARAMETERS;

        /*
       * Open the device using the previous usb_handle returned by
       * find_device
       */
      port->pl->dh = usb_open (port->pl->d);
      if (!port->pl->dh) {
            gp_port_set_error (port, _("Could not open USB device (%m)."));
            return GP_ERROR_IO;
      }
#if defined(LIBUSB_HAS_GET_DRIVER_NP) && defined(LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP)
      memset(name,0,sizeof(name));
      ret = usb_get_driver_np (port->pl->dh, port->settings.usb.interface,
            name, sizeof(name)
      );
      if (strstr(name,"usbfs") || strstr(name,"storage")) {
            /* other gphoto instance most likely
             * Also let mass storage be served by regular means.
             */
            gp_port_set_error (port, _("Camera is already in use."));
            return GP_ERROR_IO_LOCK;
      }

      if (ret >= 0) {
            gp_log (GP_LOG_DEBUG,"libusb",_("Device has driver '%s' attached, detaching it now."), name);
            ret = usb_detach_kernel_driver_np (port->pl->dh, port->settings.usb.interface);
            if (ret < 0)
                  gp_port_set_error (port, _("Could not detach kernel driver '%s' of camera device."),name);
      } else {
            if (errno != ENODATA) /* ENODATA - just no driver there */
                  gp_port_set_error (port, _("Could not query kernel driver of device."));
      }
#endif

      gp_log (GP_LOG_DEBUG,"libusb","claiming interface %d", port->settings.usb.interface);
      ret = usb_claim_interface (port->pl->dh,
                           port->settings.usb.interface);
      if (ret < 0) {
            gp_port_set_error (port, _("Could not claim "
                  "interface %d (%m). Make sure no other program "
                  "or kernel module (such as %s) is using the device "
                  "and you have read/write access to the device."),
                  port->settings.usb.interface, "sdc2xx, stv680, spca50x");
            return GP_ERROR_IO_USB_CLAIM;
      }
      return GP_OK;
}

static int
gp_port_usb_close (GPPort *port)
{
      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      if (usb_release_interface (port->pl->dh,
                           port->settings.usb.interface) < 0) {
            gp_port_set_error (port, _("Could not "
                  "release interface %d (%m)."),
                  port->settings.usb.interface);
            return (GP_ERROR_IO);
      }

#if 0
      /* This does break Canon EOS and some PowerShots ... -Marcus */
      /* This is only for our very special Canon cameras which need a good
       * whack after close, otherwise they get timeouts on reconnect.
       */
      if (port->pl->d->descriptor.idVendor == 0x04a9) {
            if (usb_reset (port->pl->dh) < 0) {
                  gp_port_set_error (port, _("Could not reset USB port (%m)."));
                  return (GP_ERROR_IO);
            }
      }
#endif
      if (usb_close (port->pl->dh) < 0) {
            gp_port_set_error (port, _("Could not close USB port (%m)."));
            return (GP_ERROR_IO);
      }

      port->pl->dh = NULL;

      return GP_OK;
}

static int
gp_port_usb_clear_halt_lib(GPPort *port, int ep)
{
      int ret=0;

      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      switch (ep) {
      case GP_PORT_USB_ENDPOINT_IN :
            ret=usb_clear_halt(port->pl->dh, port->settings.usb.inep);
            break;
      case GP_PORT_USB_ENDPOINT_OUT :
            ret=usb_clear_halt(port->pl->dh, port->settings.usb.outep);
            break;
      case GP_PORT_USB_ENDPOINT_INT :
            ret=usb_clear_halt(port->pl->dh, port->settings.usb.intep);
            break;
      default:
            gp_port_set_error (port, "gp_port_usb_clear_halt: "
                           "bad EndPoint argument");
            return GP_ERROR_BAD_PARAMETERS;
      }
      return (ret ? GP_ERROR_IO_USB_CLEAR_HALT : GP_OK);
}

static int
gp_port_usb_write (GPPort *port, const char *bytes, int size)
{
        int ret;

      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      ret = usb_bulk_write (port->pl->dh, port->settings.usb.outep,
                           (char *) bytes, size, port->timeout);
        if (ret < 0)
            return (GP_ERROR_IO_WRITE);

        return (ret);
}

static int
gp_port_usb_read(GPPort *port, char *bytes, int size)
{
      int ret;

      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      ret = usb_bulk_read(port->pl->dh, port->settings.usb.inep,
                       bytes, size, port->timeout);
        if (ret < 0)
            return GP_ERROR_IO_READ;

        return ret;
}

static int
gp_port_usb_check_int (GPPort *port, char *bytes, int size, int timeout)
{
      int ret;

      if (!port || !port->pl->dh || timeout < 0)
            return GP_ERROR_BAD_PARAMETERS;

      ret = usb_interrupt_read(port->pl->dh, port->settings.usb.intep,
                       bytes, size, timeout);
        if (ret < 0) {
            if (errno == EAGAIN)
                  return GP_ERROR_TIMEOUT;
            return GP_ERROR_IO_READ;
      }
        return ret;
}

static int
gp_port_usb_msg_write_lib(GPPort *port, int request, int value, int index,
      char *bytes, int size)
{
      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      return usb_control_msg(port->pl->dh,
            USB_TYPE_VENDOR | USB_RECIP_DEVICE,
            request, value, index, bytes, size, port->timeout);
}

static int
gp_port_usb_msg_read_lib(GPPort *port, int request, int value, int index,
      char *bytes, int size)
{
      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      return usb_control_msg(port->pl->dh,
            USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
            request, value, index, bytes, size, port->timeout);
}

/* The next two functions support the nonstandard request types 0x41 (write) 
 * and 0xc1 (read), which are occasionally needed. 
 */

static int
gp_port_usb_msg_interface_write_lib(GPPort *port, int request, 
      int value, int index, char *bytes, int size)
{
      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      return usb_control_msg(port->pl->dh, 
            USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
            request, value, index, bytes, size, port->timeout);
}


static int
gp_port_usb_msg_interface_read_lib(GPPort *port, int request, 
      int value, int index, char *bytes, int size)
{
      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      return usb_control_msg(port->pl->dh, 
            USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_ENDPOINT_IN,
            request, value, index, bytes, size, port->timeout);
}


/* The next two functions support the nonstandard request types 0x21 (write) 
 * and 0xa1 (read), which are occasionally needed. 
 */

static int
gp_port_usb_msg_class_write_lib(GPPort *port, int request, 
      int value, int index, char *bytes, int size)
{
      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      return usb_control_msg(port->pl->dh, 
            USB_TYPE_CLASS | USB_RECIP_INTERFACE,
            request, value, index, bytes, size, port->timeout);
}


static int
gp_port_usb_msg_class_read_lib(GPPort *port, int request, 
      int value, int index, char *bytes, int size)
{
      if (!port || !port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      return usb_control_msg(port->pl->dh, 
            USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_ENDPOINT_IN,
            request, value, index, bytes, size, port->timeout);
}

/*
 * This function applys changes to the device.
 *
 * New settings are in port->settings_pending and the old ones
 * are in port->settings. Compare them first and only call
 * usb_set_configuration() and usb_set_altinterface() if needed
 * since some USB devices does not like it if this is done
 * more than necessary (Canon Digital IXUS 300 for one).
 *
 */
static int
gp_port_usb_update (GPPort *port)
{
      int ret, ifacereleased = FALSE;

      gp_log (GP_LOG_DEBUG, "libusb", "gp_port_usb_update(old int=%d, conf=%d, alt=%d), (new int=%d, conf=%d, alt=%d)",
            port->settings.usb.interface,
            port->settings.usb.config,
            port->settings.usb.altsetting,
            port->settings_pending.usb.interface,
            port->settings_pending.usb.config,
            port->settings_pending.usb.altsetting
      );
      if (!port)
            return GP_ERROR_BAD_PARAMETERS;

      if (port->pl->interface == -1) port->pl->interface = port->settings.usb.interface;
      if (port->pl->config == -1) port->pl->config = port->settings.usb.config;
      if (port->pl->altsetting == -1) port->pl->altsetting = port->settings.usb.altsetting;

      /* The portname can also be changed with the device still fully closed. */
      memcpy(&port->settings.usb.port, &port->settings_pending.usb.port,
            sizeof(port->settings.usb.port));

      if (!port->pl->dh)
            return GP_ERROR_BAD_PARAMETERS;

      memcpy(&port->settings.usb, &port->settings_pending.usb,
            sizeof(port->settings.usb));

      /* The interface changed. release the old, claim the new ... */
      if (port->settings.usb.interface != port->pl->interface) {
            gp_log (GP_LOG_DEBUG, "libusb", "changing interface %d -> %d",
                  port->pl->interface,
                  port->settings.usb.interface
            );
            if (usb_release_interface (port->pl->dh,
                                 port->pl->interface) < 0) {
                  gp_log (GP_LOG_DEBUG, "gphoto2-port-usb","releasing the iface for config failed.");
                  /* Not a hard error for now. -Marcus */
            } else {
                  gp_log (GP_LOG_DEBUG,"libusb","claiming interface %d", port->settings.usb.interface);
                  ret = usb_claim_interface (port->pl->dh,
                                       port->settings.usb.interface);
                  if (ret < 0) {
                        gp_log (GP_LOG_DEBUG, "gphoto2-port-usb","reclaiming the iface for config failed.");
                        return GP_ERROR_IO_UPDATE;
                  }
                  port->pl->interface = port->settings.usb.interface;
            }
      }
      if (port->settings.usb.config != port->pl->config) {
            gp_log (GP_LOG_DEBUG, "libusb", "changing config %d -> %d",
                  port->pl->config,
                  port->settings.usb.config
            );
            /* This can only be changed with the interface released. 
             * This is a hard requirement since 2.6.12.
             */
            if (usb_release_interface (port->pl->dh,
                                 port->settings.usb.interface) < 0) {
                  gp_log (GP_LOG_DEBUG, "gphoto2-port-usb","releasing the iface for config failed.");
                  ifacereleased = FALSE;
            } else {
                  ifacereleased = TRUE;
            }
            ret = usb_set_configuration(port->pl->dh,
                             port->settings.usb.config);
            if (ret < 0) {
#if 0 /* setting the configuration failure is not fatal */
                  gp_port_set_error (port,
                        _("Could not set config %d/%d (%m)"),
                        port->settings.usb.interface,
                        port->settings.usb.config);
                  return GP_ERROR_IO_UPDATE;    
#endif
                  gp_log (GP_LOG_ERROR, "gphoto2-port-usb","setting configuration from %d to %d failed with ret = %d, but continue...", port->pl->config, port->settings.usb.config, ret);
            }

            gp_log (GP_LOG_DEBUG, "gphoto2-port-usb",
                  "Changed usb.config from %d to %d",
                  port->pl->config,
                  port->settings.usb.config);

            if (ifacereleased) {
                  gp_log (GP_LOG_DEBUG,"libusb","claiming interface %d", port->settings.usb.interface);
                  ret = usb_claim_interface (port->pl->dh,
                                       port->settings.usb.interface);
                  if (ret < 0) {
                        gp_log (GP_LOG_DEBUG, "gphoto2-port-usb","reclaiming the iface for config failed.");
                  }
            }
            /*
             * Copy at once if something else fails so that this
             * does not get re-applied
             */
            port->pl->config = port->settings.usb.config;
      }

      /* This can be changed with interface claimed. (And I think it must be claimed.) */
      if (port->settings.usb.altsetting != port->pl->altsetting) {
            ret = usb_set_altinterface(port->pl->dh, port->settings.usb.altsetting);
            if (ret < 0) {
                  gp_port_set_error (port, 
                        _("Could not set altsetting from %d "
                        "to %d (%m)"),
                        port->pl->altsetting,
                        port->settings.usb.altsetting);
                  return GP_ERROR_IO_UPDATE;
            }

            gp_log (GP_LOG_DEBUG, "gphoto2-port-usb",
                  "Changed usb.altsetting from %d to %d",
                  port->pl->altsetting,
                  port->settings.usb.altsetting);
            port->pl->altsetting = port->settings.usb.altsetting;
      }

      return GP_OK;
}

static int
gp_port_usb_find_ep(struct usb_device *dev, int config, int interface, int altsetting, int direction, int type)
{
      struct usb_interface_descriptor *intf;
      int i;

      if (!dev->config)
            return -1;

      intf = &dev->config[config].interface[interface].altsetting[altsetting];

      for (i = 0; i < intf->bNumEndpoints; i++) {
            if ((intf->endpoint[i].bEndpointAddress & USB_ENDPOINT_DIR_MASK) == direction &&
                (intf->endpoint[i].bmAttributes & USB_ENDPOINT_TYPE_MASK) == type)
                  return intf->endpoint[i].bEndpointAddress;
      }

      return -1;
}

static int
gp_port_usb_find_first_altsetting(struct usb_device *dev, int *config, int *interface, int *altsetting)
{
      int i, i1, i2;

      if (!dev->config)
            return -1;

      for (i = 0; i < dev->descriptor.bNumConfigurations; i++)
            for (i1 = 0; i1 < dev->config[i].bNumInterfaces; i1++)
                  for (i2 = 0; i2 < dev->config[i].interface[i1].num_altsetting; i2++)
                        if (dev->config[i].interface[i1].altsetting[i2].bNumEndpoints) {
                              *config = i;
                              *interface = i1;
                              *altsetting = i2;

                              return 0;
                        }

      return -1;
}

static int
gp_port_usb_find_device_lib(GPPort *port, int idvendor, int idproduct)
{
      struct usb_bus *bus;
      struct usb_device *dev;
      char *s;
      char busname[64], devname[64];

      if (!port)
            return (GP_ERROR_BAD_PARAMETERS);

      s = strchr (port->settings.usb.port,':');
      busname[0] = devname[0] = '\0';
      if (s && (s[1] != '\0')) { /* usb:%d,%d */
            strncpy(busname,s+1,sizeof(busname));
            busname[sizeof(busname)-1] = '\0';

            s = strchr(busname,',');
            if (s) {
                  strncpy(devname, s+1,sizeof(devname));
                  devname[sizeof(devname)-1] = '\0';
                  *s = '\0';
            } else {
                  busname[0] = '\0';
            }
      }
      /*
       * NULL idvendor is not valid.
       * NULL idproduct is ok.
       * Should the USB layer report that ? I don't know.
       * Better to check here.
       */
      if (!idvendor) {
            gp_port_set_error (port, _("The supplied vendor or product "
                  "id (0x%x,0x%x) is not valid."), idvendor, idproduct);
            return GP_ERROR_BAD_PARAMETERS;
      }

      for (bus = usb_busses; bus; bus = bus->next) {
            if ((busname[0] != '\0') && strcmp(busname, bus->dirname))
                  continue;

            for (dev = bus->devices; dev; dev = dev->next) {
                  if (  (devname[0] != '\0') &&
                        (dev->filename != strstr(dev->filename, devname))
                  )
                        continue;

                  if ((dev->descriptor.idVendor == idvendor) &&
                      (dev->descriptor.idProduct == idproduct)) {
                        int config = -1, interface = -1, altsetting = -1;

                        port->pl->d = dev;

                        gp_log (GP_LOG_VERBOSE, "gphoto2-port-usb",
                              "Looking for USB device "
                              "(vendor 0x%x, product 0x%x)... found.", 
                              idvendor, idproduct);

                        /* Use the first config, interface and altsetting we find */
                        gp_port_usb_find_first_altsetting(dev, &config, &interface, &altsetting);

                        /* Set the defaults */
                        if (dev->config) {
                              int i;

                              if (dev->config[config].interface[interface].altsetting[altsetting].bInterfaceClass
                                  == USB_CLASS_MASS_STORAGE) {
                                    gp_log (GP_LOG_VERBOSE, "gphoto2-port-usb",
                                          _("USB device (vendor 0x%x, product 0x%x) is a mass"
                                            " storage device, and might not function with gphoto2."
                                            " Reference: %s"),
                                          idvendor, idproduct, URL_USB_MASSSTORAGE);
                              }
                              port->settings.usb.config = dev->config[config].bConfigurationValue;
                              port->settings.usb.interface = dev->config[config].interface[interface].altsetting[altsetting].bInterfaceNumber;
                              port->settings.usb.altsetting = dev->config[config].interface[interface].altsetting[altsetting].bAlternateSetting;

                              port->settings.usb.inep = gp_port_usb_find_ep(dev, config, interface, altsetting, USB_ENDPOINT_IN, USB_ENDPOINT_TYPE_BULK);
                              port->settings.usb.outep = gp_port_usb_find_ep(dev, config, interface, altsetting, USB_ENDPOINT_OUT, USB_ENDPOINT_TYPE_BULK);
                              port->settings.usb.intep = gp_port_usb_find_ep(dev, config, interface, altsetting, USB_ENDPOINT_IN, USB_ENDPOINT_TYPE_INTERRUPT);

                              port->settings.usb.maxpacketsize = 0;
                              gp_log (GP_LOG_DEBUG, "gphoto2-port-usb", "inep to look for is %02x", port->settings.usb.inep);
                              for (i=0;i<dev->config[config].interface[interface].altsetting[altsetting].bNumEndpoints;i++) {
                                    if (port->settings.usb.inep == dev->config[config].interface[interface].altsetting[altsetting].endpoint[i].bEndpointAddress) {
                                          port->settings.usb.maxpacketsize = dev->config[config].interface[interface].altsetting[altsetting].endpoint[i].wMaxPacketSize;
                                          break;
                                    }
                              }
                              gp_log (GP_LOG_VERBOSE, "gphoto2-port-usb",
                                    "Detected defaults: config %d, "
                                    "interface %d, altsetting %d, "
                                    "inep %02x, outep %02x, intep %02x, "
                                    "class %02x, subclass %02x",
                                    port->settings.usb.config,
                                    port->settings.usb.interface,
                                    port->settings.usb.altsetting,
                                    port->settings.usb.inep,
                                    port->settings.usb.outep,
                                    port->settings.usb.intep,
                                    dev->config[config].interface[interface].altsetting[altsetting].bInterfaceClass,
                                    dev->config[config].interface[interface].altsetting[altsetting].bInterfaceSubClass
                                    );
                        }

                        return GP_OK;
                  }
            }
      }

      gp_port_set_error (port, _("Could not find USB device "
            "(vendor 0x%x, product 0x%x). Make sure this device "
            "is connected to the computer."), idvendor, idproduct);
      return GP_ERROR_IO_USB_FIND;
}

/* This function reads the Microsoft OS Descriptor and looks inside to
 * find if it is a MTP device. This is the similar to the way that
 * Windows Media Player 10 uses.
 * It is documented to some degree on various internet pages.
 */
static int
gp_port_usb_match_mtp_device(struct usb_device *dev,int *configno, int *interfaceno, int *altsettingno)
{
      char buf[1000], cmd;
      int ret,i,i1,i2;
      usb_dev_handle *devh;

      /* All of them are "vendor specific" device class */
#if 0
      if ((dev->descriptor.bDeviceClass!=0xff) && (dev->descriptor.bDeviceClass!=0))
            return 0;
#endif

      devh = usb_open (dev);
      if (!devh)
            return 0;
      /* get string descriptor at 0xEE */
      ret = usb_get_descriptor (devh, 0x03, 0xee, buf, sizeof(buf));
      if (ret > 0) gp_log_data("get_MS_OSD",buf, ret);
      if (ret < 10) goto errout;
      if (!((buf[2] == 'M') && (buf[4]=='S') && (buf[6]=='F') && (buf[8]=='T')))
            goto errout;
      cmd = buf[16];
      ret = usb_control_msg (devh, USB_ENDPOINT_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, cmd, 0, 4, buf, sizeof(buf), 1000);
      if (ret == -1) {
            gp_log (GP_LOG_ERROR, "mtp matcher", "control message says %d\n", ret);
            goto errout;
      }
      if (buf[0] != 0x28) {
            gp_log (GP_LOG_ERROR, "mtp matcher", "ret is %d, buf[0] is %x\n", ret, buf[0]);
            goto errout;
      }
      if (ret > 0) gp_log_data("get_MS_ExtDesc",buf, ret);
      if ((buf[0x12] != 'M') || (buf[0x13] != 'T') || (buf[0x14] != 'P')) {
            gp_log (GP_LOG_ERROR, "mtp matcher", "buf at 0x12 is %02x%02x%02x\n", buf[0x12], buf[0x13], buf[0x14]);
            goto errout;
      }
      ret = usb_control_msg (devh, USB_ENDPOINT_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, cmd, 0, 5, buf, sizeof(buf), 1000);
      if (ret == -1) goto errout;
      if (buf[0] != 0x28) {
            gp_log (GP_LOG_ERROR, "mtp matcher", "ret is %d, buf[0] is %x\n", ret, buf[0]);
            goto errout;
      }
      if (ret > 0) gp_log_data("get_MS_ExtProp",buf, ret);
      if ((buf[0x12] != 'M') || (buf[0x13] != 'T') || (buf[0x14] != 'P')) {
            gp_log (GP_LOG_ERROR, "mtp matcher", "buf at 0x12 is %02x%02x%02x\n", buf[0x12], buf[0x13], buf[0x14]);
            goto errout;
      }
      usb_close (devh);

      /* Now chose a nice interface for us to use ... Just take the first. */

      if (dev->descriptor.bNumConfigurations > 1)
            gp_log (GP_LOG_ERROR, "mtp matcher", "The device has %d configurations!\n", dev->descriptor.bNumConfigurations);
      for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
            struct usb_config_descriptor *config =
                  &dev->config[i];

            if (config->bNumInterfaces > 1)
                  gp_log (GP_LOG_ERROR, "mtp matcher", "The configuration has %d interfaces!\n", config->bNumInterfaces);
            for (i1 = 0; i1 < config->bNumInterfaces; i1++) {
                  struct usb_interface *interface =
                        &config->interface[i1];

                  if (interface->num_altsetting > 1)
                        gp_log (GP_LOG_ERROR, "mtp matcher", "The interface has %d altsettings!\n", interface->num_altsetting);
                  for (i2 = 0; i2 < interface->num_altsetting; i2++) {
                        *configno = i;
                        *interfaceno = i1;
                        *altsettingno = i2;
                        return 1;
                  }
            }
      }
      return 1;
errout:
      usb_close (devh);
      return 0;
}

static int
gp_port_usb_match_device_by_class(struct usb_device *dev, int class, int subclass, int protocol, int *configno, int *interfaceno, int *altsettingno)
{
      int i, i1, i2;

      if (class == 666) /* Special hack for MTP devices with MS OS descriptors. */
            return gp_port_usb_match_mtp_device (dev, configno, interfaceno, altsettingno);

      if (dev->descriptor.bDeviceClass == class &&
          (subclass == -1 ||
           dev->descriptor.bDeviceSubClass == subclass) &&
          (protocol == -1 ||
           dev->descriptor.bDeviceProtocol == protocol))
            return 1;

      if (!dev->config)
            return 0;

      for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
            struct usb_config_descriptor *config =
                  &dev->config[i];

            for (i1 = 0; i1 < config->bNumInterfaces; i1++) {
                  struct usb_interface *interface =
                        &config->interface[i1];

                  for (i2 = 0; i2 < interface->num_altsetting; i2++) {
                        struct usb_interface_descriptor *altsetting =
                              &interface->altsetting[i2];

                        if (altsetting->bInterfaceClass == class &&
                            (subclass == -1 ||
                             altsetting->bInterfaceSubClass == subclass) &&
                            (protocol == -1 ||
                             altsetting->bInterfaceProtocol == protocol)) {
                              *configno = i;
                              *interfaceno = i1;
                              *altsettingno = i2;

                              return 2;
                        }
                  }
            }
      }

      return 0;
}

static int
gp_port_usb_find_device_by_class_lib(GPPort *port, int class, int subclass, int protocol)
{
      struct usb_bus *bus;
      struct usb_device *dev;
      char *s;
      char busname[64], devname[64];

      if (!port)
            return (GP_ERROR_BAD_PARAMETERS);

      busname[0] = devname[0] = '\0';
      s = strchr (port->settings.usb.port,':');
      if (s && (s[1] != '\0')) { /* usb:%d,%d */
            strncpy(busname,s+1,sizeof(busname));
            busname[sizeof(busname)-1] = '\0';

            s = strchr(busname,',');
            if (s) {
                  strncpy(devname, s+1,sizeof(devname));
                  devname[sizeof(devname)-1] = '\0';
                  *s = '\0';
            } else {
                  busname[0] = '\0';
            }
      }
      /*
       * NULL class is not valid.
       * NULL subclass and protocol is ok.
       * Should the USB layer report that ? I don't know.
       * Better to check here.
       */
      if (!class)
            return GP_ERROR_BAD_PARAMETERS;

      for (bus = usb_busses; bus; bus = bus->next) {
            if ((busname[0] != '\0') && strcmp(busname, bus->dirname))
                  continue;

            for (dev = bus->devices; dev; dev = dev->next) {
                  int ret, config = -1, interface = -1, altsetting = -1;

                  if ((devname[0] != '\0') && strcmp(devname, dev->filename))
                        continue;

                  ret = gp_port_usb_match_device_by_class(dev, class, subclass, protocol, &config, &interface, &altsetting);
                  if (!ret)
                        continue;

                  port->pl->d = dev;
                  gp_log (GP_LOG_VERBOSE, "gphoto2-port-usb",
                        "Looking for USB device "
                        "(class 0x%x, subclass, 0x%x, protocol 0x%x)... found.", 
                        class, subclass, protocol);
                  /* Set the defaults */
                  if (dev->config) {
                        int i;

                        port->settings.usb.config = dev->config[config].bConfigurationValue;
                        port->settings.usb.interface = dev->config[config].interface[interface].altsetting[altsetting].bInterfaceNumber;
                        port->settings.usb.altsetting = dev->config[config].interface[interface].altsetting[altsetting].bAlternateSetting;

                        port->settings.usb.inep = gp_port_usb_find_ep(dev, config, interface, altsetting, USB_ENDPOINT_IN, USB_ENDPOINT_TYPE_BULK);
                        port->settings.usb.outep = gp_port_usb_find_ep(dev, config, interface, altsetting, USB_ENDPOINT_OUT, USB_ENDPOINT_TYPE_BULK);
                        port->settings.usb.intep = gp_port_usb_find_ep(dev, config, interface, altsetting, USB_ENDPOINT_IN, USB_ENDPOINT_TYPE_INTERRUPT);
                        port->settings.usb.maxpacketsize = 0;
                        gp_log (GP_LOG_DEBUG, "gphoto2-port-usb", "inep to look for is %02x", port->settings.usb.inep);
                        for (i=0;i<dev->config[config].interface[interface].altsetting[altsetting].bNumEndpoints;i++) {
                              if (port->settings.usb.inep == dev->config[config].interface[interface].altsetting[altsetting].endpoint[i].bEndpointAddress) {
                                    port->settings.usb.maxpacketsize = dev->config[config].interface[interface].altsetting[altsetting].endpoint[i].wMaxPacketSize;
                                    break;
                              }
                        }
                        gp_log (GP_LOG_VERBOSE, "gphoto2-port-usb",
                              "Detected defaults: config %d, "
                              "interface %d, altsetting %d, "
                              "idVendor ID %04x, idProduct %04x, "
                              "inep %02x, outep %02x, intep %02x",
                              port->settings.usb.config,
                              port->settings.usb.interface,
                              port->settings.usb.altsetting,
                              dev->descriptor.idVendor,
                              dev->descriptor.idProduct,
                              port->settings.usb.inep,
                              port->settings.usb.outep,
                              port->settings.usb.intep
                              );
                  }

                  return GP_OK;
            }
      }

      gp_port_set_error (port, _("Could not find USB device "
            "(class 0x%x, subclass 0x%x, protocol 0x%x). Make sure this device "
            "is connected to the computer."), class, subclass, protocol);
      return GP_ERROR_IO_USB_FIND;
}

GPPortOperations *
gp_port_library_operations (void)
{
      GPPortOperations *ops;

      ops = malloc (sizeof (GPPortOperations));
      if (!ops)
            return (NULL);
      memset (ops, 0, sizeof (GPPortOperations));

      ops->init   = gp_port_usb_init;
      ops->exit   = gp_port_usb_exit;
      ops->open   = gp_port_usb_open;
      ops->close  = gp_port_usb_close;
      ops->read   = gp_port_usb_read;
      ops->write  = gp_port_usb_write;
      ops->check_int = gp_port_usb_check_int;
      ops->update = gp_port_usb_update;
      ops->clear_halt = gp_port_usb_clear_halt_lib;
      ops->msg_write  = gp_port_usb_msg_write_lib;
      ops->msg_read   = gp_port_usb_msg_read_lib;
      ops->msg_interface_write  = gp_port_usb_msg_interface_write_lib;
      ops->msg_interface_read   = gp_port_usb_msg_interface_read_lib;
      ops->msg_class_write  = gp_port_usb_msg_class_write_lib;
      ops->msg_class_read   = gp_port_usb_msg_class_read_lib;
      ops->find_device = gp_port_usb_find_device_lib;
      ops->find_device_by_class = gp_port_usb_find_device_by_class_lib;

      return (ops);
}

Generated by  Doxygen 1.6.0   Back to index