設備驅動程序是操作系統內核和機器硬件之間的接口,由一組函數和一些私有數據組成,是應用程序和硬件設備之間的橋梁。在應用程序看來,硬件設備只是一個設備文件,應用程序可以像操作普通文件一樣對硬件設備進行操作。
設備驅動程序是內核的一部分,主要完成以下功能:對設備的初始化和釋放;把數據從內核傳送到硬件設備和從硬件設備讀取數據;讀取應用程序數據傳送給設備文件和回送應用程序請求的數據;檢測和處理硬件設備出現的錯誤。
1 Linux USB子系統分析
在Linux系統中,USB主機驅動程序由3部分組成:USB主機控制器驅動(HCD)、USB核心驅動(USBD)和不同種類的USB設備類驅動,如下所示。其中HCD和USBD被稱為協議軟件或者協議棧,這兩部分共同處理與協議相關的操作。
USB設備類驅動可以包含多個,不同的功能接口對應不同的驅動程序,它們不直接與USB設備硬件打交道,而是通過協議軟件的抽象處理來完成與設備的不同功能接口之間的通信。
在Linux USB子系統中,HCD是直接和硬件進行交互的軟件模塊,是USB協議棧的最底層部分,是USB主機控制器硬件和數據傳輸的一種抽象。
HCD向上僅對USB總線驅動程序服務,HCD提供了一個軟件接口,即HCDI,使得各種USB主機控制器的硬件特性都被軟件化,並受USB總線驅動程序的調用和管理。HCD向下則直接管理和檢測主控制器硬件的各種行為。HCD提供的功能主要有:主機控制器硬件初始化;為USBD層提供相應的接口函數;提供根HUB(ROOT HUB)設備配置、控制功能;完成4種類型的數據傳輸等。
USBD部分是整個USB主機驅動的核心,主要實現的功能有:USB總線管理;USB總線設備管理、USB總線帶寬管理、USB的4種類型數據傳輸、USB HUB驅動、為USB設備驅動提供相關接口、提供應用程序訪問USB系統的文件接口等。其中USB HUB作為一類特殊的USB設備,其驅動程序被包含在USBD層。
在嵌入式Linux系統中,已經包含HCD模塊和USB核心驅動USBD,不需要用戶重新編寫,用戶僅僅需要完成USB設備類驅動即可。
2 Linux系統中USB子系統的主要數據結構
Linux系統中,USBD通過定義一組宏、數據結構和函數來抽象出所有硬件或是設備具有依賴關系的部分。USBD中主要有四個數據結構,分別是:
1 usb_device保存一個USB設備的信息,包括設備地址,設備描述符,配置描述符等。 2 usb_bus保存一個USB總線系統的信息,包括總線上設備地址信息,根集線器,帶寬使用情況等。一個USB總線系統至少有一個主機控制器和一個根集線器,Linux系統支持多USB總線系統。 3 usb_driver保存客戶驅動信息,包括驅動名稱,以及驅動提供給USB內核使用的函數指針等。 4 URB(Universal Request Block)是進行USB通信的數據結構,USBD通過URB在USB設備類驅動和USBD、USBD和HCD間進行數據傳輸。
3 Linux系統中USB設備的加載與卸載
當把一個USB設備插入到一個USB HUB的某個端口時,集中器就會檢測到設備的接入,從而在下一次受到主機通過中斷交互查詢時就會向其報告。集中器的端口在沒有設備接入時都處於關閉狀態,插入設備之后也不會自動打開,必須由主機通過控制交互發出命令予以打開。所以,在得到集中器的報告之后,主機的USB驅動程序就會為新插入的設備調度若干個控制交互,並向集中器發出打開這個端口的命令,這樣新插入的設備就會出現在USB總線上了,並為該設備分配唯一的地址。
HUB驅動程序調用函數usb_connect(struct usb_device *dev)和usb_new_device(struct usb_device *dev)解析設備的各種描述符信息,分配資源,並與相應的設備驅動程序建立聯系。
函數usb_new_device主要完成以下工作:
(1)調用usb_set_address把新分配的設備地址傳送給設備。
(2)調用usb_get_descriptor獲得設備的設備描述符,得到設備端點的包的最大長度,接下來的控制傳輸按這個數據包最大長度進行。
(3)調用usb_get_configuration得到設備的所有配置描述符、接口描述符和端點描述符信息。
(4)調用usb_set_configuration激活當前的配置作為默認工作配置。
(5)在目錄“proc/bus/usb”中為設備創建節點。
(6)在USB子系統中,通過函數usb_find_drivers和usb_find_interface_driver,為設備的每一個接口尋找相應的驅動程序,驅動程序對接口進行配置並為它們分配所需的資源。當每個接口被成功驅動后,此設備就能正常工作了。
設備拔下時,與之相聯的集線器首先檢測到設備的拔下信號,通過中斷傳輸將信息傳送給集線器的驅動,集線器的驅動先驗證設備是否被拔下,如果是則調用usb_disconnect(struct usb_device **pdev)進行處理。設備斷開后,USB系統找到設備當前活動配置的每個接口的驅動程序,調用它們提供的disconnect接口函數,中斷它們與各個接口的數據傳輸操作,釋放它們為每個接口分配的資源。如果此設備是集線器,則遞歸調用usb_disconnect來處理它的子設備,釋放設備地址,通過usbdevfs_remove_device函數釋放給設備創建的文件節點,通過usb_free_dev釋放USBD給設備分配的資源。
4 編寫USB驅動程序步驟
(1)所有usb驅動都必須創建主要結構體struct usb_driver
struct usb_driver
struct module *owner
(有他可正確對該驅動程序引用計數,應為THIS_MODULE)
const char *name
(驅動名字,運行時可在查看 /sys/bus/usb/drivers/)
const struct usb_device_id *id_table
(包含該驅動可支持的所有不同類型的驅動設備,沒添探測回調函數不會被調用)
int (*probe)(struct usb_interface *intf,const struct usb_device_id *id)
(usb驅動探測函數,確認后struct usb_interface 應恰當初始化,然后返0,如果出錯則返負值)
void(*disconnect)(struct usb_interface *intf)
(當struct usb_interface 被從系統中移除或驅動正從usb核心中卸載時,usb核心將調用此函數)
(2)usb_register()注冊將struct usb_driver 注冊到usb核心,傳統是在usb驅動程序模塊初始化代碼中完成該工作的
(3)struct usb_device_id usb核心用該表判斷哪個設備該使用哪個驅動程序,熱插拔腳本使用它來確定當一個特定的設備插入到系統時該自動裝載哪個驅動程序。
__u16 match_flags(確定設備和結構體中下列字段中哪一個相匹配)
__u16 idVendor(設備的usb制造商id)
__u16 idProduct(設備的usb產品id)
(4)USB骨架程序的關鍵幾點如下:
1)USB驅動的注冊和注銷
Usb驅動程序在注冊時會發送一個命令給usb_register,通常在驅動程序的初始化函數里。
當要從系統卸載驅動程序時,需要注銷usb子系統。即需要usb_unregister 函數處理。
2)當usb設備插入時,為了使linux-hotplug(Linux中PCI、USB等設備熱插拔支持)系統自動裝載驅動程序,你需要創建一個MODULE_DEVICE_TABLE
USB_DEVICE宏利用廠商ID和產品ID為我們提供了一個設備的唯一標識。當系統插入一個ID匹配的USB設備到USB總線時,驅動會在USB core中注冊。驅動程序中probe 函數也就會被調用。usb_device 結構指針、接口號和接口ID都會被傳遞到函數中。
3) static void * skel_probe(struct usb_device *dev,unsigned int ifnum, const struct usb_device_id *id)
驅動程序需要確認插入的設備是否可以被接受,如果不接受,或者在初始化的過程中發生任何錯誤,probe函數返回一個NULL值。否則返回一個含有設備驅動程序狀態的指針。通過這個指針,就可以訪問所有結構中的回調函數。
4)在骨架驅動程序里,最后一點是我們要注冊devfs。
我們創建一個緩沖用來保存那些被發送給usb設備的數據和那些從設備上接受的數據,同時USB urb 被初始化,並且我們在devfs子系統中注冊設備,允許devfs用戶訪問我們的設備。注冊過程如下:
如果devfs_register函數失敗,不用擔心,devfs子系統會將此情況報告給用戶。
當然最后,如果設備從usb總線拔掉,設備指針會調用disconnect 函數。驅動程序就需要清除那些被分配了的所有私有數據、關閉urbs,並且從devfs上注銷調自己
5)其他
a struct usb_host_endpoint(描述usb端點)
(包含)struct usb_endpoint_descriptor(含真正端點信息,數據格式,是真正驅動關心的字段)
端點描述符:
bEndpointAddress = 81(in)(第8位為1是輸入設備)(usb的端點地址,包含端點方向)
bmAttibutes = 03(interrupt)(端點類型,為中斷傳輸)
wMaxPacketSize = 0008(每次傳8個字節)(端點每次可處理最大字節長度)
bInterval = 08(8ms)(如端點為中斷,該值為輪詢間隔)
b usb端點捆綁為接口,usb接口只處理一種usb邏輯連接,如鼠標鍵盤等
一個usb設備可有多接口,usb揚聲器:一個usb鍵盤用於按鍵,一個usb音頻流,則需兩個不同的驅動程序。
usb驅動 通常將struct usb_interface 轉成 struct usb_device 用函數 interface_to_usbdev轉
c struct usb_interface 描述usb接口
struct usb_host_interface * altsetting(接口結構體數組,包含所有可能用於該接口的可選設置)
struct usb_host_endpoint
unsigned num_altsetting(可選設置的數量)
struct usb_host_interface * cur_altsetting(接口當前活動設置)
int minor(usb核心分配給接口的次設備號,成功調用usb_register_dev有效)
d usb設備非常復雜,由許多不同邏輯單元組成,簡單關系如下:
設備通常有一個以上的配置
配置經常有一個以上接口
接口通常有一個以上設置
接口通常有一個以上端點
設備描述-》配置描述-》接口描述-》端點描述
e usb sysfs設備命名方案
根集線器-集線器端口號:配置。接口
對於usb hub樹中層次更高的字樹命名方案
根集線器-集線器端口號-集線器端口號:配置。接口
f linux內核的代碼通過一個成為urb(usb請求塊)和所有usb設備通信.
用struct urb描述(include/linux/usb.h中定義)
urb用異步同usb設備特定usb端點發送/接收數據,使用類似網絡代碼中的struct skbuff
urb 被動態創建,隨時可被驅動程序或usb核心取消,內部有引用計數,可被多次調用,使他們可在最后一個使用者釋放他們時自動地銷毀
urb使得流處理或其他復雜的重疊的通信成為可能,獲得高數據傳輸速度。
usb_alloc_urb() 創建urb包 usb_free_urb() 釋放urb包
usb_fill_int_urb()正確初始化將發送到usb設備的中斷端點urb
usb_fill_bulk_urb() .. .. .. ... 批量傳輸端點urb
usb_fill_control_urb() .. .. .. ... 控制端點urb
等時urb在提交給核心時必須手動初始化(很不幸,沒函數)
usb_submit_urb()urb被usb驅動正確創建和初始化后,就可提交到usb核心,發送到usb設備上了,如果調用成功,函數返0,urb控制權轉給usb核心
usb_kill_urb() or usb_unlink_urb()取消已經被提交給核心的urb 。
6 內核自帶的實例
usb開發過程總結以內核drivers\usb\usb-skeleton.c自帶的usb驅動實例為例來分析總結。
6.1 usb_driver定義
usb驅動都必須創建主要結構體usb_driver
1 static struct usb_driver skel_driver = { 2 .name = "skeleton", 3 .probe = skel_probe, 4 .disconnect = skel_disconnect,//當struct usb_interface 被從系統中移除或驅動正從usb核心中卸載時,usb核心將調用此函 5 .suspend = skel_suspend, 6 .resume = skel_resume, 7 .pre_reset = skel_pre_reset, 8 .post_reset = skel_post_reset, 9 .id_table = skel_table,//包含該驅動可支持的所有不同類型的驅動設備,沒添探測回調函數不會被調用 10 .supports_autosuspend = 1, 11 }; 12 13 module_usb_driver(skel_driver);
6.2 usb_register()注冊
將struct usb_driver 注冊到usb核心,usb驅動程序模塊初始化代碼中完成該工作的。是通過以下宏實現的:
1 #define module_usb_driver(__usb_driver) \ 2 module_driver(__usb_driver, usb_register, \ 3 usb_deregister) 4 5 #define module_driver(__driver, __register, __unregister, ...) \ 6 static int __init __driver##_init(void) \ 7 { \ 8 return __register(&(__driver) , ##__VA_ARGS__); \ 9 } \ 10 module_init(__driver##_init); \ 11 static void __exit __driver##_exit(void) \ 12 { \ 13 __unregister(&(__driver) , ##__VA_ARGS__); \ 14 } \ 15 module_exit(__driver##_exit);
6.3 struct usb_device_id usb核心用該表判斷哪個設備該使用哪個驅動程序,熱插拔腳本使用它來確定當一個特定的設備插入到系統時該自動裝載哪個驅動程序。
__u16 match_flags(確定設備和結構體中下列字段中哪一個相匹配)
__u16 idVendor(設備的usb制造商id)
__u16 idProduct(設備的usb產品id)
USB_DEVICE宏利用廠商ID和產品ID為我們提供了一個設備的唯一標識。當系統插入一個ID匹配的USB設備到USB總線時,驅動會在USB core中注冊。驅動程序中probe 函數也就會被調用。usb_device 結構指針、接口號和接口ID都會被傳遞到函數中。
6.4 usb probe函數
驅動程序需要確認插入的設備是否可以被接受,如果不接受,或者在初始化的過程中發生任何錯誤,probe函數返回一個NULL值。否則返回一個含有設備驅動程序狀態的指針。通過這個指針,就可以訪問所有結構中的回調函數。
1 static int skel_probe(struct usb_interface *interface, 2 const struct usb_device_id *id) 3 { 4 struct usb_skel *dev; 5 struct usb_host_interface *iface_desc; 6 struct usb_endpoint_descriptor *endpoint; 7 size_t buffer_size; 8 int i; 9 int retval = -ENOMEM; 10 11 /* allocate memory for our device state and initialize it */ 12 dev = kzalloc(sizeof(*dev), GFP_KERNEL); 13 if (!dev) { 14 dev_err(&interface->dev, "Out of memory\n"); 15 goto error; 16 } 17 kref_init(&dev->kref); 18 sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); 19 mutex_init(&dev->io_mutex); 20 spin_lock_init(&dev->err_lock); 21 init_usb_anchor(&dev->submitted); 22 init_waitqueue_head(&dev->bulk_in_wait); 23 24 dev->udev = usb_get_dev(interface_to_usbdev(interface)); 25 dev->interface = interface; 26 27 /* set up the endpoint information */ 28 /* use only the first bulk-in and bulk-out endpoints */ 29 iface_desc = interface->cur_altsetting; 30 for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { 31 endpoint = &iface_desc->endpoint[i].desc; 32 33 if (!dev->bulk_in_endpointAddr && 34 usb_endpoint_is_bulk_in(endpoint)) { 35 /* we found a bulk in endpoint */ 36 buffer_size = usb_endpoint_maxp(endpoint); 37 dev->bulk_in_size = buffer_size; 38 dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; 39 dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); 40 if (!dev->bulk_in_buffer) { 41 dev_err(&interface->dev, 42 "Could not allocate bulk_in_buffer\n"); 43 goto error; 44 } 45 dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); 46 if (!dev->bulk_in_urb) { 47 dev_err(&interface->dev, 48 "Could not allocate bulk_in_urb\n"); 49 goto error; 50 } 51 } 52 53 if (!dev->bulk_out_endpointAddr && 54 usb_endpoint_is_bulk_out(endpoint)) { 55 /* we found a bulk out endpoint */ 56 dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; 57 } 58 } 59 if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { 60 dev_err(&interface->dev, 61 "Could not find both bulk-in and bulk-out endpoints\n"); 62 goto error; 63 } 64 65 /* save our data pointer in this interface device */ 66 usb_set_intfdata(interface, dev); 67 68 /* we can register the device now, as it is ready */ 69 retval = usb_register_dev(interface, &skel_class); 70 if (retval) { 71 /* something prevented us from registering this driver */ 72 dev_err(&interface->dev, 73 "Not able to get a minor for this device.\n"); 74 usb_set_intfdata(interface, NULL); 75 goto error; 76 } 77 78 /* let the user know what node this device is now attached to */ 79 dev_info(&interface->dev, 80 "USB Skeleton device now attached to USBSkel-%d", 81 interface->minor); 82 return 0; 83 84 error: 85 if (dev) 86 /* this frees allocated memory */ 87 kref_put(&dev->kref, skel_delete); 88 return retval; 89 }
未完待續。。。
參考博文:https://blog.csdn.net/zqixiao_09/java/article/details/51057086