为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。
枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。枚举过程包括设备地址的分配,从设备读取设备描述符,分配加载驱动程序,选择规定的设备功耗要求和接口配置信息。
USB 架构中, hub 负责检测设备的连接和断开,利用其中断 IN 端点 (Interrupt IN Endpoint) 来向主机(Host)报告。在系统启动时,主机轮询它的根 hub(Root Hub)的状态看是否有设备(包括子hub和子hub上的设备)连接。 只要检测到有新设备连接上来,主机就会发送一系列的请求 (Resqusts) 给设备所挂载到的 hub,再由 hub 建立起一条连接主机(Host)和设备(Device)之间的通信通道。然后主机以控制传输 (Control Transfer) 的方式,通过端点0 (Endpoint 0) 对设备发送各种请求,设备收到主机发来的请求后回复相应的信息,进行枚举(Enumerate)操作。所有的 USB 设备必须支持标准请求(Standard Requests),控制传输方式(Control Transfer)和端点0(Endpoint 0)。
USB 协议定义了设备的 6 种状态,仅枚举过程,设备就经历了 4 个状态的迁移:上电状态 ( Powered),默认状态 (Default),地址状态 (Address) 和配置状态 (Configured)(其他两种是连接状态(Attached)和挂起状态(Suspend)。
设备可以连接到 USB 或者从 USB 上拔出。设备接入主机后,主机通过检测信号线上的电平变化来发现设备的接入。
USB 设备的电源可来自外部电源,也可从 USB 接口的集线器而来。电源来自外部电源的 USB 设备被称作自给电源式的 (self-powered)。尽管自给电源式的 USB 设备可能在连接上 USB 接口以前可能已经带电,但它们直到连线上 USB 接口后才能被看作是加电状态 (Powered state),这时候 VBUS 已经对设备产生作用了。
一个设备可能有既支持自给电源的,同时也支持总线电源式的配置。有一些支持其中的一种,而另一些设备配置可能只有在自给电源下才能被使用。设备对电源支持的能力是通过配置描述表 (configuration descriptor) 来反映的。当前的电源供给形式被作为设备状态的一部分被反映出来。设备可在任何时候改变它们的供电来源,比如说:从自给式向总线式改变,如果一个配置同时支持两种模式,那此状态的最大电源需求就是指设备在两种模式下从 VBUS 上获取电能的最大值。设备必须以此最大电源作为参照,而究竟处于何状态是不考虑的。如果有一配置仅支持一种电源模式,那么电源模式的改变会使得设备失去当前配置与地址,返回加电状态。如果一个设备是自给电源式,并且当前配置需要大于 100mA 电流,那么如果此设备转到了总线电源式,它必须返回地址状态 (Address state)。自给电源式集线器使用 VBUS 来为集线控制器 (Hub controller) 提供电源,因而可以仍然保持配置状态 (Configured state),尽管自给电源停止提供电源。
设备上电后,它不响应任何总线处理,直到总线接收到复位信号为止。接收到复位信号后,用默认的地址可以对设备寻址。
当用复位过程完成后,USB 设备在正确的速度下操作(即低速/全速/高速)。低速和全速的数据选择由设备的终端电阻决定,能进行高速操作的设备决定它是否在复位的过程的一部分执行高速操作。
能进行高速操作的设备在全速的电气环境中操作时,必须能以全速成功复位,设备成功复位后,设备必须成功响应设备和配置描述符请求,并且返回适当的信息。当在全速下工作时,设备可能或者不能支持预定义的功能。
所有的 USB 设备在加电复位以后都使用缺省地址,每一设备在连接或复位后由主机分配一个唯一的地址。当 USB 设备处于挂起状态时,它保持这个地址不变。 USB 设备只对缺省通道 (Pipe) 请求发生响应,而不管设备是否已经被分配地址或在使用缺省地址。
主控制器分配从设备设备的设备地址。范围为 0 – 127。包格式 ”00 05 XX XX 00 00 00 00”,其中 XX XX 为 2 字节设备地址,地址范围 0 – 128。
在 USB 设备正常工作以前,设备必须被正确配置。从设备的角度来看,配置包括一个将非零值写入设备配置寄存器的操作。配置一个设备或改变一个可变的设备设置,将会使得与这个相关接口的终端结点的所有的状态与配置值被设成缺省值。这包括将正在使用 (date toggle) 的端点 (end point) 的 (Date toggle) 被设置成DATA0。
为节省电源,USB 设备在探测不到总线传输时自动进入中止状态。当中止时,USB 设备保持本身的内部状态,包括它的地址及配置。
所有的设备在一段特定的时间内探测不到总线活动时必须进入中止态。不管设备是被分配了非缺省的地址或者是被配置了,已经连接的设备必须在任何加电的时刻随时准备中止。总线活动的中止可能是因为主机本身进入了中止状态。另外,USB 设备必须在所连接的集线器端口失效时进入中止态。这就是所指的选择性中止 (Selective suspend)。
USB 设备在总线活动来到时结束中止态。USB 设备也可以远程唤醒的电流信号来请求主机退出中止态或选择性中止态。具体设备具有的远程唤醒的能力是可选的,也就是说,如果一个设备有远程唤醒的能力,此设备必须能让主机控制此能力的有效与否,当设备复位时,远程唤醒能力必须被禁止。
整体步骤如下图: 通过 USB 协议分析仪抓取。
指的 USB 端口指的是主机下的根 hub 或主机下行端口上的 Hub 端口。Hub 给端口供电,连接着的设备处于上电状态。此时,USB 设备处于加电状态,它所连接的端口是无效的。
在 hub 端,数据线 D+ 和 D- 都有一个阻值在 14.25K 到 24.8K 的下拉电阻 Rpd(一般设置为 15K ),而在设备端,D+(全速,高速)和 D-(低速)上有一个 1.5K 的上拉电阻 Rpu。当设备插入到 hub 端口时,有上拉电阻的一根数据线被拉高到幅值的 90%的电压(大约 3V)。hub 检测到它的一根数据线是高电平,就认为是有设备插入,并能根据是 D+ 还是 D- 被拉高来判断到底是什么设备(全速/低速)插入端口(高速设备先识别书全速,然后再进一步协商确定为高速)。当 USB 设备上电后,一直监测 USB 设备接口电平变化 HUB 检测到有电压变化,将利用自己的中断端点将信息反馈给主控制器有设备连接。
每个 hub 利用它的中断端点向主机报告它的各个端口的状态(对于这个过程,设备是看不到的,也不关心),报告的内容只是 hub 端口的设备连接或断开的事件。如果有连接或断开事件发生,那么 Host 会发送一个 Get_Port_Status 请求 (request) 给 hub 以了解此次状态改变的确切含义。Get_Port_Status 等请求属于所有 hub 都要求支持的 hub 类标准请求。
hub 通过检测 USB 总线空闲 (Idle) 时差分线的高低电压来判断所连接设备的速度类型,当 host 发来 Get_Port_Status 请求时,hub 就可以将此设备的速度类型信息回复给 host。USB 2.0 规范要求速度检测要先于复位(Reset)操作。
Host 一旦得知新设备已连上以后,它至少等待 100ms 以使得插入操作的完成以及设备电源稳定工作。然后主机控制器就向 hub 发出一个 Set_Port_Feature 请求让 hub 复位其管理的端口 (刚才设备插上的端口)。hub 通过驱动数据线到复位状态( D+ 和 D- 全为低电平 ),并持续至少 10ms。当然,hub 不会把这样的复位信号发送给其他已有设备连接的端口,故其他连在该 hub上的设备自然看不到复位信号,不受影响。
根据 USB 2.0 协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,对于一个支持 USB 2.0 的高速 hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。
从设备的角度来看,如果是一个高速设备,在刚连接 hub 或上电时只能用全速信号模式运行(根据 USB 2.0 协议,高速设备必须向下兼容 USB 1.1 的全速模式)。随后 hub 会进行高速检测,之后这个设备才会切换到高速模式下工作。若连接的 hub 不支持 USB 2.0,即不是高速 hub,不能进行高速检测,设备将一直以全速工作。
主机不停地向 hub 发送 Get_Port_Status 请求,以查询设备是否复位成功。Hub 返回的报告信息中有专门的一位用来标志设备的复位状态。
当 hub 撤销了复位信号,设备就处于默认或空闲状态,准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址 0,端点号 0 进行。此时,设备能从总线上得到的最大电流是 100mA。所有的 USB 设备在总线复位后其地址都为 0,这样主机就可以跟那些刚刚插入的设备通过地址 0 通信。
默认管道(Default Pipe)在设备一端来看就是端点 0。主机此时发送的请求是默认地址 0,端点 0,虽然所有未分配地址的设备都是通过地址 0 来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。
设备描述符的第 8 字节代表设备端点 0 的最大包大小。虽然说设备所返回的设备描述符(Device Descriptor)长度只有 18 字节,但系统也不在乎(枚举过程中第一次获取设备描述符)。此时,描述符的长度信息对它来说是最重要的。当完成第一次的控制传输后,也就是完成控制传输的状态阶段,系统会要求 hub 对设备进行再一次的复位操作(USB 规范里面可没这要求),再次复位的目的是使设备进入一个确定的状态。
主机控制器通过 Set_Address 请求向设备分配一个唯一的地址。在完成这次传之后,设备进入地址状态(Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了,经历这些状态设备将重复枚举的过程。
主机再次发送 Get_Descriptor 请求到新地址读取设备描述符,这次主机发送Get_Descriptor 请求会返回整个设备描述符信息,18 字节的长度,它会认真解析设备描述符的内容。设备描述符内信息包括端点 0 的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。
主机通过解析描述符后对设备有了足够的了解,会选择一个最合适的驱动给设备。 然后 announce_device 说明设备已经找到了,最后调用设备模型提供的接口 device_add 将设备添加到 usb 总线的设备列表里,然后 usb 总线会遍历驱动列表里的每个驱动,调用自己的 match(usb_device_match) 函数看它们和设备或接口是否匹配,匹配的话调用 device_bind_driver 函数,现在就将控制权交到设备驱动了。
对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。
驱动(之后的事情都是有驱动来接管负责与设备的通信)根据前面设备回复的信息,发送 Set_Configuration 请求来正式确定选择设备的哪个配置(Configuration)作为工作配置(对于大多数设备来说,一般只有一个配置被定义)。至此,设备处于配置状态 (Configured),当然,设备也应该使能它的各个接口(Interface)。对于复合设备,主机会在这个时候根据设备接口信息,给它们挂载驱动。
1.USB枚举过程
2.从零开始学USB(十五、USB的设备状态)
3.详细分析USB枚举过程(HID键盘)