Linux下的硬體驅動——USB裝置(下)(驅動開發部分)(轉)
Linux下的硬體驅動——USB裝置(下)(驅動開發部分)(轉)[@more@]聯想軟體設計中心嵌入式研發處系統設計工程師2003年7月USB骨架程式(usb-skeleton),是USB驅動程式的基礎,透過對它原始碼的學習和理解,可以使我們迅速地瞭解USB驅動架構,迅速地開發我們自己的USB硬體的驅動。前言在上篇《Linux下的硬體驅動--USB裝置(上)(驅動配製部分)》中,我們知道了在Linux下如何去使用一些最常見的USB裝置。但對於做系統設計的程式設計師來說,這是遠遠不夠的,我們還需要具有驅動程式的閱讀、修改和開發能力。在此下篇中,就是要透過簡單的USB驅動的例子,隨您一起進入USB驅動開發的世界。USB驅動開發在掌握了USB裝置的配置後,對於程式設計師,我們就可以嘗試進行一些簡單的USB驅動的修改和開發了。這一段落,我們會講解一個最基礎USB框架的基礎上,做兩個小的USB驅動的例子。USB骨架在Linux kernel原始碼目錄中driver/usb/usb-skeleton.c為我們提供了一個最基礎的USB驅動程式。我們稱為USB骨架。透過它我們僅需要修改極少的部分,就可以完成一個USB裝置的驅動。我們的USB驅動開發也是從她開始的。那些linux下不支援的USB裝置幾乎都是生產廠商特定的產品。如果生產廠商在他們的產品中使用自己定義的協議,他們就需要為此裝置建立特定的驅動程式。當然我們知道,有些生產廠商公開他們的USB協議,並幫助Linux驅動程式的開發,然而有些生產廠商卻根本不公開他們的USB協議。因為每一個不同的協議都會產生一個新的驅動程式,所以就有了這個通用的USB驅動骨架程式, 它是以pci 骨架為模板的。如果你準備寫一個linux驅動程式,首先要熟悉USB協議規範。USB主頁上有它的幫助。一些比較典型的驅動可以在上面發現,同時還介紹了USB urbs的概念,而這個是usb驅動程式中最基本的。Linux USB 驅動程式需要做的第一件事情就是在Linux USB 子系統裡註冊,並提供一些相關資訊,例如這個驅動程式支援那種裝置,當被支援的裝置從系統插入或拔出時,會有哪些動作。所有這些資訊都傳送到USB 子系統中,在usb骨架驅動程式中是這樣來表示的:static struct usb_driver skel_driver = {name: "skeleton",probe: skel_probe,disconnect: skel_disconnect,fops: &skel_fops,minor: USB_SKEL_MINOR_BASE,id_table: skel_table,};變數name是一個字串,它對驅動程式進行描述。probe 和disconnect 是函式指標,當裝置與在id_table 中變數資訊匹配時,此函式被呼叫。fops和minor變數是可選的。大多usb驅動程式鉤住另外一個驅動系統,例如SCSI,網路或者tty子系統。這些驅動程式在其他驅動系統中註冊,同時任何使用者空間的互動操作透過那些介面提供,比如我們把SCSI裝置驅動作為我們USB驅動所鉤住的另外一個驅動系統,那麼我們此USB裝置的 read、write等操作,就相應按SCSI裝置的read、write函式進行訪問。但是對於掃描器等驅動程式來說,並沒有一個匹配的驅動系統可以使用,那我們就要自己處理與使用者空間的read、write等互動函式。Usb子系統提供一種方法去註冊一個次裝置號和file_operations函式指標,這樣就可以與使用者空間實現方便地互動。USB骨架程式的關鍵幾點如下:USB驅動的註冊和登出Usb驅動程式在註冊時會傳送一個命令給usb_register,通常在驅動程式的初始化函式里。當要從系統解除安裝驅動程式時,需要登出usb子系統。即需要usb_unregister 函式處理:static void __exit usb_skel_exit(void){/* deregister this driver with the USB subsystem */usb_deregister(&skel_driver);}module_exit(usb_skel_exit);當usb裝置插入時,為了使linux-hotplug(Linux中PCI、USB等裝置熱插拔支援)系統自動裝載驅動程式,你需要建立一個MODULE_DEVICE_TABLE。程式碼如下(這個模組僅支援某一特定裝置):/* table of devices that work with this driver */static struct usb_device_id skel_table [] = {{ USB_DEVICE(USB_SKEL_VENDOR_ID,USB_SKEL_PRODUCT_ID) },{ } /* Terminating entry */};MODULE_DEVICE_TABLE (usb, skel_table);USB_DEVICE宏利用廠商ID和產品ID為我們提供了一個裝置的唯一標識。當系統插入一個ID匹配的USB裝置到USB匯流排時,驅動會在USB core中註冊。驅動程式中probe 函式也就會被呼叫。usb_device 結構指標、介面號和介面ID都會被傳遞到函式中。static void * skel_probe(struct usb_device *dev,unsigned int ifnum, const struct usb_device_id *id)驅動程式需要確認插入的裝置是否可以被接受,如果不接受,或者在初始化的過程中發生任何錯誤,probe函式返回一個NULL值。否則返回一個含有裝置驅動程式狀態的指標。透過這個指標,就可以訪問所有結構中的回撥函式。在骨架驅動程式裡,最後一點是我們要註冊devfs。我們建立一個緩衝用來儲存那些被髮送給usb裝置的資料和那些從裝置上接受的資料,同時USB urb 被初始化,並且我們在devfs子系統中註冊裝置,允許devfs使用者訪問我們的裝置。註冊過程如下:/* initialize the devfs node for this deviceand register it */sprintf(name, "skel%d", skel->minor);skel->devfs = devfs_register(usb_devfs_handle, name,DEVFS_FL_DEFAULT, USB_MAJOR,USB_SKEL_MINOR_BASE + skel->minor,S_IFCHR | S_IRUSR | S_IWUSR |S_IRGRP | S_IWGRP | S_IROTH,&skel_fops, NULL);如果devfs_register函式失敗,不用擔心,devfs子系統會將此情況報告給使用者。當然最後,如果裝置從usb匯流排拔掉,裝置指標會呼叫disconnect 函式。驅動程式就需要清除那些被分配了的所有私有資料、關閉urbs,並且從devfs上登出調自己。/* remove our devfs node */devfs_unregister(skel->devfs);現在,skeleton驅動就已經和裝置繫結上了,任何使用者態程式要操作此裝置都可以透過file_operations結構所定義的函式進行了。首先,我們要open此裝置。在open函式中MODULE_INC_USE_COUNT 宏是一個關鍵,它的作用是起到一個計數的作用,有一個使用者態程式開啟一個裝置,計數器就加一,例如,我們以模組方式加入一個驅動,若計數器不為零,就說明仍然有使用者程式在使用此驅動,這時候,你就不能透過rmmod命令解除安裝驅動模組了。/* increment our usage count for the module */MOD_INC_USE_COUNT;++skel->open_count;/* save our object in the file's private structure */file->private_data = skel;當open完裝置後,read、write函式就可以收、發資料了。skel的write、和read函式他們是完成驅動對讀寫等操作的響應。在skel_write中,一個FILL_BULK_URB函式,就完成了urb 系統callbak和我們自己的skel_write_bulk_callback之間的聯絡。注意skel_write_bulk_callback是中斷方式,所以要注意時間不能太久,本程式中它就只是報告一些urb的狀態等。read 函式與write 函式稍有不同在於:程式並沒有用urb 將資料從裝置傳送到驅動程式,而是我們用usb_bulk_msg 函式代替,這個函式能夠不需要建立urbs 和操作urb函式的情況下,來傳送資料給裝置,或者從裝置來接收資料。我們呼叫usb_bulk_msg函式並傳提一個儲存空間,用來緩衝和放置驅動收到的資料,若沒有收到資料,就失敗並返回一個錯誤資訊。usb_bulk_msg函式當對usb裝置進行一次讀或者寫時,usb_bulk_msg 函式是非常有用的; 然而, 當你需要連續地對裝置進行讀/寫時,建議你建立一個自己的urbs,同時將urbs 提交給usb子系統。skel_disconnect函式當我們釋放裝置檔案控制程式碼時,這個函式會被呼叫。MOD_DEC_USE_COUNT宏會被用到(和MOD_INC_USE_COUNT剛好對應,它減少一個計數器),首先確認當前是否有其它的程式正在訪問這個裝置,如果是最後一個使用者在使用,我們可以關閉任何正在發生的寫,操作如下:/* decrement our usage count for the device */--skel->open_count;if (skel->open_count <= 0) {/* shutdown any bulk writes that might begoing on */usb_unlink_urb (skel->write_urb);skel->open_count = 0;}/* decrement our usage count for the module */MOD_DEC_USE_COUNT;最困難的是,usb 裝置可以在任何時間點從系統中取走,即使程式目前正在訪問它。usb驅動程式必須要能夠很好地處理解決此問題,它需要能夠切斷任何當前的讀寫,同時通知使用者空間程式:usb裝置已經被取走。如果程式有一個開啟的裝置控制程式碼,在當前結構裡,我們只要把它賦值為空,就像它已經消失了。對於每一次裝置讀寫等其它函式操作,我們都要檢查 usb_device結構是否存在。如果不存在,就表明裝置已經消失,並返回一個-ENODEV錯誤給使用者程式。當最終我們呼叫release 函式時,在沒有檔案開啟這個裝置時,無論usb_device結構是否存在、它都會清空skel_disconnect函式所作工作。Usb 骨架驅動程式,提供足夠的例子來幫助初始人員在最短的時間裡開發一個驅動程式。更多資訊你可以到linux usb開發新聞組去尋找。隨身碟、USB讀卡器、MP3、數位相機驅動對於一款windows下用的很爽的隨身碟、USB讀卡器、MP3或數位相機,可能Linux下卻不能支援。怎麼辦?其實不用傷心,也許經過一點點的工作,你就可以很方便地使用它了。通常是此隨身碟、USB讀卡器、MP3或數位相機在WindowsXP中不需要廠商專門的驅動就可以識別為移動儲存裝置,這樣的裝置才能保證成功,其他的就看你的運氣了。USB儲存裝置,他們的read、write等操作都是透過上章節中提到的鉤子,把自己的操作鉤到SCSI裝置上去的。我們就不需要對其進行具體的資料讀寫處理了。第一步:我們透過cat /proc/bus/usb/devices得到當前系統探測到的USB匯流排上的裝置資訊。它包括Vendor、ProdID、Product等。下面是我買的一款雜牌CF卡讀卡器插入後的資訊片斷:T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 5 Spd=12 MxCh= 0D: Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=8 #Cfgs= 1P: Vendor=07c4 ProdID=a400 Rev= 1.13S: Manufacturer=USBS: Product=Mass StorageC:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=70mAI: If#= 0 Alt= 0 #EPs= 2 Cls=08(vend.) Sub=06 Prot=50 Driver=usb-storageE: Ad=81(I) Atr=02(Bulk) MxPS= 64 Ivl= 0msE: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms其中,我們最關心的是Vendor=07c4 ProdID=a400和Manufacturer=USB(果然是雜牌,廠商名都看不到)Product= Mass Storage。對於這些移動儲存裝置,我們知道Linux下都是透過usb-storage.o驅動模擬成scsi裝置去支援的,之所以不支援,通常是usb- storage驅動未包括此廠商識別和產品識別資訊(在類似skel_probe的USB最初探測時被遮蔽了)。對於USB儲存裝置的硬體訪問部分,通常是一致的。所以我們要支援它,僅需要修改usb-storage中關於廠商識別和產品識別列表部分。第二部,開啟drivers/usb/storage/unusual_devs.h檔案,我們可以看到所有已知的產品登記表,都是以 UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, vendor_name, product_name, use_protocol, use_transport, init_function, Flags)方式登記的。其中相應的涵義,你就可以根據命名來判斷了。所以只要我們如下填入我們自己的註冊,就可以讓usb-storage驅動去認識和發現它。UNUSUAL_DEV(07c4, a400, 0x0000, 0xffff," USB ", " Mass Storage ",US_SC_SCSI, US_PR_BULK, NULL,US_FL_FIX_INQUIRY | US_FL_START_STOP |US_FL_MODE_XLATE )注意:新增以上幾句的位置,一定要正確。比較發現,usb-storage驅動對所有註冊都是按idVendor, idProduct數值從小到大排列的。我們也要放在相應位置。最後,填入以上資訊,我們就可以重新編譯生成核心或usb-storage.o模組。這時候插入我們的裝置就可以跟其他隨身碟一樣作為SCSI裝置去訪問了。鍵盤飛梭支援目前很多鍵盤都有飛梭和手寫板,下面我們就嘗試為一款鍵盤飛梭加入一個驅動。在通常情況,當我們插入USB介面鍵盤時,在 /proc/bus/usb/devices會看到多個USB裝置。比如:你的USB鍵盤上的飛梭會是一個,你的手寫板會是一個,若是你的USB鍵盤有 USB擴充套件連線埠,也會看到。下面是具體看到的資訊T: Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2B: Alloc= 11/900 us ( 1%), #Int= 1, #Iso= 0D: Ver= 1.00 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1P: Vendor=0000 ProdID=0000 Rev= 0.00S: Product=USB UHCI Root HubS: SerialNumber=d800C:* #Ifs= 1 Cfg#= 1 Atr=40 MxPwr= 0mAI: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hubE: Ad=81(I) Atr=03(Int.) MxPS= 8 Ivl=255msT: Bus=02 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 3 Spd=12 MxCh= 3D: Ver= 1.10 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1P: Vendor=07e4 ProdID=9473 Rev= 0.02S: Manufacturer=ALCORS: Product=Movado USB KeyboardC:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=100mAI: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hubE: Ad=81(I) Atr=03(Int.) MxPS= 1 Ivl=255ms找到相應的資訊後就可開始工作了。實際上,飛梭的定義和鍵盤鍵碼通常是一樣的,所以我們參照drivers/usb/usbkbd..c程式碼進行一些改動就可以了。因為沒能拿到相應的硬體USB協議,我無從知道飛梭在按下時通訊協議眾到底發什麼,我只能把它的資訊打出來進行分析。幸好,它比較簡單,在下面程式碼的usb_kbd_irq函式中if(kbd->new[0] == (char)0x01)和if(((kbd->new[1]>>4)&0x0f)!=0x7)就是判斷飛梭左旋。 usb_kbd_irq函式就是鍵盤中斷響應函式。他的掛接,就是在usb_kbd_probe函式中FILL_INT_URB(&kbd->irq, dev, pipe, kbd->new, maxp > 8 ? 8 : maxp,usb_kbd_irq, kbd, endpoint->bInterval);一句中實現。從usb骨架中我們知道,usb_kbd_probe函式就是在USB裝置被系統發現是執行的。其他部分就都不是關鍵了。你可以根據具體的探測值(Vendor=07e4 ProdID=9473等)進行一些修改就可以了。值得一提的是,在鍵盤中斷中,我們的做法是收到USB飛梭訊息後,把它模擬成左方向鍵和右方向鍵,在這裡,就看你想怎麼去響應它了。當然你也可以響應模擬成F14、F15等擴充套件鍵碼。在瞭解了此基本的驅動後,對於一個你已經拿到通訊協議的鍵盤所帶手寫板,你就應該能進行相應驅動的開發了吧。程式見附錄1:鍵盤飛梭驅動。使用此驅動要注意的問題:在載入此驅動時你必須先把hid裝置解除安裝,載入完usbhkey.o模組後再載入hid.o。因為若hid存在,它的probe 會遮蔽系統去利用我們的驅動發現我們的裝置。其實,飛梭本來就是一個hid裝置,正確的方法,或許你應該修改hid的probe函式,然後把我們的驅動融入其中。參考資料《LINUX裝置驅動程式》ALESSANDRO RUBINI著LISOLEG 譯《Linux系統分析與高階程式設計技術》周巍松 編著Linux Kernel-2.4.20原始碼和文件說明附錄1:鍵盤飛梭驅動#include#include#include#include#include#include#include/** Version Information*/#define DRIVER_VERSION ""#define DRIVER_AUTHOR "TGE HOTKEY "#define DRIVER_DESC "USB HID Tge hotkey driver"#define USB_HOTKEY_VENDOR_ID 0x07e4#define USB_HOTKEY_PRODUCT_ID 0x9473//廠商和產品ID資訊就是/proc/bus/usb/devices中看到的值MODULE_AUTHOR( DRIVER_AUTHOR );MODULE_DESCRIPTION( DRIVER_DESC );struct usb_kbd {struct input_dev dev;struct usb_device *usbdev;unsigned char new[8];unsigned char old[8];struct urb irq, led;// devrequest dr;//這一行和下一行的區別在於kernel2.4.20版本對usb_kbd鍵盤結構定義發生了變化struct usb_ctrlrequest dr;unsigned char leds, newleds;char name[128];int open;};//此結構來自核心中drivers/usb/usbkbd..cstatic void usb_kbd_irq(struct urb *urb){struct usb_kbd *kbd = urb->context;int *new;new = (int *) kbd->new;if(kbd->new[0] == (char)0x01){if(((kbd->new[1]>>4)&0x0f)!=0x7){handle_scancode(0xe0,1);handle_scancode(0x4b,1);handle_scancode(0xe0,0);handle_scancode(0x4b,0);}else{handle_scancode(0xe0,1);handle_scancode(0x4d,1);handle_scancode(0xe0,0);handle_scancode(0x4d,0);}}printk("new=%x %x %x %x %x %x %x %x",kbd->new[0],kbd->new[1],kbd->new[2],kbd->new[3],kbd->new[4],kbd->new[5],kbd->new[6],kbd->new[7]);}static void *usb_kbd_probe(struct usb_device *dev, unsigned int ifnum,const struct usb_device_id *id){struct usb_interface *iface;struct usb_interface_descriptor *interface;struct usb_endpoint_descriptor *endpoint;struct usb_kbd *kbd;int pipe, maxp;iface = &dev->actconfig->interface[ifnum];interface = &iface->altsetting[iface->act_altsetting];if ((dev->descriptor.idVendor != USB_HOTKEY_VENDOR_ID) ||(dev->descriptor.idProduct != USB_HOTKEY_PRODUCT_ID) ||(ifnum != 1)){return NULL;}if (dev->actconfig->bNumInterfaces != 2){return NULL;}if (interface->bNumEndpoints != 1) return NULL;endpoint = interface->endpoint + 0;pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));usb_set_protocol(dev, interface->bInterfaceNumber, 0);usb_set_idle(dev, interface->bInterfaceNumber, 0, 0);printk(KERN_INFO "GUO: Vid = %.4x, Pid = %.4x, Device = %.2x, ifnum = %.2x, bufCount = %.8x
",dev->descriptor.idVendor,dev->descriptor.idProduct,dev->descriptor.bcdDevice, ifnum, maxp);if (!(kbd = kmalloc(sizeof(struct usb_kbd), GFP_KERNEL))) return NULL;memset(kbd, 0, sizeof(struct usb_kbd));kbd->usbdev = dev;FILL_INT_URB(&kbd->irq, dev, pipe, kbd->new, maxp > 8 ? 8 : maxp,usb_kbd_irq, kbd, endpoint->bInterval);kbd->irq.dev = kbd->usbdev;if (dev->descriptor.iManufacturer)usb_string(dev, dev->descriptor.iManufacturer, kbd->name, 63);if (usb_submit_urb(&kbd->irq)) {kfree(kbd);return NULL;}printk(KERN_INFO "input%d: %s on usb%d:%d.%d
",kbd->dev.number, kbd->name, dev->bus->busnum, dev->devnum, ifnum);return kbd;}static void usb_kbd_disconnect(struct usb_device *dev, void *ptr){struct usb_kbd *kbd = ptr;usb_unlink_urb(&kbd->irq);kfree(kbd);}static struct usb_device_id usb_kbd_id_table [] = {{ USB_DEVICE(USB_HOTKEY_VENDOR_ID, USB_HOTKEY_PRODUCT_ID) },{ } /* Terminating entry */};MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);static struct usb_driver usb_kbd_driver = {name: "Hotkey",probe: usb_kbd_probe,disconnect: usb_kbd_disconnect,id_table: usb_kbd_id_table,NULL,};static int __init usb_kbd_init(void){usb_register(&usb_kbd_driver);info(DRIVER_VERSION ":" DRIVER_DESC);return 0;}static void __exit usb_kbd_exit(void){usb_deregister(&usb_kbd_driver);}module_init(usb_kbd_init);module_exit(usb_kbd_exit);關於作者趙明,聯想軟體設計中心嵌入式研發處系統設計工程師,一直致力於WinCE、WinXPE、Linux等嵌入式系統研究。您可以透過carl__zhao@163.com與他聯絡。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-938888/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Linux驅動開發筆記(四):裝置驅動介紹、熟悉雜項裝置驅動和ubuntu開發雜項裝置DemoLinux筆記Ubuntu
- 【linux】驅動-7-平臺裝置驅動Linux
- garmin USB: linux USB host驅動Linux
- 裝置樹下的 LED 驅動實驗
- 【linux】驅動-6-匯流排-裝置-驅動Linux
- 字元裝置驅動 —— 字元裝置驅動框架字元框架
- Linux裝置驅動程式學習----1.裝置驅動程式簡介Linux
- Linux驅動實踐:如何編寫【 GPIO 】裝置的驅動程式?Linux
- linux 裝置驅動基本概念Linux
- ArmSoM系列板卡 嵌入式Linux驅動開發實戰指南 之 字元裝置驅動Linux字元
- 對USB驅動下載失敗的解決
- linux怎麼檢視已裝好硬體驅動Linux
- Linux裝置驅動開發詳解:基於Linux4.0核心Linux
- 如何編寫一個簡單的Linux驅動(三)——完善裝置驅動Linux
- linux裝置驅動編寫入門Linux
- Linux USB ECM Gadget 驅動介紹Linux
- 驅動Driver-MISC雜項驅動裝置
- Linux驅動之I2C匯流排裝置以及驅動Linux
- 在Linux中,什麼是裝置驅動程式?如何安裝和解除安裝裝置驅動程式?Linux
- extcon驅動及其在USB驅動中的應用
- LED字元裝置驅動字元
- 【Linux SPI】RFID RC522 裝置驅動Linux
- 【linux】驅動-9-裝置樹外掛Linux
- win10怎麼聯網自動安裝硬體驅動_win10聯網自動安裝硬體驅動教程Win10
- HarmonyOS USB DDK助你輕鬆實現USB驅動開發
- Linux驅動實踐:你知道【字元裝置驅動程式】的兩種寫法嗎?Linux字元
- Linux驅動之裝置樹的基礎知識Linux
- linux核心原始碼閱讀-塊裝置驅動Linux原始碼
- linux驅動之獲取裝置樹資訊Linux
- platform 裝置驅動實驗Platform
- 字元驅動裝置踩坑字元
- 如何編寫linux下nandflash驅動-4LinuxNaN
- linux驅動之LED驅動Linux
- pci匯流排驅動及pci裝置驅動註冊
- 實時開發驅動下的資料智慧化
- 關於召回《Linux裝置驅動開發詳解-基於最新的Linux 4.0核心》的通知Linux
- 利用驅動精靈更新win10系統硬體驅動的方法Win10
- USB驅動正常,USB沒有反應
- MySQL驅動的下載方法MySql