Linux那些事儿 之 戏说USB(33)字符串描述符
生活随笔
收集整理的這篇文章主要介紹了
Linux那些事儿 之 戏说USB(33)字符串描述符
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
關(guān)于字符串描述符,前面的前面已經(jīng)簡(jiǎn)單描述過(guò)了,地位僅次于設(shè)備/配置/接口/端點(diǎn)四大描述符,那四大設(shè)備必須得支持,而字符串描述符對(duì)設(shè)備來(lái)說(shuō)則是可選的。
這并不是就說(shuō)字符串描述符不重要,對(duì)咱們來(lái)說(shuō),字符串要比數(shù)字親切的多,提供字符串描述符的設(shè)備也要比沒(méi)有提供的設(shè)備親切的多,不會(huì)有人會(huì)專門去記前面使用lsusb列出的04b4表示的是Cypress Semiconductor Corp.。
一提到字符串,不可避免就得提到字符串使用的語(yǔ)言。字符串親切是親切,但不像數(shù)字那樣全球通用,使用中文了,老外看不懂,使用法文阿拉伯文什么的咱又看不懂,你知道目前世界上有多少種語(yǔ)言嗎?有得說(shuō)七千多種,有得說(shuō)五千多種,無(wú)一定論,不過(guò)使用人口超過(guò)100萬(wàn)的語(yǔ)言也足足有140多種。字符串描述符也需要應(yīng)對(duì)多種語(yǔ)言的情況,當(dāng)然這并不是說(shuō)設(shè)備里就要存儲(chǔ)這么多不同語(yǔ)言的字符串描述符,這未免要求過(guò)高了些,代價(jià)也忒昂貴了些,要知道這些描述符不會(huì)憑空生出來(lái),是要存儲(chǔ)在設(shè)備的EEPROM里的,此物是需要MONEY的,要節(jié)約MONEY,盡量少占用EEPROM。所以說(shuō)只提供幾種語(yǔ)言的字符串描述符就可以了,甚至說(shuō)只提供一種語(yǔ)言,比如英語(yǔ)就可以了。
不過(guò)不管哪種語(yǔ)言,在PC里或者設(shè)備里存放都只能用二進(jìn)制數(shù)字,這就需要在語(yǔ)言與數(shù)字之間進(jìn)行編碼,這個(gè)所謂的編碼和這個(gè)世界上其它事物一樣,都是有多種的,起碼每種語(yǔ)言都會(huì)存在獨(dú)立的編碼方式,咱們的簡(jiǎn)體中文可以使用GB2312、GBK、GB18030等,臺(tái)灣那邊兒是繁體,用的就是big5,這么一來(lái)每種語(yǔ)言自己內(nèi)部交流是不成問(wèn)題了,可相互之間就像雞同鴨講了。于是世界上的某些角落就出現(xiàn)了那么一些有志青年,立志要將各種語(yǔ)言的編碼體系給統(tǒng)一起來(lái),于是就出現(xiàn)了UNICODE和ISO-10646。
Spec里就說(shuō)了,字符串描述符使用的就是UNICODE編碼,usb設(shè)備里的字符串可以通過(guò)它來(lái)支持多種語(yǔ)言,不過(guò)你需要在向設(shè)備請(qǐng)求字符串描述符的時(shí)候指定一個(gè)你期望看到的一種語(yǔ)言,俗稱語(yǔ)言ID,即Language ID。這個(gè)語(yǔ)言ID使用兩個(gè)字節(jié)表示,所有可以使用的語(yǔ)言ID在http://www.usb.org/developers/docs/USB_LANGIDs.pdf 文檔里都有列出來(lái),從這個(gè)文檔里你也可以明白為啥要使用兩個(gè)字節(jié),而不是一個(gè)字節(jié)表示。這么說(shuō)吧,比如中文是0X04,但是中文還有好幾種,所以就需要另外一個(gè)字節(jié)表示是哪種中文,簡(jiǎn)體就是0X02,注意合起來(lái)表示簡(jiǎn)體中文并不是0X0402或者0X0204,因?yàn)檫@兩個(gè)字節(jié)并不是分的那么清,bit0~9一共10位去表示Primary語(yǔ)言ID,其余6位去表示Sub語(yǔ)言ID,畢竟一種語(yǔ)言的Sub語(yǔ)言不可能會(huì)特別的多,沒(méi)必要分去整整一半8bits,所以簡(jiǎn)體中文的語(yǔ)言ID就是0X0804。
不多羅唆,還是結(jié)合代碼,從上節(jié)飄過(guò)的usb_cache_string說(shuō)起,看看如何去獲得一個(gè)字符串描述符,它在message.c里定義
也好理解,什么東西一多了,最好最節(jié)約最省事的區(qū)分方式就是編號(hào),字符串描述符當(dāng)然可以有很多個(gè),參數(shù)的index就是表示了你希望獲得其中的第幾個(gè)。但是不可疏忽大意的是,你不能指定index為0,0編號(hào)是有特殊用途的,你指定0了就什么也得不到。你去華為找工號(hào)000的,不會(huì)有人應(yīng)你,根本就沒(méi)這人,你找001,這次有人應(yīng)你,不過(guò)是保安,趕你走的,沒(méi)事兒找任老總干嗎,沒(méi)看一個(gè)接一個(gè)的自殺正是多事之秋么。
有關(guān)這個(gè)函數(shù),還需要明白兩點(diǎn),第一是它采用的方針策略,就是苦活兒累活兒找個(gè)usb_string()去做,自己一邊兒看著,這個(gè)usb_string()怎么工作的之后再看,現(xiàn)在只要注意下它的參數(shù),比usb_cache_string()的參數(shù)多了兩個(gè),就是buf和size,也就是需要傳遞一個(gè)存放返回的字符串描述符的緩沖區(qū)。但是你調(diào)用usb_cache_string()的時(shí)候并沒(méi)有指定一個(gè)明確的size,usb_cache_string()也就不知道你想要的那個(gè)字符串描述符有多大,于是它就采用了這么一個(gè)技巧,先申請(qǐng)一個(gè)足夠大的緩沖區(qū),這里是256字節(jié),拿這個(gè)緩沖區(qū)去調(diào)用usb_string(),通過(guò)usb_string()的返回值會(huì)得到字符串描述符的真實(shí)大小,然后再拿這個(gè)真實(shí)的大小去申請(qǐng)一個(gè)緩沖區(qū),并將大緩沖區(qū)里放的字符串描述符數(shù)據(jù)拷貝過(guò)來(lái),這時(shí)那個(gè)大緩沖區(qū)當(dāng)然就沒(méi)什么利用價(jià)值了,于是再把它給釋放掉。
第二就是申請(qǐng)那個(gè)小緩沖區(qū)的時(shí)候,使用的并不是usb_string()的返回值,而是多了1個(gè)字節(jié),也就是說(shuō)要從大緩沖區(qū)里多拷一個(gè)字節(jié)到小緩沖區(qū)里,為什么?這牽涉到C里字符串方面那個(gè)人見(jiàn)人愁鬼見(jiàn)鬼哭的代碼殺手——字符串結(jié)束符。如果你說(shuō)俺是危言聳聽(tīng)夸大其實(shí),那只能說(shuō)明你不是天才就是C代碼寫的少,咱不說(shuō)C++,因?yàn)镃++里更多的是用string。
字符串都需要那么一個(gè)結(jié)束符,這點(diǎn)是個(gè)正常人都知道的,但并不是每個(gè)正常人都能每時(shí)每刻的記得給字符串加上這么一個(gè)結(jié)束符。就像是個(gè)人都知道鈔票不是萬(wàn)能的,但并不是每個(gè)人都知道:鈔票不是萬(wàn)能的,有時(shí)還需要信用卡??赡苣阈⌒牧?000次,但在第1001次的時(shí)候你給忘記了,你的代碼就可能就可能掛了。
你從設(shè)備那里得到字符串之后得給它追加一個(gè)結(jié)束符。本來(lái)usb_string()里已經(jīng)為buf追加好了,但是它返回的長(zhǎng)度里還是沒(méi)有包括進(jìn)這個(gè)結(jié)束符的1個(gè)字節(jié),所以u(píng)sb_cache_string()為smallbuf申請(qǐng)內(nèi)存的時(shí)候就得多準(zhǔn)備那么一個(gè)字節(jié),以便將buf里的那個(gè)結(jié)束符也給拷過(guò)來(lái)。現(xiàn)在就看看usb_string()的細(xì)節(jié),定義在message.c里
10行,初始化buf,usb_cache_string()并沒(méi)有對(duì)這個(gè)buf初始化,所以這里必須要加上這么一步。當(dāng)然usb_string()并不僅僅只有在usb_cache_string()里調(diào)用,可能會(huì)在很多地方調(diào)用到它,不過(guò)不管在哪里,這里謹(jǐn)慎起見(jiàn),還是需要這么一步。
11行,申請(qǐng)一個(gè)256字節(jié)大小的緩沖區(qū)。前面一直強(qiáng)調(diào)說(shuō)要初始化要初始化,怎么到這里俺就自己打自己一耳光,沒(méi)有去初始化tbuf?這是因?yàn)闆](méi)必要,為什么沒(méi)必要,你看看usb_string()最后面的那一堆就明白了。
15行,使用指定的語(yǔ)言ID,或者前面獲得的默認(rèn)語(yǔ)言ID去獲得想要的那個(gè)字符串描述符?,F(xiàn)在看看定義在message.c里的usb_string_sub函數(shù)。
usb_string_sub()的核心就是message.c里定義usb_get_string函數(shù)
和獲得設(shè)備描述符時(shí)一樣,因?yàn)橐恍S商搞出的設(shè)備古靈精怪的,可能需要多試幾次才能成功。要容許設(shè)備犯錯(cuò)誤,就像人總要犯錯(cuò)誤一樣。
還是回過(guò)頭去看usb_string_sub函數(shù),如果usb_get_string()成功的得到了期待的字符串描述符,則返回獲得的字節(jié)數(shù),如果這個(gè)數(shù)目小于2,就再讀兩個(gè)字節(jié)試試,要想明白這兩個(gè)字節(jié)是什么內(nèi)容,需要看看spec Table 9-16
Table 9-15是0號(hào)字符串描述符的格式,這個(gè)Table 9-16是其它字符串描述符的格式,很明顯可以看到,它的前兩個(gè)字節(jié)分別表示了長(zhǎng)度和類型,如果讀2個(gè)字節(jié)成功的話,就可以準(zhǔn)確的獲得這個(gè)字符串描述符的長(zhǎng)度,然后可以再拿這個(gè)準(zhǔn)確的長(zhǎng)度去請(qǐng)求一次。
該嘗試的都嘗試了,現(xiàn)在看看21行,分析一下前面調(diào)用usb_get_string()的結(jié)果,如果幾次嘗試之后,它的返回值還是小于2,那就返回一個(gè)錯(cuò)誤碼。如果你的辛苦沒(méi)有白費(fèi),rc大于等于2,說(shuō)明終于獲得了一個(gè)有效的字符串描述符。
22行,buf的前兩個(gè)字節(jié)有一個(gè)為空時(shí),也就是Table 9-16的前兩個(gè)字節(jié)有一個(gè)為空時(shí),調(diào)用了message.c里定義的usb_try_string_workarounds函數(shù)
不久之前剛說(shuō)了字符串描述符使用的是UNICODE編碼,其實(shí)UNICODE指的是包含了字符集、編碼、字型等等很多規(guī)范的一整套系統(tǒng),字符集僅僅描述符系統(tǒng)中存在哪些字符,并進(jìn)行分類,并不涉及如何用數(shù)字編碼表示的問(wèn)題。UNICODE使用的編碼形式主要就是兩種UTF,即UTF-8和UTF-16。使用usb_get_string()獲得的字符串使用的是UTF-16編碼規(guī)則,而且是little-endpoint的,每個(gè)字符都需要使用兩個(gè)字節(jié)來(lái)表示。你看這個(gè)for循環(huán)里newlength每次加2,就是表示每次處理一個(gè)字符的,但是要弄明白怎么處理的,還需要知道這兩個(gè)字節(jié)分別是什么東東,這就不得不提及ASCII、ISO-8859-1等幾個(gè)名詞兒。
ASCII是用來(lái)表示英文的一種編碼規(guī)范,表示的最大字符數(shù)為256,每個(gè)字符占1個(gè)字節(jié),但是英文字符沒(méi)那么多,一般來(lái)說(shuō)128個(gè)也就夠了(最高位為0),這就已經(jīng)完全包括了控制字符、數(shù)字、大小寫字母,還有其它一些符號(hào)。對(duì)于法語(yǔ)、西班牙語(yǔ)和德語(yǔ)之類的西歐語(yǔ)言都使用叫做ISO-8859-1的東東,它擴(kuò)展了ASCII碼的最高位,來(lái)表示像n上帶有一個(gè)波浪線(241),和u上帶有兩個(gè)點(diǎn)(252)這樣的字符。而Unicode的低字節(jié),也就是在0到255上同ISO-8859-1完全一樣,它接著使用剩余的數(shù)字,256到65535,擴(kuò)展到表示其它語(yǔ)言的字符。所以可以說(shuō)ISO-8859-1就是Unicode的子集,如果Unicode的高字節(jié)為0,則它表示的字符就和ISO-8859-1完全一樣了。
有上面的理論墊底兒,咱們?cè)倏纯催@個(gè)for循環(huán),newlength從2開(kāi)始,是因?yàn)榍皟蓚€(gè)字節(jié)應(yīng)該是表示長(zhǎng)度和類型的,這里只逐個(gè)兒對(duì)上面Table 9-16里的bString中的每個(gè)字符做處理。還要知道usb_get_string()得到的結(jié)果是little-endpoint的,所以buf[newlength]和buf[newlength + 1]分別表示一個(gè)字符的低字節(jié)和高字節(jié),那么isprint(buf[newlength]就是用來(lái)判斷一下這個(gè)Unicode字符的低字節(jié)是不是可以print的,如果不是,就沒(méi)必要再往下循環(huán)了,后邊兒的字符也不再看了,然后就到了690行的if,將newlength賦給buf[0],即bLength。length指向的是usb_get_string()返回的原始數(shù)據(jù)的長(zhǎng)度,692行使用for循環(huán)計(jì)算出的有效長(zhǎng)度將它給修改了。isprint在include/linux/ctype.h里定義,你可以去看看,這里就不多說(shuō)了。
這個(gè)for循環(huán)終止的條件有兩個(gè),另外一個(gè)就是buf[newlength + 1],也就是這個(gè)Unicode字符的高字節(jié)不為0,這時(shí)它不存在對(duì)應(yīng)的ISO-8859-1形式,為什么加上這個(gè)判斷?你接著看。
usb_string_sub()的26行,buf[0]表示的就是bLength的值,如果它小于usb_get_string()獲得的數(shù)據(jù)長(zhǎng)度,說(shuō)明這些數(shù)據(jù)里存在一些垃圾,要把他們給揪出來(lái)排除掉。要知道這個(gè)rc是要做為真實(shí)有效的描述符長(zhǎng)度返回的,所以這個(gè)時(shí)候需要將buf[0]賦給rc。
29行,每個(gè)Unicode字符需要使用兩個(gè)字節(jié)來(lái)表示,所以rc必須為偶數(shù),2的整數(shù)倍,如果為奇數(shù),就得將最后那一個(gè)字節(jié)給抹掉,也就是將rc減1。咱們可以學(xué)習(xí)一下這里將一個(gè)數(shù)字轉(zhuǎn)換為偶數(shù)時(shí)采用的技巧,(rc & 1)在rc為偶數(shù)時(shí)等于0,為奇數(shù)時(shí)等于1,再使用rc減去它,得到的就是一個(gè)偶數(shù)。
從21~30這幾行,咱們應(yīng)該看得出,在成功獲得一個(gè)字符串描述符時(shí),usb_string_sub()返回的是一個(gè)NULL-terminated字符串的長(zhǎng)度,并沒(méi)有涉及到結(jié)束符。牢記這一點(diǎn),咱們回到usb_string函數(shù)的23行,先將size,也就是buf的大小減1,目的就是為結(jié)束符保留1個(gè)字節(jié)的位置。
26行,為buf追加一個(gè)結(jié)束符。咱們這節(jié)也就結(jié)束了。
這并不是就說(shuō)字符串描述符不重要,對(duì)咱們來(lái)說(shuō),字符串要比數(shù)字親切的多,提供字符串描述符的設(shè)備也要比沒(méi)有提供的設(shè)備親切的多,不會(huì)有人會(huì)專門去記前面使用lsusb列出的04b4表示的是Cypress Semiconductor Corp.。
一提到字符串,不可避免就得提到字符串使用的語(yǔ)言。字符串親切是親切,但不像數(shù)字那樣全球通用,使用中文了,老外看不懂,使用法文阿拉伯文什么的咱又看不懂,你知道目前世界上有多少種語(yǔ)言嗎?有得說(shuō)七千多種,有得說(shuō)五千多種,無(wú)一定論,不過(guò)使用人口超過(guò)100萬(wàn)的語(yǔ)言也足足有140多種。字符串描述符也需要應(yīng)對(duì)多種語(yǔ)言的情況,當(dāng)然這并不是說(shuō)設(shè)備里就要存儲(chǔ)這么多不同語(yǔ)言的字符串描述符,這未免要求過(guò)高了些,代價(jià)也忒昂貴了些,要知道這些描述符不會(huì)憑空生出來(lái),是要存儲(chǔ)在設(shè)備的EEPROM里的,此物是需要MONEY的,要節(jié)約MONEY,盡量少占用EEPROM。所以說(shuō)只提供幾種語(yǔ)言的字符串描述符就可以了,甚至說(shuō)只提供一種語(yǔ)言,比如英語(yǔ)就可以了。
不過(guò)不管哪種語(yǔ)言,在PC里或者設(shè)備里存放都只能用二進(jìn)制數(shù)字,這就需要在語(yǔ)言與數(shù)字之間進(jìn)行編碼,這個(gè)所謂的編碼和這個(gè)世界上其它事物一樣,都是有多種的,起碼每種語(yǔ)言都會(huì)存在獨(dú)立的編碼方式,咱們的簡(jiǎn)體中文可以使用GB2312、GBK、GB18030等,臺(tái)灣那邊兒是繁體,用的就是big5,這么一來(lái)每種語(yǔ)言自己內(nèi)部交流是不成問(wèn)題了,可相互之間就像雞同鴨講了。于是世界上的某些角落就出現(xiàn)了那么一些有志青年,立志要將各種語(yǔ)言的編碼體系給統(tǒng)一起來(lái),于是就出現(xiàn)了UNICODE和ISO-10646。
Spec里就說(shuō)了,字符串描述符使用的就是UNICODE編碼,usb設(shè)備里的字符串可以通過(guò)它來(lái)支持多種語(yǔ)言,不過(guò)你需要在向設(shè)備請(qǐng)求字符串描述符的時(shí)候指定一個(gè)你期望看到的一種語(yǔ)言,俗稱語(yǔ)言ID,即Language ID。這個(gè)語(yǔ)言ID使用兩個(gè)字節(jié)表示,所有可以使用的語(yǔ)言ID在http://www.usb.org/developers/docs/USB_LANGIDs.pdf 文檔里都有列出來(lái),從這個(gè)文檔里你也可以明白為啥要使用兩個(gè)字節(jié),而不是一個(gè)字節(jié)表示。這么說(shuō)吧,比如中文是0X04,但是中文還有好幾種,所以就需要另外一個(gè)字節(jié)表示是哪種中文,簡(jiǎn)體就是0X02,注意合起來(lái)表示簡(jiǎn)體中文并不是0X0402或者0X0204,因?yàn)檫@兩個(gè)字節(jié)并不是分的那么清,bit0~9一共10位去表示Primary語(yǔ)言ID,其余6位去表示Sub語(yǔ)言ID,畢竟一種語(yǔ)言的Sub語(yǔ)言不可能會(huì)特別的多,沒(méi)必要分去整整一半8bits,所以簡(jiǎn)體中文的語(yǔ)言ID就是0X0804。
不多羅唆,還是結(jié)合代碼,從上節(jié)飄過(guò)的usb_cache_string說(shuō)起,看看如何去獲得一個(gè)字符串描述符,它在message.c里定義
char *usb_cache_string(struct usb_device *udev, int index)
{char *buf;char *smallbuf = NULL;int len;if (index <= 0)return NULL;buf = kmalloc(MAX_USB_STRING_SIZE, GFP_NOIO);if (buf) {len = usb_string(udev, index, buf, MAX_USB_STRING_SIZE);if (len > 0) {smallbuf = kmalloc(++len, GFP_NOIO);if (!smallbuf)return buf;memcpy(smallbuf, buf, len);}kfree(buf);}return smallbuf;
}每個(gè)成年人都有那么一個(gè)身份證號(hào)碼,每個(gè)字符串描述符都有一個(gè)序號(hào),身份證號(hào)可能會(huì)重復(fù),字符串描述符這個(gè)序號(hào)是不能重復(fù)的,不過(guò)這點(diǎn)不用你我操心,都是設(shè)備已經(jīng)固化好了的東西,重復(fù)不重復(fù)也不是咱們要操心的事。咱們要操心的事太多了,要操心吃還要操心睡。
也好理解,什么東西一多了,最好最節(jié)約最省事的區(qū)分方式就是編號(hào),字符串描述符當(dāng)然可以有很多個(gè),參數(shù)的index就是表示了你希望獲得其中的第幾個(gè)。但是不可疏忽大意的是,你不能指定index為0,0編號(hào)是有特殊用途的,你指定0了就什么也得不到。你去華為找工號(hào)000的,不會(huì)有人應(yīng)你,根本就沒(méi)這人,你找001,這次有人應(yīng)你,不過(guò)是保安,趕你走的,沒(méi)事兒找任老總干嗎,沒(méi)看一個(gè)接一個(gè)的自殺正是多事之秋么。
有關(guān)這個(gè)函數(shù),還需要明白兩點(diǎn),第一是它采用的方針策略,就是苦活兒累活兒找個(gè)usb_string()去做,自己一邊兒看著,這個(gè)usb_string()怎么工作的之后再看,現(xiàn)在只要注意下它的參數(shù),比usb_cache_string()的參數(shù)多了兩個(gè),就是buf和size,也就是需要傳遞一個(gè)存放返回的字符串描述符的緩沖區(qū)。但是你調(diào)用usb_cache_string()的時(shí)候并沒(méi)有指定一個(gè)明確的size,usb_cache_string()也就不知道你想要的那個(gè)字符串描述符有多大,于是它就采用了這么一個(gè)技巧,先申請(qǐng)一個(gè)足夠大的緩沖區(qū),這里是256字節(jié),拿這個(gè)緩沖區(qū)去調(diào)用usb_string(),通過(guò)usb_string()的返回值會(huì)得到字符串描述符的真實(shí)大小,然后再拿這個(gè)真實(shí)的大小去申請(qǐng)一個(gè)緩沖區(qū),并將大緩沖區(qū)里放的字符串描述符數(shù)據(jù)拷貝過(guò)來(lái),這時(shí)那個(gè)大緩沖區(qū)當(dāng)然就沒(méi)什么利用價(jià)值了,于是再把它給釋放掉。
第二就是申請(qǐng)那個(gè)小緩沖區(qū)的時(shí)候,使用的并不是usb_string()的返回值,而是多了1個(gè)字節(jié),也就是說(shuō)要從大緩沖區(qū)里多拷一個(gè)字節(jié)到小緩沖區(qū)里,為什么?這牽涉到C里字符串方面那個(gè)人見(jiàn)人愁鬼見(jiàn)鬼哭的代碼殺手——字符串結(jié)束符。如果你說(shuō)俺是危言聳聽(tīng)夸大其實(shí),那只能說(shuō)明你不是天才就是C代碼寫的少,咱不說(shuō)C++,因?yàn)镃++里更多的是用string。
字符串都需要那么一個(gè)結(jié)束符,這點(diǎn)是個(gè)正常人都知道的,但并不是每個(gè)正常人都能每時(shí)每刻的記得給字符串加上這么一個(gè)結(jié)束符。就像是個(gè)人都知道鈔票不是萬(wàn)能的,但并不是每個(gè)人都知道:鈔票不是萬(wàn)能的,有時(shí)還需要信用卡??赡苣阈⌒牧?000次,但在第1001次的時(shí)候你給忘記了,你的代碼就可能就可能掛了。
你從設(shè)備那里得到字符串之后得給它追加一個(gè)結(jié)束符。本來(lái)usb_string()里已經(jīng)為buf追加好了,但是它返回的長(zhǎng)度里還是沒(méi)有包括進(jìn)這個(gè)結(jié)束符的1個(gè)字節(jié),所以u(píng)sb_cache_string()為smallbuf申請(qǐng)內(nèi)存的時(shí)候就得多準(zhǔn)備那么一個(gè)字節(jié),以便將buf里的那個(gè)結(jié)束符也給拷過(guò)來(lái)。現(xiàn)在就看看usb_string()的細(xì)節(jié),定義在message.c里
int usb_string(struct usb_device *dev, int index, char *buf, size_t size)
{unsigned char *tbuf;int err;if (dev->state == USB_STATE_SUSPENDED)return -EHOSTUNREACH;if (size <= 0 || !buf || !index)return -EINVAL;buf[0] = 0;tbuf = kmalloc(256, GFP_NOIO);if (!tbuf)return -ENOMEM;err = usb_get_langid(dev, tbuf);if (err < 0)goto errout;err = usb_string_sub(dev, dev->string_langid, index, tbuf);if (err < 0)goto errout;size--; /* leave room for trailing NULL char in output buffer */err = utf16s_to_utf8s((wchar_t *) &tbuf[2], (err - 2) / 2,UTF16_LITTLE_ENDIAN, buf, size);buf[err] = 0;if (tbuf[1] != USB_DT_STRING)dev_dbg(&dev->dev,"wrong descriptor type %02x for string %d (\"%s\")\n",tbuf[1], index, buf);errout:kfree(tbuf);return err;
}6行,這幾行做些例行檢查,設(shè)備不能是掛起的,index也不能是0的,只要傳遞了指針就是需要檢查的。
10行,初始化buf,usb_cache_string()并沒(méi)有對(duì)這個(gè)buf初始化,所以這里必須要加上這么一步。當(dāng)然usb_string()并不僅僅只有在usb_cache_string()里調(diào)用,可能會(huì)在很多地方調(diào)用到它,不過(guò)不管在哪里,這里謹(jǐn)慎起見(jiàn),還是需要這么一步。
11行,申請(qǐng)一個(gè)256字節(jié)大小的緩沖區(qū)。前面一直強(qiáng)調(diào)說(shuō)要初始化要初始化,怎么到這里俺就自己打自己一耳光,沒(méi)有去初始化tbuf?這是因?yàn)闆](méi)必要,為什么沒(méi)必要,你看看usb_string()最后面的那一堆就明白了。
15行,使用指定的語(yǔ)言ID,或者前面獲得的默認(rèn)語(yǔ)言ID去獲得想要的那個(gè)字符串描述符?,F(xiàn)在看看定義在message.c里的usb_string_sub函數(shù)。
static int usb_string_sub(struct usb_device *dev, unsigned int langid,unsigned int index, unsigned char *buf)
{int rc;/* Try to read the string descriptor by asking for the maximum* possible number of bytes */if (dev->quirks & USB_QUIRK_STRING_FETCH_255)rc = -EIO;elserc = usb_get_string(dev, langid, index, buf, 255);/* If that failed try to read the descriptor length, then* ask for just that many bytes */if (rc < 2) {rc = usb_get_string(dev, langid, index, buf, 2);if (rc == 2)rc = usb_get_string(dev, langid, index, buf, buf[0]);}if (rc >= 2) {if (!buf[0] && !buf[1])usb_try_string_workarounds(buf, &rc);/* There might be extra junk at the end of the descriptor */if (buf[0] < rc)rc = buf[0];rc = rc - (rc & 1); /* force a multiple of two */}if (rc < 2)rc = (rc < 0 ? rc : -EINVAL);return rc;
}這個(gè)函數(shù)首先檢查一下你的設(shè)備是不是屬于那種有怪僻的,如果是一個(gè)沒(méi)有毛病遵紀(jì)守法的合格設(shè)備,就調(diào)用usb_get_string()去幫著自己獲得字符串描述符。USB_QUIRK_STRING_FETCH_255就是在include/linux/usb/quirks.h里定義的那些形形色色的毛病之一,《我是Hub》里詳細(xì)的講了設(shè)備的quirk,說(shuō)了USB_QUIRK_STRING_FETCH_255就表示設(shè)備在獲取字符串描述符的時(shí)候會(huì)crash。
usb_string_sub()的核心就是message.c里定義usb_get_string函數(shù)
static int usb_get_string(struct usb_device *dev, unsigned short langid,unsigned char index, void *buf, int size)
{int i;int result;for (i = 0; i < 3; ++i) {/* retry on length 0 or stall; some devices are flakey */result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,(USB_DT_STRING << 8) + index, langid, buf, size,USB_CTRL_GET_TIMEOUT);if (result == 0 || result == -EPIPE)continue;if (result > 1 && ((u8 *) buf)[1] != USB_DT_STRING) {result = -ENODATA;continue;}break;}return result;
}我已經(jīng)不記得這是第多少次遇到usb_control_msg()了,還是簡(jiǎn)單說(shuō)一下它的一堆參數(shù),wValue的高位字節(jié)表示描述符的類型,低位字節(jié)表示描述符的序號(hào),所以有11行的(USB_DT_STRING << 8) + index,wIndex對(duì)于字符串描述符應(yīng)該設(shè)置為使用語(yǔ)言的ID,所以有11的langid,至于wLength,就是描述符的長(zhǎng)度,對(duì)于字符串描述符很難有一個(gè)統(tǒng)一的確定的長(zhǎng)度,所以一般來(lái)說(shuō)上頭兒傳遞過(guò)來(lái)的通常是一個(gè)比較大的255字節(jié)。
和獲得設(shè)備描述符時(shí)一樣,因?yàn)橐恍S商搞出的設(shè)備古靈精怪的,可能需要多試幾次才能成功。要容許設(shè)備犯錯(cuò)誤,就像人總要犯錯(cuò)誤一樣。
還是回過(guò)頭去看usb_string_sub函數(shù),如果usb_get_string()成功的得到了期待的字符串描述符,則返回獲得的字節(jié)數(shù),如果這個(gè)數(shù)目小于2,就再讀兩個(gè)字節(jié)試試,要想明白這兩個(gè)字節(jié)是什么內(nèi)容,需要看看spec Table 9-16
Table 9-15是0號(hào)字符串描述符的格式,這個(gè)Table 9-16是其它字符串描述符的格式,很明顯可以看到,它的前兩個(gè)字節(jié)分別表示了長(zhǎng)度和類型,如果讀2個(gè)字節(jié)成功的話,就可以準(zhǔn)確的獲得這個(gè)字符串描述符的長(zhǎng)度,然后可以再拿這個(gè)準(zhǔn)確的長(zhǎng)度去請(qǐng)求一次。
該嘗試的都嘗試了,現(xiàn)在看看21行,分析一下前面調(diào)用usb_get_string()的結(jié)果,如果幾次嘗試之后,它的返回值還是小于2,那就返回一個(gè)錯(cuò)誤碼。如果你的辛苦沒(méi)有白費(fèi),rc大于等于2,說(shuō)明終于獲得了一個(gè)有效的字符串描述符。
22行,buf的前兩個(gè)字節(jié)有一個(gè)為空時(shí),也就是Table 9-16的前兩個(gè)字節(jié)有一個(gè)為空時(shí),調(diào)用了message.c里定義的usb_try_string_workarounds函數(shù)
static void usb_try_string_workarounds(unsigned char *buf, int *length)
{int newlength, oldlength = *length;for (newlength = 2; newlength + 1 < oldlength; newlength += 2)if (!isprint(buf[newlength]) || buf[newlength + 1])break;if (newlength > 2) {buf[0] = newlength;*length = newlength;}
}這個(gè)函數(shù)的目的是從usb_get_string()返回的數(shù)據(jù)里計(jì)算出前面有效的那一部分的長(zhǎng)度。它的核心就是5行的那個(gè)for循環(huán),不過(guò)要搞清楚這個(gè)循環(huán),還真不是一件容易的事兒,得有相當(dāng)?shù)睦碚摴Φ住?不久之前剛說(shuō)了字符串描述符使用的是UNICODE編碼,其實(shí)UNICODE指的是包含了字符集、編碼、字型等等很多規(guī)范的一整套系統(tǒng),字符集僅僅描述符系統(tǒng)中存在哪些字符,并進(jìn)行分類,并不涉及如何用數(shù)字編碼表示的問(wèn)題。UNICODE使用的編碼形式主要就是兩種UTF,即UTF-8和UTF-16。使用usb_get_string()獲得的字符串使用的是UTF-16編碼規(guī)則,而且是little-endpoint的,每個(gè)字符都需要使用兩個(gè)字節(jié)來(lái)表示。你看這個(gè)for循環(huán)里newlength每次加2,就是表示每次處理一個(gè)字符的,但是要弄明白怎么處理的,還需要知道這兩個(gè)字節(jié)分別是什么東東,這就不得不提及ASCII、ISO-8859-1等幾個(gè)名詞兒。
ASCII是用來(lái)表示英文的一種編碼規(guī)范,表示的最大字符數(shù)為256,每個(gè)字符占1個(gè)字節(jié),但是英文字符沒(méi)那么多,一般來(lái)說(shuō)128個(gè)也就夠了(最高位為0),這就已經(jīng)完全包括了控制字符、數(shù)字、大小寫字母,還有其它一些符號(hào)。對(duì)于法語(yǔ)、西班牙語(yǔ)和德語(yǔ)之類的西歐語(yǔ)言都使用叫做ISO-8859-1的東東,它擴(kuò)展了ASCII碼的最高位,來(lái)表示像n上帶有一個(gè)波浪線(241),和u上帶有兩個(gè)點(diǎn)(252)這樣的字符。而Unicode的低字節(jié),也就是在0到255上同ISO-8859-1完全一樣,它接著使用剩余的數(shù)字,256到65535,擴(kuò)展到表示其它語(yǔ)言的字符。所以可以說(shuō)ISO-8859-1就是Unicode的子集,如果Unicode的高字節(jié)為0,則它表示的字符就和ISO-8859-1完全一樣了。
有上面的理論墊底兒,咱們?cè)倏纯催@個(gè)for循環(huán),newlength從2開(kāi)始,是因?yàn)榍皟蓚€(gè)字節(jié)應(yīng)該是表示長(zhǎng)度和類型的,這里只逐個(gè)兒對(duì)上面Table 9-16里的bString中的每個(gè)字符做處理。還要知道usb_get_string()得到的結(jié)果是little-endpoint的,所以buf[newlength]和buf[newlength + 1]分別表示一個(gè)字符的低字節(jié)和高字節(jié),那么isprint(buf[newlength]就是用來(lái)判斷一下這個(gè)Unicode字符的低字節(jié)是不是可以print的,如果不是,就沒(méi)必要再往下循環(huán)了,后邊兒的字符也不再看了,然后就到了690行的if,將newlength賦給buf[0],即bLength。length指向的是usb_get_string()返回的原始數(shù)據(jù)的長(zhǎng)度,692行使用for循環(huán)計(jì)算出的有效長(zhǎng)度將它給修改了。isprint在include/linux/ctype.h里定義,你可以去看看,這里就不多說(shuō)了。
這個(gè)for循環(huán)終止的條件有兩個(gè),另外一個(gè)就是buf[newlength + 1],也就是這個(gè)Unicode字符的高字節(jié)不為0,這時(shí)它不存在對(duì)應(yīng)的ISO-8859-1形式,為什么加上這個(gè)判斷?你接著看。
usb_string_sub()的26行,buf[0]表示的就是bLength的值,如果它小于usb_get_string()獲得的數(shù)據(jù)長(zhǎng)度,說(shuō)明這些數(shù)據(jù)里存在一些垃圾,要把他們給揪出來(lái)排除掉。要知道這個(gè)rc是要做為真實(shí)有效的描述符長(zhǎng)度返回的,所以這個(gè)時(shí)候需要將buf[0]賦給rc。
29行,每個(gè)Unicode字符需要使用兩個(gè)字節(jié)來(lái)表示,所以rc必須為偶數(shù),2的整數(shù)倍,如果為奇數(shù),就得將最后那一個(gè)字節(jié)給抹掉,也就是將rc減1。咱們可以學(xué)習(xí)一下這里將一個(gè)數(shù)字轉(zhuǎn)換為偶數(shù)時(shí)采用的技巧,(rc & 1)在rc為偶數(shù)時(shí)等于0,為奇數(shù)時(shí)等于1,再使用rc減去它,得到的就是一個(gè)偶數(shù)。
從21~30這幾行,咱們應(yīng)該看得出,在成功獲得一個(gè)字符串描述符時(shí),usb_string_sub()返回的是一個(gè)NULL-terminated字符串的長(zhǎng)度,并沒(méi)有涉及到結(jié)束符。牢記這一點(diǎn),咱們回到usb_string函數(shù)的23行,先將size,也就是buf的大小減1,目的就是為結(jié)束符保留1個(gè)字節(jié)的位置。
26行,為buf追加一個(gè)結(jié)束符。咱們這節(jié)也就結(jié)束了。
總結(jié)
以上是生活随笔為你收集整理的Linux那些事儿 之 戏说USB(33)字符串描述符的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux那些事儿 之 戏说USB(32
- 下一篇: 山药和淮山有什么区别吗?教你4招轻松分辨