一、USB總線驅動程序的作用
a)識別USB設備
1.1 分配地址
1.2 并告訴USB設備(set address)
1.3 發出命令獲取描述符
b)查找并安裝對應的設備驅動程序
c)提供USB讀寫函數
二、USB設備工作流程
由于內核自帶了USB驅動,所以我們先插入一個USB鍵盤到開發板上看打印信息發現以下字段:
如下圖,找到第一段話是位于drivers/usb/core/hub.c的第2186行:
這個hub其實就是我們的USB主機控制器的集線器,用來管理多個USB接口
以下是USB設備插入后內核中的工作流程
hub_irqkick_khubdhub_threadhub_eventshub_port_connectudev = usb_alloc_dev(hdev, hdev->bus, port1);dev->dev.bus = &usb_bus_type;/*注冊到USB總線上*/choose_devnum(udev); // 給新設備分配編號(地址)hub_port_init() // usb 1-1: new full speed USB device using s3c2410-ohci and address 3hub_set_address // 把編號(地址)告訴USB設備usb_get_device_descriptor(udev, 8); // 獲取設備描述符retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);usb_new_device(udev) err = usb_get_configuration(udev); // 把所有的描述符都讀出來,并解析usb_parse_configurationdevice_add // 把device放入usb_bus_type的dev鏈表, // 從usb_bus_type的driver鏈表里取出usb_driver,// 把usb_interface和usb_driver的id_table比較// 如果能匹配,調用usb_driver的probe
以下是設備插入后的完整流程,包括設備和接口的匹配與注冊:
硬件--主機控制器:
-
設備插入,產生中斷: USB主機控制器檢測到新設備,觸發硬件中斷。
內核--USB核心層:
-
分配設備地址: 處理中斷,內核分配唯一設備地址,并將usb_device注冊到總線上
-
獲取并解析設備描述符: 內核請求并解析設備描述符。
-
獲取并解析配置描述符: 內核請求并解析配置描述符,包括接口和端點描述符。
-
注冊接口: 為每個接口創建
usb_interface
結構,每個接口作為一個獨立的設備進行注冊,內核通過調用device_add
將每個接口注冊到設備模型中。 -
匹配接口驅動: 內核調用
usb_device_match
查找并匹配接口驅動。如果找到匹配的驅動,調用驅動的probe
函數。
內核--設備驅動層:
? ? ? ?1、具體的設備驅動driver實現(probe,usb_register_driver等)
關鍵點
-
設備和接口層次:
- 整個USB設備有一個頂層的
usb_device
結構體,該結構體管理設備的整體信息。 - 每個接口有一個
usb_interface
結構體,表示設備的一個獨立功能單元。
- 整個USB設備有一個頂層的
-
驅動程序匹配:
- 頂層的
usb_device
結構體一般不會直接匹配到驅動程序。驅動程序的匹配主要發生在接口層次,通過usb_interface
結構體進行。但有一些驅動程序是針對整個 USB 設備的,而不是單個接口。例如,某些 USB 設備驅動程序(usb_device_driver
)可能需要管理整個設備,而不僅僅是某個特定接口。通過這種設計,USB 核心可以靈活地支持設備級和接口級的驅動程序匹配。 - 內核通過解析設備的配置描述符,發現設備包含的所有接口,并為每個接口匹配對應的驅動程序。
- 頂層的
三、相關概念補充
1、USB描述符的層次及定義
- USB設備描述符(usb_device_descriptor)
- USB配置描述符(usb_config_descriptor)
- USB接口描述符(usb_interface_descriptor)
- USB端點描述符(usb_endpoint_descriptor)
- USB接口描述符(usb_interface_descriptor)
- USB配置描述符(usb_config_descriptor)
一個設備描述符可以有多個配置描述符;
一個配置描述符可以有多個接口描述符(比如聲卡驅動就有兩個接口:錄音接口和播放接口)
一個接口描述符可以有多個端點描述符;
設備描述符結構體如下:(位于include\linux\usb\Ch9.h)
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));
配置描述符如下:
struct usb_config_descriptor { __u8 bLength; //描述符的長度__u8 bDescriptorType; //描述符類型的編號__le16 wTotalLength; //配置所返回的所有數據的大小__u8 bNumInterfaces; //配置所支持的接口個數, 表示有多少個接口描述符__u8 bConfigurationValue; //Set_Configuration命令需要的參數值__u8 iConfiguration; //描述該配置的字符串的索引值__u8 bmAttributes; //供電模式的選擇__u8 bMaxPower; //設備從總線提取的最大電流
} __attribute__ ((packed));
接口描述符如下:
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)
關于描述符的解析,由下圖可知,以控制接口為例,接口描述符中寫明了CT、IT等端口的信息。
?對于端口具體細節可以看這篇UVC 1.5 Class Specification 簡解_uvc1.5-CSDN博客
2、USB的.match()函數
static int usb_device_match(struct device *dev, struct device_driver *drv)
{/* devices and interfaces are handled separately */設備和接口分別處理if (is_usb_device(dev)) { 首先,檢查 dev 是否是 USB 設備。如果是,則執行以下代碼塊struct usb_device *udev;struct usb_device_driver *udrv;/* interface drivers never match devices */if (!is_usb_device_driver(drv))return 0;udev = to_usb_device(dev);udrv = to_usb_device_driver(drv);/* If the device driver under consideration does not have a 檢查驅動程序的 id_table 和 match 函數* id_table or a match function, then let the driver's probe* function decide.*/if (!udrv->id_table && !udrv->match)return 1;return usb_driver_applicable(udev, udrv);} else if (is_usb_interface(dev)) { 檢查是否為 USB 接口,如果 dev 是 USB 接口,則執行以下代碼塊struct usb_interface *intf;struct usb_driver *usb_drv;const struct usb_device_id *id;/* device drivers never match interfaces */if (is_usb_device_driver(drv)) 如果驅動程序 drv 是 USB 設備驅動程序,則直接返回 0,表示不匹配。return 0;intf = to_usb_interface(dev);usb_drv = to_usb_driver(drv);id = usb_match_id(intf, usb_drv->id_table); 嘗試匹配usb接口和接口驅動程序if (id)return 1;id = usb_match_dynamic_id(intf, usb_drv);if (id)return 1;}return 0;
}
這段代碼邏輯通過檢查設備和驅動程序類型,并利用不同的匹配方法(如 id_table
、match
函數和動態匹配)來判斷設備和驅動程序是否適配。對于設備驅動程序和接口驅動程序的匹配邏輯分別進行了處理。
3、USB的probe()函數
此處以uvc_driver.c中的probe為例
uvc_probekzalloc //分配video_deviceuvc_register_chains uvc_register_terms uvc_register_videovdev->v4l2_dev = &dev->vdev; //設置video_devicevdev->fops = &uvc_fops; vdev->ioctl_ops = &uvc_ioctl_ops;vdev->release = uvc_release;video_register_device //注冊video_device
具體對probe函數的分析可以看這篇:UVC 設備框架在 Linux 4.15 內核的演變_v4l2 核心在嘗試映射 uvc 控件時找不到相應的文件或目錄-CSDN博客
四、相關問題梳理
1、關于驅動和設備接口注冊
與platform_driver、i2c_driver類似,usb_driver起到了牽線的作用,即在probe()函數里注冊相應的字符、tty設備(此處usb中注冊的是接口設備),在disconnect()函數里注銷相應的設備,而原先對設備的注冊和注銷一般直接發生在模塊加載和卸載函數中。
2、USB驅動用idtable匹配,不用設備樹來描寫硬件信息嗎
USB是熱插拔的,不用在dts中描述,如果寫了板子上有一個U盤,但實際上沒有,其實反而是制造了麻煩,相反,如果沒有寫,U盤一旦插入,LinuxUSB子系統會自動探測到一個U盤。
-
固定硬件配置:
- 設備樹適用于那些硬件配置相對固定的系統,這些系統的硬件在設計和制造時已經確定。設備樹提供了一種靜態描述硬件的方法,適合用于固件或操作系統在啟動時配置硬件。
- 示例:單板計算機、嵌入式系統中的 SoC(系統級芯片)。
-
動態硬件配置:
- 對于那些硬件配置可能會動態改變的系統,例如支持熱插拔設備的系統,通常不使用設備樹,而是依賴于總線驅動和熱插拔機制(如 USB、PCIe)來動態識別和配置硬件。
- 示例:PC 平臺中的 USB 設備、PCIe 設備。
3、USB驅動注冊時要分設備和接口嗎,設備驅動和接口驅動具體有什么不同(存疑)
驅動其實是與設備的邏輯接口進行匹配,有幾個接口匹配成功probe函數就調用幾次
4、USB的熱插拔機制
USB(通用串行總線)的熱插拔機制使得用戶可以在系統運行時隨時連接或斷開USB設備,而無需重新啟動系統。熱插拔的實現依賴于硬件和軟件的緊密配合,下面具體講解其工作原理和機制。
硬件層面
-
電氣信號檢測:
- USB接口有專門的引腳用于檢測設備的插入和拔出。USB主機控制器能夠檢測這些信號的變化。
- 當USB設備插入時,VBUS電壓上升,主機控制器檢測到電壓變化,并開始通信初始化過程。
-
數據線信號檢測:
- USB接口的D+和D-數據線在設備連接時會產生特定的電壓信號。主機控制器通過這些信號確認設備的連接。
軟件層面
-
設備檢測與枚舉:
- 當檢測到設備連接時,USB主機控制器會發出一個設備復位信號(Reset Signal),這會將設備置于已知狀態。
- 復位完成后,主機開始與設備通信,獲取設備的描述符信息。這包括設備的類型、制造商、產品ID等。
- 主機通過這些描述符信息來確定設備的驅動程序,并在操作系統中為設備創建相應的節點。
-
驅動加載:
- 基于設備的描述符信息,操作系統會搜索并加載相應的驅動程序。驅動程序負責與設備進行高層次的通信。
- 如果是一個存儲設備,操作系統會掛載設備并創建一個文件系統節點;如果是一個輸入設備(如鍵盤或鼠標),則系統會準備好接收輸入事件。
-
事件通知:
- 當設備被插入或移除時,內核會生成一個熱插拔事件(hotplug event),并通知用戶空間的管理進程(如udev)。這些進程可以執行相應的腳本或命令,以便用戶可以看到設備的狀態變化。
Linux 內核中的熱插拔機制
Linux 內核通過多個子系統和框架來支持USB的熱插拔:
-
USB Core 子系統:
- 處理USB設備的檢測、枚舉和基礎通信。
- 提供API和機制供上層驅動程序調用。
-
USB Host Controller Drivers (HCD):
- 實現與具體硬件的交互,如EHCI、OHCI、UHCI等。
- 負責處理底層電氣信號和數據傳輸。
-
udev:
- 用戶空間設備管理守護進程,響應內核生成的設備事件。
- 通過規則和腳本來管理設備節點的創建、權限設置等。
熱插拔的具體流程
-
設備插入:
- 檢測電壓信號變化,主機控制器發出設備復位信號。
- 設備開始回應,主機讀取設備描述符信息。
- 操作系統加載合適的驅動程序。
-
設備移除:
- 檢測電壓信號變化,主機控制器發出設備斷開信號。
- 操作系統卸載驅動程序,釋放資源。
- 通知用戶空間進程(如udev),以便執行清理操作。
實際應用中的注意事項
- 數據完整性:在移除存儲設備時,需要確保沒有正在進行的數據傳輸,以避免數據損壞。
- 電源管理:熱插拔操作需要處理好電源的管理,避免電涌或設備損壞。
通過硬件和軟件的緊密配合,USB熱插拔機制實現了方便、可靠的設備連接和管理,從而極大地提高了用戶的操作體驗和系統的靈活性。