日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Linux那些事儿 之 戏说USB(33)字符串描述符

發布時間:2023/11/27 生活经验 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux那些事儿 之 戏说USB(33)字符串描述符 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
關于字符串描述符,前面的前面已經簡單描述過了,地位僅次于設備/配置/接口/端點四大描述符,那四大設備必須得支持,而字符串描述符對設備來說則是可選的。

這并不是就說字符串描述符不重要,對咱們來說,字符串要比數字親切的多,提供字符串描述符的設備也要比沒有提供的設備親切的多,不會有人會專門去記前面使用lsusb列出的04b4表示的是Cypress Semiconductor Corp.。

一提到字符串,不可避免就得提到字符串使用的語言。字符串親切是親切,但不像數字那樣全球通用,使用中文了,老外看不懂,使用法文阿拉伯文什么的咱又看不懂,你知道目前世界上有多少種語言嗎?有得說七千多種,有得說五千多種,無一定論,不過使用人口超過100萬的語言也足足有140多種。字符串描述符也需要應對多種語言的情況,當然這并不是說設備里就要存儲這么多不同語言的字符串描述符,這未免要求過高了些,代價也忒昂貴了些,要知道這些描述符不會憑空生出來,是要存儲在設備的EEPROM里的,此物是需要MONEY的,要節約MONEY,盡量少占用EEPROM。所以說只提供幾種語言的字符串描述符就可以了,甚至說只提供一種語言,比如英語就可以了。

不過不管哪種語言,在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里定義

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;
}
每個成年人都有那么一個身份證號碼,每個字符串描述符都有一個序號,身份證號可能會重復,字符串描述符這個序號是不能重復的,不過這點不用你我操心,都是設備已經固化好了的東西,重復不重復也不是咱們要操心的事。咱們要操心的事太多了,要操心吃還要操心睡。


也好理解,什么東西一多了,最好最節約最省事的區分方式就是編號,字符串描述符當然可以有很多個,參數的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次的時候你給忘記了,你的代碼就可能就可能掛了。

你從設備那里得到字符串之后得給它追加一個結束符。本來usb_string()里已經為buf追加好了,但是它返回的長度里還是沒有包括進這個結束符的1個字節,所以usb_cache_string()為smallbuf申請內存的時候就得多準備那么一個字節,以便將buf里的那個結束符也給拷過來。現在就看看usb_string()的細節,定義在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行,這幾行做些例行檢查,設備不能是掛起的,index也不能是0的,只要傳遞了指針就是需要檢查的。

10行,初始化buf,usb_cache_string()并沒有對這個buf初始化,所以這里必須要加上這么一步。當然usb_string()并不僅僅只有在usb_cache_string()里調用,可能會在很多地方調用到它,不過不管在哪里,這里謹慎起見,還是需要這么一步。

11行,申請一個256字節大小的緩沖區。前面一直強調說要初始化要初始化,怎么到這里俺就自己打自己一耳光,沒有去初始化tbuf?這是因為沒必要,為什么沒必要,你看看usb_string()最后面的那一堆就明白了。

15行,使用指定的語言ID,或者前面獲得的默認語言ID去獲得想要的那個字符串描述符。現在看看定義在message.c里的usb_string_sub函數。
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;
}
這個函數首先檢查一下你的設備是不是屬于那種有怪僻的,如果是一個沒有毛病遵紀守法的合格設備,就調用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函數
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;
}
我已經不記得這是第多少次遇到usb_control_msg()了,還是簡單說一下它的一堆參數,wValue的高位字節表示描述符的類型,低位字節表示描述符的序號,所以有11行的(USB_DT_STRING << 8) + index,wIndex對于字符串描述符應該設置為使用語言的ID,所以有11的langid,至于wLength,就是描述符的長度,對于字符串描述符很難有一個統一的確定的長度,所以一般來說上頭兒傳遞過來的通常是一個比較大的255字節。

和獲得設備描述符時一樣,因為一些廠商搞出的設備古靈精怪的,可能需要多試幾次才能成功。要容許設備犯錯誤,就像人總要犯錯誤一樣。

還是回過頭去看usb_string_sub函數,如果usb_get_string()成功的得到了期待的字符串描述符,則返回獲得的字節數,如果這個數目小于2,就再讀兩個字節試試,要想明白這兩個字節是什么內容,需要看看spec Table 9-16

Table 9-15是0號字符串描述符的格式,這個Table 9-16是其它字符串描述符的格式,很明顯可以看到,它的前兩個字節分別表示了長度和類型,如果讀2個字節成功的話,就可以準確的獲得這個字符串描述符的長度,然后可以再拿這個準確的長度去請求一次。

該嘗試的都嘗試了,現在看看21行,分析一下前面調用usb_get_string()的結果,如果幾次嘗試之后,它的返回值還是小于2,那就返回一個錯誤碼。如果你的辛苦沒有白費,rc大于等于2,說明終于獲得了一個有效的字符串描述符。

22行,buf的前兩個字節有一個為空時,也就是Table 9-16的前兩個字節有一個為空時,調用了message.c里定義的usb_try_string_workarounds函數
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;}
}
這個函數的目的是從usb_get_string()返回的數據里計算出前面有效的那一部分的長度。它的核心就是5行的那個for循環,不過要搞清楚這個循環,還真不是一件容易的事兒,得有相當的理論功底。

不久之前剛說了字符串描述符使用的是UNICODE編碼,其實UNICODE指的是包含了字符集、編碼、字型等等很多規范的一整套系統,字符集僅僅描述符系統中存在哪些字符,并進行分類,并不涉及如何用數字編碼表示的問題。UNICODE使用的編碼形式主要就是兩種UTF,即UTF-8和UTF-16。使用usb_get_string()獲得的字符串使用的是UTF-16編碼規則,而且是little-endpoint的,每個字符都需要使用兩個字節來表示。你看這個for循環里newlength每次加2,就是表示每次處理一個字符的,但是要弄明白怎么處理的,還需要知道這兩個字節分別是什么東東,這就不得不提及ASCII、ISO-8859-1等幾個名詞兒。

ASCII是用來表示英文的一種編碼規范,表示的最大字符數為256,每個字符占1個字節,但是英文字符沒那么多,一般來說128個也就夠了(最高位為0),這就已經完全包括了控制字符、數字、大小寫字母,還有其它一些符號。對于法語、西班牙語和德語之類的西歐語言都使用叫做ISO-8859-1的東東,它擴展了ASCII碼的最高位,來表示像n上帶有一個波浪線(241),和u上帶有兩個點(252)這樣的字符。而Unicode的低字節,也就是在0到255上同ISO-8859-1完全一樣,它接著使用剩余的數字,256到65535,擴展到表示其它語言的字符。所以可以說ISO-8859-1就是Unicode的子集,如果Unicode的高字節為0,則它表示的字符就和ISO-8859-1完全一樣了。

有上面的理論墊底兒,咱們再看看這個for循環,newlength從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()的26行,buf[0]表示的就是bLength的值,如果它小于usb_get_string()獲得的數據長度,說明這些數據里存在一些垃圾,要把他們給揪出來排除掉。要知道這個rc是要做為真實有效的描述符長度返回的,所以這個時候需要將buf[0]賦給rc。

29行,每個Unicode字符需要使用兩個字節來表示,所以rc必須為偶數,2的整數倍,如果為奇數,就得將最后那一個字節給抹掉,也就是將rc減1。咱們可以學習一下這里將一個數字轉換為偶數時采用的技巧,(rc & 1)在rc為偶數時等于0,為奇數時等于1,再使用rc減去它,得到的就是一個偶數。

從21~30這幾行,咱們應該看得出,在成功獲得一個字符串描述符時,usb_string_sub()返回的是一個NULL-terminated字符串的長度,并沒有涉及到結束符。牢記這一點,咱們回到usb_string函數的23行,先將size,也就是buf的大小減1,目的就是為結束符保留1個字節的位置。

26行,為buf追加一個結束符。咱們這節也就結束了。

總結

以上是生活随笔為你收集整理的Linux那些事儿 之 戏说USB(33)字符串描述符的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。