- 本文環境:
- JZ2440V3開發闆
- Linux3.4.2核心
- arm-linux-gcc4.3.2編譯器
- 參考資料:
- 2_USB驅動程式之USB總線驅動程式 (100ask.net)
- 19.Linux-USB總線驅動分析 - 諾謙
1、幾個常見疑惑?
-
為什麼一插上就會有提示資訊?
是因為windows自帶了USB總線驅動程式;
- 那USB總線驅動程式是幹嘛用的?
- 識别USB裝置;
- 給USB裝置找到并安裝對應的驅動程式;
- 提供USB的讀寫函數。
首先,新接入的USB裝置的預設位址(編号)為0,再未配置設定新編号前,PC主機使用0位址和它通信。然後USB總線驅動程式都會給它配置設定一個位址(編号)。PC機想通路USB總線上某個USB裝置時,發出的指令都含有對應的位址(編号)。
- USB的結構是怎樣的?
- USB是一種主從結構。主機叫做Host,從機叫做Device,所有的USB傳輸,都是從USB主機這方發起;USB裝置沒有“主動”通知USB主機的能力。
- 例如:USB滑鼠滑動一下立刻産生資料,但是它還沒有能力通過OC機來讀資料,隻能被動地等待PC機來讀。
- USB可以熱拔插的硬體原理
- 在USB集線器(hub)的每個下遊端口的D+和D-上,分别接了一個15K歐姆的下拉電阻到地。這樣,在集線器的端口懸空時,就被這兩個下拉電阻拉到了低電平。
- 而在USB裝置端,在D+或者D-上接了1.5k歐姆上拉電阻,對于全速和高速裝置,上拉電阻是接在D+上;而低速裝置則是上拉電阻接在D-上。
- 這樣,當裝置插入到集線器時,由1.5k的上拉電阻和15k的下拉電阻分呀,結果就将差分資料線中的一條拉高了。集線器檢測到這個狀态後,它就報告給USB主要制器(或者通過它上一層的集線器報告給USB主要制器),這樣就檢測到裝置的插入了。
- USB高速裝置先是被識别為全速裝置,然後通過HOST和DEVICE兩者之間的确認,再切換到告訴模式的。在高速模式下,是電流傳輸模式,這時将D+上的上拉電阻斷開。
- USB的4大傳輸類型:
- 控制傳輸
- 是每一個USB裝置必須支援的,通常用來擷取裝置描述符、設定裝置的狀态等等。一個USB裝置從插入到最後的拔出這個過程一定會産生控制傳輸(即便這個USB裝置不能被這個系統支援)。
- 中斷傳輸
- 支援中斷傳輸的典型裝置有USB滑鼠、USB鍵盤等等。中斷傳輸不是說我的裝置真正發出一個中斷,然後主機會來讀取資料。它其實是一種輪詢的方式來完成資料的通信。USB裝置會在裝置驅動程式中設定一個參數叫做interval,它是endpoint的一個成員。interval是間隔時間的意思,表示我這個裝置希望主機多長時間來輪詢自己,隻要這個值确定了之後,我主機就會周期性來檢視有沒有資料需要處理。
- 批量處理
- 支援批量傳輸最典型的裝置就是U盤,它進行大數量的資料傳輸,能夠保證資料的準确性,但是時間不是固定的。
- 實時傳輸
- USB攝像頭就是實時傳輸裝置的典型代表,它同樣進行大量資料的傳輸,資料的準确性無法保證,但是對傳輸延遲非常敏感,也就是說對實時性要求比較高
- USB端點:
- 每個USB裝置與主機會有若幹個通信的“端點”。每個端點都有個端點号,除了端點0外,每一個端點隻能工作在一種傳輸類型(控制傳輸、中斷傳輸、批量傳輸、實時傳輸)下,一個傳輸方向。
- 傳輸方向都是基于USB主機的立場說的,比如:滑鼠的資料是從滑鼠傳到PC機,對應的端點稱為“中斷輸入端點”。
- 端點0是裝置的預設控制端點,既能輸出也能輸入,主要用于USB裝置的識别過程。
- USB驅動整體架構:
-
USB主機控制器類型
要想成為一個USB主機,硬體上就必須要有USB主機控制器,USB主機控制器又分為4種接口:
- OHCI(Open Host Controller Inerface):微軟主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的硬體簡單,軟體複雜。
- UHCI(Universal Host Controller Interface):Intel主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的軟體簡單,硬體複雜。
- EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)。
- xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9針腳設針,同時也支援USB2.0、1.1等。
2、USB總線驅動分析
2.1 USB描述符的層次及定義
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-OvFI8va0-1627907220913)(C:\Users\liang\AppData\Roaming\Typora\typora-user-images\image-20210731131536533.png)]
- USB裝置描述符(usb_device_descriptor)
- USB配置描述符(usb_config_descriptor)
- USB接口描述符(usb_interface_descriptor)
- USB端點描述符(usb_endpoint_descriptor)、
一個裝置描述符可以有多個配置描述符;
一個配置描述符可以有多個接口描述符(比如聲霸卡驅動就有兩個接口:錄音接口和播放接口)
一個接口描述符可以有多個端點描述符;
2.1.1 USB裝置描述符(usb_device_descriptor)
USB裝置描述符結構體如下所示:
struct usb_device_descriptor {
__u8 bLength; //本描述符的size
__u8 bDescriptorType; //描述符的類型,這裡是裝置描述符DEVICE
__u16 bcdUSB; //指明usb的版本,比如usb2.0
__u8 bDeviceClass; //類
__u8 bDeviceSubClass; //子類
__u8 bDeviceProtocol; //指定協定
__u8 bMaxPacketSize0; //端點0對應的最大包大小
__u16 idVendor; //廠家ID
__u16 idProduct; //産品ID
__u16 bcdDevice; //裝置的釋出号
__u8 iManufacturer; //字元串描述符中廠家ID的索引
__u8 iProduct; //字元串描述符中産品ID的索引
__u8 iSerialNumber; //字元串描述符中裝置序列号的索引
__u8 bNumConfigurations; //配置描述符的個數,表示有多少個配置描述符
} __attribute__ ((packed));
USB裝置描述符位于USB裝置結構體usb_device中的成員descriptor中。同樣地,配置、接口、端點描述符也是位于USB配置、接口、端點結構體中,不過這3個對于我們寫驅動的不是很常用。
- usb_device結構體如下所示:
struct usb_device {
int devnum; //裝置号,是在USB總線的位址
char devpath [16]; //用于消息的裝置ID字元串
enum usb_device_state state; //裝置狀态:已配置、未連接配接等等
enum usb_device_speed speed; //裝置速度:高速、全速、低速或錯誤
struct usb_tt *tt; //處理傳輸者資訊;用于低速、全速裝置和高速HUB
int ttport; //位于tt HUB的裝置口
unsigned int toggle[2]; //每個端點占一位,表明端點的方向([0] = IN, [1] = OUT)
struct usb_device *parent; //上一級HUB指針
struct usb_bus *bus; //總線指針
struct usb_host_endpoint ep0; //端點0資料
struct device dev; //一般的裝置接口資料結構
struct usb_device_descriptor descriptor; //USB裝置描述符,
struct usb_host_config *config; //裝置的所有配置結構體,配置結構體裡包含了配置描述符
struct usb_host_config *actconfig; //被激活的裝置配置
struct usb_host_endpoint *ep_in[16]; //輸入端點數組
struct usb_host_endpoint *ep_out[16]; //輸出端點數組
char **rawdescriptors; //每個配置的raw描述符
unsigned short bus_mA; //可使用的總線電流
u8 portnum; //父端口号
u8 level; //USB HUB的層數
unsigned can_submit:1; //URB可被送出标志
unsigned discon_suspended:1; //暫停時斷開标志
unsigned persist_enabled:1; //USB_PERSIST使能标志
unsigned have_langid:1; //string_langid存在标志
unsigned authorized:1;
unsigned authenticated:1;
unsigned wusb:1; //無線USB标志
int string_langid; //字元串語言ID
/* static strings from the device */ //裝置的靜态字元串
char *product; //産品名
char *manufacturer; //廠商名
char *serial; //産品串号
struct list_head filelist; //此裝置打開的usbfs檔案
#ifdef CONFIG_USB_DEVICE_CLASS
struct device *usb_classdev; //使用者空間通路的為usbfs裝置建立的USB類裝置
#endif
#ifdef CONFIG_USB_DEVICEFS
struct dentry *usbfs_dentry; //裝置的usbfs入口
#endif
int maxchild; //(若為HUB)接口數
struct usb_device *children[USB_MAXCHILDREN]; //連接配接在這個HUB上的子裝置
int pm_usage_cnt; //自動挂起的使用計數
u32 quirks;
atomic_t urbnum; //這個裝置所送出的URB計數
unsigned long active_duration; //激活後使用計時
#ifdef CONFIG_PM //電源管理相關
struct delayed_work autosuspend; //自動挂起的延時
struct work_struct autoresume; //(中斷的)自動喚醒需求
struct mutex pm_mutex; //PM的互斥鎖
unsigned long last_busy; //最後使用的時間
int autosuspend_delay;
unsigned long connect_time; //第一次連接配接的時間
unsigned auto_pm:1; //自動挂起/喚醒
unsigned do_remote_wakeup:1; //遠端喚醒
unsigned reset_resume:1; //使用複位替代喚醒
unsigned autosuspend_disabled:1; //挂起關閉
unsigned autoresume_disabled:1; //喚醒關閉
unsigned skip_sys_resume:1; //跳過下個系統喚醒
#endif
struct wusb_dev *wusb_dev; //(如果為無線USB)連接配接到WUSB特定的資料結構
};
2.2.2 USB配置描述符
struct usb_config_descriptor {
__u8 bLength; //描述符的長度
__u8 bDescriptorType; //描述符類型的編号
__le16 wTotalLength; //配置所傳回的所有資料的大小
__u8 bNumInterfaces; //配置所支援的接口個數, 表示有多少個接口描述符
__u8 bConfigurationValue; //Set_Configuration指令需要的參數值
__u8 iConfiguration; //描述該配置的字元串的索引值
__u8 bmAttributes; //供電模式的選擇
__u8 bMaxPower; //裝置從總線提取的最大電流
} __attribute__ ((packed));
2.2.3 接口描述符(邏輯裝置)
USB接口隻處理一種USB邏輯連接配接。一個USB接口代表一個邏輯上的裝置,比如聲霸卡驅動就有兩個接口:錄音接口和播放接口。這可以在windows系統中看出,有時插入一個USB裝置後,系統會識别出多個裝置,并安裝相應多個的驅動。
struct usb_interface_descriptor {
__u8 bLength; //描述符的長度
__u8 bDescriptorType; //描述符類型的編号
__u8 bInterfaceNumber; //接口的編号
__u8 bAlternateSetting; //備用的接口描述符編号,提供不同品質的服務參數.
__u8 bNumEndpoints; //要使用的端點個數(不包括端點0), 表示有多少個端點描述符,比如滑鼠就隻有一個端點
__u8 bInterfaceClass; //接口類型,與驅動的id_table對應
__u8 bInterfaceSubClass; //接口子類型
__u8 bInterfaceProtocol; //接口所遵循的協定
__u8 iInterface; //描述該接口的字元串索引值
} __attribute__ ((packed)
它位于usb_interface->cur_altsetting->desc 這個成員結構體裡,
- usb_interface結構體如下所示:
struct usb_interface {
/* 包含所有可用于該接口的可選設定的接口結構數組。每個 struct usb_host_interface 包含一套端點配置(即struct usb_host_endpoint結構所定義的端點配置。這些接口結構沒有特别的順序。*/
struct usb_host_interface *altsetting;
/* 指向altsetting内部的指針,表示目前激活的接口配置*/
struct usb_host_interface *cur_altsetting;
unsigned num_altsetting; /* 可選設定的數量*/
/* If there is an interface association descriptor then it will list the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;
/* 如果綁定到這個接口的 USB 驅動使用 USB 主裝置号, 這個變量包含由 USB 核心配置設定給接口的次裝置号. 這隻在一個成功的調用 usb_register_dev後才有效。*/
int minor;
... ...
}
- cur_altsetting成員的結構體是usb_host_interface,如下:
struct usb_host_interface {
struct usb_interface_descriptor desc; //目前被激活的接口描述符
struct usb_host_endpoint *endpoint; /* 這個接口的所有端點結構體的聯合數組*/
char *string; /* 接口描述字元串 */
unsigned char *extra; /* 額外的描述符 */
int extralen;
};
2.2.4 端點描述符
struct usb_endpoint_descriptor {
__u8 bLength; //描述符的長度
__u8 bDescriptorType; //描述符類型的編号
__u8 bEndpointAddress; //端點編号,比如端點1,就是1
__u8 bmAttributes; //端點的屬性, 比如中斷傳輸類型,輸入類型
__le16 wMaxPacketSize; //一個端點的最大包大小,
__u8 bInterval; //間隔時間,用在中斷傳輸上,比如間隔時間查詢滑鼠的資料
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
比如端點0就位于usb_interface->cur_altsetting->desc->endpoint[0].desc
- endpoint的結構體為usb_host_endpoint,如下所示:
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端點描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp; //超快速端點描述符
struct list_head urb_list; //本端口對應的urb連結清單
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled; //使能的話urb才能被送出到此端口
};
2.2 USB總線驅動如何識别裝置
由于核心自帶了USB驅動,是以我們先插入一個USB鍵盤到開發闆上看列印資訊發現以下字段:
如下圖,找到第一段話是位于drivers/usb/core/hub.c的第2186行:
這個hub其實就是我們的USB主機控制器的集線器,用來管理多個USB接口
2.2.1 drivers/usb/core/hub.c的第2186行位于hub_port_init()函數裡
它又是被誰調用的,如下圖所示,我們搜尋到它是通過hub_thread()函數調用的
hub_thread()函數如下:
static int hub_thread(void *__unused)
{
do {
hub_events(); //執行一次hub事件函數
wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop()); //(1).每次執行一次hub事件,都會進入一次等待事件中斷函數
try_to_freeze();
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}
從上面函數中得到, 要想執行hub_events(),都要等待khubd_wait這個中斷喚醒才行。
2.2.2 搜尋”khubd_wait”,看看是被誰喚醒
找到該中斷在kick_khubd()函數中喚醒,代碼如下:
static void kick_khubd(struct usb_hub *hub)
{
unsigned long flags;
to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
spin_lock_irqsave(&hub_event_lock, flags);
if (list_empty(&hub->event_list)) {
list_add_tail(&hub->event_list, &hub_event_list);
wake_up(&khubd_wait); //喚醒khubd_wait這個中斷
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}
2.2.3 繼續搜尋kick_khubd,發現被hub_irq()函數中調用
顯然,就是當USB裝置插入後,D+或D-就會被拉高,然後USB主機控制器就會産生一個hub_irq中斷.
2.2.4 分析hub_port_connect_change()函數如何連接配接端口
static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
{
... ...
udev = usb_alloc_dev(hdev, hdev->bus, port1); //(1)注冊一個usb_device,然後會放在usb總線上
usb_set_device_state(udev, USB_STATE_POWERED); //設定注冊的USB裝置的狀态标志
... ...
choose_address(udev); //(2)給新的裝置配置設定一個位址編号
status = hub_port_init(hub, udev, port1, i); //(3)初始化端口,與USB裝置建立連接配接
... ...
status = usb_new_device(udev); //(4)建立USB裝置,與USB裝置驅動連接配接
... ...
}
是以最終流程圖如下:
2.2.5 進入hub_port_connect_change()->usb_alloc_dev(),看它是怎麼設定usb_device
usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
struct usb_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //配置設定一個usb_device裝置結構體
... ...
device_initialize(&dev->dev); //初始化usb_device
dev->dev.bus = &usb_bus_type; //(1)設定usb_device的成員device->bus等于usb_bus總線
dev->dev.type = &usb_device_type; //設定usb_device的成員device->type等于usb_device_type
... ...
return dev; //傳回一個usb_device結構體
}
- 在第17行上,設定device成員,主要是用來後面8.2小節,注冊usb總線的device表上.
- 其中usb_bus_type是一個全局變量, 它和我們之前學的platform平台總線相似,屬于USB總線, 是Linux中bus的一種.
- 如下圖所示,每當建立一個USB裝置,或者USB裝置驅動時,USB總線都會調用match成員來比對一次,使USB裝置和USB裝置驅動聯系起來.
usb_bus_type結構體如下:
struct bus_type usb_bus_type = {
.name = "usb", //總線名稱,存在/sys/bus下
.match = usb_device_match, //比對函數,比對成功就會調用usb_driver驅動的probe函數成員
.uevent = usb_uevent, //事件函數
.suspend = usb_suspend, //休眠函數
.resume = usb_resume, //喚醒函數
};
2.2.6 進入hub_port_connect_change()->choose_address(),看它是怎麼配置設定位址編号的
static void choose_address(struct usb_device *udev)
{
int devnum;
struct usb_bus *bus = udev->bus;
devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
//在bus->devnum_next~128區間中,循環查找下一個非0(沒有裝置)的編号
if (devnum >= 128) //若編号大于等于128,說明沒有找到空餘的位址編号,從頭開始找
devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); //設定下次尋址的區間+1
if (devnum < 128) {
set_bit(devnum, bus->devmap.devicemap); //設定位
udev->devnum = devnum;
}
}
從上面代碼中分析到每次的位址編号是連續加的,USB接口最大能接127個裝置,我們連續插拔兩次USB鍵盤,也可以看出,如下圖所示:
2.2.7 再看hub_port_connect_change()->hub_port_init()函數是如何來實作連接配接USB裝置的
static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
{
... ...
for (j = 0; j < SET_ADDRESS_TRIES; ++j){
retval = hub_set_address(udev); //(1)設定位址,告訴USB裝置新的位址編号
if (retval >= 0)
break;
msleep(200);
}
retval = usb_get_device_descriptor(udev, 8); //(2)獲得USB裝置描述符前8個位元組
... ...
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //重新擷取裝置描述符資訊
... ...
}
- 上面第6行中,hub_set_address()函數主要是用來告訴USB裝置新的位址編号,hub_set_address()函數如下:
static int hub_set_address(struct usb_device *udev)
{
int retval;
... ...
retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT); //(1.1)等待傳輸完成
if (retval == 0) { //設定新的位址,傳輸完成,傳回0
usb_set_device_state(udev, USB_STATE_ADDRESS); //設定狀态标志
ep0_reinit(udev);
}
return retval;
}
- usb_control_msg()函數就是用來讓USB主機控制器把一個控制封包發給USB裝置,如果傳輸完成就傳回0.其中參數udev表示目标裝置;使用的管道為usb_sndaddr0pipe(),也就是預設的位址0加上控制端點号0; USB_REQ_SET_ADDRESS表示指令碼,既設定位址; udev->devnum表示要設定目标裝置的裝置号;允許等待傳輸完成的時間為5秒,因為USB_CTRL_SET_TIMEOUT定義為5000。
- 上面第12行中,usb_get_device_descriptor()函數主要是擷取目标裝置描述符前8個位元組,為什麼先隻開始讀取8個位元組?是因為開始時還不知道對方所支援的信包容量,這8個位元組是每個裝置都有的,後面再根據裝置的資料,通過usb_get_device_descriptor()重讀一次目标裝置的裝置描述結構.
其中USB裝置描述符結構體如下所示:
struct usb_device_descriptor {
__u8 bLength; //本描述符的size
__u8 bDescriptorType; //描述符的類型,這裡是裝置描述符DEVICE
__u16 bcdUSB; //指明usb的版本,比如usb2.0
__u8 bDeviceClass; //類
__u8 bDeviceSubClass; //子類
__u8 bDeviceProtocol; //指定協定
__u8 bMaxPacketSize0; //端點0對應的最大包大小
__u16 idVendor; //廠家ID
__u16 idProduct; //産品ID
__u16 bcdDevice; //裝置的釋出号
__u8 iManufacturer; //字元串描述符中廠家ID的索引
__u8 iProduct; //字元串描述符中産品ID的索引
__u8 iSerialNumber; //字元串描述符中裝置序列号的索引
__u8 bNumConfigurations; //可能的配置的數目
} __attribute__ ((packed));
2.2.8 再看hub_port_connect_change()->usb_new_device()函數是如何來建立USB裝置的
int usb_new_device(struct usb_device *udev)
{
... ...
err = usb_get_configuration(udev); //(1)擷取配置描述塊
... ...
err = device_add(&udev->dev); // (2)把device放入bus的dev連結清單中,并尋找對應的裝置驅動
}
- 其中usb_get_configuration()函數如下,就是擷取各個配置
int usb_get_configuration(struct usb_device *dev)
{
... ...
/* USB_MAXCONFIG 定義為8,表示裝置描述塊下有最多不能超過8個配置描述塊 */
/*ncfg表示 裝置描述塊下 有多少個配置描述塊 */
if (ncfg > USB_MAXCONFIG) {
dev_warn(ddev, "too many configurations: %d, "
"using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
}
... ...
for (cfgno = 0; cfgno < ncfg; cfgno++){ //for循環,從USB裝置裡依次讀入所有配置描述塊
//每次先讀取USB_DT_CONFIG_SIZE個位元組,也就是9個位元組,暫放到buffer中
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);
... ...
//通過wTotalLength,知道實際資料大小
length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);
bigbuffer = kmalloc(length, GFP_KERNEL); //然後再來配置設定足夠大的空間
... ...
//在調用一次usb_get_descriptor,把整個配置描述塊讀出來,放到bigbuffer中
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length);
... ...
//再将bigbuffer位址放在rawdescriptors所指的指針數組中
dev->rawdescriptors[cfgno] = bigbuffer;
result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno],
bigbuffer, length); //最後在解析每個配置塊
}
... ...
}
- 其中device_add ()函數如下
int usb_get_configuration(struct usb_device *dev)
{
dev = get_device(dev); //使dev等于usb_device下的device成員
... ...
if ((error = bus_add_device(dev))) // 把這個裝置添加到dev->bus的device表中
goto BusError;
... ...
bus_attach_device(dev); //來比對對應的驅動程式
... ...
}
當bus_attach_device()函數比對成功,就會調用驅動的probe函數
2.2.9 再看usb_bus_type這個的成員usb_device_match函數是如何比對的
usb_device_match函數如下所示:
static int usb_device_match(struct device *dev, struct device_driver *drv)
{
if (is_usb_device(dev)) { //判斷是不是USB裝置
if (!is_usb_device_driver(drv))
return 0;
return 1;
}
else
{ //否則就是USB驅動或者USB裝置的接口
struct usb_interface *intf;
struct usb_driver *usb_drv;
const struct usb_device_id *id;
if (is_usb_device_driver(drv)) //如果是USB驅動,就不需要比對,直接return
return 0;
intf = to_usb_interface(dev); //擷取USB裝置的接口
usb_drv = to_usb_driver(drv); //擷取USB驅動
id = usb_match_id(intf, usb_drv->id_table); //比對USB驅動的成員id_table
if (id)
return 1;
id = usb_match_dynamic_id(intf, usb_drv);
if (id)
return 1;
}
return 0;
}
顯然就是比對USB驅動的id_table
2.2.10 USB驅動的id_table該如何定義
id_table的結構體為usb_device_id,如下所示:
struct usb_device_id {
__u16 match_flags; //與usb裝置比對那種類型?比較類型的宏如下:
//USB_DEVICE_ID_MATCH_INT_INFO : 用于比對裝置的接口描述符的3個成員
//USB_DEVICE_ID_MATCH_DEV_INFO: 用于比對裝置描述符的3個成員
//USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION: 用于比對特定的USB裝置的4個成員
//USB_DEVICE_ID_MATCH_DEVICE:用于比對特定的USB裝置的2個成員(idVendor和idProduct)
/* 以下4個用比對描述特定的USB裝置 */
__u16 idVendor; //廠家ID
__u16 idProduct; //産品ID
__u16 bcdDevice_lo; //裝置的低版本号
__u16 bcdDevice_hi; //裝置的高版本号
/*以下3個就是用于比較裝置描述符的*/
__u8 bDeviceClass; //裝置類
__u8 bDeviceSubClass; //裝置子類
__u8 bDeviceProtocol; //裝置協定
/* 以下3個就是用于比較裝置的接口描述符的 */
__u8 bInterfaceClass; //接口類型
__u8 bInterfaceSubClass; //接口子類型
__u8 bInterfaceProtocol; //接口所遵循的協定
/* not matched against */
kernel_ulong_t driver_info;
};
參考/drivers/hid/usbhid/usbmouse.c(核心自帶的USB滑鼠驅動),看它是如何使用的:
發現,它是通過**USB_INTERFACE_INFO()**這個宏定義的.該宏如下所示:
#define USB_INTERFACE_INFO(cl,sc,pr)\
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO,//設定id_table的.match_flags成員
.bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
//設定id_table的3個成員,用于與比對USB裝置的3個成員
然後将上圖裡的usb_mouse_id_table []裡的3個值代入宏**USB_INTERFACE_INFO(cl,sc,pr)**中:
.bInterfaceClass =USB_INTERFACE_CLASS_HID;
//設定比對USB的接口類型為HID類, 因為USB_INTERFACE_CLASS_HID=0x03
//HID類是屬于人機互動的裝置,比如:USB鍵盤,USB滑鼠,USB觸摸闆,USB遊戲操作杆都要填入0X03
.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;
//設定比對USB的接口子類型為啟動裝置
.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
//設定比對USB的接口協定為USB滑鼠的協定,等于2
//當.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD時,表示USB鍵盤的協定
如下圖,我們也可以通過windows上也可以找到滑鼠的協定号,也是2:
其中VID:表示廠家(vendor)ID
PID:表示産品(Product) ID
總結:當我們插上USB裝置時,系統就會擷取USB裝置的裝置、配置、接口、端點的資料,并建立新裝置,是以我們的驅動就需要寫id_table來比對該USB裝置。
3、USB裝置(滑鼠)驅動編寫
- 達到的目的:滑鼠左鍵=按鍵L,滑鼠右鍵=按鍵S,滑鼠中鍵=Enter鍵
- .probe函數的結構:
- 1 配置設定input_dev
- 2 設定
- 3 注冊
- 硬體操作
- 使用USB總線驅動函數的讀寫函數來收發資料
- 怎麼寫USB裝置驅動程式:
- 1 配置設定/設定usb_driver結構體
- .name
- .id_table
- .probe
- .disconnect
- 2 注冊
- 代碼編寫步驟如下:
0 定義全局變量 :usb_driver結構體、input_dev指針結構體 、虛拟位址緩存區、DMA位址緩存區
1 在入口函數中
- 通過usb_register()函數注冊usb_driver結構體
2 在usb_driver的probe函數中
- 配置設定一個input_dev結構體
- 設定input_dev支援L、S、回車、3個按鍵事件
- 注冊input_dev結構體
- 設定USB資料傳輸:
->4.1)通過usb_rcvintpipe()建立一個接收中斷類型的端點管道pipe,用于端點和資料緩沖區之間的連接配接
->4.2)通過usb_buffer_alloc()申請USB緩沖區
->4.3)申請并初始化urb結構體(urb:用來傳輸資料)
->4.4) 因為我們2440支援DMA,是以要告訴urb結構體,使用DMA緩沖區位址
->4.5)使用usb_submit_urb()送出urb
3 在滑鼠中斷函數中
1)判斷緩存區資料是否改變,若改變則上傳滑鼠事件
2)使用usb_submit_urb()送出urb
4 在usb_driver的disconnect函數中
1)通過usb_kill_urb()殺掉送出到核心中的urb
2)釋放urb
3)釋放USB緩存區
4)登出input_device,釋放input_device
5 在出口函數中
3.1 先寫驅動主架構
/*
* drivers\hid\usbhid\usbmouse.c
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
/* 1. 配置設定/設定usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int usbmouse_as_key_init(void)
{
/* 2. 注冊 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
3.2 編寫.id_table
- 該USB裝置驅動比對的是接口描述符中的HID類 & BOOT子類 & MOUSE協定的裝置。
static struct usb_device_id usbmouse_as_key_id_table[] = {
{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,\
USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE)
},
//如果需要比對USB裝置的廠家和産品ID,可以添加{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
3.3 編寫.probe函數
- 開始的時候可以僅在該函數中列印一條語句,待整個架構測試通過後再逐漸添加inputceng 。
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf); //獲得usb裝置資訊
/* 以下語句可以用來列印USB裝置的廠家及産品id資訊
*printk("Found usbmouse!\n");
*printk("bcdUSB = %x\n",dev->descriptor.bcdUSB);
*printk("VID = 0X%x\n",dev->descriptor.idVendor);
*printk("PID = 0X%x\n",dev->descriptor.idProduct);
*/
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;
interface = intf->cur_altsetting;
if (interface->desc.nNumEndpoints != 1) //除了端點0之外,滑鼠的端點數如果不是1的話,則傳回錯誤
return -ENODEV;
endpoint = &interface->endpoint[0].desc; //得到第一個非零endpoint的描述符
if (!usb_endpoint_is_int_in(endpoint)) //如果不是中斷輸入型端點的話,傳回錯誤
return -ENODEV;
/* a. 配置設定一個input_dev */
uk_dev = input_allocate_device(); //在檔案開頭先定義輸入裝置(input_dev)指針uk_dev
/* b. 設定 */
/* b.1 能産生哪類事件 */
set_bit(EV_KEY, uk_dev->evbit); //能産生按鍵類事件
set_bit(EV_REP, uk_dev->evbit); //能産生按鍵的重複類事件
/* b.2 能産生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注冊 */
input_register_device(uk_dev);
/* d. 硬體相關操作 */
/* 資料傳輸“三要素”: 源,目的,長度 */
/* 1源: USB裝置的某個端點 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 2長度: 要在檔案開頭定義一個長度變量,int len*/
len = endpoint->wMaxPacketSize;
/* 3目的: (1.在檔案開頭定義一個字元串指針,char * usb_buf)
* (2.在檔案開頭定義一個usb_buf的實體位址,dma_addr_t * usb_buf_phys)
*/
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 1配置設定usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL); //先要在檔案開頭定義一個urb指針,即urb * uk_urb
/* 2使用之前的三要素設定urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 3使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
}
3.4 編寫USB中斷函數
static void usbmouse_as_key_irq(struct urb *urb)
{
//為了調試,先列印usb_buf中的資料
#if 0
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
//usb_buf[]裡存放的是滑鼠按鍵值、x坐标、y坐标和滾輪的值
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
#endif
//上報事件(USB總線驅動是不知道usb資料含義的,隻有在USB裝置驅動中加以解析)
#if 1
/* USB滑鼠資料含義
* data[0]: bit0-左鍵(1-按下, 0-松開)
* bit1-右鍵(1-按下, 0-松開)
* bit2-中鍵(1-按下, 0-松開)
*/
static unsigned char pre_val;
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0))) //如果上次資料的Bit0不等于現在的值
{
/* 左鍵發生了變化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右鍵發生了變化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中鍵發生了變化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[0];
#endif
/* 重新送出urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
3.5 編寫.disconnect函數
- 開始的時候可以僅在該函數中列印一條語句,待整個架構測試通過後再逐漸添加inputceng 。
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
//printk("disconnect usbmouse!\n");
usb_kill_urb(uk_urb); //殺死urb
usb_free_urb(uk_urb); //釋放urb
usb_free_coherent(dev, len, usb_buf, usb_buf_phys); //釋放usb buffer
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
4、測試
4.1 去掉核心滑鼠驅動支援
cd linux3.4.2
make menuconfig
->Device Drivers
->HID Devices
-> <>USB Human Interface Device (full HID) support
make uImage
4.2 使用新核心啟動開發闆
tftp 30000000 uImage //将新的核心複制到tftp共享目錄後,下載下傳到開發闆核心
//nfs 30000000 主機IP:/nfs_root/uImage //或者使用nfs指令下載下傳新核心
bootm 30000000 //從核心的30000000位址處啟動核心
4.3 安裝自己編譯的USB裝置驅動
mount -t nfs -o nolock 主機IP:/nfs_root /mnt //挂接網絡檔案系統
insmod usbmouse_as_key.ko //核心啟動後,安裝自己編譯的USB裝置驅動
4.4 測試
- 在開發闆上接入、拔出USB滑鼠,觀察序列槽輸出
- 将滑鼠當作鍵盤,輸入ls+回車指令
# ls /dev/event* //觀察目前系統有沒有其它event裝置
ls:/dev/event*:No such file or directory
接上USB滑鼠
usb 1-1:configuration #1 chosen from 1 choice
input:Unspecified device as /class/input/input0
# ls /dev/event*
/dev/event0 //對應我們剛接上去的滑鼠
# cat /dev/tty1 //将滑鼠作為鍵盤,觀察tty1口輸出
l(按下左鍵)s(按下右鍵)
(按下回車)
llllll(按住左鍵)
5、整體代碼
- usbmouse_as_key.c
/*
* drivers\hid\usbhid\usbmouse.c
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
static struct input_dev *uk_dev;
static char *usb_buf; //使用虛拟位址的usb buffer
static dma_addr_t usb_buf_phys; //使用實體位址的DMA緩沖區
static int len; //usb buffer 的大小
static struct urb *uk_urb; //usb請求塊
static struct usb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val;
#if 0
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
#endif
/* USB滑鼠資料含義
* data[0]: bit0-左鍵, 1-按下, 0-松開
* bit1-右鍵, 1-按下, 0-松開
* bit2-中鍵, 1-按下, 0-松開
*
*/
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
{
/* 左鍵發生了變化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右鍵發生了變化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中鍵發生了變化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[0];
/* 重新送出urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
/* a. 配置設定一個input_dev */
uk_dev = input_allocate_device();
/* b. 設定 */
/* b.1 能産生哪類事件 */
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_REP, uk_dev->evbit);
/* b.2 能産生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注冊 */
input_register_device(uk_dev);
/* d. 硬體相關操作 */
/* 資料傳輸3要素: 源,目的,長度 */
/* 源: USB裝置的某個端點 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 長度: */
len = endpoint->wMaxPacketSize;
/* 目的: */
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 配置設定usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素設定urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
//printk("disconnect usbmouse!\n");
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_free_coherent(dev, len, usb_buf, usb_buf_phys);
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
/* 1. 配置設定/設定usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int usbmouse_as_key_init(void)
{
/* 2. 注冊 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
- Makefile檔案
KERN_DIR = /home/leon/linux-3.4.2
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += usbmouse_as_key.o