Direct I/O 操作
框架流程
-
direct I/O(加载/存储)模块是所有传出的direct I/O packets(direct I/O 包)的来源。
-
通过direct I/O包,Rapid I/O包包含了目标设备上数据的存储地址和读取地址。
-
direct I/O 需要RapidIO源设备包含目标设备的本地存储器地址列表。
-
一旦地址列表建立,RapidIO源控制器就利用这些数据来计算目标地址,并将其插入到包header里。RapidIO目标外设从接收到的包header中提取目标地址,并通过DMA把包的payload传递给存储设备。
为方便读者理解,笔者作了示意图如下:
当CPU想把存储空间中的数据发送到外部处理单元(processing element (PE)),或者当CPU想从外部处理单元读取数据时,CPU提供RapidIO外设必须的各种信息,如DSP存储地址,目标设备ID,目标设备地址,包的优先级等。
payload是包的序列号,大家可以理解为包的指针。注意包一般都会有数据部分(data),header部分和payload部分。
一般来说,必须存在一种填满所有RapidIO包的header的所有区域的方法,Load/Store 模型提供了一种方法来处理这种信息交换。这种方法所依赖的是一系列的MMR(Memory Mapped Register 存储器映射寄存器),将这些寄存器作为传输描述符进行信息交换。CPU可以通过配置总线寻址这些寄存器。
一共有8个LSU(Load/Store Unit),每个LSU都有自己的七个寄存器,即LSU_REG0-LSU_REG6,REG0-REG4用来存储控制信息,REG5、REG6用来存储命令和状态信息。除了REG6,其它寄存器都是可读可写的,只有REG6有只读和只写两种模式。
配置完LSU_REG6之后,传输就开始。有些必须的例如传输目标,传输源,都不需要人为配置,是硬件配置好的。
Figure2-5是每个LSU所拥有的寄存器。注意途中两个红色箭头指向的是REG6的两种模式。
LSU的寄存器的域到RapidIO的包header的映射,在Table2-5中解释。
REG0中的内容代表RapidIO的地址高位,映射到RapidIO包header域中就是包要发送的扩展(这里原文档写的是Ext Destination Adress Fields,我理解为扩展地址,即如果地址位数过长可以使用这一位,也有可能是外部目标地址,具体在如何理解这个Ext)目标地址(只适用于包类型2,5,6)。
REG1中的内容代表RapidIO的地址低位,或者代表配置补偿(Config_offset),到底代表这两者中的哪一个还是要看具体的RapidIO 包的类型。如果包的类型是2,5,6,REG1中的内容就代表RapidIO的地址低位,映射到RapidIO包header域就是以下功能:这32位目标地址将与REG3中的Byte_COUNT 共同作用创建64位的对齐RapidIO包header地址。如果包的类型是维护包类型8,REG1中的内容就代表配置补偿,映射到RapidIO包header域就是以下功能:配置补偿与REG3中的Byte_COUN共同作用创建64位的对齐RapidIO包header的配置补偿。(关于配置补偿的概念笔者也不清楚,google之后大概了解了是一个对RapidIO性能寄存器和RapidIO状态寄存器起作用的概念,大概了解以下,概念不是很重要)REG1的这个域的最低的两位必须是00,因为配置的最小单位是4位。
REG2中的内容代表DSP的地址,也就是DSP读取PE中的信息或往PE中传递信息时,所需的DSP的地址。这个域没有对应的映射到RapidIO包header域。
REG3中的Byte_Count内容代表读写的总字节数,最高可以到达1MB。Byte_Count与RapidIO的目标地址一起,共同在RapidIO包header中创建WRSIZE/RDSIZE和WDPTR。具体请参照Table2-5中的内容。
REG3中的Drbll_val是控制门铃的,只应用于包类型的FType不是10的情况。如果将该位设置为0,就说明在包发送完成之后不需要发送任何门铃信息。如果将该位设置为1,就是需要发送信息,这这种情况下,如果不需要响应,就在发送完包的最后一段时发送一个门铃;如果需要响应,就在所有响应收到并没有任何错误时,用Drbll_info位产生一个门铃,如果收到的响应中有错误,门铃将不会产生。
REG4的Interrupt Req位没有到RapidIO 包header域的映射。该位是CPU控制的request位用来产生中断的。一般与non-posted 命令共同使用,在请求数据或状态到来的时候提醒CPU。该位是0,代表命令完成后没有请求中断,是1代表命令完成后请求中断。
REG4的SUP_GINT位是用来“镇压”中断的,如果该位是1,命令完成后产生的中断就会被镇压,即CPU收不到该中断,如果是0,则没有镇压的作用。
REG4的Xambs域指明扩展地址的最高位。
REG4的Priority域是用来确定包的优先级的,优先级被三个变量影响:VC,PRIO[1-0],CRF。
RapidIO的CRF域是与priority一同作用来完成优先级定义的。在拥有相同PRIO,VC变量的情况下,CRF=1标志着该包拥有低一级的优先级,CRF=0标志着该包拥有高一级的优先级。
PRIO确定包的优先级,有4个等级,请求包为了避免系统僵化,不能使优先级3。设置合适的输出优先级是软件的责任。具体设置参考Table2-5中的Priority域的描述。
VC是RapidIO的虚拟通道位,目前只支持虚拟通道0。
REG4的OutPortID域没有映射到RapidIO包header中,该域指明了传出包的port的号码,由CPU和NodeID共同设置。
REG4的ID Size 域指明了设备ID是8位的还是16位的。
REG4的SrcID_MAP域确定了本次传输要使用的ID寄存器。注意RIO_DeviceID_REGn是逻辑层的输入,并不是逻辑层的存储映射。
REG4的DestID域指明了目标设备的ID。
REG5的Ftype和TType是来确定包类型的。
REG5的Hop Count域是为类型8的维护包准备的。
REG5的DRbll Info域是为类型10的包准备的。
Table2-6展示的是状态寄存器(LSUn_REG6)的只读模式的描述。
该模式下的LTID域是LSU的传输索引。 一个LSU能支持不止一个传输,这个索引就帮助确认传输的完成信息(completion code(cc))。
该模式下的LCB域是LSU的背景位。该位的信息被传输过程利用,来确认CC的背景是否和当前的传输有关。
该模式下的FULL位指明所有的的影存器(shadow registers)正在被使用,如果该位是0,则说明至少有一个影存器可以被写入。如果该位是1,说明没有影存器可以配置给任何传输过程。
该模式下的BUSY位指明了指令寄存器的状态。如果该位是0,说明接下来的一套传输描述符可以写入之指令寄存器。如果该位是1,说明指令寄存器正在被当前的传输过程占据,不可用。
Table2-7展示的是LSU_REG6的只写模式的描述。
该模式下的Flush位有以下作用:如果LSU因为一个错误情况被冻结,该位写1将会清除掉所有匹配SRCID的影存器。这一位将比Restart有更高的优先级。
该模式下的Restart位有以下作用:如果LSU因为一个错误情况被冻结,该位写1将会重启传输过程,到错误发生之前。
该模式下的SrcID_MAP域能被软件编辑,来指明要被清除的影存器的SRCID。
该模式下的CBusy位有以下功能:写1的情况下,如果PirvID匹配,就会清空busy位。写0没有什么作用。
该模式下的PrivID域有以下作用:当试图手动释放busy位的时候,释放busy位的CPU的PirvID必须与原始锁定LSU的CPU的PirvID匹配。
每个LSU的背后都有一套影存器(shadow registers),每一个影存器都可以配置用来提前准备传输,即transaction,当LSU空闲之后,就把数据从影存器中拿出来,送到LSU中去。当数据送到LSU中的时候,LSU0-LUS5的值是可以被读取的。在数据传输到LSU中之前,系统将会读取LSU0-5中的上一个数据。上面这些话的意思就是,在写REG5寄存器之前,是不能去LSU中读数的,还有,如果当前实施的指令前面还有未执行完的指令,也不能去LSU中读数。REG6可以读时写,因为它是用来锁定和释放的。
所有能被分配给LSU的影存器都是可以配置的,一共有32套寄存器。这32套寄存器中,16套是用于LSU0-3的,其它16套是用于LSU4-7的,配给一个LSU的影存器的数量,可以通过RIO_LSU_SETUP_REG0,这个寄存器配置。
以下是RIO_LSU_SETUP_REG0的相关图表:
当对RIO_LSU_SETUP_REG0寄存器进行编辑写入的时候,以下步骤必须遵循:
- step1:通过给BLK1_EN寄存器写0,禁用LSU块。
- step2:查看BLK1_EN_STAT寄存器的值为0,以确保LSU被禁用。
- step3:写值给LSU_SETUP_REG0寄存器,分配影存器给LSU。
- step4:给BLK1_EN寄存器写1,使能LSU块。
影存器还有以下限制:
- 每个LSU至少要拥有一个影存器;
- 一个LSU拥有的影存器不能超过9个;
- LSU0-3的影存器合起来不能超过16个,这是软件必须检查的部分;
- 影存器没有特殊的MMR(存储器映射寄存器)。他们使用所属LSU的地址作为自己的地址。
Table2-8列出了预定义的影存器组合。其它任何组合都不支持。有一个与32个影存器有关的配置寄存器。配置数字是写到配置寄存器中的,基于配置数字,硬件分配影存器给每个LSU。对于LSU0-3和LSU4-7,配置数字可以是不一样的。只有在LSU禁用,外设禁用的双重前提下该配置寄存器才可以被编辑。
推荐在用LSU时使用以下模型:
核配置LSU
每个核都能将一定数量的影存器分配给LSU。如果一个LSU有6个影存器,那么就可以分配4个给核0,剩下两个给核1,当然这只是一种情况,你也可以分配1个给核0,5个给核1等等。这种分配方式保证了影存器可以在核之间公平的分配,而且也不会出现有一个核极度缺乏影存器的情况。如果一个核中的影存器被释放了,这意味着该核可以进行新的传输(transaction)了。
另一种情况是将一个LSU的所有影存器都给同一个核。
最推荐的是用户将所有传输分配给一个特定的LSU,这样会帮助缓解HOL blocking的压力。(HOL全称是head of line,意思就是由于FIFO的限制,很有可能排在队列后面的指令所需的设备已经空闲下来,但是指令因为排在队列后边,只能等待被处理,这样就浪费了等待时间内该设备的资源。)
EDMA配置LSU
该配置模型只支持一个特定的LSU上的EDMA单通道,而且只能用于一个核。LSU不能被共享,默认情况下是只有一个影存器分配给LSU,但是如果用户想要分配更多的影存器给LSU也是没有问题的。每套影存器都有LSU_Reg0-5。LSU_Reg6在Reg0-5之间是共享的,共享的目的最终是作用于一个特定的LSU。一次LSU传输是由写LSU_Reg0-5开始的,这些写的动作其实发生在影存器当中。如果LSU空闲,那么马上就选择一个可用的影存器,为 NREAD, NWRITE,NWRITE_R, SWRITE, ATOMIC, 或者 MAINTENANCE RapidIO transaction初始化一次数据传输。如果LSU正在工作于一组影存器之下,那么其余的影存器所共同配置的,正在等待的数据传输,应该等到当前传送结束。一旦LSU完成了它的当前任务,它将马上载入下一组影存器信息。因此,如果一个LSU有3个影存器,那么在他的队列里就永远都会有两个等待数据传输和一个正在进行的数据传输。
Full bit(满位)
CPU在给影存器写入数据之前,必须检测哪些影存器是可用的。因此CPU就会读取REG6的Full位来确认是否有寄存器空闲,如果该位是1,说明没有空闲的影存器了;如果该位是0,说明至少还有一个影存器正处于空闲状态。Full位只会在以下情况下被系统自动写1:当最后的影存器的LSU_Reg5的写入完成之后。所以Busy位和Full位是不可能同时被置1 的。
Busy bit(繁忙位)
为了避免两个CPU同时访问同一套影存器,设置了LSU_Reg6的Busy位。如果一个CPU想夺取LSU的使用权,它必须按照下面几种情况中的描述执行:
1、锁住寄存器
CPU在确认LSU_Reg6的Busy位和Full位都是0的过程中,会发生以下几种情况:
a、Full位是1:这说明没有空闲的影存器,CPU必须重复读取Full位直到有空闲的影存器出现,如果软件设置了每一个CPU都能使用影存器的话,这种情况不可能出现。
b、有些CPU把LSU给锁了:在多核环境里,有可能发生一个CPU要读取的LSU被其它CPU锁住的情况。这种情况下读取LSU_Reg6得到的结果是Busy位是1,该核必须重复读取Reg6以尝试自己锁定它,EDMA没必要做这样的读取,因为它自己有专属的LSU所以不会导致冲突。
c、正常:CPU读取发现Busy位是0,这意味着可以使用LSU寄存器了,硬件将储存锁定LSU的核的PrivID,然后Busy位将会被置为1。这种情况下的唯一特例是,如果读的操作是来自于debugger端的动作,但尽管寄存器的值会被返回,然后这样的读取不会造成其他任何变化。
如果一个核锁了一个LSU,但是通过其他外设中读取了该LSU的busy位,那么该busy位就会一直被读取为1。因此,我们能够利用busy位将一套LSU寄存器在同一个核的不同任务中共享。只要REG5的被写操作完成,busy位就会清除,即变为0。
对REG6的读操作将会返回以下信息:
i. LTID(LSU transaction ID):这是一种暂时依附于transaction的ID,在传输的最后,软件可以使用这个ID来读取传输完成的相关代码。这个ID不能超过映射给LSU的寄存器的数量。例如对于LSU0,如果分给LSU0最多4个影存器,那么LTID只能是0-3.
ii. LCB (LSU Context Bit):这给status位给了一个参考系,来确定它是否是当前的transaction或者之前/之后的transaction。重置之后,如果某个LSU为了一个特定transactionID而锁定了,那LCB会返回一个‘1’值,下一次如果LSU又为了相同的transactionID而锁定,LCB就会返回一个‘0’值。因此如果读取的时候LCB位为1,而完成代码中指示的LCB位为0,软件就知道了完成信息并不是我们想要的。重置之后新的LCB对一个新的transactionID的返回值将会是‘1’。(这部分说的有点乱,意思就是LCB起到了一个区分同一个LSU的不同寄存器所做的传输的作用)。
注意:Busy位和Full位是要一起被读取的,因为如果CPU读取了Full位并获取了LSU寄存器的使用权,busy位马上就会被置1,试图查看busy位之后就会马上导致busy位被置1。所以Busy位和Full位一定要一起读取。
Figure2-8是官方文档中给出的一个LOCK LSU的图示:
先读取REG6确定是否Full位和Busy位都是0,如果都是0,那么LSUx马上进入锁死工作阶段,直到它完成当前的任务,它才会被释放。如果有一个位是1,那么想要使用LSU的核就会一直读取Full位和Busy位来等待使用LSU。
2、配置寄存器
REG0-4用CPU配置,CPU写REG0-4的请求的PrivID必须和之前锁定LSU的privID匹配,如果不匹配,CPU将无法对REG0-4进行写入。
当然,如果LSU被EDMA使用,那就另当别论,此时不需要进行PrivID的匹配。注意配置寄存器的时候,LSU可能在处理其他请求。
Figure2-9 显示了官方文档中对LSU寄存器的配置过程:
3、解除锁定
最终对REG5的写入将会导致Busy位的清除。此时影存器已经准备好被LSU使用了。如果LSU正在繁忙当中,数据就会暂时存在影存器中,直到LSU完成了他当前的传输工作,影存器就会为LSU对他的使用,再次做好准备。
Figure2-10显示了官方文档中对LSU的释放和传输的触发过程:
4、由锁定而未写入导致的持续锁定
如果由于某些原因,锁定LSU的那个CPU没有继续完成写入其余寄存器的工作,LSU将锁定。另一个想要用这个LSU的CPU一直在读取REG6的返回内容,但是在一些尝试后,发现这个LSU一直被锁定,因为它发现busy位一直是1,并且LTID和LCB一直是同一个值,此时该CPU突然意识到有个CPU锁定了这个LSU并且没有再回来管它。
为了避免以上这种情况,在LSU_REG的只写模式中有一个位,叫CBUSY(clear busy),设备的master可以出面并且对cbusy置位。如果PrivID符合之前没有来的那个CPU的PrivID,硬件就会释放busy位。这个动作(释放busy位)将会对CC位写‘111’,这样这个特定的影存器的写入就完成了。注意下一个锁定该LSU的CPU将会使用完全不同的LTID和LCB。
寄存器 RIO_LSU_STAT_REG0-2用来与不同transaction的CC(completion code)保持密切联系,利用LTID附上信息返回给CC位。因为软件知道transaction分配给了哪个LSU,所以它知道要监控 RIO_LSU_STAT_REG0-2的哪一位。寄存器中的LCB位用来通知软件现在的参考系是否适用于当前传输,或之前和之后的传输,这个信息在软件咨询CC位的时候需要利用。
我们还需要影存器的状态位来显示影存器的状态。LSU4-7中也有一套3个相似的寄存器(RIO_LSU_STAT_REG3-5),如果CPU对LSU发出了清除或者重启的命令,CC位就会置为000,所有 RIO_LSU_STAT_REG0-5 寄存器的reset结果都是0。
Table2-2是对LSU_STATUS(LSUx_STAT)寄存器的各个位的功能描述:
可以看到Completion Code域有3位二进制数,指明了正在等待的命令的状态。
completion code域的取值如果是000,说明传输完成,没有错误,包括被提交的错误和没有提交的错误,都没有;如果取值是001,说明在未提交的传输中发生了传输时间用尽;如果是010,说明传输完成,但包没有发送,因为流控封锁了;如果是011,说明传输完成,未提交的响应包(类型8和13)包含了错误状态,或者响应负载长度错误;如果是100,传输完成,包没有发送,原因是包的类型不支持,或者出现了对至少一个LSU的编码是无效的;如果是101,说明DMA传输错误;如果是110,是重试门铃响应已经收到的情况,或者不允许atomic 测试交换;如果是111,说明传输完成,没有发送包因为传输已经被Cbusy位的改变消灭(即Cbusy位改变导致旧的传输终结)。
LSU Context Bit域是当前CC的参考系,对一次传输来说,这需要LCB在读取LSU_REG6之后是同样匹配的,意思就是LCB在REG6读取之后和传输完成之后应该是同一个数值。
DirectIO内容很多,准备分两篇博客来学习,以上是第一篇。