4.1 主控初始化
系统启动时,主控被枚举,分配一个基地址给寄存器空间,BIOS设置FLADJ寄存器一个特有的值。在加电或者HCResst(硬件或者通过 USBCMD寄存器的HCReset位),所有的操作的寄存器被设置成默认的值,硬件复位后,只有不包含在Auxiliary power(辅助电力)中的操作寄存器是默认值。
为了初始化主控,软件要做下面步骤
a)给CTRLDSSEGMENT寄存器指定一个4GB的段,所有的接口数据结构都在那里。
b)写一个合适的值到USBINTR寄存器,能用一个合适的中断。
c)写Periodic Frame List的基地址到PERIODICLIST BASE寄存器,如果periodic schedule中没有工作,Periodic Frame List所有的元素应该把T位设置成1。
d)写USBCMD寄存器,设置需要的中断,frame list的大小,然后通过设置Run/Stop位打开主控。
e)写1到CONFIGFLAG寄存器,路由所有的端口到EHCI控制器(看4.2节)。
这时,主控运行,端口寄存器将报告设备的连接。系统软件枚举一个端口通过复位过程(当这个端口是可用状态时)。这时,高速端口能用了,但是 schedules还不能用,低速和全速端口也不能用。
为了通过asynchronous schedule和设备通信,系统软件必须把控制或者批量Queue Head的地址写到ASYNDLISTADDR寄存器。软件写1到USBCMD寄存器中的Asynchronous Schedule Enable位,使asynchronous schedule能用。为了通过periodic schedule和设备通信,系统软件必须写1到USBCMD寄存器中的Periodic Schedule Enable位,使periodic schedule能用。注意在第一个端口复位之前,schedules可以被打开。
任何时候,写USBCMD寄存器,系统软件必须注意保存其它位。
4.11 Ping 控制
某家注:<<< 在全速模式时,每个OUT传输发送OUT数据包,不考虑外设是否处于“忙”状态而不能接收数据。针对这种浪费带宽的情况,在高速模式时推荐使用新的PID 类型“PING”。主机先对OUT端点发出个较短的“PING”令牌,访问当前外设是否有数据文凭间来存放OUT的数据包。仅仅当外部设备回答“ACK” 时,主机才发送较长OUT数据包。>>>
USB2.0协议为高速设备定义了一个事务叫作Ping。所有的USB2.0高速批量和控制端点都需要Ping。split- transaction流不允许Ping。这项协议扩展消除了OUT端点不应答的副作用。Status域有一个Ping State位,主控使用它确定下一个事务使用的实际PID。当满足下面所有约束的时候,主控才使用Ping State位。
a)Queue head不是中断。
b)EPS域表示高速。
c)PIDCode域等于OUT。
表4.12是主控管理ping协议的一个表,参考usb2.0协议第8节关于ping协议的详细描述。
Ping State位值的意思是:
=0,Do OUT,// 主控对这个端点的下一个事务使用OUT PID。
=1,Do Ping,// 主控对这个端点的下一个事务使用Ping PID。
当我们不知道设备上还有没有空间时,以Do OUT开始。
主控管理Ping State位,系统软件初始化queue head的时候,设置此位的初始值。执行这个queue时,主控一直保存Ping State。意思是说,当新的qTD写到queue head的overlay区域时,以前的Ping State状态值被保存着。
4.4 Schedule传送规则
主控执行和设备之间的传输使用一个简单的,共享内存的Schedule(方案)。方案由一些数据结构构成,组成两个截然不同的链表。这些数据结 构被设计成最大的灵活性,最小的内存,最小的硬件软件复杂性。
系统软件包括两个schedules: periodic schedule和asynchronous schedule, 对应periodic schedule是PERIODICLISTBASE寄存器,是periodic frame list的物理内存基地址。periodic frame list是一组物理内存指针,必须指向有效的schedule数据结构。在每一个微祯,如果periodic schedule可用,主控必须执行periodic schedule在asynchronous schedule之前。只有在遇到periodic schedule结束的时候才会执行asynchronous schedule。主控沿着periodic schedule执行,构造一个偏移量FRINDEX registers。主控取Periodic Frame List中的元素,然后传送链接的schedule数据结构。
periodic schedule的最后,schedule数据结构的next link pointer的T位设置成了1。主控在periodic list中水平前进时遇到了T位设置成1,认为是End-Of-Periodic-List 标志。主控立即转向asynchronous schedule。转向后,主控执行asynchronous schedule直到微祯结束。
当主控决定执行asynchronous list的时候,它使用操作寄存器ASYNCLISTADDR去访问asynchronous schedule。
ASYNCLISTADDR包含一个指向next queue head的物理地址。主控转向asynchronous schedule时,它读ASYNCLISTADDR寄存器指向的queue head。软件必须设置queue head的水平指针T位是0,表示queue head在asynchronous schedule中。
4.10.4 写回qTD
当Active位变成0的时候,主控就从“执行事务”状态进入“写回qTD”状态。要写回的源数据是——queue head中的transfer results区域。主控使用Current qTD Pointer域作为qTD的目标地址。queue head的transfer results区域被写回到目标qTD的transfer result区域。这个状态也叫做:“qTD退休”。必须写回qTD的域包括:Total Bytes to Transfer,Cerr,和Status。
这个状态的持续时间取决于-什么时候qTD的写回被提交。
4.8 Asynchronous Schedule
USBCMD寄存器中的Asynchronous Schedule Enable位决定Asynchronous schedule传输能或者不能。设置成0的时候,主控不会去访问asynchronous schedule;设置成1的时候,主控使用ASYNCLISTADDR寄存器横行asynchronous schedule。改变那一位不会立即有效,只有主控再一次在ASYNCLISTADDR寄存器取值的时候,那一位的新值才被考虑。
USBSTS寄存器中的Asynchronous Schedule Status位,指示了asynchronous schedule的状态。系统软件通过写1(或者0)到USBCMD寄存器的Asynchronous Schedule Enable位使asynchronous schedule能(或者不能)。然后软件读Asynchronous Schedule Status位判断要求的事务是否被执行了。当Asynchronous Schedule Enable位和Asynchronous Schedule Status位相同的时候,软件才可以改变Asynchronous Schedule Enable位。
asynchronous schedule用来管理所有的控制和批量传输,控制和批量传输使用queue head数据结构。asynchronous schedule的基地址在ASYNCLISTADDR寄存器重要。复位后ASYNCLISTADDR寄存器的默认值不确定,当Asynchronous Schedule Enable位设置成0的时候这个schedule不可用。[她奶奶的,一句话老是重复来重复去]
只有schedule不可用的时候软件才可以写ASYNCLISTADDR寄存器。也就是说在USBCMD寄存器的 Asynchronous Schedule Enable位和USBSTS寄存器的Asynchronous Schedule Status位都是0的时候。软件把一个有效的内存地址(queue head的)写到这个寄存器。然后再把Asynchronous Schedule Enable设置成1,使asychronous schedule可用。当Asynchronous Schedule Status是1的时候asynchronous schedule就真正可用了。
主控使用ASYNCLISTADDR寄存器的值来开始asynchronous schedule(任务)。主控读指向的第一个数据结构开始执行事务,然后沿着链表前进。主控完成asynchronous schedule的处理后,把最后访问的queue head的水平指针保存在ASYNCLISTADDR寄存器。下一次synchronous schedule被访问时,这就是要处理的第一个数据结构。
当下面一个事件发生的时候,主控就完成了asynchronous schedule的处理。
a)一个微祯结束。
b)主控发现空链(第4.8.3节)。
c)USBCMD寄存器的Asynchronous Schedule Enable位被设置成1。
asynchronous list中的queue head被连成了一个简单的环。只有合法的queue head数据结构才可以被连接进asynchronous schedule。把iTD或者siTD放进asynchronous schedule会有意想不到的结果。
异步传输中queue head连成了一个环,iTD和siTD在异步任务重会有意想不到的结果。
queue head中的Maximum Packet Length域被设置成适应所有非等时传输类型的需要。 USB2.0手册说明了每一种传输类型和速度传输速度的最大包长度。系统软件应该根据手册改变queue head中的参数。[不太理解,Maximum Packet Length = 0x400肯定没错]
4.8.1 增加Queue Heads到Asynchronous Schedule(异步任务)
这是一个软件需要的章节,添加queue head到asynchronous schedule有两个独立的事件,第一是激活asynchronous list,第二是插入新的queue head到asynchronous list。
链表激活很简单,系统软件把queue head的物理内存地址写到ASYNCLISTADDR寄存器,然后设置Asynchronous Schedule Enable位是1。
插入一个queue head到链表的时候,软件必须保证从主控的角度来看schedule是连贯的。意思是软件必须保证所有queue head的指针域都是有效的。如是说,qTD 指针有一个T位设置成1,或者指向有效的qTDs,而且水平指针指向有效的queue head数据结构,下面的算法表达这个功能需要:
InsertQueueHead (pQHeadCurrent, pQueueHeadNew)
{
//
// 需要:所有输入参数必须被初始化。
//
pQueueHeadNew.HorizontalPointer = pQueueHeadCurrent.HorizontalPointer;
pQueueHeadCurrent.HorizontalPointer = physicalAddressOf(pQueueHeadNew);
}
4.8.2 从Asynchronous Schedule中去掉Queue Heads?
这个也是软件需要的章节。也有两个独立的事件。第一是停止asynchronous list。第二是从链表取出来一个queue head。
软件把USBCMD寄存器的Asynchronous Schedule Enable位设置成0就停止了asynchronous schedule。然后检测到Asynchronous Schedule Status位是0的时候,链表就空闲了。
移除一个queue head的通常做法是不停止asynchronous schedule。软件不能从schedule中移除一个活动的queue head。软件应该首先使所有的活动qTD不活动,等待queue head变成不活动,然后从asynchronous list移除queue head。按照下面的算法去做。说明:去链很简单。软件仅仅保证,对于主控来说,所有的指针保持连贯。
UnlinkQueueHead (pQHeadPrevious, pQueueHeadToUnlink, pQHeadNext)
{
//
// 需要:所有输入参数必须被初始化。
//
// 如果主机软件只有一个queue head,pQHeadNext必须和
// pQueueheadToUnlink.HorizontalPointer相同。如果主机软件要去连接一串
// queue heads, pQHeadNext必须指向schedule中剩余的queue head
//
pQueueHeadPrevious.HorizontalPointer = pQueueHeadToUnlink.HorizontalPointer;
pQueueHeadToUnlink.HorizontalPointer = pQHeadNext;
}
如果软件移除一个H位设置成1的queue head,它必须把schedule中的另一个queue head的H位也设置成1。应该在移除这个queue head之前完成。要求就是保证asynchronous schedule中的一个queue head的H位是1。
软件移除一个或多个queue head的时候,它不知道主控缓存的指针是否指向它们。……。因此它必须保持queue head的连贯性。软件不能打扰刚刚移除的queue head,直到它知道主控中不再有指向它们的指针。
何时修改已被移除的queue head是安全的,方法是软件和主控握握手。
握手使用主控中的3个位。一、命令位,USBCMD寄存器中的Interrupt on Async Advance Doorbell位,软件用它通知主控一些东西从asynchronous schedule移除了。二、状态位,USBSTS寄存器中的Interrupt on Async Advance位,主控在释放某些状态时设置此位,这些可能指向被移除的数据结构。设置这一位是1的时候也设置了前面那位是0。三、中断 位,USBINTR寄存器的Interrupt on Async Advance位。这一位设置成1,interrupt enable位也会是1,主控就会发生一个硬件中断。
图4-10说明了一个通用的例子。在这个例子中,连贯的queue head(B和C)从schedule中“去连接”。在去连接操作之前,主控中有一份queue head A的拷贝。去连接算法要求:当软件去连接每一个queue head时,去连接的queue head必须指向asynchronous schedule中剩余的queue head。
当主控发现doorbell位设置成1的时候,它记录schedule的信息。在这个例子中,schedule的信息包括queue head A和B。主控设置状态位(同时清除doorbell位),在它跑到schedule信息外面的时候(例子中,跑到B外面的时候)。
或者说,主控在设置Advance on Async status是1之前,主控可以在整个asynchronous schedule list中横行。
软件发现Interrupt on Async Advance status位被设置1之后就可以重新使用已移除的queue head的内存,在判断doorbell之后。软件应该确认Interrupt on Async Advance status位,在再次使用doorbell握手之前。