USB: Clean up root hub string descriptors
authorGeorge Spelvin <linux@horizon.com>
Tue, 25 Aug 2009 02:06:41 +0000 (22:06 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 23 Sep 2009 13:46:37 +0000 (06:46 -0700)
The previous code had a bug that would add a trailing null byte to
the returned descriptor.

Signed-off-by: George Spelvin <linux@horizon.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/hcd.c

index 95ccfa0..34de475 100644 (file)
@@ -337,72 +337,89 @@ static const u8 ss_rh_config_descriptor[] = {
 
 /*-------------------------------------------------------------------------*/
 
-/*
- * helper routine for returning string descriptors in UTF-16LE
- * input can actually be ISO-8859-1; ASCII is its 7-bit subset
+/**
+ * ascii2desc() - Helper routine for producing UTF-16LE string descriptors
+ * @s: Null-terminated ASCII (actually ISO-8859-1) string
+ * @buf: Buffer for USB string descriptor (header + UTF-16LE)
+ * @len: Length (in bytes; may be odd) of descriptor buffer.
+ *
+ * The return value is the number of bytes filled in: 2 + 2*strlen(s) or
+ * buflen, whichever is less.
+ *
+ * USB String descriptors can contain at most 126 characters; input
+ * strings longer than that are truncated.
  */
-static unsigned ascii2utf(char *s, u8 *utf, int utfmax)
+static unsigned
+ascii2desc(char const *s, u8 *buf, unsigned len)
 {
-       unsigned retval;
+       unsigned n, t = 2 + 2*strlen(s);
 
-       for (retval = 0; *s && utfmax > 1; utfmax -= 2, retval += 2) {
-               *utf++ = *s++;
-               *utf++ = 0;
-       }
-       if (utfmax > 0) {
-               *utf = *s;
-               ++retval;
+       if (t > 254)
+               t = 254;        /* Longest possible UTF string descriptor */
+       if (len > t)
+               len = t;
+
+       t += USB_DT_STRING << 8;        /* Now t is first 16 bits to store */
+
+       n = len;
+       while (n--) {
+               *buf++ = t;
+               if (!n--)
+                       break;
+               *buf++ = t >> 8;
+               t = (unsigned char)*s++;
        }
-       return retval;
+       return len;
 }
 
-/*
- * rh_string - provides manufacturer, product and serial strings for root hub
- * @id: the string ID number (1: serial number, 2: product, 3: vendor)
+/**
+ * rh_string() - provides string descriptors for root hub
+ * @id: the string ID number (0: langids, 1: serial #, 2: product, 3: vendor)
  * @hcd: the host controller for this root hub
- * @data: return packet in UTF-16 LE
- * @len: length of the return packet
+ * @data: buffer for output packet
+ * @len: length of the provided buffer
  *
  * Produces either a manufacturer, product or serial number string for the
  * virtual root hub device.
+ * Returns the number of bytes filled in: the length of the descriptor or
+ * of the provided buffer, whichever is less.
  */
-static unsigned rh_string(int id, struct usb_hcd *hcd, u8 *data, unsigned len)
+static unsigned
+rh_string(int id, struct usb_hcd const *hcd, u8 *data, unsigned len)
 {
-       char buf [100];
+       char buf[100];
+       char const *s;
+       static char const langids[4] = {4, USB_DT_STRING, 0x09, 0x04};
 
        // language ids
-       if (id == 0) {
-               buf[0] = 4;    buf[1] = 3;      /* 4 bytes string data */
-               buf[2] = 0x09; buf[3] = 0x04;   /* MSFT-speak for "en-us" */
-               len = min_t(unsigned, len, 4);
-               memcpy (data, buf, len);
+       switch (id) {
+       case 0:
+               /* Array of LANGID codes (0x0409 is MSFT-speak for "en-us") */
+               /* See http://www.usb.org/developers/docs/USB_LANGIDs.pdf */
+               if (len > 4)
+                       len = 4;
+               memcpy(data, langids, len);
                return len;
-
-       // serial number
-       } else if (id == 1) {
-               strlcpy (buf, hcd->self.bus_name, sizeof buf);
-
-       // product description
-       } else if (id == 2) {
-               strlcpy (buf, hcd->product_desc, sizeof buf);
-
-       // id 3 == vendor description
-       } else if (id == 3) {
+       case 1:
+               /* Serial number */
+               s = hcd->self.bus_name;
+               break;
+       case 2:
+               /* Product name */
+               s = hcd->product_desc;
+               break;
+       case 3:
+               /* Manufacturer */
                snprintf (buf, sizeof buf, "%s %s %s", init_utsname()->sysname,
                        init_utsname()->release, hcd->driver->description);
-       }
-
-       switch (len) {          /* All cases fall through */
+               s = buf;
+               break;
        default:
-               len = 2 + ascii2utf (buf, data + 2, len - 2);
-       case 2:
-               data [1] = 3;   /* type == string */
-       case 1:
-               data [0] = 2 * (strlen (buf) + 1);
-       case 0:
-               ;               /* Compiler wants a statement here */
+               /* Can't happen; caller guarantees it */
+               return 0;
        }
-       return len;
+
+       return ascii2desc(s, data, len);
 }