Linux那些事儿之我是USB
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人
 35.字符串描述符

35.字符串描述符

字符串描述符的地位仅次于设备/配置/接口/端点四大描述符,那么四大设备必须支持它。而字符串描述符对设备来说则是可选的。

这并不是就说字符串描述符不重要,对咱们来说,字符串要比数字亲切得多,提供字符串描述符的设备也要比没有提供的设备亲切得多,不会有人专门去记前面使用lsusb列出的04b4表示Cypress Semiconductor Corp.。

一提到字符串,不可避免就得提到字符串使用的语言。虽然字符串是亲切,但不像数字那样全球通用。当然这并不是说设备中就要存储这么多不同语言的字符串描述符,这未免要求过高了一些,代价也昂贵了一些,要知道这些描述符不会凭空生出来,是要存储在设备的EEPROM里的,此物是需要记忆的。所以说只提供几种语言的字符串描述符就可以了,甚至说只提供一种语言,比如英语就可以了。

其实咱们现在说的语言就是太多了,不过不管哪种语言,在PC里或者设备中存放都只能用二进制数字,这就需要在语言与数字之间进行编码,这个所谓的编码和这个世界上其他事物一样,都是有多种的。

Spec里就说了,字符串描述符使用的就是UNICODE编码,USB设备中的字符串可以通过它来支持多种语言,不过需要在向设备请求字符串描述符时指定一个你期望看到的一种语言,俗称语言ID,即Language ID。这个语言ID使用2字节表示,所有可以使用的语言ID在http://www.usb.org/developers/docs/USB_LANGIDs.pdf文档里都列出来了,从这个文档里你也可以明白为什么要使用2字节,而不是1字节表示。

这么说吧,比如中文是0X04,但是中文还有好几种,所以就需要另外1字节表示是哪种中文,简体就是0X02。注意合起来表示简体中文并不是0X0402或者0X0204,因为这2字节并不是分得那么清,bit0~ bit 9一共10位去表示Primary语言ID,其余6位去表示Sub语言ID,毕竟一种语言的Sub语言不可能会特别多,没必要分去整整一半8bit,所以简体中文的语言ID就是0X0804。

不多啰嗦,还是结合代码从usb_cache_string说起,看一看如何去获得一个字符串描述符,它在message.c中定义。

825 char *usb_cache_string(struct usb_device *udev, int index)
826 {
827     char *buf;
828     char *smallbuf = NULL;
829     int len;
830
831     if (index > 0 && (buf = kmalloc(256, GFP_KERNEL)) != NULL) {
832         if ((len = usb_string(udev, index, buf, 256)) > 0) {
833             if ((smallbuf = kmalloc(++len, GFP_KERNEL)) == NULL)
834                 return buf;
835             memcpy(smallbuf, buf, len);
836         }
837         kfree(buf);
838     }
839     return smallbuf;
840 }

每个字符串描述符都有一个序号,字符串描述符序号是不能重复的。

字符串描述符当然可以有很多位,参数index就是表示了你希望获得其中的第几个。但是不可疏忽大意的是,不能指定index为0,0编号是有特殊用途的,指定为0就什么也得不到。

有关usb_cache_string()函数,还需要明白两点,第一是它采用的方针策略,就是苦活儿累活儿找usb_string()去做。先不看这个usb_string()是怎么工作的,现在只要注意它的参数,比usb_cache_string()的参数多了两个,buf和size,也就是需要传递一个存放返回的字符串描述符的缓冲区。但是你调用usb_cache_string()时并没有指定一个明确的size,usb_cache_string(),也就不知道你想要的字符串描述符有多大,于是它就采用了这么一个技巧,先申请一个足够大的缓冲区(这里是256字节),拿这个缓冲区去调用usb_string(),通过usb_string()的返回值会得到字符串描述符的真实大小,然后再拿这个值去申请一个缓冲区,并将大缓冲区里放的字符串描述符数据复制过来,这时那个大缓冲区当然就没什么利用价值了,于是再把它给释放掉。

第二就是usb_cache_string()申请小缓冲区时,使用的并不是usb_string()的返回值,而是多了1字节,也就是说要从大缓冲区里多复制1字节到小缓冲区里,为什么?这牵涉C语言里的字符串结束符。

字符串都需要结束符,但并不是每个人都能记得给字符串加上结束符。下面举一个例子。

1 #include <stdio.h>
2 #include <string.h>
3
4 int main(int argc, char *argv[])
5 {
6   #define MAX (100)
7
8   char buf[512], tmp[32];
9   int i, count = 0;
10
11  for ( i = 0; i< MAX; ++i){
12          sprintf(tmp, "0x%.4X,", i);
13
14      strcat(buf, tmp);
15
16      if (count++ == 10){
17               printf("%s\n", buf);
18               buf[0] = '\0';
19               count = 0;
20               }
21   }
22
23   return 0;
24 }

这程序简单直白:打印100个数,每行10个。你只需要查看它能不能得到预期的结果。

当然这程序是有问题的,在第10行少了下面这一句:

buf[0] = '\0'; ////////////////// here !

也就是说忘记将buf初始化了,传递给strcat的是一个没有初始化的buf,这个时候buf的内容都非0,strcat不知道它的结尾在哪里,它能猜到buf的开始,却猜不到buf的结束。me mset就比较消耗CPU了,而这里buf[0] = '\0'就足够用了。为了更好地说明问题,这里放上内核中对strcat的定义。

171 char *strcat(char *dest, const char *src)
172 {
173    char *tmp = dest;
174
175    while (*dest)
176        dest++;
177    while ((*dest++ = *src++) != '\0')
178        ;
179    return tmp;
180 }

strcat会从dest的起始位置开始去寻找一个字符串的结束符,只有找到175行的while循环才会结束。但是如果dest没有初始化过,义无反顾地“strcat”并不会有好结果。本来strcat的目的是将tmp追加到buf里字符串的后面,但是因为buf没有初始化,没有一个结束符,strcat就会一直找下去,就算它在哪儿停了下来,如果这个停下的位置超出了buf的范围,就会把src的数据写到不应该写的地方,就可能会破坏其他很重要的数据,你的系统可能就“死”掉了。

解决这种问题的方法很简单,就是记着在使用指针、数组前首先统统初始化,到最后觉得哪里影响CPU性能了再去优化它。

问一个问题,这个字符串结束符具体是什么?C和C++里一般是指'\0',像上面的那句buf[0] = '\0'。这里再引用spec里的一句话:“The UNICODE string descriptor is not NULL-terminated.”什么是NULL-terminated字符串?其实就是以'\0'结尾的字符串,下面查看内核中对NULL的定义。

6 #undef NULL
7 #if defined(__cplusplus)
8 #define NULL 0
9 #else
10 #define NULL ((void *)0)
11 #endif

0是一个整数,但它又不仅仅是一个整数。由于各种标准转换,0可以被用于作为任意的整型、浮点类型、指针。0的类型将由上、下文来确定。典型情况下,0被表示为一个适当大小的全零二进制位的模式。所以,无论NULL是定义为常数0还是((void *)0)这个零指针,NULL指的都是0值,而不是非0值。而字符的'\0'和整数的0也可以通过转型来相互转换。再多说一点,'\0'是C语言定义的用'\'加八进制ASCII码来表示字符的一种方法,'\0'就是表示一个ASCII码值为0的字符。

所以更准确地说,NULL-terminated字符串就是以0值结束的字符串,那么spec的那句话说字符串描述符不是一个NULL-terminated字符串,意思也就是字符串描述符没有一个结束符,你从设备那里得到字符串之后得给它追加一个结束符。本来usb_string()里已经为buf追加好了,但是它返回的长度里还是没有包括这个结束符的1字节,所以usb_cache_string()为smallbuf申请内存时就得多准备1字节,以便将buf里的那个结束符也给复制过来。现在就查看usb_string()的细节,定义在message.c里。

757 int usb_string(struct usb_device *dev, int index, char *buf, size_t size)
758 {
759     unsigned char *tbuf;
760     int err;
761     unsigned int u, idx;
762
763     if (dev->state == USB_STATE_SUSPENDED)
764         return -EHOSTUNREACH;
765     if (size <= 0 || !buf || !index)
766         return -EINVAL;
767     buf[0] = 0;
768     tbuf = kmalloc(256, GFP_KERNEL);
769     if (!tbuf)
770         return -ENOMEM;
771
772     /* get langid for strings if it's not yet known */
773     if (!dev->have_langid) {
774         err = usb_string_sub(dev, 0, 0, tbuf);
775         if (err < 0) {
776             dev_err (&dev->dev,
777                     "string descriptor 0 read error: %d\n",
778                         err);
779             goto errout;
780         } else if (err < 4) {
781             dev_err (&dev->dev, "string descriptor 0 too short\n");
782                     err = -EINVAL;
783             goto errout;
784         } else {
785           dev->have_langid = 1;
786           dev->string_langid = tbuf[2] | (tbuf[3]<< 8);
787           /* always use the first langid listed */
788           dev_dbg (&dev->dev, "default language 0x%04x\n",
789                             dev->string_langid);
790         }
791     }
792
793     err = usb_string_sub(dev, dev->string_langid, index, tbuf);
794     if (err < 0)
795         goto errout;
796
797     size--; /* leave room for trailing NULL char in output buffer */
798     for (idx = 0, u = 2; u < err; u += 2) {
799          if (idx >= size)
800               break;
801          if (tbuf[u+1])      /* high Byte */
802              buf[idx++] = '?'; /* non ISO-8859-1 character */
803          else
804              buf[idx++] = tbuf[u];
805     }
806     buf[idx] = 0;
807     err = idx;
808
809     if (tbuf[1] != USB_DT_STRING)
810          dev_dbg(&dev->dev, "wrong descriptor type %02x for string
%d (\"%s\")\n", tbuf[1], index, buf);
811
812  errout:
813     kfree(tbuf);
814     return err;
815 }

763行,这几行做些例行检查,设备不能是挂起的,index也不能是0,只要传递了指针就需要检查。

767行,初始化buf,usb_cache_string()并没有对这个buf初始化,所以这里必须要加上这么一步。当然usb_string()并不仅仅只有在usb_cache_string()里调用,可能会在很多地方调用到它,不过不管在哪里,这里为了谨慎起见,还是需要这一步。

768行,申请一个256字节大小的缓冲区。前面一直强调要初始化,怎么到这里没有去初始化tbuf?这是因为没必要。为什么没必要?查看usb_string()最后面的那些代码就明白了。

773行,struct usb_device里有have_langid和string_langid这么两个字段是和字符串描述符有关的,string_langid用来指定使用哪种语言,have_langid用来指定string_langid是否有效。如果have_langid为空,就说明没有指定使用哪种语言,那么获得的字符串描述符使用的是哪种语言就完全看设备了。你可能会疑惑,为什么当have_langid为空时,要在774行和793行调用两次usb_string_sub()?就像usb_string()是替usb_cache_string()做“苦工”一样,usb_string_sub()是替usb_string()做“苦工”的,也就是说usb_string()是靠usb_string_sub()去获得字符串描述符的,那为什么have_langid为空时,要获取两遍的字符串描述符?

你可以比较两次调用usb_string_sub()的参数有什么区别。第一次调用参数时,语言ID和index都为0,第二次调用参数时就明确指定了语言ID和index。这里的玄机就在index为0时,也就是0编号的字符串描述符是什么,前面只说了它有特殊的用途,现在必须得解释。

spec在9.6.7节说了,编号0的字符串描述符包括了设备所支持的所有语言ID,对应的就是如图1.35.1所示的表。

第一次调用usb_string_sub()就是为了获得这张表,获得这张表做什么用?接着往下看。

775行,usb_string_sub()返回一个负数,就表示没有获得这张表,没有取到0号字符串描述符。如果返回值比4要小,就表示获得的表里没有包含任何一个语言ID。要知道一个语言ID占用2字节,还有前2字节表示表的长度及类型,所以得到的数据至少要为4,才能够得到一个有效的语言ID。如果返回值比4要大,就使用获得的数据的第3字节和第4字节设置string_langid,同时设置have_langid为1。

图1.35.1 0号字符串描述符

现在很明显了,773行到791行这些代码就是在你没有指定使用哪种语言时,去获取设备中默认使用的语言,也就是0号字符串描述符里的第一个语言ID所指定的语言。如果没有找到这个默认的语言ID,即usb_string_sub()返回值小于4的情况,就没有办法再去获得其他字符串描述符了。因为没有指定语言,设备不知道你是要求的是英语还是中文或是其他的。

793行,使用指定的语言ID,或者前面获得的默认语言ID去获得想要的字符串描述符。现在看一看定义在message.c里的usb_string_sub函数。

696 static int usb_string_sub(struct usb_device *dev, unsigned int langid,
697              unsigned int index, unsigned char *buf)
698 {
699      int rc;
700
701     /* Try to read the string descriptor by asking for the maximum
702      * possible number of Bytes */
703     if (dev->quirks & USB_QUIRK_STRING_FETCH_255)
704         rc = -EIO;
705     else
706         rc = usb_get_string(dev, langid, index, buf, 255);
707
708     /* If that failed try to read the descriptor length, then
709     * ask for just that many Bytes */
710     if (rc < 2) {
711         rc = usb_get_string(dev, langid, index, buf, 2);
712         if (rc == 2)
713             rc = usb_get_string(dev, langid, index, buf, buf[0]);
714    }
715
716   if (rc >= 2) {
717      if (!buf[0] && !buf[1])
718          usb_try_string_workarounds(buf, &rc);
719
720      /* There might be extra junk at the end of the descriptor */
721      if (buf[0] < rc)
722          rc = buf[0];
723
724      rc = rc - (rc & 1); /* force a multiple of two */
725    }
726
727    if (rc < 2)
728        rc = (rc < 0 ? rc : -EINVAL);
729
730    return rc;
731 }

这个函数首先检查你的设备是不是属于合格的设备,然后就调用usb_get_string()去获得字符串描述符。USB_QUIRK_STRING_FETCH_255就是在include/linux/usb/quirks.h中定义的那些形形色色的“毛病”之一,表示设备在获取字符串描述符时会“crash”。

usb_string_sub()的核心就是message.c中定义usb_get_string函数。

664 static int usb_get_string(struct usb_device *dev, unsigned short langid,
665         unsigned char index, void *buf, int size)
666 {
667 int i;
668  int result;
669
670  for (i = 0; i < 3; ++i) {
671   /* retry on length 0 or stall; some devices are flakey */
672   result = usb_control_ msg(dev, usb_rcvctrlpipe(dev, 0),
673         USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
674         (USB_DT_STRING << 8) + index, langid, buf, size,
675         USB_CTRL_GET_TIMEOUT);
676   if (!(result == 0 || result == -EPIPE))
677     break;
678  }
679  return result;
680 }

我已经不记得这是第几次遇到usb_control_ msg()了。还是简单说它的参数。wValue的高位字节表示描述符的类型,低位字节表示描述符的序号,所以有674行的(USB_DT_STRING << 8) + index,wIndex。对于字符串描述符应该设置为使用语言的ID,所以有674行的langid。至于wLength,就是描述符的长度,对于字符串描述符很难有一个统一的确定长度,所以一般来说上面传递过来的通常是一个比较大的255字节。

和获得设备描述符时一样,因为一些厂商生产出的设备“古灵精怪”,可能需要多试几次才能成功。要容许设备犯错误。

还是回过头去看usb_string_sub函数,如果usb_get_string()成功地得到了期待的字符串描述符,则返回获得的字节数,如果这个数目小于2,就再读2字节试一试,要想明白这2字节是什么内容,需要查看如图1.35.2所示的表。

图1.35.2 其他字符串描述符

图1.35.1描述的是0号字符串描述符的格式,图1.35.2描述的是其他字符串描述符的格式。很明显可以看到,它的前2字节分别表示了长度和类型,如果读2字节成功,就可以准确地获得这个字符串描述符的长度,然后再拿这个准确的长度去请求一次。

该尝试的都尝试了,现在看716行,分析前面调用usb_get_string()的结果,如果几次尝试之后,它的返回值还是小于2,那就返回一个错误码。rc大于等于2,说明终于获得了一个有效的字符串描述符。

717行,buf的前2字节有一个为空时,也就是字符串描述符的前2字节有一个为空时,调用了message.c中定义的usb_try_string_workarounds函数。

682 static void usb_try_string_workarounds(unsigned char *buf, int *length)
683 {
684    int newlength, oldlength = *length;
685
686    for (newlength = 2; newlength + 1 < oldlength; newlength += 2)
687        if (!isprint(buf[newlength]) || buf[newlength + 1])
688            break;
689
690    if (newlength > 2) {
691        buf[0] = newlength;
692        *length = newlength;
693    }
694 }

这个函数的目的是从usb_get_string()返回的数据里计算出前面有效的长度。它的核心就是686行的for循环,不过要搞清楚这个循环,还真不是一件容易的事情,得有相当的理论功底。

前面刚说了字符串描述符使用的是UNICODE编码,其实UNICODE指的是包含了字符集、编码、字型等很多规范的一整套系统,字符集仅仅描述符系统中存在那些字符,并进行分类,并不涉及如何用数字编码表示的问题。

UNICODE使用的编码形式主要有两种UTF,即UTF-8和UTF-16。使用usb_get_string()获得的字符串使用的是UTF-16编码规则,而且是little-endpoint,每个字符都需要使用2字节来表示。在这个for循环里newlength每次加2,就是表示每次处理一个字符,但是要弄明白怎么处理的,还需要知道这2字节分别是什么,这就不得不提及ASCII、ISO-8859-1等几个名词。

ASCII是用来表示英文的一种编码规范,表示的最大字符数为256,每个字符占1字节。但是英文字符没这么多,一般来说128个也就够了(最高位为0),这就已经完全包括了控制字符、数字、大小写字母,还有其他一些符号。对于法语、西班牙语和德语之类的西欧语言都使用叫做ISO-8859-1,它扩展了ASCII码的最高位,来表示Ñ(241),和Ü(252)这样的字符。而Unicode的低字节,也就是在0到255同ISO-8859-1完全一样,它接着使用剩余的数字,256到65 535,扩展到表示其他语言的字符。可以说ISO-8859-1就是Unicode的子集,如果Unicode的高字节为0,则它表示的字符就和ISO-8859-1完全一样了。

再看一看686行这个for循环,newlength从2开始,是因为前2字节应该是表示长度和类型的,这里只逐个儿对上面Table 9-16里的bString中的每个字符做处理。还要知道usb_get_string()得到的结果是little-endpoint的,所以buf[newlength]和buf[newlength + 1]分别表示一个字符的低字节和高字节,那么isprint(buf[newlength]就是用来判断这个Unicode字符的低字节是不是可以print的。如果不是,就没必要再往下循环了,后边儿的字符也不再看了,然后就到了690行的if函数,将newlength赋给buf[0],即bLength。length指向的是usb_get_string()返回的原始数据的长度,692行使用for循环计算出的有效长度将它修改了。isprint在include/linux/ctype.h中定义。

这个for循环终止的条件有两个,另外一个就是buf[newlength + 1],也就是这个Unicode字符的高字节不为0,这时它不存在对应的ISO-8859-1形式,为什么加上这个判断?接着往下看。

usb_string_sub()的721行,buf[0]表示的就是bLength的值,如果它小于usb_get_string()获得的数据长度,说明这些数据里存在一些垃圾,要把它们给揪出来排除掉。要知道这个rc是要作为真实有效的描述符长度返回的,所以这时需要将buf[0]赋给rc。

724行,每个Unicode字符需要使用2字节来表示,所以rc必须为偶数,即2的整数倍。如果为奇数,就得将最后1字节给去掉,也就是将rc减1。我们可以学习这里将一个数字转换为偶数时采用的技巧,(rc & 1)在rc为偶数时等于0,为奇数时等于1,再使用rc减去它,得到的就是一个偶数。

从716行到725行这几行,应该可以看出,在成功获得一个字符串描述符时,usb_string_sub()返回的是一个NULL-terminated字符串的长度,并没有涉及结束符。牢记这一点,回到usb_string函数的797行,先将size,也就是buf的大小减1,目的就是为结束符保留1字节的位置。

798行,tbuf里保存的是GET_DESCRIPTOR请求返回的原始数据,err是usb_string_sub()的返回值,一切顺利的话,它表示的就是一个有效的描述符的大小。这里idx的初始值为0,而u的初始值为2,目的是从tbuf的第3字节开始复制给buf,毕竟这里的目的是获得字符串描述符里的bString,而不是整个描述符的内容。u每次都是增加2,这是因为采用的UTF-16是用2字节表示一个字符的,所以循环一次要处理2字节。

801行,这个if-else组合你可能比较糊涂,要搞清楚,还要看前面刚讲过的一些理论。tbuf里每个Unicode字符2字节,又是little-endpoint的,所以801行就是判断这个Unicode字符的高位字节是不是为0,如果不为0,则ISO-8859-1里没有这个字符,就设置buf里的对应字符为'?'。如果它的高位字节为0,就说明这个Unicode字符ISO-8859-1里也有,就直接将它的低位字节赋给buf。一个for循环下来,就将tbuf里的Unicode字符串转化成了buf里的ISO-8859-1字符串。

806行,为buf追加一个结束符。