autodetect USB MTP device characteristics without libusb_open()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

we had the following problem in libmtp:

- We have to autoprobe every plugged-in device to figure out if it is
  MTP or not. These devices are numerous and we can no longer
  count on users reporting each and every VID+PID out there

- Class codes cannot be used

- Have to look for devices with interfaces with certain endpoints:
  - 1 BULK in
  - 1 BULK out
  - 1 INTERRUPT in

- Then we do deeper probing.

- However some devices bug out on libusb_open() which is in turn
  needed to use libusb to figure out if the interface has these
  BULK and INTERRUPT characteristics

So since I cannot use libusb_open() I have added code like below
to libmtp to instead inspect sysfs *before* starting any libusb-based
business. It is run from udev and udev provides the sysfs node and
that is passed to check_sysfs().

So:

- Is this a good idea?

- What is the scary business that libusb_open() does that makes
  a lot of substandard USB devices totally freak out?

enum ep_type {
  OTHER_EP,
  BULK_OUT_EP,
  BULK_IN_EP,
  INTERRUPT_IN_EP,
  INTERRUPT_OUT_EP,
};

static enum ep_type get_ep_type(char *path)
{
  char pbuf[FILENAME_MAX];enum ep_type {
  OTHER_EP,
  BULK_OUT_EP,
  BULK_IN_EP,
  INTERRUPT_IN_EP,
  INTERRUPT_OUT_EP,
};

static enum ep_type get_ep_type(char *path)
{
  char pbuf[FILENAME_MAX];
  int len = strlen(path);
  int fd;
  char buf[128];
  int bread;
  int is_out = 0;
  int is_in = 0;
  int is_bulk = 0;
  int is_interrupt = 0;
  int i;

  strcpy(pbuf, path);
  pbuf[len++] = '/';

  /* Check the type */
  strncpy(pbuf + len, "type", FILENAME_MAX - len);
  pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */

  fd = open(pbuf, O_RDONLY);
  if (fd < 0)
    return OTHER_EP;
  bread = read(fd, buf, sizeof(buf));
  close(fd);
  if (bread < 2)
    return OTHER_EP;

  for (i = 0; i < bread; i++)
    if(buf[i] == 0x0d || buf[i] == 0x0a)
      buf[i] = '\0';

  if (!strcmp(buf, "Bulk"))
    is_bulk = 1;
  if (!strcmp(buf, "Interrupt"))
    is_interrupt = 1;

  /* Check the direction */
  strncpy(pbuf + len, "direction", FILENAME_MAX - len);
  pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */

  fd = open(pbuf, O_RDONLY);
  if (fd < 0)
    return OTHER_EP;
  bread = read(fd, buf, sizeof(buf));
  close(fd);
  if (bread < 2)
    return OTHER_EP;

  for (i = 0; i < bread; i++)
    if(buf[i] == 0x0d || buf[i] == 0x0a)
      buf[i] = '\0';

  if (!strcmp(buf, "in"))
    is_in = 1;
  if (!strcmp(buf, "out"))
    is_out = 1;

  if (is_bulk && is_in)
    return BULK_IN_EP;
  if (is_bulk && is_out)
    return BULK_OUT_EP;
  if (is_interrupt && is_in)
    return INTERRUPT_IN_EP;
  if (is_interrupt && is_out)
    return INTERRUPT_OUT_EP;

  return OTHER_EP;
}

static int has_3_ep(char *path)
{
  char pbuf[FILENAME_MAX];
  int len = strlen(path);
  int fd;
  char buf[128];
  int bread;

  strcpy(pbuf, path);
  pbuf[len++] = '/';
  strncpy(pbuf + len, "bNumEndpoints", FILENAME_MAX - len);
  pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */

  fd = open(pbuf, O_RDONLY);
  if (fd < 0)
    return -1;
  /* Read all contents to buffer */
  bread = read(fd, buf, sizeof(buf));
  close(fd);
  if (bread < 2)
    return 0;

  /* 0x30, 0x33 = "03", maybe we should parse it? */
  if (buf[0] == 0x30 && buf[1] == 0x33)
    return 1;

  return 0;
}

static int check_interface(char *sysfspath)
{
  char dirbuf[FILENAME_MAX];
  int len = strlen(sysfspath);
  DIR *dir;
  struct dirent *dent;
  regex_t r;
  int ret;
  int bulk_out_ep_found = 0;
  int bulk_in_ep_found = 0;
  int interrupt_in_ep_found = 0;

  ret = has_3_ep(sysfspath);
  if (ret <= 0)
    return ret;

  /* Yes it has three endpoints ... look even closer! */
  dir = opendir(sysfspath);
  if (!dir)
    return -1;

  strcpy(dirbuf, sysfspath);
  dirbuf[len++] = '/';

  /* Check for dirs that identify endpoints */
  ret = regcomp(&r, "^ep_[0-9a-f]+$", REG_EXTENDED | REG_NOSUB);
  if (ret)
    return -1;

  while ((dent = readdir(dir))) {
    struct stat st;

    /* No need to check those beginning with a period */
    if (dent->d_name[0] == '.')
      continue;

    strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
    dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    ret = lstat(dirbuf, &st);
    if (ret)
      continue;
    if (S_ISDIR(st.st_mode) && !regexec(&r, dent->d_name, 0, 0, 0)) {
      enum ep_type ept;

      ept = get_ep_type(dirbuf);
      if (ept == BULK_OUT_EP)
	bulk_out_ep_found = 1;
      else if (ept == BULK_IN_EP)
	bulk_in_ep_found = 1;
      else if (ept == INTERRUPT_IN_EP)
	interrupt_in_ep_found = 1;
    }
  }

  regfree(&r);
  closedir(dir);

  /*
   * If this is fulfilled the interface is an MTP candidate
   */
  if (bulk_out_ep_found &&
      bulk_in_ep_found &&
      interrupt_in_ep_found) {
    return 1;
  }

  return 0;
}

static int check_sysfs(char *sysfspath)
{
  char dirbuf[FILENAME_MAX];
  int len = strlen(sysfspath);
  DIR *dir;
  struct dirent *dent;
  regex_t r;
  int ret;
  int look_closer = 0;

  dir = opendir(sysfspath);
  if (!dir)
    return -1;

  strcpy(dirbuf, sysfspath);
  dirbuf[len++] = '/';

  /* Check for dirs that identify interfaces */
  ret = regcomp(&r, "^[0-9]+-[0-9]+\\:[0-9]+\\.[0-9]+$", REG_EXTENDED
| REG_NOSUB);
  if (ret)
    return -1;

  while ((dent = readdir(dir))) {
    struct stat st;
    int ret;

    /* No need to check those beginning with a period */
    if (dent->d_name[0] == '.')
      continue;

    strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
    dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    ret = lstat(dirbuf, &st);
    if (ret)
      continue;

    /* Look closer at dirs that may be interfaces */
    if (S_ISDIR(st.st_mode)) {
      if (!regexec(&r, dent->d_name, 0, 0, 0))
      if (check_interface(dirbuf) > 0)
	/* potential MTP interface! */
	look_closer = 1;
    }
  }

  regfree(&r);
  closedir(dir);
  return look_closer;
}

  int len = strlen(path);
  int fd;
  char buf[128];
  int bread;
  int is_out = 0;
  int is_in = 0;
  int is_bulk = 0;
  int is_interrupt = 0;
  int i;

  strcpy(pbuf, path);
  pbuf[len++] = '/';

  /* Check the type */
  strncpy(pbuf + len, "type", FILENAME_MAX - len);
  pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */

  fd = open(pbuf, O_RDONLY);
  if (fd < 0)
    return OTHER_EP;
  bread = read(fd, buf, sizeof(buf));
  close(fd);
  if (bread < 2)
    return OTHER_EP;

  for (i = 0; i < bread; i++)
    if(buf[i] == 0x0d || buf[i] == 0x0a)
      buf[i] = '\0';

  if (!strcmp(buf, "Bulk"))
    is_bulk = 1;
  if (!strcmp(buf, "Interrupt"))
    is_interrupt = 1;

  /* Check the direction */
  strncpy(pbuf + len, "direction", FILENAME_MAX - len);
  pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */

  fd = open(pbuf, O_RDONLY);
  if (fd < 0)
    return OTHER_EP;
  bread = read(fd, buf, sizeof(buf));
  close(fd);
  if (bread < 2)
    return OTHER_EP;

  for (i = 0; i < bread; i++)
    if(buf[i] == 0x0d || buf[i] == 0x0a)
      buf[i] = '\0';

  if (!strcmp(buf, "in"))
    is_in = 1;
  if (!strcmp(buf, "out"))
    is_out = 1;

  if (is_bulk && is_in)
    return BULK_IN_EP;
  if (is_bulk && is_out)
    return BULK_OUT_EP;
  if (is_interrupt && is_in)
    return INTERRUPT_IN_EP;
  if (is_interrupt && is_out)
    return INTERRUPT_OUT_EP;

  return OTHER_EP;
}

static int has_3_ep(char *path)
{
  char pbuf[FILENAME_MAX];
  int len = strlen(path);
  int fd;
  char buf[128];
  int bread;

  strcpy(pbuf, path);
  pbuf[len++] = '/';
  strncpy(pbuf + len, "bNumEndpoints", FILENAME_MAX - len);
  pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */

  fd = open(pbuf, O_RDONLY);
  if (fd < 0)
    return -1;
  /* Read all contents to buffer */
  bread = read(fd, buf, sizeof(buf));
  close(fd);
  if (bread < 2)
    return 0;

  /* 0x30, 0x33 = "03", maybe we should parse it? */
  if (buf[0] == 0x30 && buf[1] == 0x33)
    return 1;

  return 0;
}

static int check_interface(char *sysfspath)
{
  char dirbuf[FILENAME_MAX];
  int len = strlen(sysfspath);
  DIR *dir;
  struct dirent *dent;
  regex_t r;
  int ret;
  int bulk_out_ep_found = 0;
  int bulk_in_ep_found = 0;
  int interrupt_in_ep_found = 0;

  ret = has_3_ep(sysfspath);
  if (ret <= 0)
    return ret;

  /* Yes it has three endpoints ... look even closer! */
  dir = opendir(sysfspath);
  if (!dir)
    return -1;

  strcpy(dirbuf, sysfspath);
  dirbuf[len++] = '/';

  /* Check for dirs that identify endpoints */
  ret = regcomp(&r, "^ep_[0-9a-f]+$", REG_EXTENDED | REG_NOSUB);
  if (ret)
    return -1;

  while ((dent = readdir(dir))) {
    struct stat st;

    /* No need to check those beginning with a period */
    if (dent->d_name[0] == '.')
      continue;

    strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
    dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    ret = lstat(dirbuf, &st);
    if (ret)
      continue;
    if (S_ISDIR(st.st_mode) && !regexec(&r, dent->d_name, 0, 0, 0)) {
      enum ep_type ept;

      ept = get_ep_type(dirbuf);
      if (ept == BULK_OUT_EP)
	bulk_out_ep_found = 1;
      else if (ept == BULK_IN_EP)
	bulk_in_ep_found = 1;
      else if (ept == INTERRUPT_IN_EP)
	interrupt_in_ep_found = 1;
    }
  }

  regfree(&r);
  closedir(dir);

  /*
   * If this is fulfilled the interface is an MTP candidate
   */
  if (bulk_out_ep_found &&
      bulk_in_ep_found &&
      interrupt_in_ep_found) {
    return 1;
  }

  return 0;
}

static int check_sysfs(char *sysfspath)
{
  char dirbuf[FILENAME_MAX];
  int len = strlen(sysfspath);
  DIR *dir;
  struct dirent *dent;
  regex_t r;
  int ret;
  int look_closer = 0;

  dir = opendir(sysfspath);
  if (!dir)
    return -1;

  strcpy(dirbuf, sysfspath);
  dirbuf[len++] = '/';

  /* Check for dirs that identify interfaces */
  ret = regcomp(&r, "^[0-9]+-[0-9]+\\:[0-9]+\\.[0-9]+$", REG_EXTENDED
| REG_NOSUB);
  if (ret)
    return -1;

  while ((dent = readdir(dir))) {
    struct stat st;
    int ret;

    /* No need to check those beginning with a period */
    if (dent->d_name[0] == '.')
      continue;

    strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
    dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    ret = lstat(dirbuf, &st);
    if (ret)
      continue;

    /* Look closer at dirs that may be interfaces */
    if (S_ISDIR(st.st_mode)) {
      if (!regexec(&r, dent->d_name, 0, 0, 0))
      if (check_interface(dirbuf) > 0)
	/* potential MTP interface! */
	look_closer = 1;
    }
  }

  regfree(&r);
  closedir(dir);
  return look_closer;
}

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux