亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Linux環境下USB的原理、驅動和配置

系統 1866 0

隨著生活水平的提高,人們對USB設備的使用也越來越多,鑒于Linux在硬件配置上尚不能全部即插即用,因此關于Linux如何配置和使用,成為困擾我們的一大問題。

什么是 USB

USB是英文Universal Serial Bus的縮寫,意為通用串行總線。USB最初是為了替代許多不同的低速總線(包括并行、串行和鍵盤連接)而設計的,它以單一類型的總線連接各種不同的類型的設備。USB的發展已經超越了這些低速的連接方式,它現在可以支持幾乎所有可以連接到PC上的設備。最新的USB規范修訂了理論上高達480Mbps的高速連接。Linux內核支持兩種主要類型的USB驅動程序:宿主系統上的驅動程序和設備上的驅動程序,從宿主的觀點來看(一個普通的宿主也就是一個PC機),宿主系統的USB設備驅動程序控制插入其中的USB設備,而USB設備的驅動程序控制該設備如何作為一個USB設備和主機通信。

USB 的具體構成

在動手寫USB驅動程序這前,讓我們先看看寫的USB驅動程序在內核中的結構,如下圖:

Linux環境下USB的原理、驅動和配置

USB驅動程序存在于不同的內核子系統和USB硬件控制器之間,USB核心為USB驅動程序提供了一個用于訪問和控制USB硬件的接口,而不必考慮系統當前存在的各種不同類型的USB硬件控制器。USB是一個非常復雜的設備,linux內核為我們提供了一個稱為USB的核心的子系統來處理大部分的復雜性,USB設備包括配置(configuration)、接口(interface)和端點(endpoint),USB設備綁定到接口上,而不是整個USB設備。如下圖所示:

Linux環境下USB的原理、驅動和配置

USB通信最基本的形式是通過端點(USB端點分中斷、批量、等時、控制四種,每種用途不同),USB端點只能往一個方向傳送數據,從主機到設備或者從設備到主機,端點可以看作是單向的管道(pipe)。所以我們可以這樣認為:設備通常具有一個或者更多的配置,配置經常具有一個或者更多的接口,接口通常具有一個或者更多的設置,接口沒有或具有一個以上的端點。驅動程序把驅動程序對象注冊到USB子系統中,稍后再使用制造商和設備標識來判斷是否已經安裝了硬件。USB核心使用一個列表(是一個包含制造商ID和設備號ID的一個結構體)來判斷對于一個設備該使用哪一個驅動程序,熱插撥腳本使用它來確定當一個特定的設備插入到系統時該自動裝載哪一個驅動程序。

上面我們簡要說明了驅動程序的基本理論,在寫一個設備驅動程序之前,我們還要了解以下兩個概念:模塊和設備文件。

模塊 :是在內核空間運行的程序,實際上是一種目標對象文件,沒有鏈接,不能獨立運行,但是可以裝載到系統中作為內核的一部分運行,從而可以動態擴充內核的功能。模塊最主要的用處就是用來實現設備驅動程序。Linux下對于一個硬件的驅動,可以有兩種方式:直接加載到內核代碼中,啟動內核時就會驅動此硬件設備。另一種就是以模塊方式,編譯生成一個.ko文件(在2.4以下內核中是用.o作模塊文件,我們以2.6的內核為準,以下同)。當應用程序需要時再加載到內核空間運行。所以我們所說的一個硬件的驅動程序,通常指的就是一個驅動模塊。

設備文件 :對于一個設備,它可以在/dev下面存在一個對應的邏輯設備節點,這個節點以文件的形式存在,但它不是普通意義上的文件,它是設備文件,更確切的說,它是設備節點。這個節點是通過mknod命令建立的,其中指定了主設備號和次設備號。主設備號表明了某一類設備,一般對應著確定的驅動程序;次設備號一般是區分不同屬性,例如不同的使用方法,不同的位置,不同的操作。這個設備號是從/proc/devices文件中獲得的,所以一般是先有驅動程序在內核中,才有設備節點在目錄中。這個設備號(特指主設備號)的主要作用,就是聲明設備所使用的驅動程序。驅動程序和設備號是一一對應的,當你打開一個設備文件時,操作系統就已經知道這個設備所對應的驅動程序。對于一個硬件,Linux是這樣來進行驅動的:首先,我們必須提供一個.ko的驅動模塊文件。我們要使用這個驅動程序,首先要加載它,我們可以用insmod xxx.ko,這樣驅動就會根據自己的類型(字符設備類型或塊設備類型,例如鼠標就是字符設備而硬盤就是塊設備)向系統注冊,注冊成功系統會反饋一個主設備號,這個主設備號就是系統對它的唯一標識。驅動就是根據此主設備號來創建一個一般放置在/dev目錄下的設備文件。在我們要訪問此硬件時,就可以對設備文件通過open、read、write、close等命令進行。而驅動就會接收到相應的read、write操作而根據自己的模塊中的相應函數進行操作了。

USB 驅動程序如何應用

了解了上述理論后,我們就可以動手寫驅動程序,如果你基本功好,而且寫過linux下的硬件驅動,USB的硬件驅動和pci_driver很類似,那么寫USB的驅動就比較簡單了,如果你只是大體了解了linux的硬件驅動,那也不要緊,因為在linux的內核源碼中有一個框架程序可以拿來借用一下,這個框架程序在/usr/src/~(你的內核版本,以下同)/drivers/usb下,文件名為usb-skeleton.c。寫一個USB的驅動程序最基本的要做四件事:驅動程序要支持的設備、注冊USB驅動程序、探測和斷開、提交和控制urb(USB請求塊)(當然也可以不用urb來傳輸數據,下文我們會說到)。

驅動程序支持的設備: 有一個結構體struct usb_device_id,這個結構體提供了一列不同類型的該驅動程序支持的USB設備,對于一個只控制一個特定的USB設備的驅動程序來說,struct usb_device_id表被定義為:

/* 驅動程序支持的設備列表*/

static struct usb_device_id skel_table [] = {

{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },

{ } /* 終止入口*/

};

MODULE_DEVICE_TABLE (usb, skel_table);

對于PC驅動程序,MODULE_DEVICE_TABLE是必需的,而且usb必需為該宏的第一個值,而USB_SKEL_VENDOR_ID和USB_SKEL_PRODUCT_ID就是這個特殊設備的制造商和產品的ID了,我們在程序中把定義的值改為我們這款USB的,如:

/* 定義制造商和產品的ID號*/

#define USB_SKEL_VENDOR_ID 0x1234

#define USB_SKEL_PRODUCT_ID 0x2345

這兩個值可以通過命令lsusb,當然你得先把USB設備先插到主機上了?;蛘卟榭磸S商的USB設備的手冊也能得到,在我機器上運行lsusb是這樣的結果:

Bus 004 Device 001: ID 0000:0000

Bus 003 Device 002: ID 1234:2345 Abc Corp.

Bus 002 Device 001: ID 0000:0000

Bus 001 Device 001: ID 0000:0000

得到這兩個值后把它定義到程序里就可以了。

注冊 USB 驅動程序: 所有的USB驅動程序都必須創建的結構體是struct usb_driver。這個結構體必須由USB驅動程序來填寫,包括許多回調函數和變量,它們向USB核心代碼描述USB驅動程序。創建一個有效的struct usb_driver結構體,只須要初始化五個字段就可以了,在框架程序中是這樣的:

static struct usb_driver skel_driver = {

.owner =THIS_MODULE,

.name = "skeleton",

.probe = skel_probe,

.disconnect = skel_disconnect,

.id_table = skel_table,

};

struct module *owner :指向該驅動程序的模塊所有者的批針。USB核心使用它來正確地對該USB驅動程序進行引用計數,使它不會在不合適的時刻被卸載掉,這個變量應該被設置為THIS_MODULE宏。

const char *name:指向驅動程序名字的指針,在內核的所有USB驅動程序中它必須是唯一的,通常被設置為和驅動程序模塊名相同的名字。

int (*probe) (struct usb_interface *intf,const struct usb_device_id *id):這個是指向USB驅動程序中的探測函數的指針。當USB核心認為它有一個接口(usb_interface)可以由該驅動程序處理時,這個函數被調用。

void (disconnect)(struct usb_interface *intf):指向USB驅動程序中的斷開函數的指針,當一個USB接口(usb_interface)被從系統中移除或者驅動程序正在從USB核心中卸載時,USB核心將調用這個函數。

const struct usb_device_id *id_table:指向ID設備表的指針,這個表包含了一列該驅動程序可以支持的USB設備,如果沒有設置這個變量,USB驅動程序中的探測回調函數就不會被調用。

在這個結構體中還有其它的幾個回調函數不是很常用,這里就不一一說明了。以struct usb_driver 指針為參數的usb_register_driver函數調用把struct usb_driver注冊到USB核心。一般是在USB驅動程序的模塊初始化代碼中完成這個工作的:

static int __init usb_skel_init(void)

{

int result;

/* 驅動程序注冊到USB子系統中*/

result = usb_register(&skel_driver);

if (result)

err("usb_register failed. Error number %d", result);

return result;

}

當USB驅動程序將要被卸開時,需要把struct usb_driver從內核中注銷。通過調用usb_deregister_driver來完成這個工作,當調用發生時,當前綁定到該驅動程序上的任何USB接口都被斷開,斷開函數將被調用:

static void __exit usb_skel_exit(void)

{

/* 從子系統注銷驅動程序*/

usb_deregister(&skel_driver);

}

探測和斷開: 當一個設備被安裝而USB核心認為該驅動程序應該處理時,探測函數被調用,探測函數檢查傳遞給它的設備信息,確定驅動程序是否真的適合該設備。當驅動程序因為某種原因不應該控制設備時,斷開函數被調用,它可以做一些清理工作。探測回調函數中,USB驅動程序初始化任何可能用于控制USB設備的局部結構體,它還把所需的任何設備相關信息保存到一個局部結構體中,下面是探測函數的部分源碼,我們加以分析。

/* 設置端點信息*/

/* 只使用第一個批量IN和批量OUT端點*/

iface_desc = interface->cur_altsetting;

for (i = 0; i desc.bNumEndpoints; ++i) {

endpoint = &iface_desc->endpoint[i].desc;

if (!dev->bulk_in_endpointAddr &&

(endpoint->bEndpointAddress &USB_DIR_IN) &&

((endpoint->bmAttributes &USB_ENDPOINT_XFERTYPE_MASK)

== USB_ENDPOINT_XFER_BULK)) {

/* 找到一個批量IN端點*/

buffer_size = endpoint->wMaxPacketSize;

dev->bulk_in_size = buffer_size;

dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;

dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);

if (!dev->bulk_in_buffer) {

err("Could not allocate bulk_in_buffer");

goto error;

}

}

if (!dev->bulk_out_endpointAddr &&

!(endpoint->bEndpointAddress &USB_DIR_IN) &&

((endpoint->bmAttributes &USB_ENDPOINT_XFERTYPE_MASK)

== USB_ENDPOINT_XFER_BULK)) {

/* 找到一個批量OUT端點*/

dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;

}

}

if (!(dev->bulk_in_endpointAddr &&dev->bulk_out_endpointAddr)) {

err("Could not find both bulk-in and bulk-out endpoints");

goto error;

}

在探測函數里,這個循環首先訪問該接口中存在的每一個端點,給該端點一個局部指針以便以后訪問:

for (i = 0; i desc.bNumEndpoints; ++i) {

endpoint = &iface_desc->endpoint[i].desc;

在一輪探測過后,我們就有了一個端點,在還沒有發現批量IN類型的端點時,探測該端點方向是否為IN,這可以通過檢查USB_DIR_IN是否包含在bEndpointAddress端點變量有確定,如果是的話,我們在探測該端點類型是否為批量,先用USB_ENDPOINT_XFERTYPE_MASK位掩來取bmAttributes變量的值,然后探測它是否和USB_ENDPOINT_XFER_BULK值匹配:

if (!dev->bulk_out_endpointAddr &&

!(endpoint->bEndpointAddress &USB_DIR_IN) &&

((endpoint->bmAttributes &USB_ENDPOINT_XFERTYPE_MASK)

== USB_ENDPOINT_XFER_BULK))

如果所有這些探測都通過了,驅動程序就知道它已經發現了正確的端點類型,可以把該端點的相關信息保存到一個局部結構體中以便稍后用它來和端點進行通信:

/* 找到一個批量IN類型的端點*/

buffer_size = endpoint->wMaxPacketSize;

dev->bulk_in_size = buffer_size;

dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;

dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);

if (!dev->bulk_in_buffer) {

err("Could not allocate bulk_in_buffer");

goto error;

}

因為USB驅動程序要在設備的生命周期的稍后時間獲取和接口相關聯的局部數據結構體,所以調用了usb_set_intfdata函數,把它保存到struct usb_interface結構體中以便后面的訪問

/* 把數據指針保存到這個接口設備中*/

usb_set_intfdata(interface, dev);

我們以后調用usb_set_intfdata函數來獲取數據。當這一切都完成后,USB驅動程序必須在探測函數中調用usb_register_dev函數來把該設備注冊到USB核心里:

/* 注冊設備到USB核心*/

retval = usb_register_dev(interface, &skel_class);

if (retval) {

/* 有些情況下是不允許注冊驅動程序的*/

err("Not able to get a minor for this device.");

usb_set_intfdata(interface, NULL);

goto error;

}

當一個USB設備被斷開時,和該設備相關聯的所有資源都應該被盡可能的清理掉,在此時,如果已在在探測函數中調用了注冊函數來為該USB設備分配了一個次設備號話,必須調用usb_deregister_dev函數來把次設備號交還給USB核心。在斷開函數中,從接口獲取之前調用usb_set_intfdata設置的任何數據也是很重要的。然后設置struct usb_interface結構體中的數據指針為NULL,以防任何不適當的對該數據的錯誤訪問。

在探測函數中會對每一個接口進行一次探測,所以我們在寫USB驅動程序的時候,只要做好第一個端點,其它的端點就會自動完成探測。在探測函數中我們要注意的是在內核中用結構體struct usb_host_endpoint來描述USB端點,這個結構體在另一個名為struct usb_endpoint_descriptor的結構體中包含了真正的端點信息,struct usb_endpoint_descriptor結構體包含了所有的USB特定的數據,該結構體中我們要關心的幾個字段是:

bEndpointAddress:這個是特定的USB地址,可以結合USB_DIR_IN和USB_DIR_OUT來使用,以確定該端點的數據是傳向設備還是主機。

bmAttributes:這個是端點的類型,這個值可以結合位掩碼USB_ENDPOINT_XFERTYPE_MASK來使用,以確定此端點的類型是USB_ENDPOINT_XFER_ISOC(等時)、USB_ENDPOINT_XFER_BULK(批量)、USB_ENDPOINT_XFER_INT的哪一種。

wMaxPacketSize:這個是端點一次可以處理的最大字節數,驅動程序可以發送數量大于此值的數據到端點,在實際傳輸中,數據量如果大于此值會被分割。

bInterval:這個值只有在端點類型是中斷類型時才起作用,它是端點中斷請求的間隔時間,以毫秒為單位。

提交和控制urb: 當驅動程序有數據要發送到USB設備時(大多數情況是在驅動程序的寫函數中),要分配一個urb來把數據傳輸給設備:

/* 創建一個urb,并且給它分配一個緩存*/

urb = usb_alloc_urb(0, GFP_KERNEL);

if (!urb) {

retval = -ENOMEM;

goto error;

}

當urb被成功分配后,還要創建一個DMA緩沖區來以高效的方式發送數據到設備,傳遞給驅動程序的數據要復制到這塊緩沖中去:

buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);

if (!buf) {

retval = -ENOMEM;

goto error;

}

if (copy_from_user(buf, user_buffer, count)) {

retval = -EFAULT;

goto error;

}

當數據從用戶空間正確復制到局部緩沖區后,urb必須在可以被提交給USB核心之前被正確初始化:

/* 初始化urb */

usb_fill_bulk_urb(urb, dev->udev,

usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),

buf, count, skel_write_bulk_callback, dev);

urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

然后urb就可以被提交給USB核心以傳輸到設備了:

/* 把數據從批量OUT端口發出*/

retval = usb_submit_urb(urb, GFP_KERNEL);

if (retval) {

err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);

goto error;

}

當urb被成功傳輸到USB設備之后,urb回調函數將被USB核心調用,在我們的例子中,我們初始化urb,使它指向skel_write_bulk_callback函數,以下就是該函數:

static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)

{

struct usb_skel *dev;

dev = (struct usb_skel *)urb->context;

if (urb->status &&

!(urb->status == -ENOENT ||

urb->status == -ECONNRESET ||

urb->status == -ESHUTDOWN)) {

dbg("%s - nonzero write bulk status received: %d",

__FUNCTION__, urb->status);

}

/* 釋放已分配的緩沖區*/

usb_buffer_free(urb->dev, urb->transfer_buffer_length,

urb->transfer_buffer, urb->transfer_dma);

}

有時候USB驅動程序只是要發送或者接收一些簡單的數據,驅動程序也可以不用urb來進行數據的傳輸,這是里涉及到兩個簡單的接口函數:usb_bulk_msg和usb_control_msg ,在這個USB框架程序里讀操作就是這樣的一個應用:

/* 進行阻塞的批量讀以從設備獲取數據*/

retval = usb_bulk_msg(dev->udev,

usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),

dev->bulk_in_buffer,

min(dev->bulk_in_size, count),

&count, HZ*10);

/*如果讀成功,復制到用戶空間*/

if (!retval) {

if (copy_to_user(buffer, dev->bulk_in_buffer, count))

retval = -EFAULT;

else

retval = count;

}

usb_bulk_msg接口函數的定義如下:

int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,

void *data,int len,int *actual_length,int timeout);

其參數為:

struct usb_device *usb_dev:指向批量消息所發送的目標USB設備指針。

unsigned int pipe:批量消息所發送目標USB設備的特定端點,此值是調用usb_sndbulkpipe或者usb_rcvbulkpipe來創建的。

void *data:如果是一個OUT端點,它是指向即將發送到設備的數據的指針。如果是IN端點,它是指向從設備讀取的數據應該存放的位置的指針。

int len:data參數所指緩沖區的大小。

int *actual_length:指向保存實際傳輸字節數的位置的指針,至于是傳輸到設備還是從設備接收取決于端點的方向。

int timeout:以Jiffies為單位的等待的超時時間,如果該值為0,該函數一直等待消息的結束。

如果該接口函數調用成功,返回值為0,否則返回一個負的錯誤值。

usb_control_msg接口函數定義如下:

int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8 request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)

除了允許驅動程序發送和接收USB控制消息之外,usb_control_msg函數的運作和usb_bulk_msg函數類似,其參數和usb_bulk_msg的參數有幾個重要區別:

struct usb_device *dev:指向控制消息所發送的目標USB設備的指針。

unsigned int pipe:控制消息所發送的目標USB設備的特定端點,該值是調用usb_sndctrlpipe或usb_rcvctrlpipe來創建的。

__u8 request:控制消息的USB請求值。

__u8 requesttype:控制消息的USB請求類型值。

__u16 value:控制消息的USB消息值。

__u16 index:控制消息的USB消息索引值。

void *data:如果是一個OUT端點,它是指身即將發送到設備的數據的指針。如果是一個IN端點,它是指向從設備讀取的數據應該存放的位置的指針。

__u16 size:data參數所指緩沖區的大小。

int timeout:以Jiffies為單位的應該等待的超時時間,如果為0,該函數將一直等待消息結束。

如果該接口函數調用成功,返回傳輸到設備或者從設備讀取的字節數;如果不成功它返回一個負的錯誤值。

這兩個接口函數都不能在一個中斷上下文中或者持有自旋鎖的情況下調用,同樣,該函數也不能被任何其它函數取消,使用時要謹慎。

我們要給未知的USB設備寫驅動程序,只需要把這個框架程序稍做修改就可以用了,前面我們已經說過要修改制造商和產品的ID號,把0xfff0這兩個值改為未知USB的ID號。

#define USB_SKEL_VENDOR_ID 0xfff0

#define USB_SKEL_PRODUCT_ID 0xfff0

還有就是在探測函數中把需要探測的接口端點類型寫好,在這個框架程序中只探測了批量(USB_ENDPOINT_XFER_BULK)IN和OUT端點,可以在此處使用掩碼(USB_ENDPOINT_XFERTYPE_MASK)讓其探測其它的端點類型,驅動程序會對USB設備的每一個接口進行一次探測,當探測成功后,驅動程序就被綁定到這個接口上。再有就是urb的初始化問題,如果你只寫簡單的USB驅動,這塊不用多加考慮,框架程序里的東西已經夠用了,這里我們簡單介紹三個初始化urb的輔助函數:

usb_fill_int_urb 它的函數原型是這樣的:

void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,

unsigned int pipe,void *transfer_buff,

int buffer_length,usb_complete_t complete,

void *context,int interval);

這個函數用來正確的初始化即將被發送到USB設備的中斷端點的urb。

usb_fill_bulk_urb 它的函數原型是這樣的:

void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,

unsigned int pipe,void *transfer_buffer,

int buffer_length,usb_complete_t complete)

這個函數是用來正確的初始化批量urb端點的。

usb_fill_control_urb 它的函數原型是這樣的:

void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);

這個函數是用來正確初始化控制urb端點的。

還有一個初始化等時urb的,它現在還沒有初始化函數,所以它們在被提交到USB核心前,必須在驅動程序中手工地進行初始化,可以參考內核源代碼樹下的/usr/src/~/drivers/usb/media下的konicawc.c文件。

驅動模塊的編譯、配置和使用

現在我們的驅動程序已經大體寫好了,然后在linux下把它編譯成模塊就可以把驅動模塊插入到內核中運行了,編譯的Makefile文件可以這樣來寫:

ifneq ($(KERNELRELEASE),)

obj-m := xxx.o

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

clean:

rm -rf *.mod.* *.o *.ko .*.ko.* .tmp* .*.mod.o.* .*.o.*

其中xxx是源文件的文件名,在linux下直接執行make就可以生成驅動模塊(xxx.ko)了。生成驅動模塊后使用insmod xxx.ko就可以插入到內核中運行了,用lsmod可以看到你插入到內核中的模塊,也可以從系統中用命令rmmod xxx把模塊卸載掉;如果把編譯出來的驅動模塊拷貝到/lib/modules/~/kernel/drivers/usb/下,然后depmod一下,那么你在插入USB設備的時候,系統就會自動為你加載驅動模塊的;當然這個得有hotplug的支持;加載驅動模塊成功后就會在/dev/下生成設備文件了,如果用命令cat /proc/bus/usb/devices,我們可以看到驅動程序已經綁定到接口上了:

T: Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=12 MxCh= 0

D: Ver= 1.10 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1

P: Vendor=1234 ProdID=2345 Rev= 1.10

C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr= 0mA

I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=test_usb_driver /*我們的驅動*/

E: Ad=01(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms

E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms

此框架程序生成的是skel0(可以自由修改)的設備文件,現在就可以對這個設備文件進行打開、讀寫、關閉等的操作了。

面對層出不窮的新的USB設備,必須有人不斷編寫新的驅動程序以便讓這些設備能夠在linux下正常的工作,從這個意義上講,驅動程序的編寫本身就是一件非常有意義的工作,本文只是起到一個拋磚引玉的作用,幫助那些有志于寫驅動程序的開發人員進一步了解USB驅動程序的設計思路,從而吸引更多的人加入到這個隊伍中來。linux不僅為我們提供了一個頂級質量的操作系統,而且也為我們提供了參與到其未來開發過程的機會,我們完全可以從中得到無盡的快樂!

Linux環境下USB的原理、驅動和配置


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!??!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 5g国产精品影院天天5g天天爽 | 久久福利网 | 在线播放成人毛片免费视 | 国产成人理在线观看视频 | 免费女人18毛片a级毛片视频 | 久草在线观看视频 | 韩国 欧美 日产 国产精品 | 天天干天天拍天天射 | 天天看天天爽 | 精品美女 | 精品一区二区三区免费爱 | 欧美成人精品一区二区三区 | 九九影院在线观看 | 国产农村精品一级毛片视频 | 日本黄 色 成 年 人免费观看 | 欧美三级成人观看 | 国产一区二区在线 |播放 | 午夜dj影院在线视频观看完整 | 国产一级特黄a大片免费 | 欧美日韩色黄大片在线视频 | 97久久精品视频 | 在线看片不卡 | 亚洲欧美国产一区二区三区 | 久久国产视频网 | 国产精品一区二区在线观看 | 国产成人在线播放视频 | 伊人精品视频在线观看 | 性生活视频网 | 国产精品合集久久久久青苹果 | 一本色道久久爱88a 一本色道久久爱88av俺来也 | 国产精品线在线精品国语 | 国产精品福利午夜h视频 | 欧美黄色网页 | 国产成年女一区二区三区 | 97影院秋霞国产精品 | 久久精品在线免费观看 | 成年超爽大片免费视频播放 | 国产精品亚洲精品观看不卡 | 国产精品玖玖玖影院 | 青草青草久热精品视频99 | 精品亚洲一区二区三区在线播放 |