/* AUTHOR: Pinus
* Creat on : 2018-11-5
* KERNEL : linux-4.4.145
* REFS : Linux USB驅動學習總結(二)---- USB設備驅動
Linux USB 驅動開發(三)—— 編寫USB 驅動程序
*/
USB驅動程序框架:
app:
-------------------------------------------
USB設備驅動程序 // 知道數據含義
內核 --------------------------------------
USB總線驅動程序 // 1. 識別, 2. 找到匹配的設備驅動, 3. 提供USB讀寫函數 (它不知道數據含義)
-------------------------------------------
USB主機控制器
UHCI OHCI EHCI
硬件 -----------
USB設備
| UHCI: intel, 低速(1.5Mbps)/全速(12Mbps)
| OHCI: microsoft 低速/全速
| EHCI: 高速(480Mbps)
USB總線驅動程序的作用
1. 識別USB設備
1.1 分配地址
1.2 並告訴USB設備(set address)
1.3 發出命令獲取描述符
2. 查找並安裝對應的設備驅動程序
3. 提供USB讀寫函數
===========================================================
前面學習了USB驅動的一些基礎概念與重要的數據結構,那麼究竟如何編寫一個USB 驅動程序呢?編寫與一個USB設備驅動程序的方法和其他總線驅動方式類似,驅動程序把驅動程序對象註冊到USB子系統中,稍後再使用製造商和設備標識來判斷是否安裝了硬件。當然,這些製造商和設備標識需要我們編寫進USB 驅動程序中。
USB 驅動程序依然遵循設備模型 —— 總線、設備、驅動。和其他總線設備驅動編寫一樣,所有的USB驅動程序都必須創建的主要結構體是 struct usb_driver,它們向USB 核心代碼描述了USB 驅動程序。但這是個外殼,只是實現設備和總線的掛接,具體的USB 設備是什麼樣的,如何實現的,比如一個字符設備,我們還需填寫相應的文件操作接口 ,下面我們從外到裏進行剖析,學習如何搭建這樣的一個USB驅動外殼框架:
實際驅動編寫
這是根據韋東山教程編寫的簡化的鼠標驅動,將鼠標看做三個按鍵(左鍵,滾輪按鍵,右鍵)
/* 目標:usb鼠標用作按鍵
* 左:L
* 右:S
* 中:Enter
*/
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 */
};
/* 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 __init int usbmouse_as_key_init(void)
{
usb_register(&usbmouse_as_key_driver); // 註冊驅動
return 0;
}
static __exit 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");
接下來分析,這個驅動程序做了些什麼?這些操作又有什麼意義?
usb_register(&usbmouse_as_key_driver); // 註冊驅動
根據經驗,這就是一個註冊的函數,簡單追溯一下注冊流程
usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
usb_register_driver(struct usb_driver *new_driver, struct module *owner, const char *mod_name)
|
driver_register(&new_driver->drvwrap.driver);
usb_create_newid_files(new_driver)
driver_register()這是讓人熟悉的函數啊,在分析platform_driver_register(drv) 時也調用了這個函數,看來是內核的通用總線驅動函數啊,其操作主要是根據bus type將new drv添加進相應總線驅動鏈表,並進行一次driver_attach,嘗試與具體設備連接(usb採用id table的方式進行匹配),如果成功掛接則調用new drv->probe。具體可以參考(3.7)一個按鍵所能涉及的:設備驅動分層分離的概念(platform bus)
由此,接下來分析 usbmouse_as_key_probe() 函數
首先涉及了struct usb_device ,這是USB設備的內核表示
struct usb_device {
int devnum; // USB設備號;在USB總線上的地址
char devpath[16]; // 爲了在信息中使用的設備ID字符串 (e.g., /port/...)
u32 route; //與XHCI一起使用的樹拓撲六進制字符串
enum usb_device_state state; //設備的狀態,此時處於configured狀態而不是attached狀態
enum usb_device_speed speed; //表示設備是高速/全速/低速 (or error)
struct usb_tt *tt; //事務傳輸信息,用於低速/全速設備,以及高速hub
int ttport; //usb設備在tt hub上的port
unsigned int toggle[2]; // 0代表IN端點,1代表OUT端點
struct usb_device *parent; //代表hub,除非你是root hub
struct usb_bus *bus; //設備所屬的usb總線
struct usb_host_endpoint ep0; //端點0(默認的控制pipe)
struct device dev; //通用設備接口
struct usb_device_descriptor descriptor; //usb設備描述符,對應usb協議
struct usb_host_bos *bos; //USB設備BOS描述符集
struct usb_host_config *config; //設備所對應的所有配置
struct usb_host_config *actconfig; //當前活躍的配置
struct usb_host_endpoint *ep_in[16]; //IN端點數組
struct usb_host_endpoint *ep_out[16]; //OUT端點數組
char **rawdescriptors; //每個配置的原始描述符
unsigned short bus_mA; //目前可從總線獲得
u8 portnum; // 父端口號(默認是1)
u8 level; //級別:USB集線器祖先數 usb hub的數量
unsigned can_submit:1; //urb可以被提交
unsigned persist_enabled:1; // 設備持續啓用
unsigned have_langid:1; // whether string_langid is valid
unsigned authorized:1; //授權:(用戶空間)策略決定是否授權該設備使用或不使用。默認情況下,有線USB設備是授權的。無線USB設備不是,直到我們從用戶空間授權他們。
unsigned wusb:1; //是無線usb設備
unsigned lpm_capable:1; //設備支持lpm
unsigned usb2_hw_lpm_capable:1; //設備可以執行USB2硬件LPM
unsigned usb2_hw_lpm_besl_capable:1; // 設備可以執行USB2硬件BESL LPM
unsigned usb2_hw_lpm_enabled:1; //使能執行USB2硬件LPM
unsigned usb2_hw_lpm_allowed:1; //用戶空間允許USB 2.0 LPM被使能
unsigned usb3_lpm_enabled:1; //使能執行USB3硬件LPM
unsigned usb3_lpm_u1_enabled:1; //USB3硬件 U1 LPM使能
unsigned usb3_lpm_u2_enabled:1; //USB3硬件 U2 LPM使能
int string_langid; //字符串的語言ID
/* static strings from the device 從設備獲得的固定字符串 */
char *product; //產品ID字符串,如果存在
char *manufacturer; //廠家ID字符串,如果存在
char *serial; //串口號字符串,如果存在
struct list_head filelist; //爲着被設備而打開的usb文件系統的文件列表
int maxchild; //如果是hub的接口,總端口數
u32 quirks; // 整個裝置的怪癖
atomic_t urbnum; //對整個設備來說被提交的urb的個數
unsigned long active_duration; //活躍時間,設備不被掛起的總時間
#ifdef CONFIG_PM
unsigned long connect_time; //usb設備首次連接時間
unsigned do_remote_wakeup:1; //遠程喚醒使能
unsigned reset_resume:1; //復位代替重啓
unsigned port_is_suspended:1; // 上游端口暫停
#endif
struct wusb_dev *wusb_dev; // 如果這是一個無線USB設備,鏈接到WUSB特定設備的數據。
int slot_id; // XHCI分配的時隙ID
enum usb_device_removable removable; //設備可在物理上被移除
struct usb2_lpm_parameters l1_params; //USF2 L1 LPM狀態和L1超時的最佳EFF服務等待時間。
struct usb3_lpm_parameters u1_params; //退出USP3 U1 LPM狀態的延遲,以及輪轂啓動超時。
struct usb3_lpm_parameters u2_params; //退出USP3U2LPM狀態的延遲,以及輪轂啓動超時。
unsigned lpm_disable_count; // 由usb_disable_lpm()和usb_enable_lpm() 使用的REF計數,用於跟蹤需要爲此usb_設備禁用USB 3.0鏈路電源管理的函數的數量。這個計數應該只由這些函數來操縱,而帶寬是互斥的。
};
第二個結構體struct usb_host_interface
/* 主機側封裝,用於一個接口設置的解析描述符 */
struct usb_host_interface {
struct usb_interface_descriptor desc; // 前文的設備描述符之一
int extralen; //額外描述符的長度
unsigned char *extra; /* Extra descriptors 額外的描述符*/
/* array of desc.bNumEndpoints endpoints associated with this
* interface setting. these will be in no particular order.
* 與此接口設置相關聯的desc.bNumEndpoints端點數組。這些將沒有特別的順序。
*/
struct usb_host_endpoint *endpoint;
char *string; /* iInterface string, if present 接口字符串 */
};
其中struct 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隊列;有USB core維護
void *hcpriv; // 由HCD使用;通常持有硬件DMA隊列頭(QH),每個URB具有一個或多個傳輸描述符(TDS)。
struct ep_device *ep_dev; /* For sysfs info 爲sysfs提供信息*/
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled; //URBs可以被提供給這個端點
int streams; //端點上分配的UB-3流數
};
在接下來的幾步,
首先,採用了輸入子系統結構,設置註冊了input dev,以前做過很多次,看上面程序很清楚。
然後設置了urb,用於USB傳輸
/* 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;
整個probe函數很明顯就是兩部分,一是註冊input設備,而是構建urb,用於信息傳輸,那麼正確理解urb明顯至關重要【思考一:urb的作用?】,再用函數usb_fill_int_urb設置urb時,設置了,urb完成函數函數usbmouse_as_key_irq(),其中無非就是根據上報input event,最後再次提交urb
==============================================================
在drivers\usb\core\usb.c
subsys_initcall(usb_init); // 聲明子系統調用,會在內核啓動時初始化usb
/*
* Init
*/
static int __init usb_init(void)
{
int retval;
if (usb_disabled()) {
pr_info("%s: USB support disabled\n", usbcore_name);
return 0;
}
usb_init_pool_max();
retval = usb_debugfs_init();
if (retval)
goto out;
usb_acpi_register();
retval = bus_register(&usb_bus_type);
if (retval)
goto bus_register_failed;
retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
if (retval)
goto bus_notifier_failed;
retval = usb_major_init();
if (retval)
goto major_init_failed;
retval = usb_register(&usbfs_driver);
if (retval)
goto driver_register_failed;
retval = usb_devio_init();
if (retval)
goto usb_devio_init_failed;
retval = usb_hub_init();
if (retval)
goto hub_init_failed;
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
if (!retval)
goto out;
usb_hub_cleanup();
hub_init_failed:
usb_devio_cleanup();
usb_devio_init_failed:
usb_deregister(&usbfs_driver);
driver_register_failed:
usb_major_cleanup();
major_init_failed:
bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_notifier_failed:
bus_unregister(&usb_bus_type);
bus_register_failed:
usb_acpi_unregister();
usb_debugfs_cleanup();
out:
return retval;
}
其中現在我們只關心最需要的
int usb_hub_init(void)
{
usb_register(&hub_driver); //註冊USB hub驅動
...
/* 創建工作隊列,此函數在usb_hub_init中分配隊列,替代了以前的thrread_run的功能 */
hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
...
}
【思考二:聊一聊Linux中的工作隊列2】
hub_probe
|
hub_configure
|
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, //hub 中斷
hub, endpoint->bInterval);
當有中斷產生時將調用hub_irq
實際USB觸發
把USB設備接到開發板上,看輸出信息:
/ # usb 1-1: new full-speed USB device number 2 using s3c2410-ohci
拔掉:
/ # usb 1-1: USB disconnect, device number 2
再接上:
/ # usb 1-1: new full-speed USB device number 3 using s3c2410-ohci
拔掉:
/ # usb 1-1: USB disconnect, device number 3
在內核目錄drivers/下搜:
grep "USB device number" * -nR
搜到:
usb/core/hub.c:4365: "%s %s USB device number %d using %s\n",
usb/core/hub.c:4498: "%s SuperSpeed%s USB device number %d using %s\n",
由此可以知道,當某個usb設備插上,因爲硬件上的某些原理,會觸發某些操作,從hub.c開始分析,這也與上文分析一致
hub_irq
kickkick_hub_wq
hub_events
port_event
hub_port_connect_change
hub_port_connect
udev = usb_alloc_dev(hdev, hdev->bus, port1);
choose_devnum(udev); // 給新設備分配編號(對於usb2.0也是地址)
hub_port_init(hub, udev, port1, i); // 初始化,打印信息
usb_new_device(udev); //新建一個usb設備
device_add(&udev->dev); //添加設備
...
device_attach // 嘗試匹配usb驅動
總結
怎麼寫USB設備驅動程序?
1. 分配/設置usb_driver結構體
.id_table
.probe
.disconnect
2. 註冊
測試
測試1th/2th:
1. make menuconfig去掉原來的USB鼠標驅動
-> Device Drivers
-> HID Devices
<> USB Human Interface Device (full HID) support
2. make uImage 並使用新的內核啓動
3. insmod usbmouse_as_key.ko
4. 在開發板上接入、拔出USB鼠標
測試3th:
1. insmod usbmouse_as_key.ko
2. ls /dev/event*
3. 接上USB鼠標
4. ls /dev/event*
5. 操作鼠標觀察數據
測試4th:
1. insmod usbmouse_as_key.ko
2. ls /dev/event*
3. 接上USB鼠標
4. ls /dev/event*
5. cat /dev/tty1 然後按鼠標鍵
6. hexdump /dev/event0