草庐IT

浅谈USB的枚举过程

胖墩儿二号 2023-05-05 原文

目录

一、概述

USB 枚举,在有些资料中,认为是插入USB后,USB 设备被分配到新的设备地址,就可以认为枚举结束,这可能是更准确的定义,但为了更好的说明,也可以把插入后的整个交互过程都称为枚举,也就是设备能被主机正确识别,可以被上位机使用了。

二、USB 设备状态

USB 枚举完成之前,USB 设备要经过一系列的状态变化,才能够最终完成枚举。在 USB 设备接入主机到枚举完成的过程可分为以下的状态变化:连接状态、供电状态、默认状态、地址状态、配置状态、挂起状态,其中,当设备变成配置状态时,即可认为 USB 设备和 USB 主机间的枚举完成。

  • 首先,连接状态指的是 USB 设备需要先和主机建立起物理上的连接,USB 设备检测是否连接到主机以及 USB 主机检测是否有 USB 设备连接是 USB 协议能够进行后续工作的前提。简单说来,检测连接状态的必要条件就是在 USB 设备接入时某些状态发生了变化。USB 硬件和协议上可通过检测 VBUS 、D+、D- 这些信号线的电平信号状态来进行检测连接的方法,当然,USB 主机和设备也必须要具有检测断开的能力。关于检测连接的具体协议部分,大家感兴趣的可以去详读 USB协议规范对应章节,在此不在赘述。

  • 供电状态,USB 设备可以从 USB 的 VBUS 上获取电源,或者通过外部电源获取电源,这一种称为自供电设备。而我们比较常见的是通过 VBUS 供电的设备称为总线供电设备。

  • 默认状态指的是 USB 设备进入供电状态后,在被复位之前,不能响应总线上的任何事务。只有当 USB 设备被复位,处于默认状态后,才会响应主机发送过来的请求。

  • 地址状态,指的是 USB 设备在被复位后,且在 USB 主机给 USB 设备设置一个新的地址之前,所有的 USB 设备都使用默认的 0 地址与主机进行通信。当 USB 设备在收到主机发送的设置地址请求之后,USB 设备就会得到一个唯一的地址。USB 设备会一直保存这个地址并使用它与主机进行通信,直到设备被复位或者断开。即使 USB 设备在获取唯一地址之后进入挂起状态,设备依然会保留这个地址,并在总线恢复后继续使用该地址。在设备挂起期间,主机也不能把该唯一地址分配给其他的 USB 设备。

  • 配置状态,指的是在 USB 设备功能能够使用之前,USB 设备和 USB 主机必须协商确定功能相关的配置项,所有的配置项都以描述符的形式提供,其中配置状态所需的描述符主要有以下 6 种:
    设备描述符、配置描述符、字符串描述符、接口描述符、端点描述符、设备限定描述符,当然,这里还有一种特殊的描述符称为接口关联描述符,用于将一组有关的描述符关联起来共同描述符一个特定的功能。USB 主机通过获取描述符请求来获得 USB 设备相关的描述符,通过设置接口和设置配置来设置设备的相关配置项。一旦相关功能参数被配置后,USB 设备就可以正常工作了。正常的 USB 设备在收到 USB 主机发出的复位信号后会进入默认状态,之前所指定的地址、所配置的接口和配置项都将无效,需要由 USB 主机重新指定设备地址,获取描述符并配置相关配置项,才能够使设备重新工作。

  • 挂起状态,指的是 USB 设备在 USB 总线持续 3ms 没有活动时,设备就会自动进入挂起状态。有挂起则意味着有恢复状态,这两个状态切换时 USB 协议实现低功耗的一种机制。进入挂起状态后,USB 设备要维持所有的内部状态。

三、USB 枚举流程

根据上面的状态变化过程,我们可以对 USB 协议进一步的了解。当 USB 主机检测到 USB 设备插入后,就要对设备进行枚举了。其实枚举的作用就是通过协议读取设备的相关信息,知道设备是什么样的设备,然后进行通信,这样主机端就可以根据这些信息安装合适的驱动程序了。USB 设备状态从初始的连接状态到最终的配置状态的变化过程就是整个枚举流程。

枚举流程可分为以下几个步骤:

  • 获取设备描述符
  • 设置设备的地址
  • 获取完整设备描述符
  • 获取配置描述符
  • 获取字符串描述符
  • 获取限定描述符
  • 配置设备的配置描述符
  • 获取设备描述符,这是开始枚举的第一步,主机通过获取描述符请求来获取设备的描述符,对于高速设备和低速设备,用于控制传输的端点 0 支持的最大包长度是确定的,分别是 64 和 8 字节,但对于全速设备,端点 0 支持的最大包长度可以是 8、16、32、64 字节,在 USB 主机获取到设备的设备描述符之前,USB 主机无法确定 USB 设备端点 0 的最大包长度,这样 USB 主机获取设备描述符的请求有可能不能正常完成。

  • 在双方通信前,USB 主机必须要指定端点 0 上的最大包长度,以便于正确检测控制传输种数据阶段的结束,假设 USB 主机端指定的 USB 端点 0 的最大包长度为 64 字节,而实际的 USB 设备端点 0 的最大包长度为 8 字节,由于 USB 设备描述符长度为固定的18 字节,当主机发送第一个获取设备描述符请求时将会要求设备端发送 18 字节的数据,USB 设备在收到请求后,准备发送 18 字节的数据,但实际最大包长度为 8 字节,所以 18 字节需要分三个事务去发送完成,但主机端指定的最大包长度为 64 字节,当主机接收到第一个 8 字节的事务时,就认为数据阶段已经完成,而 USB 设备此时正在准备发送第二个 8 字节的事务,导致这个控制传输由于双方的通信状态不同步而无法正常完成。这也是复位后主机为什么需要再次获取设备描述符,也就是上面说到的针对一些 USB 设备,设备描述符可能需要分多次获取,这样有可能获取的描述符是不完整的,而复位后是要获取设备描述符的全部 18 字节。

针对这个问题,不同 USB 主机的做法也有些不同。

  • 比如 Windows 7 系统在第一次获取设备描述符后,直接进行复位,将设备状态重置为初始状态。而由于设备端所支持的最小的最大包长度为 8 ,此时主机一定能获取到设备描述符的前 8 个字节,而设备描述符的第 8 个字节就是设备端点0所支持的最大包长度,所以主机可以在后续的控制传输中使用这个设备端的最大包长度来保证双方正常通信。
    而 Ubuntu 的 USB 主机做法则有些不同,主机端第一次获取设备描述符只获取其前 8 字节,不管 USB 设备端点的最大包长度是多少,USB 设备只能发送 8字节的数据,同时通信双方同步结束数据阶段。之后 USB 主机读取 USB 设备描述符的第 8 个字节,得到设备端点0的最大包长度,用于后续的控制传输。
  • 设置设备的地址,主机发送设置地址请求来设置设备的地址,请求中的 Setup 数据内包含主机为设备分配的新地址,设备收到地址后,进入地址状态,在该设备未复位或断开之前,设备会一直使用该地址与主机通信,在本次传输中,USB 主机和设备都仍然使用地址 0 进行通信。在设备收到设置地址请求后,必须要在 50ms 内处理该请求并完成该请求的状态阶段,在完成状态阶段后,可以有 2ms 的恢复时间,在这个时间内 ,USB 主机不能使用其刚刚指定的地址与设备通信,在这个时间之后,设备必须要使用该新地址与主机进行通信,同时,设备不能对发送到旧地址的任何请求产生回复,除非新旧地址一致。

  • 获取完整设备描述符,前面我们讲到了设备描述符部分,并且在上面的新地址设置完成后,主机与设备将会用新的地址通信,USB 主机会用新地址与设备进行通信,来获取完整的 18 字节的 USB 设备描述符。USB 设备描述符的第 18 字节表示该设备配置描述符的数目。

  • 获取配置描述符,由于每个设备配置描述符的长度都不一样,USB 主机无法提前得知设备的配置描述符长度,因此配置描述符的数据中必须要有表示长度的部分,由于第 9 字节代表该配置下 USB 设备的最大消耗电流值,因此主机将会先获取配置描述符的前 9 个字节,若该配置下的最大消耗电流值超过了 USB 主机可提供的额度,该配置将不可用,如果设备中的所有配置都不可用,该设备也会不可用,这将会中止整个枚举流程。

  • 获取字符串描述符,该部分主要包括了设备的一些字符串格式的信息,如设备制造商、产品信息和序列号、语言种类等。

  • 获取限定描述符,USB 主机可能会尝试去获取设备的限定描述符,以获取 USB 设备在其他速度模式下的信息。USB 设备也不一定会支持限定描述符,在这种情况下,USB 设备将会回复 STALL 表示设备不支持该描述符,这种控制传输的失败并不影响设备的正常枚举和使用。

  • 配置设备的配置描述符,主机在获取设备的某个配置描述符之后,会解析该描述符中所有的接口信息,和端点信息,之后主机就可以发出设置配置请求来通知 USB 设备使用哪一个配置。该请求命令中包含一个配置值,这个值是主机从设备的配置描述符中解析得来的,设备收到请求后,会判断配置值是否合法,若合法,设备就进入配置状态,设备会根据配置值选择对应的配置描述符,进行初始化操作,完成初始化后,设备就具备了与主机进行数据传输的能力,此时枚举阶段结束。以上则是整个枚举的过程,后续的 USB 通信将主要发生在配置描述符的接口或端点描述符所描述符的端点上。

四、总结

其实枚举过程可以简化为主机向设备发送标准请求,设备向主机发送描述符。主机需要知道设备的各种信息,设备需要把自己的信息发送到主机上。

有关浅谈USB的枚举过程的更多相关文章

  1. ruby - 引用具有指定索引的枚举器值 - 2

    假设我有一个可枚举对象enum,现在我想获取第三个项目。我知道一种通用方法是转换成数组,然后使用索引访问,如:enum.to_a[2]但这种方式会创建一个临时数组,效率可能很低。现在我使用:enum.each_with_index{|v,i|breakvifi==2}但这非常丑陋和多余。执行此操作最有效的方法是什么? 最佳答案 你可以使用take剥离前三个元素,然后剥离last从take给你的数组中获取第三个元素:third=enum.take(3).last如果您根本不想生成任何数组,那么也许:#Ifenumisn'tanEnum

  2. ruby-on-rails - Ruby on Rails,使用同名枚举的问题 - 2

    以下是我认为的一些下拉列表:'form-control')%>和'form-control')%>这是我的application_helper.rbdefget_advance_bookingret=[{:require_booking=>'No'},{:require_booking=>'Yes'}]enddefget_instant_bookingret=[{:instant_booking=>'No'},{:instant_booking=>'Yes'}]end但现在的问题是,在我的模型product.rb中,我无法设置具有相同名称的枚举:classProduct我收到的错误是您

  3. ruby-on-rails - i18n 与枚举 gem - 2

    我正在使用Enumerizegemhttps://github.com/brainspec/enumerize/它允许我以简单的形式使用漂亮的选择。并且此选择中的所有选项均已翻译。en:enumerize:user:sex:male:'Man'female:'Woman'所以,在我的表单中,我选择了变体“男人”和“女人”。当我用“男人”值保存记录时,我得到了“男性”值的性别属性。现在我想在显示页面上将该值显示为“Man”,但是=@user.sex输出为'male'而不是'Man' 最佳答案 我可能会使用.text方法(您可以通过使用

  4. ruby-on-rails - Ruby:如何在 fixture 中配置枚举? - 2

    给定这个类:classUser我想创建一个如下所示的fixture:testuser1:id:1username:sampermission::permission_staff我尝试了多种语法变体,但没有找到有效的方法。结果user.permission为nil或0。我知道enum是最近添加的。这可以做到吗? 最佳答案 根据enumdocs你可以像这样通过类引用可枚举的:User.permissions[:permission_staff]工厂只是ruby​​代码——所以他们应该能够以相同的方式访问值testuser1:id:1us

  5. arrays - Ruby 可枚举 - 查找最多 n 次匹配元素 - 2

    我有以下数组:arr=[1,3,2,5,2,4,2,2,4,4,2,2,4,2,1,5]我想要一个包含前三个奇数元素的数组。我知道我可以做到:arr.select(&:odd?).take(3)但我想避免遍历整个数组,而是在找到第三个匹配项后返回。我想出了以下解决方案,我相信它可以满足我的要求:my_arr.each_with_object([])do|el,memo|memo但是有没有更简单/惯用的方法来做到这一点? 最佳答案 使用lazyenumerator与Enumerable#lazy:arr.lazy.select(&:o

  6. ruby - 使用 Ransack 搜索枚举字段 - 2

    我有一个表,'jobs'和一个枚举字段'status'。status具有以下枚举集:enumstatus:[:draft,:active,:archived]使用ransack,我如何过滤表,比如说,所有事件记录? 最佳答案 你可以像这样在模型中声明自己的掠夺者:ransacker:status,formatter:proc{|v|statuses[v]}do|parent|parent.table[:status]end然后您可以使用默认的搜索语法_eq来检查相等性,如下所示:Model.ransack(status_eq:'ac

  7. Ruby 删除可枚举列表中的重复项 - 2

    ruby中有没有一个很好的方法来删除可枚举列表中的重复项(即拒绝等) 最佳答案 对于数组你可以使用uniq()方法a=["a","a","b","b","c"]a.uniq#=>["a","b","c"]所以如果你只是(1..10).to_a.uniq或%w{antbatcatant}.to_a.uniq因为无论如何,几乎所有您实现的方法都将作为Array类返回。 关于Ruby删除可枚举列表中的重复项,我们在StackOverflow上找到一个类似的问题: h

  8. ruby - Ruby 中的 block 和过程 - 2

    我已经开始学习Ruby,我已经阅读了一些教程,甚至还买了一本书(“ProgrammingRuby1.9-ThePragmaticProgrammers'Guide”),我遇到了一些以前从未见过的新东西使用我知道的任何其他语言(我是一名PHP网络开发人员)。block和过程。我想我明白它们是什么,但我不明白的是为什么它们如此伟大,以及我应该在何时何地使用它们。我到处都看到他们说block和过程是Ruby中的一个很棒的特性,但我不理解它们。这里有人能给像我这样的Ruby新手一些解释吗? 最佳答案 block有很多好处。电梯演讲:bloc

  9. 智能客服 | 浅谈人工智能聊天机器人ChatGPT - 2

    2022年底,OpenAI的预训练模型ChatGPT给人工智能领域的爱好者和研究人员留下了深刻的印象和启发,他展现的惊人能力将人工智能的研究和应用热度推向高潮,网上也充斥着和ChatGPT的各种聊天,他可以作诗、写小说、写代码、讨论疫情问题等。下面就是一些他的神回复:人命关天的坑: 写歌,留给词作者的机会不多了。。。 回答人类怎么样面对人工智能: 什么是ChatGPT?借用网上的一段介绍,ChatGPT是由人工智能研究实验室OpenAI在2022年11月30日发布的全新聊天机器人模型,一款人工智能技术驱动的自然语言处理工具。它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动

  10. ruby - 跨线程共享枚举器 - 2

    我想从不同线程调用一个公共(public)枚举器。当我执行以下操作时,enum=(0..1000).to_enumt1=Thread.newdopenum.nextsleep(1)endt2=Thread.newdopenum.nextsleep(1)endt1.joint2.join它引发了一个错误:Fibercalledacrossthreads.当enum在从t1调用一次后从t2调用时。为什么Ruby设计为不允许跨线程调用枚举器(或纤程),以及是否有其他方法可以提供类似的功能?我猜测枚举器/纤程上的操作的原子性在这里是相关的,但我不完全确定。如果这是问题所在,那么在使用时独占锁定

随机推荐