Linux--USB驱动开发(二)插入USB后的内核执行程序

news2024/9/21 2:44:27

一、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热插拔机制实现了方便、可靠的设备连接和管理,从而极大地提高了用户的操作体验和系统的灵活性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL中的谓词与谓词下推

在 SQL 查询中,谓词(Predicate)是用来对数据进行过滤的条件。它们决定了数据从数据库表中被选择的条件。理解和正确使用 SQL 谓词对于编写高效查询至关重要。 目录 什么是谓词?一个真实的故事SQL 谓词的代码示例比较谓词逻辑谓词…

服务客户,保证质量:腾讯云产品的质量实践

分享主题是“服务客户,保证质量”。自从20年开始,我们把质量提升到了一个前所未有的高度。为什么会如此重视质量呢?在竞争激烈和复杂的市场环境中,产品质量对于企业的重要性不言而喻。一旦出现了质量事故,对客户和企业…

SCI二区|母亲优化算法(MOA)原理及实现【免费获取Matlab代码】

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2023年,I Matoušov受到母亲与孩子之间的人际互动启发,提出了母亲优化算法(Mother Optimization Algorithm, MOA)。 2.算法原理 2.1算法思…

PHP中的函数与调用:深入解析与应用

目录 一、函数基础 1.1 函数的概念 1.2 函数的定义 1.3 函数的调用 二、PHP函数的分类 2.1 内置函数 2.2 用户自定义函数 2.3 匿名函数 2.4 递归函数 2.5 回调函数 2.6 魔术方法 三、函数的参数与返回值 3.1 参数传递 3.2 返回值 四、函数的高级特性 4.1 可变函…

【HarmonyOS】鸿蒙中如何获取用户相册图片?photoAccessHelper.PhotoViewPicker

【HarmonyOS】鸿蒙中如何获取用户相册图片?photoAccessHelper.PhotoViewPicker 前言 有同学私聊我说,之前的博客文章提到的没有HarmonyOS白名单帐号,如何在OpenHarmony Gitee开发仓里学习API接口。需要注意一个点,默认看到的文档…

07 物以类聚 基于特征的七种算法模型

你好,我是大壮。在 06 讲中,我们介绍了协同过滤(CF)算法,它主要通过用户行为构建用户物品共现矩阵,然后通过 CF 算法预测结果实现个性化推荐。其实,除了利用用户行为特征之外,我们还…

决策树(ID3,C4.5,C5.0,CART算法)以及条件推理决策树R语言实现

### 10.2.1 ID3算法基本原理 ### mtcars2 <- within(mtcars[,c(cyl,vs,am,gear)], {am <- factor(am, labels c("automatic", "manual"))vs <- factor(vs, labels c("V", "S"))cyl <- ordered(cyl)gear <- ordered…

VMware与centos安装

目录 VM安装 安装centos VM安装 VMware Workstation Pro是VMware&#xff08;威睿公司发布的一袋虚拟机软件&#xff09;&#xff0c;它主要功能是可以给用户在单一的桌面上同时运行不同的操作系统&#xff0c;也是可以进行开发、测试、部署新的应用程序的最佳解决方案。 开始…

力扣144题:二叉树的先序遍历

给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a;root [1] 输出&am…

跳妹儿学编程之ScratchJr(9):程序控制积木篇—短跑比赛

跳妹儿学编程之ScratchJr(7)&#xff1a;动作积木篇—爸爸去散步 跳妹儿学编程之ScratchJr(8)&#xff1a;外观积木篇—捉迷藏 跳妹儿学编程之ScratchJr(9)&#xff1a;程序控制积木篇—短跑比赛 引言 在之前的一篇文章中&#xff0c;我们了解了ScratchJr的动作积木和外观积…

排序(三)——归并排序(MergeSort)

欢迎来到繁星的CSDN&#xff0c;本期内容主要包括归并排序(MergeSort)的实现 一、归并排序的主要思路 归并排序和上一期讲的快速排序很像&#xff0c;都利用了分治的思想&#xff0c;将一整个数组拆成一个个小数组&#xff0c;排序完毕后进行再排序&#xff0c;直到整个数组排序…

php反序列化--2--PHP反序列化漏洞基础知识

一、什么是反序列化&#xff1f; 反序列化是将序列化的字符串还原为PHP的值的过程。 二、如何反序列化 使用unserialize()函数来执行反序列化操作 代码1&#xff1a; $serializedStr O:8:"stdClass":1:{s:4:"data";s:6:"sample";}; $origina…

autoware.universe源码略读(3.15)--perception:object_merger

autoware.universe源码略读3.15--perception:object_merger Overviewnode&#xff08;enum&#xff09;MSG_COV_IDX&#xff08;Class&#xff09;ObjectAssociationMergerNode&#xff08;Func&#xff09;isUnknownObjectOverlapped&#xff08;Func&#xff09;convertListT…

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

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

【Linux系列】TEE 命令:同时输出到终端和文件

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

(leetcode学习)15. 三数之和

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例 1&a…

java算法day13

java算法day13 104 二叉树的最大深度111 二叉树的最小深度226 翻转二叉树101 对称二叉树100 相同的树 104 二叉树的最大深度 我最开始想到的是用层序遍历。处理每一层然后计数。思路非常的清楚。 迭代法&#xff1a; /*** Definition for a binary tree node.* public class…

Nginx入门到精通三(反向代理1)

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

Linux系统搭建轻量级个人博客VanBlog并一键发布公网远程访问

文章目录 前言1. Linux本地部署2. VanBlog简单使用3. 安装内网穿透4. 创建公网地址5. 创建固定公网地址 前言 今天和大家分享如何在Linux Ubuntu系统搭建一款轻量级个人博客VanBlog&#xff0c;并结合cpolar内网穿透软件生成公网地址&#xff0c;轻松实现随时随地远程访问本地…

Python与自动化脚本编写

Python与自动化脚本编写 Python因其简洁的语法和强大的库支持&#xff0c;成为了自动化脚本编写的首选语言之一。在这篇文章中&#xff0c;我们将探索如何使用Python来编写自动化脚本&#xff0c;以简化日常任务。 一、Python自动化脚本的基础 1. Python在自动化中的优势 Pyth…