機械社區
標題: 嵌入式Linux USB驅動開發之教你一步步編寫USB驅動程序 [打印本頁]
作者: tainqing 時間: 2017-11-3 10:58
標題: 嵌入式Linux USB驅動開發之教你一步步編寫USB驅動程序
編寫與一個USB設備驅動程序的方法和其他總線驅動方式類似,驅動程序把驅動程序對象注冊到USB子系統中,稍后再使用制造商和設備標識來判斷是否安裝了硬件。當然,這些制造商和設備標識需要我們編寫進USB 驅動程序中。
USB 驅動程序依然遵循設備模型 —— 總線、設備、驅動。和I2C 總線設備驅動編寫一樣,所有的USB驅動程序都必須創建的主要結構體是 struct usb_driver,它們向USB 核心代碼描述了USB 驅動程序。但這是個外殼,只是實現設備和總線的掛接,具體的USB 設備是什么樣的,如何實現的,比如一個字符設備,我們還需填寫相應的文件操作接口 ,下面我們從外到里進行剖析,學習如何搭建這樣的一個USB驅動外殼框架:
一、注冊USB驅動程序
Linux的設備驅動,特別是這種hotplug的USB設備驅動,會被編譯成模塊,然后在需要時掛在到內核。所以USB驅動和注冊與正常的模塊注冊、卸載是一樣的,下面是USB驅動的注冊與卸載:
[cpp] view plain copy
1. static int __init usb_skel_init(void)
2. {
3. int result;
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_MODULE6 P+ v. _: `2 f0 ]' Q9 V
模塊的名字 skeleton
# \# ^, d' R+ p0 p! Fprobe函數 skel_probe
+ ~3 T, k' T# y" N; }, h7 Bdisconnect函數skel_disconnect
: U$ k0 ?& S- s M- N$ \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,以便確定應該調用哪一個驅動處理該設備。 2 K; p4 _& l6 e
我們下面所要做的就是對probe函數與disconnect函數的填充了,但是在對probe函數與disconnect函數填充之前,有必要先學習三個重要的數據結構,這在我們后面probe函數與disconnect函數中有很大的作用:
二、USB驅動程序中重要數據結構
1、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" ^6 J1 A0 s! Y' Y% [
一個接口interface
. ?7 A7 ^; k2 M用于并發訪問控制的semaphore(信號量) limit_sem% ?, L7 w0 O& l& v4 L1 {- ]
用于接收數據的緩沖bulk_in_buffer
% n. Z% O- u4 s0 v$ p! b用于接收數據的緩沖尺寸bulk_in_size, ?7 U ^; Q# K- M+ O# l# V
批量輸入端口地址bulk_in_endpointAddr" G4 q+ G! Q) {$ |, W q
批量輸出端口地址bulk_out_endpointAddr( M+ _9 ] B( T8 h
內核使用的引用計數器
從開發人員的角度看,每一個usb設備有若干個配置(configuration)組成,每個配置又可以有多個接口(interface)(我理解就是USB設備的一項功能),每個接口又有多個設置,而接口本身可能沒有端點或者多個端點(end point)
2、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定義的端點配置。但這些配置次序是不定的。
$ Y5 Q( w4 z% E. yb -- 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之后才有效。
3、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
如果端點是中斷類型,該值是端點的間隔設置,以毫秒為單位
三、探測和斷開函數分析
USB驅動程序指定了兩個USB核心在適當時間調用的函數。
1、探測函數
當一個設備被安裝而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.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. }
, X0 O# v1 N6 w9 u/ O9 H, B; _5 i' U
作者: 無敵老大 時間: 2017-11-3 14:04
好長的廣告
作者: 只有快樂 時間: 2017-11-3 19:42
把這么多代碼弄上去也是耐心
作者: xiaobing86203 時間: 2017-11-3 20:30
又是C語言,感覺像是火星文
作者: 未來第一站 時間: 2017-11-3 21:10
連注解都是英文。
作者: 864939134 時間: 2017-11-21 10:28
好
9 \& B* I% [' }( O, H! z& K
作者: sheng143 時間: 2017-11-23 01:35
支持一下吧~~~~~~~~~~~~~~~~~~
歡迎光臨 機械社區 (http://www.ytsybjq.com/) |
Powered by Discuz! X3.5 |