Skip to content

Commit

Permalink
usb-common.c: Add API-neutral nut_usb_get_string()
Browse files Browse the repository at this point in the history
This function doesn't use usb_get_string_simple() from libusb0 or
libusb_get_string_descriptor_ascii() from libusb1 but replaces them to
be able to add a workaround for devices with broken langid descriptors.

If the langid descriptor is invalid, the en_US language is assumed.
(See networkupstools#1925)

It also adds retries to the string descriptor fetching, which may help
in some cases. (See networkupstools#414)
A small delay is introduced between the retries.

Signed-off-by: Tormod Volden <[email protected]>
  • Loading branch information
tormodvolden committed Sep 3, 2024
1 parent 0f4d80f commit 130d830
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
82 changes: 82 additions & 0 deletions drivers/usb-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,85 @@ void warn_if_bad_usb_port_filename(const char *fn) {
__func__, fn);
return;
}

/* Retries were introduced for "Tripp Lite" devices, see
* https://github.com/networkupstools/nut/issues/414
*/
#define MAX_STRING_DESC_TRIES 3

/* API neutral, handles retries */
static int nut_usb_get_string_descriptor(
usb_dev_handle *udev,
int StringIdx,
int langid,
char *buf,
size_t buflen)
{
int ret = -1;
int tries = MAX_STRING_DESC_TRIES;

while (tries--) {
ret = usb_get_string(udev, (usb_ctrl_strindex)StringIdx, langid, (usb_ctrl_charbuf)buf, buflen);
if (ret >= 0) {
break;
} else if (tries) {
upsdebugx(1, "%s: string descriptor %d request failed, retrying...", __func__, StringIdx);
usleep(50000); /* 50 ms, might help in some cases */
}
}
return ret;
}

/* API neutral, assumes en_US if langid descriptor is broken */
int nut_usb_get_string(
usb_dev_handle *udev,
int StringIdx,
char *buf,
size_t buflen)
{
int ret;
char buffer[255];
int langid;
int len;
int i;

if (!udev || StringIdx < 1 || StringIdx > 255) {
return -1;
}

/* request langid descriptor */
ret = nut_usb_get_string_descriptor(udev, 0, 0, buffer, 4);
if (ret < 0)
return ret;

if (ret == 4 && buffer[0] >= 4 && buffer[1] == USB_DT_STRING) {
langid = buffer[2] | (buffer[3] << 8);
} else {
upsdebugx(1, "%s: Broken language identifier, assuming en_US", __func__);
langid = 0x0409;
}

/* retrieve string in preferred language */
ret = nut_usb_get_string_descriptor(udev, StringIdx, langid, buffer, sizeof(buffer));
if (ret < 0) {
#ifdef WIN32
/* only for libusb0 ? */
errno = -ret;
#endif
return ret;
}

/* translate simple UTF-16LE to 8-bit */
len = ret < (int)buflen ? ret : (int)buflen;
len = len / 2 - 1; // 16-bit characters, without header
len = len < (int)buflen - 1 ? len : (int)buflen - 1; // reserve for null terminator
for (i = 0; i < len; i++) {
if (buffer[2 + i * 2 + 1] == 0)
buf[i] = buffer[2 + i * 2];
else
buf[i] = '?'; // not decoded
}
buf[i] = '\0';

return len;
}
5 changes: 5 additions & 0 deletions drivers/usb-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,4 +560,9 @@ void nut_usb_addvars(void);
* here, to use in several USB-capable drivers. */
void warn_if_bad_usb_port_filename(const char *fn);

/* Retrieves string descriptor from device, decoded to 8-bit string,
* applies retries on failures, and assumes en_US in case the
* langid descriptor is invalid. */
int nut_usb_get_string(usb_dev_handle *udev, int StringIdx, char *buf, size_t buflen);

#endif /* NUT_USB_COMMON_H */

0 comments on commit 130d830

Please sign in to comment.