/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gphoto2-port-usb.c
*
* Copyright 2001 Lutz Mueller <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., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#define _BSD_SOURCE
#include "config.h"
#include <gphoto2/gphoto2-port-library.h>
#include <stdlib.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>
#ifndef ENODATA
# define ENODATA 120 /* No data available */
#endif
#include <gphoto2/gphoto2-port.h>
#include <gphoto2/gphoto2-port-result.h>
#include <gphoto2/gphoto2-port-log.h>
#if defined(LIBUSB_HAS_GET_DRIVER_NP) && defined(LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP)
/* Pull in USBDEVFS_CONNECT */
#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>
#endif
#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;
int detached;
};
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;
/* generic matcher. This will catch passed XXX,YYY entries for instance. */
gp_port_info_new (&info);
gp_port_info_set_type (info, GP_PORT_USB);
gp_port_info_set_name (info, "");
gp_port_info_set_path (info, "^usb:");
gp_port_info_list_append (list, info); /* do not check here */
usb_init ();
usb_find_busses ();
usb_find_devices ();
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) ||
(dev->descriptor.bDeviceClass == 0xe0) /* wireless / bluetooth */
)
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) ||
(intf->bInterfaceClass == 0xe0) /* wireless/bluetooth*/
)
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 0
/* If we already added usb:, and have 0 or 1 devices we have nothing to do.
* This should be the standard use case.
*/
/* We never want to return just "usb:" ... also return "usb:XXX,YYY", and
* let upper layers filter out the usb: */
if (nrofdevices <= 1)
return (GP_OK);
#endif
/* 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;
char path[200];
/* 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.
*/
gp_port_info_new (&info);
gp_port_info_set_type (info, GP_PORT_USB);
gp_port_info_set_name (info, "Universal Serial Bus");
snprintf (path,sizeof(path), "usb:%s,%s", bus->dirname, dev->filename);
/* On MacOS X we might get usb:006,002-04a9-3139-00-00. */
s = strchr(path, '-');if (s) *s='\0';
gp_port_info_set_path (info, path);
CHECK (gp_port_info_list_append (list, info));
}
bus = bus->next;
}
/* This will only be added if no other device was ever added.
* Users doing "usb:" usage will enter the regular expression matcher case. */
if (nrofdevices == 0) {
gp_port_info_new (&info);
gp_port_info_set_type (info, GP_PORT_USB);
gp_port_info_set_name (info, "Universal Serial Bus");
gp_port_info_set_path (info, "usb:");
CHECK (gp_port_info_list_append (list, info));
}
return (GP_OK);
}
static int gp_port_usb_init (GPPort *port)
{
C_MEM (port->pl = calloc (1, 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)
{
free (port->pl);
port->pl = NULL;
return (GP_OK);
}
static int gp_port_usb_find_path_lib(GPPort *);
static int
gp_port_usb_open (GPPort *port)
{
int ret;
char name[64];
GP_LOG_D ("gp_port_usb_open(%p)", port);
C_PARAMS (port);
if (!port->pl->d) {
ret = gp_port_usb_find_path_lib(port);
C_PARAMS (port->pl->d);
}
/*
* Open the device using the previous usb_handle returned by
* find_device
*/
port->pl->dh = usb_open (port->pl->d);
if (!port->pl->dh) {
int saved_errno = errno;
gp_port_set_error (port, _("Could not open USB device (%s)."),
strerror(saved_errno));
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 (name[0] != 0)
GP_LOG_D ("Device has driver '%s' attached.", name);
if (strstr(name,"usbfs") || strstr(name,"storage")) {
/* other gphoto instance most likely */
gp_port_set_error (port, _("Camera is already in use."));
return GP_ERROR_IO_LOCK;
}
if (ret >= 0) {
GP_LOG_D ("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
port->pl->detached = 1;
} else {
if (errno != ENODATA) /* ENODATA - just no driver there */
gp_port_set_error (port, _("Could not query kernel driver of device."));
}
#endif
GP_LOG_D ("claiming interface %d", port->settings.usb.interface);
ret = usb_claim_interface (port->pl->dh,
port->settings.usb.interface);
if (ret < 0) {
int saved_errno = errno;
gp_port_set_error (port, _("Could not claim interface %d (%s). "
"Make sure no other program (%s) "
"or kernel module (such as %s) "
"is using the device and you have "
"read/write access to the device."
),
port->settings.usb.interface,
strerror(saved_errno),
#ifdef __linux__
"gvfs-gphoto2-volume-monitor",
#else
#if defined(__APPLE__)
N_("MacOS PTPCamera service"),
#else
N_("unknown libgphoto2 using program"),
#endif
#endif
"sdc2xx, stv680, spca50x");
return GP_ERROR_IO_USB_CLAIM;
}
return GP_OK;
}
static int
gp_port_usb_close (GPPort *port)
{
C_PARAMS (port && port->pl->dh);
if (usb_release_interface (port->pl->dh,
port->settings.usb.interface) < 0) {
int saved_errno = errno;
gp_port_set_error (port, _("Could not release "
"interface %d (%s)."),
port->settings.usb.interface,
strerror(saved_errno));
return (GP_ERROR_IO);
}
#if 0
/* This confuses the EOS 5d camera and possible other EOSs. *sigh* */
/* 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) {
int saved_errno = errno;
gp_port_set_error (port, _("Could not reset "
"USB port (%s)."),
strerror(saved_errno));
return (GP_ERROR_IO);
}
}
#endif
#if defined(LIBUSB_HAS_GET_DRIVER_NP) && defined(LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP) && defined(USBDEVFS_CONNECT)
if (port->pl->detached) {
char filename[PATH_MAX + 1];
int fd;
/* FIXME shouldn't be a fixed path to usb root */
snprintf(filename, sizeof(filename) - 1, "%s/%s/%s", "/dev/bus/usb", port->pl->d->bus->dirname, port->pl->d->filename);
fd = open(filename, O_RDWR);
if (fd >= 0) {
struct usbdevfs_ioctl command;
command.ifno = 0;
command.ioctl_code = USBDEVFS_CONNECT;
command.data = NULL;
if (ioctl(fd, USBDEVFS_IOCTL, &command))
GP_LOG_D ("reattach kernel driver failed");
close(fd);
}
}
#endif
if (usb_close (port->pl->dh) < 0) {
int saved_errno = errno;
gp_port_set_error (port, _("Could not close USB port (%s)."),
strerror(saved_errno));
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;
C_PARAMS (port && port->pl->dh);
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;
C_PARAMS (port && port->pl->dh);
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;
C_PARAMS (port && port->pl->dh);
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_reset(GPPort *port)
{
int ret;
GP_LOG_D ("Reseting port");
C_PARAMS (port && port->pl->dh);
ret = usb_reset(port->pl->dh);
if (ret < 0) {
GP_LOG_E ("gp_port_reset: %d", ret);
return GP_ERROR_IO_READ;
}
return GP_OK;
}
static int
gp_port_usb_check_int (GPPort *port, char *bytes, int size, int timeout)
{
int ret;
C_PARAMS (port && port->pl->dh && timeout >= 0);
/* 0 timeout is okish ... libusb 0.x it will do 1 poll though */
ret = usb_interrupt_read(port->pl->dh, port->settings.usb.intep,
bytes, size, timeout);
if (ret < 0) {
if ((errno == EAGAIN) || (errno == ETIMEDOUT))
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)
{
C_PARAMS (port && port->pl->dh);
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)
{
C_PARAMS (port && port->pl->dh);
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)
{
C_PARAMS (port && port->pl->dh);
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)
{
C_PARAMS (port && port->pl->dh);
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)
{
C_PARAMS (port && port->pl->dh);
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)
{
C_PARAMS (port && port->pl->dh);
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;
C_PARAMS (port);
GP_LOG_D ("gp_port_usb_update(old int=%d, conf=%d, alt=%d) port %s, (new int=%d, conf=%d, alt=%d), port %s",
port->settings.usb.interface,
port->settings.usb.config,
port->settings.usb.altsetting,
port->settings.usb.port,
port->settings_pending.usb.interface,
port->settings_pending.usb.config,
port->settings_pending.usb.altsetting,
port->settings_pending.usb.port
);
#if 0
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;
#endif
/* 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_OK; /* the port might not be opened, yet. that is ok */
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_D ("changing interface %d -> %d",
port->pl->interface,
port->settings.usb.interface
);
if (usb_release_interface (port->pl->dh,
port->pl->interface) < 0) {
GP_LOG_D ("releasing the iface for config failed.");
/* Not a hard error for now. -Marcus */
} else {
GP_LOG_D ("claiming interface %d", port->settings.usb.interface);
ret = usb_claim_interface (port->pl->dh,
port->settings.usb.interface);
if (ret < 0) {
GP_LOG_D ("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_D ("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_D ("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 */
int saved_errno = errno;
gp_port_set_error (port,
_("Could not set config %d/%d (%s)"),
port->settings.usb.interface,
port->settings.usb.config,
strerror(saved_errno));
return GP_ERROR_IO_UPDATE;
#endif
GP_LOG_E ("setting configuration from %d to %d failed with ret = %d, but continue...", port->pl->config, port->settings.usb.config, ret);
}
GP_LOG_D ("Changed usb.config from %d to %d", port->pl->config, port->settings.usb.config);
if (ifacereleased) {
GP_LOG_D ("claiming interface %d", port->settings.usb.interface);
ret = usb_claim_interface (port->pl->dh,
port->settings.usb.interface);
if (ret < 0)
GP_LOG_D ("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) {
int saved_errno = errno;
gp_port_set_error (port,
_("Could not set altsetting from %d "
"to %d (%s)"),
port->pl->altsetting,
port->settings.usb.altsetting,
strerror(saved_errno));
return GP_ERROR_IO_UPDATE;
}
GP_LOG_D ("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];
C_PARAMS (port);
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_D ("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_D ("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_D ("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_D ("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;
}
}
}
#if 0
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);
#endif
return GP_ERROR_IO_USB_FIND;
}
static int
gp_port_usb_find_path_lib(GPPort *port)
{
struct usb_bus *bus;
struct usb_device *dev;
char *s;
char busname[64], devname[64];
C_PARAMS (port);
s = strchr (port->settings.usb.port,':');
busname[0] = devname[0] = '\0';
C_PARAMS (s && s[1] != '\0'); /* generic usb: match ... can't do */
if (s && (s[1] != '\0')) { /* usb:%d,%d */
strncpy(busname,s+1,sizeof(busname));
busname[sizeof(busname)-1] = '\0';
s = strchr(busname,',');
C_PARAMS (s); /* generic usb: match ... can't do */
strncpy(devname, s+1,sizeof(devname));
devname[sizeof(devname)-1] = '\0';
*s = '\0';
}
for (bus = usb_busses; bus; bus = bus->next) {
if (strcmp(busname, bus->dirname))
continue;
for (dev = bus->devices; dev; dev = dev->next) {
int config = -1, interface = -1, altsetting = -1;
if ((dev->filename != strstr(dev->filename, devname)))
continue;
port->pl->d = dev;
GP_LOG_D ("Found device for path %s", port->settings.usb.port);
/* 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;
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_D ("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_D ("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;
}
}
#if 0
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);
#endif
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.
*/
#if 0
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, xifaces,xnocamifaces;
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
xifaces = xnocamifaces = 0;
if (dev->config) {
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
unsigned int j;
for (j = 0; j < dev->config[i].bNumInterfaces; j++) {
int k;
xifaces++;
for (k = 0; k < dev->config[i].interface[j].num_altsetting; k++) {
struct usb_interface_descriptor *intf = &dev->config[i].interface[j].altsetting[k];
if ( (intf->bInterfaceClass == USB_CLASS_HID) ||
(intf->bInterfaceClass == USB_CLASS_PRINTER) ||
(intf->bInterfaceClass == USB_CLASS_AUDIO) ||
(intf->bInterfaceClass == USB_CLASS_HUB) ||
(intf->bInterfaceClass == USB_CLASS_COMM) ||
(intf->bInterfaceClass == 0xe0) /* wireless/bluetooth*/
)
xnocamifaces++;
}
}
}
}
if (xifaces == xnocamifaces) /* only non-camera ifaces */
return 0;
/* Marcus: Avoid this probing altogether, its too unstable on some devices */
return 0;
#if 0
devh = usb_open (dev);
if (!devh)
return 0;
/*
* Loop over the device configurations and interfaces. Nokia MTP-capable
* handsets (possibly others) typically have the string "MTP" in their
* MTP interface descriptions, that's how they can be detected, before
* we try the more esoteric "OS descriptors" (below).
*/
if (dev->config) {
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
unsigned int j;
for (j = 0; j < dev->config[i].bNumInterfaces; j++) {
int k;
for (k = 0; k < dev->config[i].interface[j].num_altsetting; k++) {
buf[0] = '\0';
ret = usb_get_string_simple(devh,
dev->config[i].interface[j].altsetting[k].iInterface,
(char *) buf,
1024);
if (ret < 3)
continue;
if (strcmp((char *) buf, "MTP") == 0) {
GP_LOG_D ("Configuration %d, interface %d, altsetting %d:\n", i, j, k);
GP_LOG_D (" Interface description contains the string \"MTP\"\n");
GP_LOG_D (" Device recognized as MTP, no further probing.\n");
goto found;
}
}
}
}
}
/* get string descriptor at 0xEE */
ret = usb_get_descriptor (devh, 0x03, 0xee, buf, sizeof(buf));
if (ret > 0) GP_LOG_DATA (buf, ret, "get_MS_OSD");
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_E ("control message says %d\n", ret);
goto errout;
}
if (buf[0] != 0x28) {
GP_LOG_E ("ret is %d, buf[0] is %x\n", ret, buf[0]);
goto errout;
}
if (ret > 0) GP_LOG_DATA (buf, ret, "get_MS_ExtDesc");
if ((buf[0x12] != 'M') || (buf[0x13] != 'T') || (buf[0x14] != 'P')) {
GP_LOG_E ("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_E ("ret is %d, buf[0] is %x\n", ret, buf[0]);
goto errout;
}
if (ret > 0) GP_LOG_DATA (buf, ret, "get_MS_ExtProp");
if ((buf[0x12] != 'M') || (buf[0x13] != 'T') || (buf[0x14] != 'P')) {
GP_LOG_E ("buf at 0x12 is %02x%02x%02x\n", buf[0x12], buf[0x13], buf[0x14]);
goto errout;
}
found:
usb_close (devh);
/* Now chose a nice interface for us to use ... Just take the first. */
if (dev->descriptor.bNumConfigurations > 1)
GP_LOG_E ("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_E ("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_E ("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;
#endif
}
#endif
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 0
if (class == 666) /* Special hack for MTP devices with MS OS descriptors. */
return gp_port_usb_match_mtp_device (dev, configno, interfaceno, altsettingno);
#endif
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];
C_PARAMS (port);
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.
*/
C_PARAMS (class);
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;
GP_LOG_D ("Looking for USB device (class 0x%x, subclass, 0x%x, protocol 0x%x)...", class, subclass, protocol);
ret = gp_port_usb_match_device_by_class(dev, class, subclass, protocol, &config, &interface, &altsetting);
if (!ret)
continue;
port->pl->d = dev;
GP_LOG_D ("Found USB class device (class 0x%x, subclass, 0x%x, protocol 0x%x)", 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_D ("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_D ("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;
}
}
#if 0
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);
#endif
return GP_ERROR_IO_USB_FIND;
}
GPPortOperations *
gp_port_library_operations (void)
{
GPPortOperations *ops;
ops = calloc (1, sizeof (GPPortOperations));
if (!ops)
return (NULL);
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->reset = gp_port_usb_reset;
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);
}