《Linux那些事兒之我是USB》我是U盤(19)冬天來了,春天還會遠嗎?(五)

看完了get_transport()繼續看get_protocol()函數和get_pipes()函數。仍然是來自drivers/usb/storage/usb.c中:

672 static int get_protocol(struct us_data *us)

673 {

674   switch (us->subclass) {

675    caseUS_SC_RBC:

676        us->protocol_name = "Reduced Block Commands (RBC)";

677        us->proto_handler = usb_stor_transparent_scsi_command;

678         break;

679

680   case US_SC_8020:

681         us->protocol_name = "8020i";

682         us->proto_handler = usb_stor_ATAPI_command;

683         us->max_lun = 0;

684         break;

685

686    caseUS_SC_QIC:

687         us->protocol_name = "QIC-157";

688       us->proto_handler = usb_stor_qic157_command;

689        us->max_lun = 0;

690         break;

691

692    caseUS_SC_8070:

693       us->protocol_name = "8070i";

694        us->proto_handler = usb_stor_ATAPI_command;

695      us->max_lun= 0;

696        break;

697

698    caseUS_SC_SCSI:

699       us->protocol_name = "Transparent SCSI";

700         us->proto_handler = usb_stor_transparent_scsi_command;

701         break;

702

703    caseUS_SC_UFI:

704        us->protocol_name = "Uniform Floppy Interface (UFI)";

705         us->proto_handler = usb_stor_ufi_command;

706        break;

707

708 #ifdef CONFIG_USB_STORAGE_ISD200

709   case US_SC_ISD200:

710        us->protocol_name = "ISD200 ATA/ATAPI";

711         us->proto_handler = isd200_ata_command;

712        break;

713 #endif

714

715   default:

716        return -EIO;

717    }

718     US_DEBUGP("Protocol:%s\n", us->protocol_name);

719    return 0;

720 }

這段代碼非常淺顯易懂。根據us->subclass來判斷。對於U盤來說,spec裏面規定了,它的SubClass是US_SC_SCSI,所以這裏就是兩句賦值語句:一個是令us的protocol_name爲“Transparent SCSI”,另一個是令us的proto_handler爲usb_stor_transparent_scsi_command,後者又是一個函數指針。

然後是get_pipes(),來自drivers/usb/storage/usb.c:

723 static int get_pipes(struct us_data *us)

724 {

725    structusb_host_interface *altsetting =

726                us->pusb_intf->cur_altsetting;

727    int i;

728   struct usb_endpoint_descriptor*ep;

729   struct usb_endpoint_descriptor*ep_in = NULL;

730   struct usb_endpoint_descriptor*ep_out = NULL;

731    structusb_endpoint_descriptor *ep_int = NULL;

732

733    /*

734    * Find the first endpoint of each type we need.

735     * We are expecting a minimum of 2endpoints - in and out (bulk).

736     * An optional interrupt-in is OK(necessary for CBI protocol).

737    * We will ignore any others.

738    */

739  for (i = 0; i <altsetting->desc.bNumEndpoints; i++) {

740        ep = &altsetting->endpoint[i].desc;

741

742         if (usb_endpoint_xfer_bulk(ep)) {

743             if (usb_endpoint_dir_in(ep)) {

744                 if (!ep_in)

745                    ep_in = ep;

746             } else {

747                  if (!ep_out)

748                      ep_out = ep;

749              }

750       }

751

752        else if (usb_endpoint_is_int_in(ep)) {

753           if (!ep_int)

754                ep_int = ep;

755        }

756   }

757

758   if (!ep_in || !ep_out ||(us->protocol == US_PR_CBI && !ep_int)) {

759        US_DEBUGP("Endpoint sanity check failed! Rejectingdev.\n");

760         return -EIO;

761    }

762

763   /* Calculate and store the pipevalues */

764   us->send_ctrl_pipe =usb_sndctrlpipe(us->pusb_dev, 0);

765    us->recv_ctrl_pipe= usb_rcvctrlpipe(us->pusb_dev, 0);

766   us->send_bulk_pipe =usb_sndbulkpipe(us->pusb_dev,

767                ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);

768   us->recv_bulk_pipe =usb_rcvbulkpipe(us->pusb_dev,

769                ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);

770   if (ep_int) {

771       us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev,

772               ep_int->bEndpointAddress& USB_ENDPOINT_NUMBER_MASK);

773         us->ep_bInterval = ep_int->bInterval;

774  }

775   return 0;

776 }

這個函數應該可以說是比較複雜的一個。請容我慢慢給您道來。

726行,us->pusb_intf,可還記得,在associate_dev中賦的值,如不記得請回過去查一下。沒錯,us->pusb_intf就是我們故事中最開始一再提到的interface(指針)。而它的成員cur_altsetting,就是當前的設置。在講associate_dev時也已經遇到過,它是一個structusb_host_interface的結構體指針。現在這裏用另一個指針臨時代替一下,altsetting。接下來會用到它的成員:desc和endpoint。

回顧structusb_host_interface,可以看到它兩個成員:struct usb_interface_descriptor desc和struct usb_host_endpoint *endpoint。其中,desc不用多說,正是這個接口的接口描述符,而endpoint這個指針記錄的是幾個端點,它們以數組的形式被存儲,而endpoint指向數組頭。這些都是在USB Core枚舉時就設置好了,我們無需操任何心,只需拿來使用就是了。這裏給出struct usb_host_endpoint的定義,來自include/linux/usb.h:

59 struct usb_host_endpoint {

60    structusb_endpoint_descriptor  desc;

61     structlist_head               urb_list;

62     void                           *hcpriv;

63   struct ep_device               *ep_dev;        /* For sysfs info */

64

65   unsigned char *extra;   /* Extra descriptors */

66     intextralen;

67 };

接着定義了幾個struct usb_endpoint_descriptor的結構體指針。顧名思義,這就是對應端點的描述符。其定義來自於include/linux/usb/ch9.h:

312 /* USB_DT_ENDPOINT: Endpoint descriptor */

313 struct usb_endpoint_descriptor {

314  __u8 bLength;

315    __u8  bDescriptorType;

316

317     __u8  bEndpointAddress;

318     __u8  bmAttributes;

319    __le16wMaxPacketSize;

320    __u8  bInterval;

321

322   /* NOTE:  these two are _only_ in audio endpoints. */

323   /* use USB_DT_ENDPOINT*_SIZE inbLength, not sizeof. */

324    __u8  bRefresh;

325   __u8  bSynchAddress;

326 } __attribute__ ((packed));

至此,四大描述符一一亮相,在繼續講之前,我們先來小結一下:究竟什麼是描述符?每個USB設備都有四大描述符,這裏拿U盤來舉例。聽說過Flash Memory嗎?Intel、三星,這些都是做Flash Memory的,當然通常人們就簡稱Flash。Flash在U盤中扮演什麼角色?Flash是用來給用戶存儲數據的,而U盤中的Flash就相當於PC中的硬盤,存儲數據主要就靠它。那麼除了給用戶存儲數據以外,設備自己還需要存儲一些設備本身固有的信息,比如設備姓甚名誰?誰生產的?還有一些信息,比如該設備有幾種配置,有幾個接口,等許多特性。

這個世界上,除了Flashmemory外,還有一個東西叫做EEPROM,也是用來存儲的,它是EEPROM的前身,而Flash是基於EEPROM技術發展起來的一種低成本的ROM產品。

EEPROM和Flash相同,都是需要電擦除,但EEPROM可以按字節擦除,而不像Flash那樣一次擦除一個block,這樣在只需改動很少數據的情況下使用EEPROM就很方便了。因此EEPROM的這一特性使它的電路要複雜一些,並且集成度不高,一個bit需要兩個管子:一個用來儲存電荷信息,一個充當開關。所以EEPROM的成本高,Flash簡化了一些電路,成本降低了很多。

因此,在USB設備中通常會有一個Flash芯片,以及一個EEPROM芯片。Flash用於爲客戶存儲數據,而EEPROM用來存儲設備本身的信息。這就是爲什麼當Intel把Flash芯片賣給摩托羅拉之後,客戶看到的手機廠商是摩托羅拉而不是Intel,因爲我們雖然在做Flash時把我們的廠商ID寫在了Flash上,但是對於最終的成品對外來看,提供的信息都是來自EEPROM,所以當你把USB設備通過USB接口連到電腦上去,電腦上如果能顯示廠家,那麼一定是最終的包裝廠家,而不可能是那塊Flash的廠家。而EEPROM裏邊寫什麼?按什麼格式寫?這正是USB spec規定的,這種格式就是一個個描述符的格式。設備描述符、配置描述符、接口描述符、端點描述符,以及其他一些某一些類別的設備特有的描述符,比如Hub描述符都是很規範的,尤其對於這四種標準的描述符,每個USB設備都是規規矩矩地支持的。所以USB Core層可以用一段相同的代碼把它們都給讀出來,而不用再讓我們設備驅動程序去自己讀了,這就是權力集中的好處,反正大家都要做的事情,乾脆讓上頭一起做了好了。

739行到756行,循環,bNumEndpoints就是接口描述符中的成員,表示這個接口有多少個端點,不過這其中不包括0號端點,0號端點是任何一個USB設備都必須是提供的,這個端點專門用於進行控制傳輸,即它是一個控制端點。正因爲如此,所以即使一個設備沒有進行任何設置,USB主機也可以開始跟它進行一些通信,因爲即使不知道其他端點,但至少知道它一定有一個0號端點,或者說一個控制端點。

此外,通常USB Mass Storage會有兩個批量端點,用於批量傳輸,即所謂的批量傳輸。我們日常的讀寫U盤裏的文件,就是屬於批量傳輸。所以毫無疑問,對於Mass Storage設備來說,批量傳輸是它的主要工作方式,道理很簡單,我們使用U盤就是用來讀寫文件的。和這些描述符打交道無非就是爲了幫助我們最終實現讀寫文件的工作,這纔是每一個USB存儲設備真正的使命。

於是我們來看這段循環到底在幹什麼,altsetting->endpoint[i].desc,對照struct usb_host_endpoint這個結構體的定義可知,desc正是一個struct usb_endpoint_descriptor的變量。剛剛定義了四個這種結構體的指針,ep,ep_in,ep_out和ep_int,很簡單,就是用來記錄端點描述符的,ep_in用於Bulk-IN,ep_out用於Bulk-OUT,ep_int用於記錄中斷端點(如果有的話)。而ep,只是一個臨時指針。

我們看structusb_endpoint_descriptor,在它的成員中,bmAttributes表示屬性,總共8位,其中bit1和bit0共同稱爲Transfer Type,即傳輸類型,00表示控制,01表示等時,10表示批量,11表示中斷。因此通過比較這些成員,就可以判斷該端點的傳輸類型,這個函數usb_endpoint_xfer_bulk()定義於include/linux/usb.h中:

571 static inline int usb_endpoint_xfer_bulk(conststruct usb_endpoint_ descriptor *epd)

572 {

573    return((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==

574                USB_ENDPOINT_XFER_BULK);

575 }

而USB_ENDPOINT_XFERTYPE_MASK這個宏定義於include/linux/usb/ch9.h中:

338 #define USB_ENDPOINT_XFERTYPE_MASK      0x03    /* in bmAttributes */

339 #define USB_ENDPOINT_XFER_CONTROL       0

340 #define USB_ENDPOINT_XFER_ISOC      1

341 #define USB_ENDPOINT_XFER_BULK      2

342 #define USB_ENDPOINT_XFER_INT       3

從上面的註釋可以看出,742行就是判斷這個端點描述符描述的是不是一個批量端點。如果是,繼續比較。我們先看bEndpointAddress,這個struct usb_endpoint_descriptor中的另一個成員,也是8個bit,或者說1個Byte,其中bit7表示這個端點的方向,0表示OUT,1表示IN。OUT與IN是對主機而言,OUT就是從主機到設備,IN就是從設備到主機。因此比較這個成員就可以判斷端點的方向,幹這件事的函數usb_endpoint_dir_in()也來自include/linux/usb.h:

549 static inline int usb_endpoint_dir_in(conststruct usb_endpoint_ descriptor *epd)

550 {

551 return ((epd->bEndpointAddress&USB_ENDPOINT_DIR_MASK)==USB_DIR_IN);

552 }

而宏USB_DIR_IN和USB_ENDPOINT_DIR_MASK仍然來自include/linux/usb_ch9.h:

48 #define USB_DIR_OUT                    0              /* to device */

49 #define USB_DIR_IN                     0x80           /* to host */

336 #define USB_ENDPOINT_DIR_MASK           0x80

所以這裏意思很明顯,就是爲了讓ep_in和ep_out指向該指的endpoint descriptor。

而接下來752行,usb_endpoint_is_int_in()來自include/linux/usb.h:

646 static inline int usb_endpoint_is_int_in(conststruct usb_endpoint_ descriptor *epd)

647 {

648   return (usb_endpoint_xfer_int(epd)&& usb_endpoint_dir_in(epd));

649 }

有了前面兩個函數作爲基礎,這個函數就不用再說了。總之,752行中else if的作用就是如果這個端點是中斷端點,那麼就讓ep_int指向它。我們說了,每一類USB設備其上面有多少端點有何種端點都是不確定的,都得遵守該類設備的規範,而USB Mass Storage的規範中規定了,一個USB Mass Storage設備至少應該有兩個批量端點,控制端點顯然是必需的。毋庸置疑,另外,可能會有一箇中斷端點,這種設備支持CBI協議,即Control/Bulk/Interrupt協議。我們也說過了,U盤遵守的是Bulk-only協議,它不需要有中斷端點。

758行到761行這段代碼,沒什麼好說的,就是判斷ep_in或者ep_out是否存在,或者是遵守CBI協議但是沒有中斷端點,這些都是不合理的,當然就會出錯!

剩下一小段代碼,我們下節再看。需要說的是,這個函數結束之後我們將開始最精彩的部分,它就是偉大的usb_stor_acquire_resources()。黑暗即將過去,黎明已經帶我們上路。讓我們共同期待吧。同時,我們小結一下,此前我們花了很大的篇幅來爲usb_stor_acquire_resources()做鋪墊,那我們來回顧一下,它究竟做了哪些事情?

首先我們從storage_probe出發,一共調用了五個函數,它們是assocaite_dev,get_device_info,get_transport,get_protocol,get_pipes。我們這樣做的目的是什麼?很簡單,就是爲了建立一個數據結構,它就是傳說中的struct us_data,它的名字叫做us。我們把它建立了起來,爲它申請了內存,爲它的各個元素賦了值,目的就是爲了讓以後我們可以很好地利用它。

這五個函數都不難,你一定也會寫。難的是如何去定義struct us_data,別忘了這個數據結構是寫代碼的同志們專門爲usb-storage模塊而設計的。所謂編程,無非就是數據結構加上算法。沒錯,這個定義於drivers/usb/storage/usb.h中的數據結構長達60行,關於她的成員,我們還有很多沒遇到,不過別急,後面會遇到的。好了,雖然get_pipes還有一小段沒講,但是我們可以提前和這5個函數說再見了,席慕蓉說過,若不得不分離,也要好好地說聲再見,也要在心裏存着一份感謝,謝謝她給你一份記憶。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章