Skip to content

Commit

Permalink
linux_usbfs: Gracefully handle buggy devices with a configuration 0
Browse files Browse the repository at this point in the history
The USB spec states that a configuration value of 0 is reserved and is
used to indicate the device in not configured (e.g. is in the address
state). Unfortunately some devices do exist that violate this and use 0
as the bConfigurationValue of the configuration descriptor.

Improve how the Linux backend handles such non-conformant devices by
adding special handling around the configuration value 0. Most devices
will not require this special handling, but for those that do there is
no way to distinguish between the device being unconfigured and using
configuration 0.

Closes #850

Signed-off-by: Chris Dickens <christopher.a.dickens@gmail.com>
  • Loading branch information
dickens committed Feb 8, 2021
1 parent c486d01 commit f38f09d
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 36 deletions.
94 changes: 59 additions & 35 deletions libusb/os/linux_usbfs.c
Expand Up @@ -128,7 +128,7 @@ struct linux_device_priv {
void *descriptors;
size_t descriptors_len;
struct config_descriptor *config_descriptors;
uint8_t active_config; /* cache val for !sysfs_available */
int active_config; /* cache val for !sysfs_available */
};

struct linux_device_handle_priv {
Expand Down Expand Up @@ -169,6 +169,21 @@ struct linux_transfer_priv {
int iso_packet_offset;
};

static int dev_has_config0(struct libusb_device *dev)
{
struct linux_device_priv *priv = usbi_get_device_priv(dev);
struct config_descriptor *config;
uint8_t idx;

for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) {
config = &priv->config_descriptors[idx];
if (config->desc->bConfigurationValue == 0)
return 1;
}

return 0;
}

static int get_usbfs_fd(struct libusb_device *dev, mode_t mode, int silent)
{
struct libusb_context *ctx = DEVICE_CTX(dev);
Expand Down Expand Up @@ -574,22 +589,12 @@ static int sysfs_scan_device(struct libusb_context *ctx, const char *devname)
}

/* read the bConfigurationValue for a device */
static int sysfs_get_active_config(struct libusb_device *dev, uint8_t *config)
static int sysfs_get_active_config(struct libusb_device *dev, int *config)
{
struct linux_device_priv *priv = usbi_get_device_priv(dev);
int ret, tmp;

ret = read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue",
UINT8_MAX, &tmp);
if (ret < 0)
return ret;

if (tmp == -1)
tmp = 0; /* unconfigured */

*config = (uint8_t)tmp;

return 0;
return read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue",
UINT8_MAX, config);
}

int linux_get_device_address(struct libusb_context *ctx, int detached,
Expand Down Expand Up @@ -765,6 +770,9 @@ static int parse_config_descriptors(struct libusb_device *dev)
}
}

if (config_desc->bConfigurationValue == 0)
usbi_warn(ctx, "device has configuration 0");

priv->config_descriptors[idx].desc = config_desc;
priv->config_descriptors[idx].actual_len = config_len;

Expand Down Expand Up @@ -798,7 +806,7 @@ static int op_get_active_config_descriptor(struct libusb_device *dev,
{
struct linux_device_priv *priv = usbi_get_device_priv(dev);
void *config_desc;
uint8_t active_config;
int active_config;
int r;

if (priv->sysfs_dir) {
Expand All @@ -810,12 +818,12 @@ static int op_get_active_config_descriptor(struct libusb_device *dev,
active_config = priv->active_config;
}

if (active_config == 0) {
if (active_config == -1) {
usbi_err(DEVICE_CTX(dev), "device unconfigured");
return LIBUSB_ERROR_NOT_FOUND;
}

r = op_get_config_descriptor_by_value(dev, active_config, &config_desc);
r = op_get_config_descriptor_by_value(dev, (uint8_t)active_config, &config_desc);
if (r < 0)
return r;

Expand Down Expand Up @@ -863,17 +871,26 @@ static int usbfs_get_active_config(struct libusb_device *dev, int fd)

/* we hit this error path frequently with buggy devices :( */
usbi_warn(DEVICE_CTX(dev), "get configuration failed, errno=%d", errno);

/* assume the current configuration is the first one if we have
* the configuration descriptors, otherwise treat the device
* as unconfigured. */
if (priv->config_descriptors)
priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue;
else
priv->active_config = -1;
} else if (active_config == 0) {
/* some buggy devices have a configuration 0, but we're
* reaching into the corner of a corner case here, so let's
* not support buggy devices in these circumstances.
* stick to the specs: a configuration value of 0 means
* unconfigured. */
usbi_warn(DEVICE_CTX(dev), "active cfg 0? assuming unconfigured device");
if (dev_has_config0(dev)) {
/* some buggy devices have a configuration 0, but we're
* reaching into the corner of a corner case here. */
priv->active_config = 0;
} else {
priv->active_config = -1;
}
} else {
priv->active_config = (int)active_config;
}

priv->active_config = active_config;

return LIBUSB_SUCCESS;
}

Expand Down Expand Up @@ -1004,9 +1021,9 @@ static int initialize_device(struct libusb_device *dev, uint8_t busnum,
usbi_warn(ctx, "Missing rw usbfs access; cannot determine "
"active configuration descriptor");
if (priv->config_descriptors)
priv->active_config = priv->config_descriptors[0].desc->bConfigurationValue;
priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue;
else
priv->active_config = 0; /* No config dt */
priv->active_config = -1; /* No config dt */

return LIBUSB_SUCCESS;
}
Expand Down Expand Up @@ -1428,22 +1445,27 @@ static int op_get_configuration(struct libusb_device_handle *handle,
uint8_t *config)
{
struct linux_device_priv *priv = usbi_get_device_priv(handle->dev);
int active_config;
int r;

if (priv->sysfs_dir) {
r = sysfs_get_active_config(handle->dev, config);
r = sysfs_get_active_config(handle->dev, &active_config);
} else {
struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);

r = usbfs_get_active_config(handle->dev, hpriv->fd);
if (r == LIBUSB_SUCCESS)
*config = priv->active_config;
active_config = priv->active_config;
}
if (r < 0)
return r;

if (*config == 0)
usbi_err(HANDLE_CTX(handle), "device unconfigured");
if (active_config == -1) {
usbi_warn(HANDLE_CTX(handle), "device unconfigured");
active_config = 0;
}

*config = (uint8_t)active_config;

return 0;
}
Expand All @@ -1467,11 +1489,13 @@ static int op_set_configuration(struct libusb_device_handle *handle, int config)
return LIBUSB_ERROR_OTHER;
}

if (config == -1)
config = 0;
/* if necessary, update our cached active config descriptor */
if (!priv->sysfs_dir) {
if (config == 0 && !dev_has_config0(handle->dev))
config = -1;

/* update our cached active config descriptor */
priv->active_config = (uint8_t)config;
priv->active_config = config;
}

return LIBUSB_SUCCESS;
}
Expand Down
2 changes: 1 addition & 1 deletion libusb/version_nano.h
@@ -1 +1 @@
#define LIBUSB_NANO 11603
#define LIBUSB_NANO 11604

0 comments on commit f38f09d

Please sign in to comment.