关于字符串描述符,前面的前面已经简单描述过了,但是因为现在长夜漫漫,孤枕难眠,所以多说点。字符串描述符的地位仅次于设备/配置/接口/端点四大描述符,那四大设备必须得支持,而字符串描述符对设备来说则是可选的,类似于网球里马德里大师赛与四大满贯之间的地位差异,四大满贯是个碗儿都争着抢着爬着也要去,而号称第五大满贯的马德里大师赛却动不动就会被大碗儿们因各种理由给涮了。
这并不是就说字符串描述符不重要,对咱们来说,字符串要比数字亲切的多,提供字符串描述符的设备也要比没有提供的设备亲切的多,不会有人会专门去记前面使用lsusb列出的04b4表示的是Cypress Semiconductor Corp.,如果谁真费心去记了,俺也不会pfpf,而是只会发出和看到06年那几个超女时一样的感叹:动物的种类在减少,人的种类在增加吗?
一提到字符串,不可避免就得提到字符串使用的语言。字符串亲切是亲切,但不像数字那样全球通用,使用中文了,老外看不懂,使用法文阿拉伯文什么的咱又看不懂,你知道目前世界上有多少种语言吗?有得说七千多种,有得说五千多种,无一定论,不过使用人口超过100万的语言也足足有140多种。字符串描述符也需要应对多种语言的情况,当然这并不是说设备里就要存储这么多不同语言的字符串描述符,这未免要求过高了些,代价也忒昂贵了些,要知道这些描述符不会凭空生出来,是要存储在设备的EEPROM里的,此物是需要MONEY的,现在都在提倡节约型社会,要节约MONEY,尽量少占用EEPROM,要节约用水,尽量和女友一起洗澡。所以说只提供几种语言的字符串描述符就可以了,甚至说只提供一种语言,比如英语就可以了。
其实咱们现在说的语言的问题和秦始皇统一六国时遇到的语言问题一样,就是太多了,鸡说鸡的,鸭说鸭的,交流成问题。你说啥时候地球上或整个宇宙上只说一种语言了,咱也不用费劲去学什么英语了,就是外星人来了,大家和外星mm交流也不成问题。不过不管哪种语言,在PC里或者设备里存放都只能用二进制数字,这就需要在语言与数字之间进行编码,这个所谓的编码和这个世界上其它事物一样,都是有多种的,你说连人的种类都有很多了,人造出来的编码种类能没有很多么?起码每种语言都会存在独立的编码方式,咱们的简体中文可以使用GB2312、GBK、GB18030等,台湾那边儿是繁体,用的就是big5,这么一来每种语言自己内部交流是不成问题了,可相互之间就像鸡同鸭讲了。于是世界上的某些角落就出现了那么一些有志青年,立志要将各种语言的编码体系给统一起来,于是就出现了UNICODE和ISO-10646。比起他们,俺是太没追求了,父亲问我人生有什么追求?我回答金钱和美女,父亲凶狠的打了我的脸,我回答事业与爱情,父亲赞赏的摸了我的头。
Spec里就说了,字符串描述符使用的就是UNICODE编码,usb设备里的字符串可以通过它来支持多种语言,不过你需要在向设备请求字符串描述符的时候指定一个你期望看到的一种语言,俗称语言ID,即Language ID。这个语言ID使用两个字节表示,所有可以使用的语言ID在http://www.usb.org/developers/docs/USB_LANGIDs.pdf 文档里都有列出来,从这个文档里你也可以明白为啥要使用两个字节,而不是一个字节表示。这么说吧,比如中文是0X04,但是中文还有好几种,所以就需要另外一个字节表示是哪种中文,简体就是0X02,注意合起来表示简体中文并不是0X0402或者0X0204,因为这两个字节并不是分的那么清,bit0~9一共10位去表示Primary语言ID,其余6位去表示Sub语言ID,毕竟一种语言的Sub语言不可能会特别的多,没必要分去整整一半8bits,所以简体中文的语言ID就是0X0804。
不多罗唆,还是结合代码,从上节飘过的usb_cache_string说起,看看如何去获得一个字符串描述符,它在message.c里定义 817 /** 818 * usb_cache_string - read a string descriptor and cache it for later use 819 * @udev: the device whose string descriptor is being read 820 * @index: the descriptor index 821 * 822 * Returns a pointer to a kmalloc'ed buffer containing the descriptor string, 823 * or NULL if the index is 0 or the string could not be read. 824 */ 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; 835memcpy(smallbuf, buf, len); 836 } 837kfree(buf); 838 } 839 return smallbuf; 840 }
每个成年人都有那么一个身份证号码,每个字符串描述符都有一个序号,身份证号可能会重复,字符串描述符这个序号是不能重复的,不过这点不用你我操心,都是设备已经固化好了的东西,重复不重复也不是咱们要操心的事。咱们要操心的事太多了,要操心吃还要操心睡,加菲猫告诉我们,除了吃和睡,生命或许还有别的意义,不过我觉得没有就挺好。
也好理解,什么东西一多了,最好最节约最省事的区分方式就是编号,字符串描述符当然可以有很多个,参数的index就是表示了你希望获得其中的第几个。但是不可疏忽大意的是,你不能指定index为0,0编号是有特殊用途的,你指定0了就什么也得不到。你去华为找工号000的,不会有人应你,根本就没这人,你找001,这次有人应你,不过是保安,赶你走的,没事儿找任老总干吗,没看一个接一个的自杀正是多事之秋么。
有关这个函数,还需要明白两点,第一是它采用的方针策略,就是苦活儿累活儿找个usb_string()去做,自己一边儿看着,加菲猫还告诉我们,工作好有意思耶!尤其是看着别人工作。这个usb_string()怎么工作的之后再看,现在只要注意下它的参数,比usb_cache_string()的参数多了两个,就是buf和size,也就是需要传递一个存放返回的字符串描述符的缓冲区。但是你调用usb_cache_string()的时候并没有指定一个明确的size,usb_cache_string()也就不知道你想要的那个字符串描述符有多大,于是它就采用了这么一个技巧,先申请一个足够大的缓冲区,这里是256字节,拿这个缓冲区去调用usb_string(),通过usb_string()的返回值会得到字符串描述符的真实大小,然后再拿这个真实的大小去申请一个缓冲区,并将大缓冲区里放的字符串描述符数据拷贝过来,这时那个大缓冲区当然就没什么利用价值了,于是再把它给释放掉。
第二就是申请那个小缓冲区的时候,使用的并不是usb_string()的返回值,而是多了1个字节,也就是说要从大缓冲区里多拷一个字节到小缓冲区里,为什么?这牵涉到C里字符串方面那个人见人愁鬼见鬼哭的代码杀手——字符串结束符。如果你说俺是危言耸听夸大其实,那只能说明你不是天才就是C代码写的少,咱不说C++,因为C++里更多的是用string。
字符串都需要那么一个结束符,这点是个正常人都知道的,但并不是每个正常人都能每时每刻的记得给字符串加上这么一个结束符。就像是个人都知道钞票不是万能的,但并不是每个人都知道:钞票不是万能的,有时还需要信用卡。可能你小心了1000次,但在第1001次的时候你给忘记了,你的代码就可能就可能挂了。不说玄乎儿的了,搞点实在的,给个例子
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的结束,多伤感啊。当然初始化的方法有多种,你可以使用memset将整个buf初始化为0,但是如果buf比较大的话,memset就比较的消耗CPU了,要时刻牢记咱们要创建一个节约型的社会,而这里buf[0] = '/0'就足够用了,所以没必要使用memset去全方位的搞一下。为了更好的说明问题,这里贴一下内核里对strcat的定义 171 char *strcat(char *dest, const char *src) 172 { 173 char *tmp = dest; 174 175 while (*dest) 176dest++; 177 while ((*dest++ = *src++) != '/0') 178 ; 179 return tmp; 180 }
strcat会从dest的起始位置开始去寻找一个字符串的结束符,只有找到,175行的while循环才会结束。“骰子已经掷了,就这样吧!”义无返顾的恺撒毅然跨越卢比肯河,一举击溃了庞培,最终占领了罗马古城。但是如果dest没有初始化过,义无反顾的strcat并不会有好运得到恺撒的结果。本来strcat的目的是将tmp追加到buf里字符串的后面,但是因为buf没有初始化,没有那么一个结束符,strcat就会一直的找下去,就算它运气好在哪儿停了下来,如果这个停下的位置超出了buf的范围,就会把src的数据写到不应该的地方,就可能会破坏其它可能很重要的数据,你的系统可能就挂掉了。
解决这种问题的方法很简单,就是记着遇到指针、数组什么的使用前首先统统的初始化掉,到最后真觉得哪里不必要影响性能了再去优化它,还是那句至理名言:过早优化便是罪。
问个大家都会笑的问题,这个字符串结束符具体是什么东东?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语言定义的用'/'加8进制ASCII码来表示字符的一种方法,'/0'就是表示一个ASCII码值为0的字符。
所以更准确的说,NULL-terminated字符串就是以0值结束的字符串,那么spec的那句话说字符串描述符不是一个NULL-terminated字符串,意思也就是字符串描述符没有一个结束符,你从设备那里得到字符串之后得给它追加一个结束符。本来usb_string()里已经为buf追加好了,但是它返回的长度里还是没有包括进这个结束符的1个字节,所以usb_cache_string()为smallbuf申请内存的时候就得多准备那么一个字节,以便将buf里的那个结束符也给拷过来。现在就看看usb_string()的细节,定义在message.c里 733 /** 734 * usb_string - returns ISO 8859-1 version of a string descriptor 735 * @dev: the device whose string descriptor is being retrieved 736 * @index: the number of the descriptor 737 * @buf: where to put the string 738 * @size: how big is "buf"? 739 * Context: !in_interrupt () 740 * 741 * This converts the UTF-16LE encoded strings returned by devices, from 742 * usb_get_string_descriptor(), to null-terminated ISO-8859-1 encoded ones 743 * that are more usable in most kernel contexts. Note that all characters 744 * in the chosen descriptor that can't be encoded using ISO-8859-1 745 * are converted to the question mark ("?") character, and this function 746 * chooses strings in the first language supported by the device. 747 * 748 * The ASCII (or, redundantly, "US-ASCII") character set is the seven-bit 749 * subset of ISO 8859-1. ISO-8859-1 is the eight-bit subset of Unicode, 750 * and is appropriate for use many uses of English and several other 751 * Western European languages. (But it doesn't include the "Euro" symbol.) 752 * 753 * This call is synchronous, and may not be used in an interrupt context. 754 * 755 * Returns length of the string (>= 0) or usb_control_msg status (< 0). 756 */ 757 int usb_string(struct usb_device *dev, int index, char *buf, size_tsize) 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; 767buf[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) { 774err = usb_string_sub(dev, 0, 0, tbuf); 775 if (err < 0) { 776dev_err (&dev->dev, 777 "string descriptor 0 read error: %d/n", 778err); 779 goto errout; 780 } else if (err < 4) { 781dev_err (&dev->dev, "string descriptor 0 too short/n"); 782err = -EINVAL; 783 goto errout; 784 } else { 785dev->have_langid = 1; 786dev->string_langid = tbuf[2] | (tbuf[3]<< 8); 787 /* always use the first langid listed */ 788dev_dbg (&dev->dev, "default language 0x%04x/n", 789dev->string_langid); 790 } 791 } 792 793err = usb_string_sub(dev, dev->string_langid, index, tbuf); 794 if (err < 0) 795 goto errout; 796 797size--; /* 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 */ 802buf[idx++] = '?'; /* non ISO-8859-1 character */ 803 else 804buf[idx++] = tbuf; 805 } 806buf[idx] = 0; 807err = idx; 808 809 if (tbuf[1] != USB_DT_STRING) 810dev_dbg(&dev->dev, "wrong descriptor type %02x for string %d (/"%s/")/n", tbuf[1],index, buf); 811 812 errout: 813kfree(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,对应的就是Table 9-15
第一次调用usb_string_sub()就是为了获得这张表,获得这张表做嘛用?你接着往下看。
775行,usb_string_sub()返回个负数,就表示没有获得这张表,没有取到0号字符串描述符。如果返回值比4要小,就表示获得的表里没有包含任何一个语言ID。要知道一个语言ID占用2个字节,还有前两个字节表示表的长度还有类型,所以得到的数据至少要为4,才能够得到一个有效的语言ID。如果返回值比4要大,就使用获得的数据的第3,4两个字节设置string_langid,同时设置have_langid为1。现在很明显了,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) 704rc = -EIO; 705 else 706rc = 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) { 711rc = usb_get_string(dev, langid, index, buf, 2); 712 if (rc == 2) 713rc = usb_get_string(dev, langid, index, buf, buf[0]); 714 } 715 716 if (rc >= 2) { 717 if (!buf[0] && !buf[1]) 718usb_try_string_workarounds(buf, &rc); 719 720 /* There might be extra junk at the end of the descriptor */ 721 if (buf[0] < rc) 722rc = buf[0]; 723 724rc = rc - (rc & 1); /* force a multiple of two */ 725 } 726 727 if (rc < 2) 728rc = (rc < 0 ? rc : -EINVAL); 729 730 return rc; 731 }
这个函数首先检查一下你的设备是不是属于那种有怪僻的,如果是一个没有毛病遵纪守法的合格设备,就调用usb_get_string()去帮着自己获得字符串描述符。USB_QUIRK_STRING_FETCH_255就是在include/linux/usb/quirks.h里定义的那些形形色色的毛病之一,《我是Hub》里详细的讲了设备的quirk,说了USB_QUIRK_STRING_FETCH_255就表示设备在获取字符串描述符的时候会crash。
usb_string_sub()的核心就是message.c里定义usb_get_string函数 642 /** 643 * usb_get_string - gets a string descriptor 644 * @dev: the device whose string descriptor is being retrieved 645 * @langid: code for language chosen (from string descriptor zero) 646 * @index: the number of the descriptor 647 * @buf: where to put the string 648 * @size: how big is "buf"? 649 * Context: !in_interrupt () 650 * 651 * Retrieves a string, encoded using UTF-16LE (Unicode, 16 bits per character, 652 * in little-endian byte order). 653 * The usb_string() function will often be a convenient way to turn 654 * these strings into kernel-printable form. 655 * 656 * Strings may be referenced in device, configuration, interface, or other 657 * descriptors, and could also be used in vendor-specific ways. 658 * 659 * This call is synchronous, and may not be used in an interrupt context. 660 * 661 * Returns the number of bytes received on success, or else the status code 662 * returned by the underlying usb_control_msg() call. 663 */ 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 */ 672result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), 673USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, 674 (USB_DT_STRING << 8) + index, langid, buf, size, 675USB_CTRL_GET_TIMEOUT); 676 if (!(result == 0 || result == -EPIPE)) 677 break; 678 } 679 return result; 680 }
我已经不记得这是第多少次遇到usb_control_msg()了,佛说前生500次的回眸才换得今生的一次擦肩而过,那咱们现在至少也成千上万次回眸了。老习惯,还是简单说一下它的一堆参数,wValue的高位字节表示描述符的类型,低位字节表示描述符的序号,所以有674行的(USB_DT_STRING << 8) + index,wIndex对于字符串描述符应该设置为使用语言的ID,所以有674行的langid,至于wLength,就是描述符的长度,对于字符串描述符很难有一个统一的确定的长度,所以一半来说上头儿传递过来的通常是一个比较大的255字节。
和获得设备描述符时一样,因为一些厂商搞出的设备古灵精怪的,可能需要多试几次才能成功。要容许设备犯错误,就像人总要犯错误一样,否则正确之路人满为患了。
还是回过头去看usb_string_sub函数,如果usb_get_string()成功的得到了期待的字符串描述符,则返回获得的字节数,如果这个数目小于2,就再读两个字节试试,要想明白这两个字节是什么内容,需要看看spec Table 9-16