各位还记得”任何传输都是由host发起的”这句话么~
在usb设备插入pc中到拔出usb设备,都是由host进行询问的
一个usb鼠标的工作流程可以表达如下:
usb鼠标插入pc中:
主机询问设备:给我你的设备信息(控制传输)
主机根据usb鼠标的设备信息进行驱动配置,配置结束后
主机询问设备:给我你的数据信息(中断传输)
一定的时间间隔之后....
主机询问设备:给我你的数据信息(中断传输)
一定的时间间隔之后...
主机询问设备:给我你的数据信息(中断传输)
.................
直到这个设备被拔出,主机不断要求设备提供鼠标的按键和移动数据
因为任何传输都是由host发起的,所以host是我解析的第一对象,而UHCI这个host是我将介绍的对象,目前PC上有3类主机控制器,分别为UHCI,OHCI和EHCI
UHCI和OHCI是低速和全速USB设备的主机控制器,所有高速设备都是EHCI这个主机控制器管理的
为什么选择UHCI而不是OHCI呢~ 很简单,因为我是参考fudan_abc的BLOG进行解析的,他写的是关于UHCI的,呢么我只有写UHCI的,因为很多技术规范只有UHCI的生产商英特尔才知道,包括他的BUG
下面我先简短的介绍一下UHCI
UHCI全称Universal Host Controller Interface,也就是通用主机控制器接口,他的一个重要责任就是发送USB通信中最关键的令牌包,下面我们就看看他是怎么发送令牌包的
在UHCI的软件控制中,最小的处理为一个叫Transfer Descriptor的结构,这个结构为16字节
黄色区域表示主机控制器硬件可以读写的,白色表示主机控制器硬件只能读,不能写
所有字段都能由主机控制器驱动,也就是软件来写
td的这4个字段从上倒下分别为连接字段,控制字段,令牌字段和缓冲字段
呢么这个TD完成一个什么样的功能呢?~
很可惜,从软件上看,这个TD负责完成一个事务的传输,也就是令牌包,数据包和握手包这3个包的传输都由TD来描述,所以单独令牌包的发送是硬件自动完成的,软件是参与不到的,所以我们只能从一个控制传输来看看UHCI是如何完成事务传输的
不过在这之前还需要介绍1个知识,Frame List
Frame List是一个由1024个Frame组成的帧列表,干嘛用呢?~很简单,所有的td都是挂载在Frame上的,然后UHCI分配给每帧1ms的时间做传输,执行Frame上的td队列,
1ms到期后就处理下一帧的td队列
现在我们假定设备的地址为20,使用0号端点,发送一个setup事务,任务为取得大小为18字节的设备描述符
这个setup事务使用一个setup事务,数据阶段使用一个in事务,状态阶段使用一个out事务, 好,现在开始构建td队列
第一个事务为setup事务
1. 首先填充 td令牌字段中的pid字段,setup事务中的令牌包为setup包,setup包的pid为1101,pid效验为0010,则pid字段为0x2D
2. 填充td令牌字段中的地址字段,这里我们使用的地址为20,则填充0x14
3. 填充td令牌字段中的端点字段,这里我们使用的端点为0,则填充0x0
4. 填充td令牌字段中的数据长度字段,setup事务中data的数据大小为8字节,则填充0x8
5. 填充td令牌字段中的数据包类型字段,setup默认为0,这里填充为0
6. 填充td的缓冲字段,这个缓冲字段是一个指针,指向一个DMA地址,UHCI硬件根据事务是写还是读来直接读写这个地址上的数据,不需要CPU的参与,假设我已经为8字节的数据申请了一个DMA地址,为0xFFFF,则填充0xFFFF到缓冲字段
7. 填充td的连接字段,因为还有一个数据阶段的in事务和状态阶段的out事务,这里连接数据阶段的in事务所使用的td地址
第二个事务为数据阶段的in事务
1. 首先填充首先填充 td令牌字段中的pid字段,in事务中的令牌包为in包,in包的pid为1001,pid效验为0110,则pid字段为0x69
2. 填充td令牌字段中的地址字段,这里我们使用的地址为20,则填充0x14
3. 填充td令牌字段中的端点字段,这里我们使用的端点为0,则填充0x0
4. 填充td令牌字段中的数据长度字段,我们所需要的设备描述符的大小为18字节,则这里填写为0x12
5. 填充td令牌字段中的数据包类型字段,因为这是第一个数据包,第一个数据包所使用的类型为data1,这里填充为1
6. 填充td的缓冲字段,这个缓冲字段是一个指针,指向一个DMA地址,UHCI硬件根据事务是写还是读来直接读写这个地址上的数据,不需要CPU的参与,假设我已经为18字节的数据申请了一个DMA地址,为0xCCCC,则填充0xCCCC到缓冲字段
7. 填充td的连接字段,因为还有一个状态阶段的out事务,这里连接状态阶段out事务所使用的td地址
第三个事务为状态阶段所使用的out事务
1. 首先填充首先填充 td令牌字段中的pid字段,out事务中的令牌包为out包,out包的pid为0001,pid效验为1110,则pid字段为0xE1
2. 填充td令牌字段中的地址字段,这里我们使用的地址为20,则填充0x14
3. 填充td令牌字段中的端点字段,这里我们使用的端点为0,则填充0x0
4. 填充td令牌字段中的数据长度字段,状态阶段的数据包中的数据长度为0,这里填充为0x0
5. 填充td令牌字段中的数据包类型字段,因为这是状态阶段的数据包,规定使用的数据包类型为data1,这里填充为1
6. 填充td的缓冲字段,因为该数据包的数据为0,这里填充0
7. 填充td的连接字段,因为后面没有事务了,这里填充停止标志
现在把这3个事务连接到帧上,
形成下图
UHCI如何进行操作呢?~ 我形象的比喻一下
1号td(setup事务)-setup阶段
1. 主机:我发setup包了,等一下我还要发个数据包
2. 主机:我发数据包了
3. 设备:我收到了
2号td(in事务)-数据阶段
1:主机:我发in包了,你给我返还一个数据包
2:设备:我发数据包了
3:主机:我收到了
3号td(out事务)-状态阶段
1. 主机:我发out包了,等一下我还要发个数据包
2. 主机:我发数据包了
3. 设备:我收到了
这样,一个控制传输就完成了
是不是觉得很简单呢?~
其实USB真正复杂的地方在于休眠和恢复,这些都是由主机控制器来完成的,平时USB驱动程序是不会接触到的,但在之后的UHCI在LINUX中的驱动中我将尽自己的能力分析,毕竟我的数模电路基础几乎为0,大家多包涵 = 3=
了解了主机端,我们再看看设备端,设备端的工作相对较少,为什么呢?看看前面控制传输的图,可以发现,所有的事务的第一个包都是黄色的,也就是都是由主机发送的,设备所要发送的包类型只有两种,数据包和握手包,处理流程如下
在PDIUSBD12中,应答包是由硬件自动判断的,剩下给软件编程人员的就只剩根据令牌包是否发数据包的权利了,真是悲惨啊~