編寫與一個USB設備驅動程序的方法和其餘總線驅動方式相似,驅動程序把驅動程序對象註冊到USB子系統中,稍後再使用製造商和設備標識來判斷是否安裝了硬件。固然,這些製造商和設備標識須要咱們編寫進USB 驅動程序中。數組
USB 驅動程序依然遵循設備模型 —— 總線、設備、驅動。和I2C 總線設備驅動編寫同樣,全部的USB驅動程序都必須建立的主要結構體是 struct usb_driver,它們向USB 核心代碼描述了USB 驅動程序。但這是個外殼,只是實現設備和總線的掛接,具體的USB 設備是什麼樣的,如何實現的,好比一個字符設備,咱們還需填寫相應的文件操做接口 ,下面咱們從外到裏進行剖析,學習如何搭建這樣的一個USB驅動外殼框架:數據結構
1、註冊USB驅動程序併發
Linux的設備驅動,特別是這種hotplug的USB設備驅動,會被編譯成模塊,而後在須要時掛在到內核。因此USB驅動和註冊與正常的模塊註冊、卸載是同樣的,下面是USB驅動的註冊與卸載:框架
[cpp] view plain copy函數
1. static int __init usb_skel_init(void) 學習
2. { this
3. int result; orm
4. /* register this driver with the USB subsystem */ 對象
5. result = usb_register(&skel_driver); 接口
6. if (result)
7. err("usb_register failed. Error number %d", result);
8.
9. return result;
10. }
11.
12. static void __exit usb_skel_exit(void)
13. {
14. /* deregister this driver with the USB subsystem */
15. usb_deregister(&skel_driver);
16. }
17.
18. module_init (usb_skel_init);
19. module_exit (usb_skel_exit);
20. MODULE_LICENSE("GPL");
USB設備驅動的模塊加載函數通用的方法是在I2C設備驅動的模塊加載函數中使用usb_register(struct *usb_driver)函數添加usb_driver的工做,而在模塊卸載函數中利用usb_deregister(struct *usb_driver)作相反的工做。 對比I2C設備驅動中的 i2c_add_driver(&i2c_driver)與i2c_del_driver(&i2c_driver)。
struct usb_driver是USB設備驅動,咱們須要實現其成員函數:
[cpp] view plain copy
1. static struct usb_driver skel_driver = {
2. .owner = THIS_MODULE,
3. .name = "skeleton",
4. .id_table = skel_table,
5. .probe = skel_probe,
6. .disconnect = skel_disconnect,
7. };
從代碼看來,usb_driver須要初始化五個字段:
模塊的全部者 THIS_MODULE
模塊的名字 skeleton
probe函數 skel_probe
disconnect函數skel_disconnect
id_table
最重要的固然是probe函數與disconnect函數,這個在後面詳細介紹,先談一下id_table:
id_table 是struct usb_device_id 類型,包含了一列該驅動程序能夠支持的全部不一樣類型的USB設備。若是沒有設置該變量,USB驅動程序中的探測回調該函數將不會被調用。對比I2C中struct i2c_device_id *id_table,一個驅動程序能夠對應多個設備,i2c 示例:
[cpp] view plain copy
1. static const struct i2c_device_id mpu6050_id[] = {
2. { "mpu6050", 0},
3. {}
4. };
usb子系統經過設備的production ID和vendor ID的組合或者設備的class、subclass跟protocol的組合來識別設備,並調用相關的驅動程序做處理。咱們能夠看看這個id_table究竟是什麼東西:
[cpp] view plain copy
1. static struct usb_device_id skel_table [] = {
2. { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
3. { } /* Terminating entry */
4. };
5.
6. MODULE_DEVICE_TABLE (usb, skel_table);
MODULE_DEVICE_TABLE的第一個參數是設備的類型,若是是USB設備,那天然是usb。後面一個參數是設備表,這個設備表的最後一個元素是空的,用於標識結束。代碼定義了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是說,當有一個設備接到集線器時,usb子系統就會檢查這個設備的vendor ID和product ID,若是它們的值是0xfff0時,那麼子系統就會調用這個skeleton模塊做爲設備的驅動。
當USB設備接到USB控制器接口時,usb_core就檢測該設備的一些信息,例如生產廠商ID和產品的ID,或者是設備所屬的class、subclass跟protocol,以便肯定應該調用哪個驅動處理該設備。
咱們下面所要作的就是對probe函數與disconnect函數的填充了,可是在對probe函數與disconnect函數填充以前,有必要先學習三個重要的數據結構,這在咱們後面probe函數與disconnect函數中有很大的做用:
2、USB驅動程序中重要數據結構
一、usb-skeleton
usb-skeleton 是一個局部結構體,用於與端點進行通訊。下面先看一下Linux內核源碼中的一個usb-skeleton(就是usb驅動的骨架咯),其定義的設備結構體就叫作usb-skel:
[cpp] view plain copy
1. struct usb_skel {
2. struct usb_device *udev; /* the usb device for this device */
3. struct usb_interface *interface; /* the interface for this device */
4. struct semaphore limit_sem; /* limiting the number of writes in progress */
5. unsigned char *bulk_in_buffer; /* the buffer to receive data */
6. size_t bulk_in_size; /* the size of the receive buffer */
7. __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
8. __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
9. struct kref kref;
10. };
他擁有:
描述usb設備的結構體udev
一個接口interface
用於併發訪問控制的semaphore(信號量) limit_sem
用於接收數據的緩衝bulk_in_buffer
用於接收數據的緩衝尺寸bulk_in_size
批量輸入端口地址bulk_in_endpointAddr
批量輸出端口地址bulk_out_endpointAddr
內核使用的引用計數器
從開發人員的角度看,每個usb設備有若干個配置(configuration)組成,每一個配置又能夠有多個接口(interface)(我理解就是USB設備的一項功能),每一個接口又有多個設置,而接口自己可能沒有端點或者多個端點(end point)
二、USB 接口數據結構 struct usb_interface
[cpp] view plain copy
1. struct usb_interface
2. {
3. struct usb_host_interface *altsetting;
4. struct usb_host_interface *cur_altsetting;
5. unsigned num_altsetting;
6. int minor;
7. enum usb_interface_condition condition;
8. unsigned is_active:1;
9. unsigned needs_remote_wakeup:1;
10. struct device dev;
11. struct device *usb_dev;
12. int pm_usage_cnt;
13. };
在邏輯上,一個USB設備的功能劃分是經過接口來完成的。好比說一個USB揚聲器,可能會包括有兩個接口:一個用於鍵盤控制,另一個用於音頻流傳輸。而事實上,這種設備須要用到不一樣的兩個驅動程序來操做,一個控制鍵盤,一個控制音頻流。但也有例外,好比藍牙設備,要求有兩個接口,第一用於ACL跟EVENT的傳輸,另一個用於SCO鏈路,但二者經過一個驅動控制。在Linux上,接口使用struct usb_interface來描述,如下是該結構體中比較重要的字段:
a -- struct usb_host_interface *altsetting(注意不是usb_interface)
其實據我理解,他應該是每一個接口的設置,雖然名字上有點奇怪。該字段是一個設置的數組(一個接口能夠有多個設置),每一個usb_host_interface都包含一套由struct usb_host_endpoint定義的端點配置。但這些配置次序是不定的。
b -- struct usb_host_interface *cur_altsetting
當前活動的設置,指向altsetting數組中的一個
struct usb_host_interface數據結構:
[cpp] view plain copy
1. struct usb_host_interface
2. {
3. struct usb_interface_descriptor desc;//usb描述符,主要有四種usb描述符,設備描述符,配置描述符,接口描述符和端點描述符,協議裏規定一個usb設備是必須支持這四大描述符的信盈達嵌入式要領吧五六零五四五吧。
4. //usb描述符放在usb設備的eeprom裏邊
5. /* array of desc.bNumEndpoint endpoints associated with this
6. * interface setting. these will be in no particular order.
7. */
8. struct usb_host_endpoint *endpoint;//這個設置所使用的端點
9.
10. char *string; /* iInterface string, if present */
11. unsigned char *extra; /* Extra descriptors */關於額外描述符
12. int extralen;
13. };
c -- unsigned num_altstting
可選設置的數量,即altsetting所指數組的元素個數
d -- int minor
當捆綁到該接口的USB驅動程序使用USB主設備號時,USB core分配的次設備號。僅在成功調用usb_register_dev以後纔有效。
三、USB 端點 struct usb_host_endpoint
Linux中用struct usb_host_endpoint 來描述USB端點
[cpp] view plain copy
1. struct usb_host_endpoint
2. {
3. struct usb_endpoint_descriptor desc;
4. struct list_head urb_list;//端點要處理的urb隊列.urb是usb通訊的主角,設備中的每一個端點均可以處理一個urb隊列.要想和你的usb通訊,就得建立一個urb,而且爲它賦好值,
5. //交給我們的usb core,它會找到合適的host controller,從而進行具體的數據傳輸
6. void *hcpriv;//這是提供給HCD(host controller driver)用的
7. struct ep_device *ep_dev; /* For sysfs info */
8.
9. unsigned char *extra; /* Extra descriptors */
10. int extralen;
11. };
每一個usb_host_endpoint中包含一個struct usb_endpoint_descriptor結構體,當中包含該端點的信息以及設備自定義的各類信息,這些信息包括:
a -- bEndpointAddress(b for byte)
8位端點地址,其地址還隱藏了端點方向的信息(以前說過,端點是單向的),能夠用掩碼USB_DIR_OUT和USB_DIR_IN來肯定。
b -- bmAttributes
端點的類型,結合USB_ENDPOINT_XFERTYPE_MASK能夠肯定端點是USB_ENDPOINT_XFER_ISOC(等時)、USB_ENDPOINT_XFER_BULK(批量)仍是USB_ENDPOINT_XFER_INT(中斷)。
c -- wMaxPacketSize
端點一次處理的最大字節數。發送的BULK包能夠大於這個數值,但會被分割傳送。
d -- bInterval
若是端點是中斷類型,該值是端點的間隔設置,以毫秒爲單位
3、探測和斷開函數分析
USB驅動程序指定了兩個USB核心在適當時間調用的函數。
一、探測函數
當一個設備被安裝而USB核心認爲該驅動程序應該處理時,探測函數被調用;
探測函數應該檢查傳遞給他的設備信息,肯定驅動程序是否真的適合該設備。當驅動程序由於某種緣由不該控制設備時,斷開函數被調用,它能夠作一些清潔的工做。
系統會傳遞給探測函數的信息是什麼呢?一個usb_interface * 跟一個struct usb_device_id *做爲參數。他們分別是該USB設備的接口描述(通常會是該設備的第0號接口,該接口的默認設置也是第0號設置)跟它的設備ID描述(包括Vendor ID、Production ID等)。
USB驅動程序應該初始化任何可能用於控制USB設備的局部結構體,它還應該把所需的任何設備相關信息保存到局部結構體中。例如,USB驅動程序一般須要探測設備對的端點地址和緩衝區大小,由於須要他們才能和端點通訊。
下面具體分析探測函數作了哪些事情:
a -- 探測設備的端點地址、緩衝區大小,初始化任何可能用於控制USB設備的數據結構
下面是一個實例代碼,他們探測批量類型的IN和OUT端點,把相關信息保存到一個局部設備結構體中:
[cpp] view plain copy
1. /* set up the endpoint information */
2. /* use only the first bulk-in and bulk-out endpoints */
3. iface_desc = interface->cur_altsetting;
4. for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
5. endpoint = &iface_desc->endpoint[i].desc;
6.
7. if ( !dev->bulk_in_endpointAddr &&
8. ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&
9. ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {
10. /* we found a bulk in endpoint */
11. buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
12. dev->bulk_in_size = buffer_size;
13. dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
14. dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
15. if (!dev->bulk_in_buffer) {
16. err("Could not allocate bulk_in_buffer");
17. goto error;
18. }
19. }
20.
21. if (!dev->bulk_out_endpointAddr &&
22. ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&
23. ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {
24. /* we found a bulk out endpoint */
25. dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
26. }
27. }
28.
29. if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
30. err("Could not find both bulk-in and bulk-out endpoints");
31. goto error;
32. }