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_irq
	kick_khubd
		hub_thread
			hub_events
				hub_port_connect
				
					udev = 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 3
						
						hub_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_configuration
							
							device_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_probe
    kzalloc //分配video_device
        uvc_register_chains  
            uvc_register_terms  
                uvc_register_video
                    vdev->v4l2_dev = &dev->vdev; //设置video_device
                    vdev->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热插拔机制实现了方便、可靠的设备连接和管理,从而极大地提高了用户的操作体验和系统的灵活性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/799753.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS-0_3 CSS和单位

文章目录 CSS的值和单位属性值长度单位CSS和绝对单位CSS和相对单位百分比em & rem视口 颜色单位 碎碎念 CSS的值和单位 我们知道,CSS是由属性和属性值所组成的表 随着CSS的发展,属性不说几千也有几百,我从来不支持去背诵所有的可能性。…

K8S系列-Kubernetes基本概念及Pod、Deployment、Service的使用

一、Kubernetes 的基本概念和术语 一、资源对象 ​ Kubernetes 的基本概念和术语大多是围绕资源对象 Resource Object 来说的,而资源对象在总体上可分为以下两类: 1、某种资源的对象 ​ 例如节点 Node) Pod 服务 (Service) 、存储卷 (Volume)。 2、…

Nginx入门到精通五(动静分离)

下面内容整理自bilibili-尚硅谷-Nginx青铜到王者视频教程 Nginx相关文章 Nginx入门到精通一(基本概念介绍)-CSDN博客 Nginx入门到精通二(安装配置)-CSDN博客 Nginx入门到精通三(Nginx实例1:反向代理&a…

从0-1搭建一个web项目(页面布局详解)详解

本章分析页面布局详解详解 ObJack-Admin一款基于 Vue3.3、TypeScript、Vite3、Pinia、Element-Plus 开源的后台管理框架。在一定程度上节省您的开发效率。另外本项目还封装了一些常用组件、hooks、指令、动态路由、按钮级别权限控制等功能。感兴趣的小伙伴可以访问源码点个赞 地…

java数组之冒泡排序、快速排序

一、排序算法概述 1.算法定义 排序&#xff1a;假设含有n个记录的序列为{R1&#xff0c;R2&#xff0c;...,Rn},其相应的关键字序列为{K1&#xff0c;K2&#xff0c;...,Kn}。将这些记录重新排序为{Ri1,Ri2,...,Rin},使得相应的关键字值满足条Ki1<Ki2<...<Kin,这样的…

使用Keepalived实现双机热备(虚拟漂移IP地址)详细介绍

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

BI工具的AI革新:对话式分析如何引领企业智能转型?

在数据驱动的时代&#xff0c;数据分析早已成为企业决策制定的关键支撑。但是&#xff0c;很多企业在数字化转型的过程中&#xff0c;常常面临门槛高、流程复杂等问题。而AI技术的发展为解决上述问题带来了突破。 为了简化企业智能转型路径&#xff0c;帆软接入AI大模型技术&a…

Scherlokk - Mac 文件快速搜索对比工具

Scherlokk 是一款适用于 Mac 的文件内容快搜比较工具&#xff0c;在 Scherlokk 内输入关键词&#xff0c;即可在本地磁盘 / 移动硬盘 / 网络驱动器等区域内&#xff0c;查找包含该词的文件&#xff0c;快速定位所需文件&#xff0c;并提供文件比较、快速筛选过滤等功能。 两种…

SpringCloud--常用组件和服务中心

常用组件 Euroke和nacos 区别 负载均衡 负载均衡策略有哪些 自定义负载均衡策略

Power Apps使用oData访问表数据并赋值前端

在使用OData查询语法通过Xrm.WebApi.retrieveMultipleRecords方法过滤数据时&#xff0c;你可以指定一个OData $filter 参数来限制返回的记录集。 以下是一个使用Xrm.WebApi.retrieveMultipleRecords方法成功的例子&#xff0c;它使用了OData $filter 参数来查询实体的记录&am…

YOLOv5分类任务——手势识别

1. 下载YOLOv5官方代码 ONNX > CoreML > TFLite (github.com)">ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite (github.com) 2. 配置环境 打开终端,先建立名为YOLO5的环境,再将路径切换为requirements.txt文件夹所在的路径…

C#环境与数据类型

文章目录 C#环境.NET 框架集成开发环境 创建一个C#项目数据类型值类型引用类型对象类型object动态类型dynamic字符串类型string 指针类型 类型转换隐式转换显示转换&#xff08;强制转换&#xff09;C#提供的类型转换方法Convert类Parse方法TryParse方法 C#环境 .NET 框架 C#是…

Directory Opus 13 专业版(Windows 增强型文件管理器)值得购买?

在使用电脑时&#xff0c;总少不了和文件打交道。系统自带的 Explorer 资源管理器功能又非常有限&#xff0c;想要拥有一个多功能文件管理器吗&#xff1f; Directory Opus 是一款老牌多功能文件管理器&#xff0c;能很好地接管 Windows 资源管理器。 接管资源管理器 Directo…

Kotlin标准函数(语法糖)let with run also apply快速讲解

目录 1、知识储备——扩展函数 原理 定义扩展函数 调用扩展函数 2、返回值为上下文对象的标准函数 apply also 3、返回值为Lambda表达式结果 let run with 4、一表总结 1、知识储备——扩展函数 原理 Kotlin 在不继承父类或实现接口下&#xff0c;也能扩展一个类的…

硅谷甄选4(项目主体)

1.路由配置 1.1路由组件的雏形 src\views\home\index.vue&#xff08;以home组件为例&#xff09; 安装插件&#xff1a; 1.2路由配置 1.2.1路由index文件 src\router\index.ts //通过vue-router插件实现模板路由配置 import { createRouter, createWebHashHistory } fro…

【Qt 常用控件】

文章目录 1. Push Button 1. Push Button &#x1f427;给按钮设置图标

链接追踪系列-09.spring cloud项目整合elk显示业务日志

准备工作&#xff1a; 参看本系列之前篇&#xff1a;服务器安装elastic search 本机docker启动的kibana-tencent 使用本机安装的logstash。。。 本微服务实现的logstash配置如下&#xff1a; 使用腾讯云redis 启动本机mysql 启动本机docker 启动nacos,微服务依赖它作为…

防火墙的双机热备实验和通道策略

需求&#xff1a; 12&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW3&#xff0c;生产区和办公区的流量走FW1 13&#xff0c;办公区上网用户限制流量不超过100M&#xff0c;…

象过河云进销存管理系统,简单、方便、高效!

在当今这个快节奏的商业时代&#xff0c;企业的日常运营管理愈发注重效率和便捷性。基于这样的需求&#xff0c;象过河云进销存管理系统应运而生&#xff0c;它以“简单、方便、高效”为核心价值&#xff0c;为众多企业量身打造了一站式的解决方案。 象过河云进销存管理系统打破…

MDK KEIL程序代码编译成静态库文件及库引用笔记教程

1、为什么要编译成库文件 在商业性的程序代码或软件中&#xff0c;各种静态库、动态库是非常常见的。甚至有许多的开源程序&#xff0c;其开放的源码工程中&#xff0c;也有一些程序代码是并不对外开放的&#xff0c;以一个静态库或动态库和一个头文件及部分说明文件的方式提供…