当前位置: 首页 > news >正文

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 安卓 APK 安装过程详解
  • 深入理解Scikit-learn:决策树与随机森林算法详解
  • “12松”计划-1.0.1-星期一-冥想1.0.9
  • LabVIEW电容器充放电监测系统
  • 电气工程VR虚拟仿真实训平台以趣味化方式增强吸引力
  • ctf中php常见内容(比较、变量覆盖)
  • Unity最新第三方开源插件《Stateful Component》管理中大型项目MonoBehaviour各种序列化字段 ,的高级解决方案
  • C语言 | Leetcode C语言题解之第228题汇总区间
  • FastAPI 学习之路(四十一)定制返回Response
  • rollup打包工具
  • ArcGIS的智慧与情怀
  • 蚁剑编码器——高级
  • 骨头的诱惑
  • Directory Opus 13 专业版(Windows 增强型文件管理器)值得购买?
  • wps批量删除空白单元格
  • 【EOS】Cleos基础
  • input实现文字超出省略号功能
  • Java超时控制的实现
  • PHP的类修饰符与访问修饰符
  • Sequelize 中文文档 v4 - Getting started - 入门
  • springboot_database项目介绍
  • SpringCloud集成分布式事务LCN (一)
  • Spring思维导图,让Spring不再难懂(mvc篇)
  • tab.js分享及浏览器兼容性问题汇总
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • vue-router的history模式发布配置
  • 高度不固定时垂直居中
  • 如何实现 font-size 的响应式
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 无服务器化是企业 IT 架构的未来吗?
  • 用Canvas画一棵二叉树
  • 再谈express与koa的对比
  • 在Unity中实现一个简单的消息管理器
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • Java数据解析之JSON
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​DB-Engines 12月数据库排名: PostgreSQL有望获得「2020年度数据库」荣誉?
  • ​secrets --- 生成管理密码的安全随机数​
  • #define用法
  • $.ajax,axios,fetch三种ajax请求的区别
  • (12)目标检测_SSD基于pytorch搭建代码
  • (4)(4.6) Triducer
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (原创)可支持最大高度的NestedScrollView
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • (转)JAVA中的堆栈
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • .mysql secret在哪_MYSQL基本操作(上)
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .net core Redis 使用有序集合实现延迟队列
  • .Net Core和.Net Standard直观理解