編寫與一個USB設(shè)備驅(qū)動程序的方法和其他總線驅(qū)動方式類似,驅(qū)動程序把驅(qū)動程序?qū)ο笞缘?/font>USB子系統(tǒng)中,稍后再使用制造商和設(shè)備標(biāo)識來判斷是否安裝了硬件。當(dāng)然,這些制造商和設(shè)備標(biāo)識需要我們編寫進(jìn)USB 驅(qū)動程序中。 USB 驅(qū)動程序依然遵循設(shè)備模型 —— 總線、設(shè)備、驅(qū)動。和I2C 總線設(shè)備驅(qū)動編寫一樣,所有的USB驅(qū)動程序都必須創(chuàng)建的主要結(jié)構(gòu)體是 struct usb_driver,它們向USB 核心代碼描述了USB 驅(qū)動程序。但這是個外殼,只是實現(xiàn)設(shè)備和總線的掛接,具體的USB 設(shè)備是什么樣的,如何實現(xiàn)的,比如一個字符設(shè)備,我們還需填寫相應(yīng)的文件操作接口 ,下面我們從外到里進(jìn)行剖析,學(xué)習(xí)如何搭建這樣的一個USB驅(qū)動外殼框架: 一、注冊USB驅(qū)動程序 Linux的設(shè)備驅(qū)動,特別是這種hotplug的USB設(shè)備驅(qū)動,會被編譯成模塊,然后在需要時掛在到內(nèi)核。所以USB驅(qū)動和注冊與正常的模塊注冊、卸載是一樣的,下面是USB驅(qū)動的注冊與卸載: [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設(shè)備驅(qū)動的模塊加載函數(shù)通用的方法是在I2C設(shè)備驅(qū)動的模塊加載函數(shù)中使用usb_register(struct *usb_driver)函數(shù)添加usb_driver的工作,而在模塊卸載函數(shù)中利用usb_deregister(struct *usb_driver)做相反的工作。 對比I2C設(shè)備驅(qū)動中的 i2c_add_driver(&i2c_driver)與i2c_del_driver(&i2c_driver)。 struct usb_driver是USB設(shè)備驅(qū)動,我們需要實現(xiàn)其成員函數(shù): [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
& M! `0 L3 B& i0 f+ D: M$ x模塊的名字 skeleton
, l/ g" S( z1 e- Qprobe函數(shù) skel_probe: n4 h. [( l) y* i$ t
disconnect函數(shù)skel_disconnect) y6 j) m+ p9 Z
id_table 最重要的當(dāng)然是probe函數(shù)與disconnect函數(shù),這個在后面詳細(xì)介紹,先談一下id_table: id_table 是struct usb_device_id 類型,包含了一列該驅(qū)動程序可以支持的所有不同類型的USB設(shè)備。如果沒有設(shè)置該變量,USB驅(qū)動程序中的探測回調(diào)該函數(shù)將不會被調(diào)用。對比I2C中struct i2c_device_id *id_table,一個驅(qū)動程序可以對應(yīng)多個設(shè)備,i2c 示例: [cpp] view plain copy 1. static const struct i2c_device_id mpu6050_id[] = { 2. { "mpu6050", 0}, 3. {} 4. }; usb子系統(tǒng)通過設(shè)備的production ID和vendor ID的組合或者設(shè)備的class、subclass跟protocol的組合來識別設(shè)備,并調(diào)用相關(guān)的驅(qū)動程序作處理。我們可以看看這個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的第一個參數(shù)是設(shè)備的類型,如果是USB設(shè)備,那自然是usb。后面一個參數(shù)是設(shè)備表,這個設(shè)備表的最后一個元素是空的,用于標(biāo)識結(jié)束。代碼定義了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是說,當(dāng)有一個設(shè)備接到集線器時,usb子系統(tǒng)就會檢查這個設(shè)備的vendor ID和product ID,如果它們的值是0xfff0時,那么子系統(tǒng)就會調(diào)用這個skeleton模塊作為設(shè)備的驅(qū)動。 當(dāng)USB設(shè)備接到USB控制器接口時,usb_core就檢測該設(shè)備的一些信息,例如生產(chǎn)廠商ID和產(chǎn)品的ID,或者是設(shè)備所屬的class、subclass跟protocol,以便確定應(yīng)該調(diào)用哪一個驅(qū)動處理該設(shè)備。
9 Z' j( ^" l+ S) p" q- S e 我們下面所要做的就是對probe函數(shù)與disconnect函數(shù)的填充了,但是在對probe函數(shù)與disconnect函數(shù)填充之前,有必要先學(xué)習(xí)三個重要的數(shù)據(jù)結(jié)構(gòu),這在我們后面probe函數(shù)與disconnect函數(shù)中有很大的作用: 二、USB驅(qū)動程序中重要數(shù)據(jù)結(jié)構(gòu) 1、usb-skeleton usb-skeleton 是一個局部結(jié)構(gòu)體,用于與端點進(jìn)行通信。下面先看一下Linux內(nèi)核源碼中的一個usb-skeleton(就是usb驅(qū)動的骨架咯),其定義的設(shè)備結(jié)構(gòu)體就叫做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設(shè)備的結(jié)構(gòu)體udev
- |7 ]% h- g8 A9 y一個接口interface2 F* y5 [- t. ?. m9 P( }
用于并發(fā)訪問控制的semaphore(信號量) limit_sem
! N3 a5 B- j0 G( t% ^用于接收數(shù)據(jù)的緩沖bulk_in_buffer; F/ ?( S( _& L& z, N+ z
用于接收數(shù)據(jù)的緩沖尺寸bulk_in_size
( L- G) W/ n" J1 ?* y批量輸入端口地址bulk_in_endpointAddr
4 e/ U5 R/ A6 m" W' L批量輸出端口地址bulk_out_endpointAddr
/ |5 O, {7 B1 M: q9 }# h4 {內(nèi)核使用的引用計數(shù)器 從開發(fā)人員的角度看,每一個usb設(shè)備有若干個配置(configuration)組成,每個配置又可以有多個接口(interface)(我理解就是USB設(shè)備的一項功能),每個接口又有多個設(shè)置,而接口本身可能沒有端點或者多個端點(end point) 2、USB 接口數(shù)據(jù)結(jié)構(gòu) 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設(shè)備的功能劃分是通過接口來完成的。比如說一個USB揚(yáng)聲器,可能會包括有兩個接口:一個用于鍵盤控制,另外一個用于音頻流傳輸。而事實上,這種設(shè)備需要用到不同的兩個驅(qū)動程序來操作,一個控制鍵盤,一個控制音頻流。但也有例外,比如藍(lán)牙設(shè)備,要求有兩個接口,第一用于ACL跟EVENT的傳輸,另外一個用于SCO鏈路,但兩者通過一個驅(qū)動控制。在Linux上,接口使用struct usb_interface來描述,以下是該結(jié)構(gòu)體中比較重要的字段: a -- struct usb_host_interface *altsetting(注意不是usb_interface) 其實據(jù)我理解,他應(yīng)該是每個接口的設(shè)置,雖然名字上有點奇怪。該字段是一個設(shè)置的數(shù)組(一個接口可以有多個設(shè)置),每個usb_host_interface都包含一套由struct usb_host_endpoint定義的端點配置。但這些配置次序是不定的。 . ^- y X; s6 w" ]8 E
b -- struct usb_host_interface *cur_altsetting 當(dāng)前活動的設(shè)置,指向altsetting數(shù)組中的一個 struct usb_host_interface數(shù)據(jù)結(jié)構(gòu): [cpp] view plain copy 1. struct usb_host_interface 2. { 3. struct usb_interface_descriptor desc;//usb描述符,主要有四種usb描述符,設(shè)備描述符,配置描述符,接口描述符和端點描述符,協(xié)議里規(guī)定一個usb設(shè)備是必須支持這四大描述符的信盈達(dá)嵌入式要領(lǐng)吧五六零五四五吧。 4. //usb描述符放在usb設(shè)備的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;//這個設(shè)置所使用的端點 9. 10. char *string; /* iInterface string, if present */ 11. unsigned char *extra; /* Extra descriptors */關(guān)于額外描述符 12. int extralen; 13. }; c -- unsigned num_altstting 可選設(shè)置的數(shù)量,即altsetting所指數(shù)組的元素個數(shù) d -- int minor 當(dāng)捆綁到該接口的USB驅(qū)動程序使用USB主設(shè)備號時,USB core分配的次設(shè)備號。僅在成功調(diào)用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通信的主角,設(shè)備中的每個端點都可以處理一個urb隊列.要想和你的usb通信,就得創(chuàng)建一個urb,并且為它賦好值, 5. //交給咱們的usb core,它會找到合適的host controller,從而進(jìn)行具體的數(shù)據(jù)傳輸 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結(jié)構(gòu)體,當(dāng)中包含該端點的信息以及設(shè)備自定義的各種信息,這些信息包括: a -- bEndpointAddress(b for byte) 8位端點地址,其地址還隱藏了端點方向的信息(之前說過,端點是單向的),可以用掩碼USB_DIR_OUT和USB_DIR_IN來確定。 b -- bmAttributes 端點的類型,結(jié)合USB_ENDPOINT_XFERTYPE_MASK可以確定端點是USB_ENDPOINT_XFER_ISOC(等時)、USB_ENDPOINT_XFER_BULK(批量)還是USB_ENDPOINT_XFER_INT(中斷)。 c -- wMaxPacketSize 端點一次處理的最大字節(jié)數(shù)。發(fā)送的BULK包可以大于這個數(shù)值,但會被分割傳送。 d -- bInterval 如果端點是中斷類型,該值是端點的間隔設(shè)置,以毫秒為單位 三、探測和斷開函數(shù)分析 USB驅(qū)動程序指定了兩個USB核心在適當(dāng)時間調(diào)用的函數(shù)。 1、探測函數(shù) 當(dāng)一個設(shè)備被安裝而USB核心認(rèn)為該驅(qū)動程序應(yīng)該處理時,探測函數(shù)被調(diào)用; 探測函數(shù)應(yīng)該檢查傳遞給他的設(shè)備信息,確定驅(qū)動程序是否真的適合該設(shè)備。當(dāng)驅(qū)動程序因為某種原因不應(yīng)控制設(shè)備時,斷開函數(shù)被調(diào)用,它可以做一些清潔的工作。 系統(tǒng)會傳遞給探測函數(shù)的信息是什么呢?一個usb_interface * 跟一個struct usb_device_id *作為參數(shù)。他們分別是該USB設(shè)備的接口描述(一般會是該設(shè)備的第0號接口,該接口的默認(rèn)設(shè)置也是第0號設(shè)置)跟它的設(shè)備ID描述(包括Vendor ID、Production ID等)。 USB驅(qū)動程序應(yīng)該初始化任何可能用于控制USB設(shè)備的局部結(jié)構(gòu)體,它還應(yīng)該把所需的任何設(shè)備相關(guān)信息保存到局部結(jié)構(gòu)體中。例如,USB驅(qū)動程序通常需要探測設(shè)備對的端點地址和緩沖區(qū)大小,因為需要他們才能和端點通信。 下面具體分析探測函數(shù)做了哪些事情: a -- 探測設(shè)備的端點地址、緩沖區(qū)大小,初始化任何可能用于控制USB設(shè)備的數(shù)據(jù)結(jié)構(gòu) 下面是一個實例代碼,他們探測批量類型的IN和OUT端點,把相關(guān)信息保存到一個局部設(shè)備結(jié)構(gòu)體中: [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. }
8 K" ~- X+ C; X6 ? |