Linux那些事儿 之 戏说USB(28)设备的生命线(十一)
生活随笔
收集整理的這篇文章主要介紹了
Linux那些事儿 之 戏说USB(28)设备的生命线(十一)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
現在已經使用GET_DESCRIPTOR請求取到了包含一個配置里所有相關描述符內容的一堆數據,這些數據是raw的,即原始的,所有數據不管是配置描述符、接口描述符還是端點描述符都彼此的擠在一起,所以得想辦法將它們給分開,丁是丁卯是卯的,于是usb_parse_configuration()登上了歷史舞臺,顯然它們兩個不管是誰想簡短幾句就搞定是不可能的,不過也沒什么可怕的,咱寫不會,看還不會么?
drivers/usb/core/config.c
static int usb_parse_configuration(struct usb_device *dev, int cfgidx,struct usb_host_config *config, unsigned char *buffer, int size)
{struct device *ddev = &dev->dev;unsigned char *buffer0 = buffer;int cfgno;int nintf, nintf_orig;int i, j, n;struct usb_interface_cache *intfc;unsigned char *buffer2;int size2;struct usb_descriptor_header *header;int len, retval;u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];unsigned iad_num = 0;memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);if (config->desc.bDescriptorType != USB_DT_CONFIG ||config->desc.bLength < USB_DT_CONFIG_SIZE ||config->desc.bLength > size) {dev_err(ddev, "invalid descriptor for config index %d: ""type = 0x%X, length = %d\n", cfgidx,config->desc.bDescriptorType, config->desc.bLength);return -EINVAL;}cfgno = config->desc.bConfigurationValue;buffer += config->desc.bLength;size -= config->desc.bLength;nintf = nintf_orig = config->desc.bNumInterfaces;if (nintf > USB_MAXINTERFACES) {dev_warn(ddev, "config %d has too many interfaces: %d, ""using maximum allowed: %d\n",cfgno, nintf, USB_MAXINTERFACES);nintf = USB_MAXINTERFACES;}/* Go through the descriptors, checking their length and counting the* number of altsettings for each interface */n = 0;for ((buffer2 = buffer, size2 = size);size2 > 0;(buffer2 += header->bLength, size2 -= header->bLength)) {if (size2 < sizeof(struct usb_descriptor_header)) {dev_warn(ddev, "config %d descriptor has %d excess ""byte%s, ignoring\n",cfgno, size2, plural(size2));break;}header = (struct usb_descriptor_header *) buffer2;if ((header->bLength > size2) || (header->bLength < 2)) {dev_warn(ddev, "config %d has an invalid descriptor ""of length %d, skipping remainder of the config\n",cfgno, header->bLength);break;}if (header->bDescriptorType == USB_DT_INTERFACE) {struct usb_interface_descriptor *d;int inum;d = (struct usb_interface_descriptor *) header;if (d->bLength < USB_DT_INTERFACE_SIZE) {dev_warn(ddev, "config %d has an invalid ""interface descriptor of length %d, ""skipping\n", cfgno, d->bLength);continue;}inum = d->bInterfaceNumber;if ((dev->quirks & USB_QUIRK_HONOR_BNUMINTERFACES) &&n >= nintf_orig) {dev_warn(ddev, "config %d has more interface ""descriptors, than it declares in ""bNumInterfaces, ignoring interface ""number: %d\n", cfgno, inum);continue;}if (inum >= nintf_orig)dev_warn(ddev, "config %d has an invalid ""interface number: %d but max is %d\n",cfgno, inum, nintf_orig - 1);/* Have we already encountered this interface?* Count its altsettings */for (i = 0; i < n; ++i) {if (inums[i] == inum)break;}if (i < n) {if (nalts[i] < 255)++nalts[i];} else if (n < USB_MAXINTERFACES) {inums[n] = inum;nalts[n] = 1;++n;}} else if (header->bDescriptorType ==USB_DT_INTERFACE_ASSOCIATION) {if (iad_num == USB_MAXIADS) {dev_warn(ddev, "found more Interface ""Association Descriptors ""than allocated for in ""configuration %d\n", cfgno);} else {config->intf_assoc[iad_num] =(struct usb_interface_assoc_descriptor*)header;iad_num++;}} else if (header->bDescriptorType == USB_DT_DEVICE ||header->bDescriptorType == USB_DT_CONFIG)dev_warn(ddev, "config %d contains an unexpected ""descriptor of type 0x%X, skipping\n",cfgno, header->bDescriptorType);} /* for ((buffer2 = buffer, size2 = size); ...) */size = buffer2 - buffer;config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0);if (n != nintf)dev_warn(ddev, "config %d has %d interface%s, different from ""the descriptor's value: %d\n",cfgno, n, plural(n), nintf_orig);else if (n == 0)dev_warn(ddev, "config %d has no interfaces?\n", cfgno);config->desc.bNumInterfaces = nintf = n;/* Check for missing interface numbers */for (i = 0; i < nintf; ++i) {for (j = 0; j < nintf; ++j) {if (inums[j] == i)break;}if (j >= nintf)dev_warn(ddev, "config %d has no interface number ""%d\n", cfgno, i);}/* Allocate the usb_interface_caches and altsetting arrays */for (i = 0; i < nintf; ++i) {j = nalts[i];if (j > USB_MAXALTSETTING) {dev_warn(ddev, "too many alternate settings for ""config %d interface %d: %d, ""using maximum allowed: %d\n",cfgno, inums[i], j, USB_MAXALTSETTING);nalts[i] = j = USB_MAXALTSETTING;}len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j;config->intf_cache[i] = intfc = kzalloc(len, GFP_KERNEL);if (!intfc)return -ENOMEM;kref_init(&intfc->ref);}/* FIXME: parse the BOS descriptor *//* Skip over any Class Specific or Vendor Specific descriptors;* find the first interface descriptor */config->extra = buffer;i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,USB_DT_INTERFACE, &n);config->extralen = i;if (n > 0)dev_dbg(ddev, "skipped %d descriptor%s after %s\n",n, plural(n), "configuration");buffer += i;size -= i;/* Parse all the interface/altsetting descriptors */while (size > 0) {retval = usb_parse_interface(ddev, cfgno, config,buffer, size, inums, nalts);if (retval < 0)return retval;buffer += retval;size -= retval;}/* Check for missing altsettings */for (i = 0; i < nintf; ++i) {intfc = config->intf_cache[i];for (j = 0; j < intfc->num_altsetting; ++j) {for (n = 0; n < intfc->num_altsetting; ++n) {if (intfc->altsetting[n].desc.bAlternateSetting == j)break;}if (n >= intfc->num_altsetting)dev_warn(ddev, "config %d interface %d has no ""altsetting %d\n", cfgno, inums[i], j);}}return 0;
}代碼太生猛了,還是先說點理論墊墊底兒,其實前面也說到過的,使用GET_DESCRIPTOR請求時,得到的數據并不是雜亂無序的,而是有規可循的,一般來說,配置描述符后面跟的是第一個接口的接口描述符,接著是這個接口里第一個端點的端點描述符,如果有class-和vendor-specific描述符的話,會緊跟在對應的標準描述符后面,不管接口有多少端點有多少都是按照這個規律順序排列。當然有些廠商會特立獨行一些,非要先返回第二個接口然后再返回第一個接口,但配置描述符后面總歸先是接口描述符再是端點描述符。
5行,buffer里保存的就是GET_DESCRIPTOR請求獲得的那堆數據,要解析這些數據,不可避免的要對buffer指針進行操作,這里先將它備份一下。
17行,config是參數里傳遞過來的,是設備struct usb_device結構體里的struct usb_host_config結構體數組config中的一員。不出意外的話buffer的前USB_DT_CONFIG_SIZE個字節對應的就是配置描述符,那么這里的意思就很明顯了。然后做些檢驗,看看這USB_DT_CONFIG_SIZE字節的內容究竟是不是正如我們所期待的那樣是個配置描述符,如果不是,那buffer里的數據問題可就大了,沒什么利用價值了,還是返回吧,不必要再接著解析了。
28行,buffer的前USB_DT_CONFIG_SIZE個字節已經理清了,接下來該解析剩下的數據了,buffer需要緊跟形勢的發展,位置和長度都要做相應的修正。
31行,獲得這個配置所擁有的接口數目,不能簡單一賦值就完事兒了,得知道系統里對這個數目是有個USB_MAXINTERFACES這樣的限制的,如果數目比這個限制還大,就改為USB_MAXINTERFACES。
42~124行,這函數真是酷到家了,連里面一個循環都這么長這么酷,不過別看它cool,完成的事情卻很單一,就是統計記錄一下這個配置里每個接口所擁有的設置數目。提醒你一下,千萬別被寫代碼的哥們兒給迷惑了,這個循環里使用的是buffer2和size2, buffer和size的兩個替身,buffer和size就停在42行享受陽光海灘。
46行,這里遇到一個新的結構struct usb_descriptor_header,在include/uapi/linux/usb/ch9.h里定義
struct usb_descriptor_header {__u8 bLength;__u8 bDescriptorType;
} __attribute__ ((packed));這個結構比俺還單純,就包括了兩個成員,你研究一下所有的那些標準描述符,會興奮的發現它們的前兩個字節都是一樣的,一個表示描述符的長度,一個表示描述符的類型。那么為什么要專門搞這么一個結構?試想一下,有塊數據緩沖區,讓你判斷一下里面保存的是哪個描述符,或者是其它什么東西,你怎么做?你當然可以直接將它的前兩個字節內容讀出來,判斷判斷bDescriptorType,再判斷判斷bLength,不過這樣的代碼就好像你自己畫的一副抽象畫,太藝術化了,過個若干年自己都不知道啥意思,更別說別人了。53行做了個很好的示范,把buffer2指針轉化為struct usb_descriptor_header的結構體指針,然后就可以使用‘->’來取出bLength和bDescriptorType,這樣寫的人順心看的人舒心,你好我好大家好。
那么46行就表示如果GET_DESCRIPTOR請求返回的數據里除了包括一個配置描述符外,連兩個字節都沒有,那就說明這個配置在進行裸體行為藝術,能看不能用。
61行,如果這是個接口描述符就說明這個配置的某個接口擁有一個設置,是沒有什么所謂的設置描述符的,一個接口描述符就代表了存在一個設置,接口描述里的bInterfaceNumber會指出這個設置隸屬于哪個接口。那么這里除了是接口描述符還有可能是什么?還有可能是class-和vendor-specific描述符。
65行,既然覺得這是個接口描述符,就把這個指針轉化為struct usb_interface_descriptor結構體指針,你可別被C里的這種指針游戲給轉暈了,一個地址如果代碼不給它賦予什么意義,它除了表示一個地址外就什么都不是。同樣一個地址,上面轉化為struct usb_descriptor_header結構體指針和這里轉化為struct usb_interface_descriptor結構體指針,它就不再僅僅是一個地址,而是代表了不同的含義。
66行,仍然不忘保持革命斗爭警惕性。bDescriptorType等于USB_DT_INTERFACE并不說明它就一定是接口描述符了,它的bLength還必須要等于USB_DT_INTERFACE_SIZE。bLength和bDescriptorType一起才能決定一個描述符。
91~102這幾行是用來考驗咱們的耐心和勇氣的。首先要明白n、inums和nalts這幾個枯燥的東東是表示什么的,n記錄的是接口的數目,數組inums里的每一項都表示一個接口號,數組nalts里的每一項記錄的是每個接口擁有的設置數目,inums和nalts兩個數組里的元素是一一對應的,inums[0]就對應nalts[0],inums[1]就對應nalts[1]。其次還要謹記一個殘酷的事實,發送GET_DESCRIPTOR請求時,設備并不一定會按照接口1,接口2這樣的順序循規蹈矩的返回數據,雖說協議里是這么要求的,但都在江湖行走誰能沒點個性。
125行,buffer的最后邊兒可能會有些垃圾數據,為了去除這些洋垃圾,這里需要將size和配置描述符里的那個wTotalLength修正一下。
128行,經過上面那個超酷的循環之后,如果統計得到的接口數目和配置描述符里的bNumInterfaces不符,或者干脆就沒有發現配置里有什么接口,就警告一下。
137行,又一個for循環,目的是看看是不是遺漏了哪個接口號,比如說配置6個接口,為什么是6那,因為俺的幸運數字是6,呵呵,每個接口號都應該對應數組inums里的一項,如果在inums里面沒有發現這個接口號,比如2吧,那2這個接口號就神秘失蹤了,你找不到接口2。這個當然也屬于違章駕駛,需要警告一下,開票罰600,不開票罰200,你自己選。
148行,再一個for循環,struct usb_interface_caches做嘛用的早就說過了,USB_MAXALTSETTING的定義在config.c里
#define USB_MAXALTSETTING 128 /* Hard limit */一個接口最多可以有128個設置,足夠了。158行根據每個接口擁有的設置數目為對應的intf_cache數組項申請內存。
169行,配置描述符后面緊跟的不一定就是接口描述符,還可能是class-和vendor-specific描述符,如果有的話。不管有沒有,先把buffer的地址賦給extra,如果沒有擴展的描述符,則170行返回的i就等于0,extralen也就為0。
170行,調用find_next_descriptor()在buffer里尋找配置描述符后面跟著的第一個接口描述符。它也在config.c里定義,進去看看
static int find_next_descriptor(unsigned char *buffer, int size,int dt1, int dt2, int *num_skipped)
{struct usb_descriptor_header *h;int n = 0;unsigned char *buffer0 = buffer;/* Find the next descriptor of type dt1 or dt2 */while (size > 0) {h = (struct usb_descriptor_header *) buffer;if (h->bDescriptorType == dt1 || h->bDescriptorType == dt2)break;buffer += h->bLength;size -= h->bLength;++n;}/* Store the number of descriptors skipped and return the* number of bytes skipped */if (num_skipped)*num_skipped = n;return buffer - buffer0;
}這個函數需要傳遞兩個描述符類型的參數,11行已經清清楚楚的表明它是腳踩兩只船的,一個多情的種。它不是專一的去尋找一種描述符,而是去尋找兩種描述符,比如你指定dt1為USB_DT_INTERFACE,dt2為USB_DT_ENDPOINT時,只要能夠找到接口描述符或端點描述符中的一個,這個函數就返回。usb_parse_configuration函數的170行只需要尋找下一個接口描述符,所以dt1和dt2都設置為USB_DT_INTERFACE。
這個函數結束后,num_skipped里記錄的是搜索過程中忽略的dt1和dt2之外其它描述符的數目,返回值表示搜索結束時buffer的位置比搜索開始時前進的字節數。其它沒什么好講的。還是回到usb_parse_configuration函數。
176行,根據find_next_descriptor的結果修正buffer和size。你可能因為受過很多面試的摧殘,或者代碼里錯過很多次,對C里的按引用傳遞和按值傳遞已經爛熟于心,看到find_next_descriptor()那里傳遞的是buffer,一個指針,條件反射的覺得它里面對buffer的修改必定影響了外面的buffer,所以認為buffer已經指向了尋找到的接口描述符。但是你的這種如意算盤此時并不能如意,find_next_descriptor里修改的只是參數里buffer的值,并沒有修改它指向的內容,對于地址本身來說仍然只能算是按值傳遞,怎么修改都影響不到函數外邊,所以這里的176行仍然要對buffer的位置進行修正。
180行,事不過三,三個for循環之后輪到了一個while循環,如果size大于0,就說明配置描述符后面找到了一個接口描述符,根據這個接口描述符的長度,已經可以解析出一個完整的接口描述符了,但是仍然沒到樂觀的時候,這個接口描述符后面還會跟著一群端點描述符,再然后還會有其它的接口描述符。所以我們又迎來了另一個變態函數usb_parse_interface,先不管這個它長什么樣子,畢竟usb_parse_configuration()就快到頭兒了,暫時只需要知道它返回的時候,buffer的位置已經在下一個接口描述符那里了,還是那個理兒,對buffer地址本身來說是按值傳遞的,所以186行要對這個位置和長度進行下調整以適應新形勢。那么這個while循環的意思就很明顯了,對buffer一段一段的解析,直到再也找不到接口描述符了。
191行,最后這個for循環沒啥實質性的內容,就是找一下每個接口是不是有哪個設置編號給漏過去了,只要有耐心,你就能看得懂。咱們接下來還是看config.c里的那個usb_parse_interface()
static int usb_parse_interface(struct device *ddev, int cfgno,struct usb_host_config *config, unsigned char *buffer, int size,u8 inums[], u8 nalts[])
{unsigned char *buffer0 = buffer;struct usb_interface_descriptor *d;int inum, asnum;struct usb_interface_cache *intfc;struct usb_host_interface *alt;int i, n;int len, retval;int num_ep, num_ep_orig;d = (struct usb_interface_descriptor *) buffer;buffer += d->bLength;size -= d->bLength;if (d->bLength < USB_DT_INTERFACE_SIZE)goto skip_to_next_interface_descriptor;/* Which interface entry is this? */intfc = NULL;inum = d->bInterfaceNumber;for (i = 0; i < config->desc.bNumInterfaces; ++i) {if (inums[i] == inum) {intfc = config->intf_cache[i];break;}}if (!intfc || intfc->num_altsetting >= nalts[i])goto skip_to_next_interface_descriptor;/* Check for duplicate altsetting entries */asnum = d->bAlternateSetting;for ((i = 0, alt = &intfc->altsetting[0]);i < intfc->num_altsetting;(++i, ++alt)) {if (alt->desc.bAlternateSetting == asnum) {dev_warn(ddev, "Duplicate descriptor for config %d ""interface %d altsetting %d, skipping\n",cfgno, inum, asnum);goto skip_to_next_interface_descriptor;}}++intfc->num_altsetting;memcpy(&alt->desc, d, USB_DT_INTERFACE_SIZE);/* Skip over any Class Specific or Vendor Specific descriptors;* find the first endpoint or interface descriptor */alt->extra = buffer;i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,USB_DT_INTERFACE, &n);alt->extralen = i;if (n > 0)dev_dbg(ddev, "skipped %d descriptor%s after %s\n",n, plural(n), "interface");buffer += i;size -= i;/* Allocate space for the right(?) number of endpoints */num_ep = num_ep_orig = alt->desc.bNumEndpoints;alt->desc.bNumEndpoints = 0; /* Use as a counter */if (num_ep > USB_MAXENDPOINTS) {dev_warn(ddev, "too many endpoints for config %d interface %d ""altsetting %d: %d, using maximum allowed: %d\n",cfgno, inum, asnum, num_ep, USB_MAXENDPOINTS);num_ep = USB_MAXENDPOINTS;}if (num_ep > 0) {/* Can't allocate 0 bytes */len = sizeof(struct usb_host_endpoint) * num_ep;alt->endpoint = kzalloc(len, GFP_KERNEL);if (!alt->endpoint)return -ENOMEM;}/* Parse all the endpoint descriptors */n = 0;while (size > 0) {if (((struct usb_descriptor_header *) buffer)->bDescriptorType== USB_DT_INTERFACE)break;retval = usb_parse_endpoint(ddev, cfgno, inum, asnum, alt,num_ep, buffer, size);if (retval < 0)return retval;++n;buffer += retval;size -= retval;}if (n != num_ep_orig)dev_warn(ddev, "config %d interface %d altsetting %d has %d ""endpoint descriptor%s, different from the interface ""descriptor's value: %d\n",cfgno, inum, asnum, n, plural(n), num_ep_orig);return buffer - buffer0;skip_to_next_interface_descriptor:i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,USB_DT_INTERFACE, NULL);return buffer - buffer0 + i;
}14行,傳遞過來的buffer里開頭兒那部分只能是一個接口描述符,沒有什么可質疑的,所以這里將地址轉化為struct usb_interface_descriptor結構體指針,然后調整buffer的位置和size。
18行,只能是并不說明它就是,只有bLength等于USB_DT_INTERFACE_SIZE才說明開頭兒的USB_DT_INTERFACE_SIZE字節確實是個接口描述符。否則就沒必要再對這些數據進行什么處理了,直接跳到最后吧。先看看這個函數的最后都發生了什么,從新的位置開始再次調用find_next_descriptor()在buffer里尋找下一個接口描述符。
22行,因為數組inums并不一定是按照接口的順序來保存接口號的,inums[1]對應的可能是接口1也可能是接口0,所以這里要用for循環來尋找這個接口對應著inums里的哪一項,從而根據在數組里的位置獲得接口對應的struct usb_interface_cache結構體。usb_parse_configuration()已經告訴了我們,同一個接口在inums和intf_cache這兩個數組里的位置是一樣的。
34行,獲得這個接口描述符對應的設置編號,然后根據這個編號從接口的cache里搜索看這個設置是不是已經遇到過了,如果已經遇到過,就沒必要再對這個接口描述符進行處理,直接跳到最后,否則意味著發現了一個新的設置,要將它添加到cache里,并cache里的設置數目num_altsetting加1。要記住,設置是用struct usb_host_interface結構來表示的,一個接口描述符就對應一個設置。
51行,這段代碼好熟悉啊。現在buffer開頭兒的那個接口描述符已經理清了,要解析它后面的那些數據了。先把位置賦給這個剛解析出來的接口描述符的extra,然后再從這個位置開始去尋找下一個距離最近的一個接口描述符或端點描述符。如果這個接口描述符后面還跟有class-或vendor-specific描述符,則find_next_descriptor的返回值會大于0,buffer的位置和size也要進行相應的調整,來指向新找到的接口描述符或端點描述符。
這里find_next_descriptor的dt1參數和dt2參數就不再一樣了,因為如果一個接口只用到端點0,它的接口描述符后邊兒是不會跟有端點描述符的。
62行,獲得這個設置使用的端點數目,然后將相應接口描述符里的bNumEndpoints置0,為什么?你要往下看。USB_MAXENDPOINTS在include/linux/usb.h里定義
#define USB_MAXENDPOINTS 30為什么這個最大上限為30?前面也提到過,如果你不想頻繁的驀然回首那就簡單認為是協議里這么規定的好了。然后根據端點數為接口描述符里的endpoint數組申請內存。
81行,走到這里,buffer開頭兒的那個接口描述符已經理清了,而且也找到了下一個接口描述符或端點描述符的位置,該從這個新的位置開始解析了,于是又遇到了一個似曾相識的while循環。82行先判斷一下前面找到的是接口描述符還是端點描述符,如果是接口描述符就中斷這個while循環,返回與下一個接口描述符的距離。否則說明在buffer當前的位置上待著的是一個端點描述符,因此就要迎來另一個函數usb_parse_endpoint對面緊接著的數據進行解析。usb_parse_endpoint()返回的時候,buffer的位置已經在下一個端點描述符那里了,91行調整buffer的位置長度,這個while循環的也很明顯了,對buffer一段一段的解析,直到遇到下一個接口描述符或者已經走到buffer結尾。現在看看config.c里定義的usb_parse_endpoint函數
static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,int asnum, struct usb_host_interface *ifp, int num_ep,unsigned char *buffer, int size)
{unsigned char *buffer0 = buffer;struct usb_endpoint_descriptor *d;struct usb_host_endpoint *endpoint;int n, i, j, retval;d = (struct usb_endpoint_descriptor *) buffer;buffer += d->bLength;size -= d->bLength;if (d->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE)n = USB_DT_ENDPOINT_AUDIO_SIZE;else if (d->bLength >= USB_DT_ENDPOINT_SIZE)n = USB_DT_ENDPOINT_SIZE;else {dev_warn(ddev, "config %d interface %d altsetting %d has an ""invalid endpoint descriptor of length %d, skipping\n",cfgno, inum, asnum, d->bLength);goto skip_to_next_endpoint_or_interface_descriptor;}i = d->bEndpointAddress & ~USB_ENDPOINT_DIR_MASK;if (i >= 16 || i == 0) {dev_warn(ddev, "config %d interface %d altsetting %d has an ""invalid endpoint with address 0x%X, skipping\n",cfgno, inum, asnum, d->bEndpointAddress);goto skip_to_next_endpoint_or_interface_descriptor;}/* Only store as many endpoints as we have room for */if (ifp->desc.bNumEndpoints >= num_ep)goto skip_to_next_endpoint_or_interface_descriptor;endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints];++ifp->desc.bNumEndpoints;memcpy(&endpoint->desc, d, n);INIT_LIST_HEAD(&endpoint->urb_list);/* Fix up bInterval values outside the legal range. Use 32 ms if no* proper value can be guessed. */i = 0; /* i = min, j = max, n = default */j = 255;if (usb_endpoint_xfer_int(d)) {i = 1;switch (to_usb_device(ddev)->speed) {case USB_SPEED_SUPER:case USB_SPEED_HIGH:/* Many device manufacturers are using full-speed* bInterval values in high-speed interrupt endpoint* descriptors. Try to fix those and fall back to a* 32 ms default value otherwise. */n = fls(d->bInterval*8);if (n == 0)n = 9; /* 32 ms = 2^(9-1) uframes */j = 16;/** Adjust bInterval for quirked devices.* This quirk fixes bIntervals reported in* linear microframes.*/if (to_usb_device(ddev)->quirks &USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL) {n = clamp(fls(d->bInterval), i, j);i = j = n;}break;default: /* USB_SPEED_FULL or _LOW *//* For low-speed, 10 ms is the official minimum.* But some "overclocked" devices might want faster* polling so we'll allow it. */n = 32;break;}} else if (usb_endpoint_xfer_isoc(d)) {i = 1;j = 16;switch (to_usb_device(ddev)->speed) {case USB_SPEED_HIGH:n = 9; /* 32 ms = 2^(9-1) uframes */break;default: /* USB_SPEED_FULL */n = 6; /* 32 ms = 2^(6-1) frames */break;}}if (d->bInterval < i || d->bInterval > j) {dev_warn(ddev, "config %d interface %d altsetting %d ""endpoint 0x%X has an invalid bInterval %d, ""changing to %d\n",cfgno, inum, asnum,d->bEndpointAddress, d->bInterval, n);endpoint->desc.bInterval = n;}/* Some buggy low-speed devices have Bulk endpoints, which is* explicitly forbidden by the USB spec. In an attempt to make* them usable, we will try treating them as Interrupt endpoints.*/if (to_usb_device(ddev)->speed == USB_SPEED_LOW &&usb_endpoint_xfer_bulk(d)) {dev_warn(ddev, "config %d interface %d altsetting %d ""endpoint 0x%X is Bulk; changing to Interrupt\n",cfgno, inum, asnum, d->bEndpointAddress);endpoint->desc.bmAttributes = USB_ENDPOINT_XFER_INT;endpoint->desc.bInterval = 1;if (usb_endpoint_maxp(&endpoint->desc) > 8)endpoint->desc.wMaxPacketSize = cpu_to_le16(8);}/** Some buggy high speed devices have bulk endpoints using* maxpacket sizes other than 512. High speed HCDs may not* be able to handle that particular bug, so let's warn...*/if (to_usb_device(ddev)->speed == USB_SPEED_HIGH&& usb_endpoint_xfer_bulk(d)) {unsigned maxp;maxp = usb_endpoint_maxp(&endpoint->desc) & 0x07ff;if (maxp != 512)dev_warn(ddev, "config %d interface %d altsetting %d ""bulk endpoint 0x%X has invalid maxpacket %d\n",cfgno, inum, asnum, d->bEndpointAddress,maxp);}/* Parse a possible SuperSpeed endpoint companion descriptor */if (to_usb_device(ddev)->speed == USB_SPEED_SUPER)usb_parse_ss_endpoint_companion(ddev, cfgno,inum, asnum, endpoint, buffer, size);/* Skip over any Class Specific or Vendor Specific descriptors;* find the next endpoint or interface descriptor */endpoint->extra = buffer;i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,USB_DT_INTERFACE, &n);endpoint->extralen = i;retval = buffer - buffer0 + i;if (n > 0)dev_dbg(ddev, "skipped %d descriptor%s after %s\n",n, plural(n), "endpoint");return retval;skip_to_next_endpoint_or_interface_descriptor:i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,USB_DT_INTERFACE, NULL);return buffer - buffer0 + i;
}
一個一個變態的函數看過來,到現在都已經麻木了。但是遇到這種函數,你誰都不能罵,對linus,對Greg,對Alan,你有的只能是崇敬。
10行,buffer開頭兒只能是一個端點描述符,所以這里將地址轉化為struct usb_endpoint_descriptor結構體指針,然后調整buffer的位置和size。
14行,這里要明白的是端點描述符與配置描述符、接口描述符不一樣,它是可能有兩種大小的。
25行,得到端點號。這里的端點號不能為0,因為端點0是沒有描述符的,也不能大于16,為什么?同樣如果你不想驀然回首,就當成協議里規定的吧。
34行,要知道這個bNumEndpoints在usb_parse_interface()的63行是被賦為0了的。
37行,要知道這個endpoint數組在usb_parse_interface()的74行也是已經申請好內存了的。從這里你應該明白bNumEndpoints是被當成了一個計數器,發現一個端點描述符,它就加1,并把找到的端點描述符copy到設置的endpoint數組里。
41行,初始化端點的urb隊列urb_list。
43~98行,這堆代碼的目的是處理端點的bInterval,你要想不被它們給忽悠了,得明白幾個問題。第一個就是,i,j,n分別表示什么。45~90這么多行就為了給它們選擇一個合適的值,i和j限定了bInterval的一個范圍,bInterval如果在這里邊兒,它就是合法的,如果超出了這個范圍,它就是非法的,就要修理修理它,像97行做的那樣將n賦給它,那么n表示的就是bInterval的一個默認值。i和j的默認值分別為0和255,也就是說合法的范圍默認是0~255,對于批量端點和控制端點,bInterval對你我來說并沒有太大的用處,不過協議里還是規定了,這個范圍只能為0~255。對于中斷端點和等時端點,bInterval表演的舞臺就很大了,對這個范圍也要做一些調整。
第二個問題就是如何判斷端點是中斷的還是等時的。這涉及到兩個函數usb_endpoint_xfer_int和usb_endpoint_xfer_isoc,它們都在include/uapi/linux/usb/ch9.h里定義
static inline int usb_endpoint_xfer_int(const struct usb_endpoint_descriptor *epd)
{return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==USB_ENDPOINT_XFER_INT);
}
static inline int usb_endpoint_xfer_isoc(const struct usb_endpoint_descriptor *epd)
{return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==USB_ENDPOINT_XFER_ISOC);
}這倆函數so直白,一點都不含蓄,你根本不用去猜它們的心思就能明明白白了。一桌麻將還差兩個,另外兩個就是usb_endpoint_xfer_bulk和usb_endpoint_xfer_control,用來判斷批量端點和控制端點的。
第三個問題是to_usb_device。usb_parse_endpoint()的參數是struct device結構體,要獲得設備的速度就需要使用to_usb_device將它轉化為struct usb_device結構體,這是個include/linux/usb.h里定義的宏
#define to_usb_device(d) container_of(d, struct usb_device, dev)OK,接著繼續看usb_parse_endpoint的139行,現在你對這幾行玩的把戲應該很明白了。這里接著在buffer里尋找下一個端點描述符或者接口描述符。
經過usb_parse_configuration、usb_parse_interface和usb_parse_endpoint這三個函數一步一營的層層推進,通過GET_DESCRIPTOR請求所獲得那堆數據現在已經解析的清清白白。現在,設備的各個配置信息已經了然于胸,那接下來設備的那條生命線該怎么去走?它已經可以進入Configured狀態了么?事情沒這么簡單,光是獲得設備各個配置信息沒用,要進入Configured狀態,你還得有選擇有目的有步驟有計劃的去配置設備,那怎么去有選擇有目的有步驟有計劃?這好像就不是core能夠答復的問題了,畢竟它并不知道你希望你的設備采用哪種配置,只有你的設備的驅動才知道,所以接下來設備要做的是去在設備模型的茫茫人海中尋找屬于自己的驅動。
做為一個負責任的男人,絕對不能忘記的是設備的那個struct usb_device結構體在出生的時候就帶有usb_bus_type和usb_device_type這樣的胎記,Linux設備模型根據總線類型usb_bus_type將設備添加到usb總線的那條有名的設備鏈表里,然后去輪詢usb總線的另外一條有名的驅動鏈表,針對每個找到的驅動去調用usb總線的match函數,也就是usb_device_match(),去為設備尋找另一個匹配的半圓。match函數會根據設備的自身條件和類型usb_device_type安排設備走設備那條路,從而匹配到那個對所有usb_device_type類型的設備都來者不拒的花心大蘿卜,usb世界里唯一的那個usb設備驅動(不是usb接口驅動)struct device_driver結構體對象usb_generic_driver。
總結
以上是生活随笔為你收集整理的Linux那些事儿 之 戏说USB(28)设备的生命线(十一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux那些事儿 之 戏说USB(27
- 下一篇: Linux那些事儿 之 戏说USB(29