Linux--USB驅動開發(二)插入USB后的內核執行程序

一、USB總線驅動程序的作用

a)識別USB設備

1.1 分配地址
1.2 并告訴USB設備(set address)
1.3 發出命令獲取描述符

b)查找并安裝對應的設備驅動程序

c)提供USB讀寫函數

二、USB設備工作流程

由于內核自帶了USB驅動,所以我們先插入一個USB鍵盤到開發板上看打印信息發現以下字段:

img

如下圖,找到第一段話是位于drivers/usb/core/hub.c的第2186行:

img

這個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

以下是設備插入后的完整流程,包括設備和接口的匹配與注冊:

硬件--主機控制器:

  1. 設備插入,產生中斷: USB主機控制器檢測到新設備,觸發硬件中斷。

內核--USB核心層:

  1. 分配設備地址: 處理中斷,內核分配唯一設備地址,并將usb_device注冊到總線上

  2. 獲取并解析設備描述符: 內核請求并解析設備描述符。

  3. 獲取并解析配置描述符: 內核請求并解析配置描述符,包括接口和端點描述符。

  4. 注冊接口: 為每個接口創建 usb_interface 結構,每個接口作為一個獨立的設備進行注冊,內核通過調用 device_add 將每個接口注冊到設備模型中。

  5. 匹配接口驅動: 內核調用 usb_device_match 查找并匹配接口驅動。如果找到匹配的驅動,調用驅動的 probe 函數。

內核--設備驅動層:

? ? ? ?1、具體的設備驅動driver實現(probe,usb_register_driver等)

關鍵點

  • 設備和接口層次

    • 整個USB設備有一個頂層的 usb_device 結構體,該結構體管理設備的整體信息。
    • 每個接口有一個 usb_interface 結構體,表示設備的一個獨立功能單元。
  • 驅動程序匹配

    • 頂層的 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)

一個設備描述符可以有多個配置描述符;
一個配置描述符可以有多個接口描述符(比如聲卡驅動就有兩個接口:錄音接口和播放接口)
一個接口描述符可以有多個端點描述符;

設備描述符結構體如下:(位于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_tablematch 函數和動態匹配)來判斷設備和驅動程序是否適配。對于設備驅動程序接口驅動程序的匹配邏輯分別進行了處理。

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設備,而無需重新啟動系統。熱插拔的實現依賴于硬件和軟件的緊密配合,下面具體講解其工作原理和機制。

硬件層面

  1. 電氣信號檢測

    • USB接口有專門的引腳用于檢測設備的插入和拔出。USB主機控制器能夠檢測這些信號的變化。
    • 當USB設備插入時,VBUS電壓上升,主機控制器檢測到電壓變化,并開始通信初始化過程。
  2. 數據線信號檢測

    • USB接口的D+和D-數據線在設備連接時會產生特定的電壓信號。主機控制器通過這些信號確認設備的連接。

軟件層面

  1. 設備檢測與枚舉

    • 當檢測到設備連接時,USB主機控制器會發出一個設備復位信號(Reset Signal),這會將設備置于已知狀態。
    • 復位完成后,主機開始與設備通信,獲取設備的描述符信息。這包括設備的類型、制造商、產品ID等。
    • 主機通過這些描述符信息來確定設備的驅動程序,并在操作系統中為設備創建相應的節點。
  2. 驅動加載

    • 基于設備的描述符信息,操作系統會搜索并加載相應的驅動程序。驅動程序負責與設備進行高層次的通信。
    • 如果是一個存儲設備,操作系統會掛載設備并創建一個文件系統節點;如果是一個輸入設備(如鍵盤或鼠標),則系統會準備好接收輸入事件。
  3. 事件通知

    • 當設備被插入或移除時,內核會生成一個熱插拔事件(hotplug event),并通知用戶空間的管理進程(如udev)。這些進程可以執行相應的腳本或命令,以便用戶可以看到設備的狀態變化。

Linux 內核中的熱插拔機制

Linux 內核通過多個子系統和框架來支持USB的熱插拔:

  1. USB Core 子系統

    • 處理USB設備的檢測、枚舉和基礎通信。
    • 提供API和機制供上層驅動程序調用。
  2. USB Host Controller Drivers (HCD)

    • 實現與具體硬件的交互,如EHCI、OHCI、UHCI等。
    • 負責處理底層電氣信號和數據傳輸。
  3. udev

    • 用戶空間設備管理守護進程,響應內核生成的設備事件。
    • 通過規則和腳本來管理設備節點的創建、權限設置等。

熱插拔的具體流程

  1. 設備插入

    • 檢測電壓信號變化,主機控制器發出設備復位信號。
    • 設備開始回應,主機讀取設備描述符信息。
    • 操作系統加載合適的驅動程序。
  2. 設備移除

    • 檢測電壓信號變化,主機控制器發出設備斷開信號。
    • 操作系統卸載驅動程序,釋放資源。
    • 通知用戶空間進程(如udev),以便執行清理操作。

實際應用中的注意事項

  • 數據完整性:在移除存儲設備時,需要確保沒有正在進行的數據傳輸,以避免數據損壞。
  • 電源管理:熱插拔操作需要處理好電源的管理,避免電涌或設備損壞。

通過硬件和軟件的緊密配合,USB熱插拔機制實現了方便、可靠的設備連接和管理,從而極大地提高了用戶的操作體驗和系統的靈活性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/45304.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/45304.shtml
英文地址,請注明出處:http://en.pswp.cn/web/45304.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Google Colab 云端硬盤路徑讀取

加載云端硬盤 需要在左上角點擊這個文件圖標; from google.colab import drive drive.mount("/content/drive") # 掛載云端硬盤import os path"/content/drive/MyDrive/TextClassificationCustom" os.chdir(path) # 以路徑path作為當前工作目…

在 SwiftUI 中的作用域動畫

文章目錄 前言簡單示例動畫視圖修飾符使用多個可動畫屬性使用 ViewBuilder總結 前言 從一開始,動畫就是 SwiftUI 最強大的功能之一。你可以在 SwiftUI 中快速構建流暢的動畫。唯一的缺點是每當我們需要運行多步動畫或將動畫范圍限定到視圖層次結構的特定部分時&…

docker emqx 配置密碼和禁用匿名連接

mqtt版本emqx/emqx:4.4.3 1.首先把鏡像內目錄/opt/emqx/etc拷貝到本地 2.做映射 3.allow_anonymous, false改成true 4. 5.MQTTX連不上的話看看下圖的有沒有打開

【nginx】nginx的優點

目錄 一、高性能1.1 高并發處理1.2 低內存消耗1.3 快速響應 二、高擴展性2.1 模塊化設計2.2 動態模塊擴展 三、高可靠性3.1 核心框架穩定3.2 進程管理3.3 負載均衡與健康檢查3.4 熱部署 四、功能豐富4.1 反向代理4.2 HTTP緩存4.3 安全功能 五、易于配置和管理5.1 配置文件簡單5…

windows下環境變量開啟方式

第一種方法: 使用快捷鍵打開“運行”對話框:按下 Win R 組合鍵,這將打開“運行”窗口。 輸入系統屬性命令:在“運行”窗口中輸入 sysdm.cpl 然后按回車鍵。這將打開“系統屬性”對話框。【sysdm.cpl是"System Data Manager…

【Go系列】Go的指針

承上啟下 我們在前面的文章中,首先介紹了GO的基礎語法,然后介紹了Goroutine和channel這個最具有特色的東西,同時介紹了Sync和context,以及在上篇文章中詳細距離說明了Go里面用于高并發的多種寫法。基礎的使用方法也告一段落了&…

Linux多線程編程-哲學家就餐問題詳解與實現(C語言)

在哲學家就餐問題中,假設有五位哲學家圍坐在圓桌前,每位哲學家需要進行思考和進餐兩種活動。他們的思考不需要任何資源,但進餐需要使用兩根筷子(左右兩側各一根)。筷子是共享資源,哲學家們在進行進餐時需要…

Qt qml詳細介紹

一.基本類型 QML的基本類型包括了很多不同的類型,這些類型可以用于定義用戶界面元素、屬性和信號。以下是一些常用的QML基本類型及其詳細介紹: 數值類型:包括整數類型(int、uint、short、ushort等)和浮點數類型&#…

c++ :運算符重載函數中的細節

賦值運算符重載與拷貝構造函數 (1)區分初始化時的賦值(一般就叫初始化),和非初始化時的賦值(一般就叫賦值) (2)實驗驗證初始化和賦值時各自對應 避免賦值運算符中的自賦值 (1)自賦值就是Person a; a a; (2)自賦值如…

鞭炮插畫:成都亞恒豐創教育科技有限公司

鞭炮插畫:年味里的絢爛記憶 在歲末年初的溫柔時光里,總有一抹色彩,能瞬間喚醒沉睡的年味——那便是鞭炮插畫中躍動的紅與金,成都亞恒豐創教育科技有限公司 它們不僅僅是紙與墨的交織,更是情感與記憶的橋梁&#xff0c…

自適應手機版大學職業技術學院網站模版源碼系統 帶完整的安裝代碼包以及搭建部署教程

系統概述 隨著智能手機的普及和移動互聯網技術的飛速發展,用戶越來越傾向于通過移動設備訪問網站。對于大學職業技術學院而言,一個能夠自適應各種屏幕尺寸、操作流暢、內容豐富的移動端網站,不僅能夠提升用戶體驗,還能有效擴大學…

最短路之樸素版的dij板子

模板&#xff1a; 注意這個只是單向的雙向的需要在更新一次 #include<bits/stdc.h>using namespace std;typedef long long ll; typedef pair<int, int>PII; const int N2e510; const int MOD 998244353; const int INF0X3F3F3F3F; const int dx[]{-1,1,0,0,-1,…

【Python Tips】將一個列表List元素添加進另一個列表List

一、引言 在處理Python列表數據類型時&#xff0c;有時需要合并兩個列表&#xff0c;下面是幾種列表合并的操作代碼&#xff0c;尤其是對于長列表的高效合并方式&#xff0c;記錄在此。 二、列表合并方式 1. 使用extend方法 extend方法將一個列表中的所有元素添加到另一個列表…

mysql快速精通(三)表關系

主打一個實用 一. 一對多&#xff08;多對一&#xff09;關系 例如班級和學生&#xff0c;這種類型我們一般建兩個表,一方為主表&#xff0c;多方為從表 二. 多對多 例如課程與學生&#xff0c;這種類型我們一般需要建三張表&#xff0c;兩張一方主表&#xff0c;與一張多方從表…

初識影刀:EXCEL根據部門篩選低值易耗品

第一次知道這個辦公自動化的軟件還是在招聘網站上&#xff0c;了解之后發現對于辦公中重復性的工作還是挺有幫助的&#xff0c;特別是那些操作非EXCEL的重復性工作&#xff0c;當然用在EXCEL上更加方便&#xff0c;有些操作比寫VBA便捷。 下面就是一個了解基本操作后&#xff…

[Linux]CentOS軟件的安裝

一、Linux 軟件包管理器 yum 1.Linux安裝軟件的方式 在linux中安裝軟件常用的有三種方式&#xff1a; 源代碼安裝&#xff08;我們還需要進行編譯運行后才可以&#xff0c;很麻煩&#xff09; rpm安裝&#xff08;Linux的安裝包&#xff0c;需要下載一些rpm包&#xff0c;但是…

基于機器學習的鋰離子電池容量估計(MATLAB R2021B)

鋰離子電池已經廣泛應用于電動汽車或混合動力汽車的能源存儲裝置。由于電化學成分的衰退&#xff0c;鋰離子電池隨著使用時間的增加&#xff0c;電池性能不斷退化&#xff0c;導致電池容量和功率發生衰退。電池容量衰退的因素主要有金屬鋰沉積&#xff0c;活性物質分解和電解液…

深度學習DeepLearning多元線性回歸 學習筆記

文章目錄 多維特征變量與術語公式多元線性回歸正規方程法Mean normalizationZ-score normalization設置合適的學習率Feature engineering 多維特征 變量與術語 列屬性xj屬性數n x ? \vec{x} x (i)行向量某個值 x ? j i \vec{x}_j^i x ji?上行下列均值μ標準化標準差σsigm…

SpringMVC 中常用注解

在 SpringMVC 框架的開發中&#xff0c;注解的合理運用能夠極大地提高開發效率和代碼的可維護性。今天&#xff0c;讓我們一起來總結一下 SpringMVC 中一些常用的注解及其用法。 一、Controller 注解 Controller 用于標識一個控制器類&#xff0c;該類中的方法用于處理用戶的請…

ArduPilot開源代碼之AP_AHRS_Backend

ArduPilot開源代碼之AP_AHRS_Backend 1. 源由2. 類繼承關系3. 框架設計2.1 構造函數和析構函數2.2 不可復制2.3 嵌套結構和枚舉2.4 虛方法2.5 靜態方法2.6 實用方法2.7 純虛方法2.8 條件編譯 3. 虛方法設計3.1 初始化3.1.1 構造函數3.1.2 析構函數3.1.3 AP_AHRS_Backend::init …