本文转自公众号欢迎关注
一.描述符概述
1.0 前言
对于DWC Ethernet QoS驱动的编写来说,初始化完成之后,核心操作就是DMA的描述符链表配置(linked list of descriptors)。DMA根据描述符链表自动在FIFO和用户指定的缓存之间搬运数据。对于熟悉新思的IP的用户来说,这种套路会有似曾相识的感觉,没错新思的大部分高速IP的数据流都是这么处理的,比如在DWC2 USB的IP中就是Scatter/Gather DMA,叫法不一样,实现和思想基本是一样的。这种方式可以解放CPU,使用DMA根据描述符链表搬运数据,对于高速数据流的流式处理有利,相对单个描述符的DMA,链表式描述符一次可以处理更多数据更高效,且能够实现环形不间断流式处理。
参考手册《21 Descriptors》
1.1描述符类型
Normal Descriptor: 描述要传输的数据包和其控制信息。包含两个缓冲区和两个地址指针,这种设计以实现较大的灵活性,支持不同的内存管理方案。
Context Descriptor: 描述要传输的数据包的控制信息。
注意:单个数据包可以使用的描述符数量没有限制,虽然这里没有限制,但是受限于Ring_Length寄存器,描述符链表实际最多只能配置为0x3FF即1024个描述符。
1.2描述符结构
描述链表整体看是一个环形结构即到了描述符的末尾可以绕回.
有一些寄存器对该环形描述符进行定义
1.2.1描述符的颗粒(间隙)
收发都是一样的
上述描述Descriptor n本身的大小是固定的即RDES0~RDES3,4个WORD(根据不同的总线宽度和大小端,有不同的layout,但是其大小总是4个WORD大小,见后面说明).
还支持描述符之间有间隙,间隙的单位根据总线宽度而定,即32,64,128位总线宽度分别对应 Word, Dword, Lword为单位,寄存器DMA_CH(#i)_Control的DSL位域即用于描述该间隙。
再次提醒注意,这里的单位是根据总线宽度而定,不是固定为WORD。
比如这里DSL=0,则表示描述符之间没有间隙,
设置为1,如果是32位总线则间隙为1 WORD。
1.2.2描述符的个数
收发分别由如下寄存器指定
DMA_CH(#i)_TxDesc_Ring_Length
DMA_CH(#i)_RxDesc_Ring_Length
1.2.3描述符的首末地址
1)发由以下寄存器决定:
基地址
DMA_CHi_Tx_Control的ST=0,即TX停止时才能修改以下寄存器。
DMA_CH(#i)_TxDesc_List_HAddress 发送描述符链表基地址的高位,只有40,48位地址模式才使用。
DMA_CH(#i)_TxDesc_List_Address 发送描述符链表基地址的低位,必须根据总线宽度按照Word, Dword,Lword对齐,DMA会自动根据总线宽度忽略低位。
尾地址
DMA_CH(#i)_TxDesc_Tail_Pointer 发送描述符链表尾地址,必须根据总线宽度按照Word, Dword,Lword对齐。
这里为什么没有高位寄存器了呢,因为不需要了,高位和DMA_CH(#i)_TxDesc_List_HAddress一样。
2)收由以下寄存器决定 :
基地址
DMA_CH(#i)_RX_Control 的SR=0,即RX停止时才能修改以下寄存器。
DMA_CH(#i)_RxDesc_List_HAddress 接收描述符链表基地址的高位,只有40,48位地址模式才使用。
DMA_CH(#i)_RxDesc_List_Address
接收描述符链表基地址的低位,必须根据总线宽度按照Word, Dword,Lword对齐,DMA会自动根据总线宽度忽略低位。
尾地址
DMA_CH(#i)_RxDesc_Tail_Pointer接收描述符链表尾地址,必须根据总线宽度按照Word, Dword,Lword对齐。
这里为什么没有高位寄存器了呢,因为不需要了,高位和DMA_CH(#i)_RxDesc_List_HAddress一样。
1.2.4当前处理的Buffer地址
以下只读寄存器可以调试使用,确认当前处理的描述符对应的Buffer地址,reset时这些寄存器会清零。
发送Buffer
DMA_CH(#i)_Current_App_TxBuffer_H 地址高位
DMA_CH(#i)_Current_App_TxBuffer 地址低位
接收Buffer
DMA_CH(#i)_Current_App_RxBuffer_H
DMA_CH(#i)_Current_App_RxBuffer
需要有收发数据才会更新当前处理的Buffer地址。
1.2.5当前描述符指针
以下只读寄存器可以调试使用,确认当前处理的描述符,reset时这些寄存器会清零。
发送
DMA_CH(#i)_Current_App_TxDesc
接收
DMA_CH(#i)_Current_App_RxDesc
Current寄存器是什么时候更新的呢,测试可以知道是写Base寄存器时更新
进一步测试可知是在DMA停止,即DMA_CH(#i)_RX_Control的SR为0时写DMA_CH(#i)_RxDesc_List_Address 时Current寄存器会自动设置为DMA_CH(#i)_RxDesc_List_Address的值。如果SR=1则不会。
TX也类似。在DMA工作之后,该寄存器更新为当前正在处理的描述符。
1.3描述符的内存布局
描述符的大小总是4x4字节的,但是其布局和总线宽度和大小端有关。
且必须根据总线宽度Word, DWord,或 LWord 对齐。
三种总线宽度,2种大小端模式一共有6种组合
32位的大端和小端
64位小端
64位大端
128位小端
128位大端
1.4描述链表的工作过程
1.4.1环形结构
前面介绍的描述符首末地址,当前描述符指针和描述符个数这几个寄存器决定了链表的行为。
DMA可以处理的描述符范围是:[Base,Tail)
注意[]表示包括本身,()表示不包括本身,即包括Base寄存器对应的描述符,不包括Tail寄存器对应的描述符。
当前描述符指针则从Base到Tail-1的位置遍历,当Current=Tai时DMA停止工作。
同时还受描述符个数寄存器限制,
如果描述个数设置为N,则Current到Base~N-1的位置之后会绕回到Base,即以N个为单位回环,这里假设Tail是在N个描述符之后的,如果在之前则回环之前就Current=Tail停止了。
1.4.2停止条件:
Current=Tail
或者Current的位置没有就绪的描述符,即不是Owned by DMA的描述符。
所以Tail至少要大于base才能传输。
1.4.3重启条件
重启条件即保证current处的描述符就绪,且current%N
写Tail寄存器时会触发一次硬件的重启条件检测,来检测上述条件。
所以停止了之后读写 都需要写Tail寄存器才能重启 。
1.4.4几种情况分析
Tail 指向 N-1位置之后,此时current不可能达到tail,因为到N-1时就绕回了,
所以停止条件只能是current处的描述符未就绪,即不是Owned by DMA。
此时current不断回环遍历,只要软件能保证current处的描述符一直就绪,DMA就一直传输处理,形成了不间断的持续流处理。
Tail刚好指向N-1位置或者更向前
则当current=current后停止。
或者current处的描述符未就绪即停止。
此时没法形成持续的流,因为current到了tail之后就会停止。
1.4.5驱动的设计
为了充分利用上述描述符的回环模式,能够连续不断的收发,需要保证DMA处理完之前就更新好后续描述符。使得DMA一直有描述符能处理,不至于current处描述符未就绪而导致停止,且tail设置为在N个描述符之后,这样current始终不会因为=tail而停止。
要保证连续不断的流,需要软件准备描述符的速度大于等于DMA处理描述符的速度。
即current处始终有描述符可以处理,所以软件准备好的描述符在current及其之后,需要软件维护一个变量index记录当前软件准备好的描述到了哪。
即current追赶index。
当index>current时,[current,index)之间是已经准备好的描述符待DMA处理的,[index,Tail)
[base,current)这两部分是DMA已经处理完,软件需要更新的描述符。
当index<=current时,[index,current)是DMA已经处理完,软件需要更新的描述符。
[current,Tail),[Base,index)这两部分是DMA未处理的部分。
所以驱动处理流程如下
初始化时先尽可能多的填充好描述符,对于接收可以初始化所有描述符为接收状态,对于发送当然刚开始可能并不需要发送很多数据,那么就需要发送多少就准备多少描述符。
然后接收中断中处理DMA已经接收完的描述符,应用处理完对应的数据后再将这些描述符设置为接收状态。
对于发送则查询哪些描述符DMA已经发送完就可将其更新为发送状态进行发送。
比如如下开始准备了n个描述符
然后启动DMA传输,
此时有两种情况,一种是硬件处理DMA比如软件更新描述符块,还有一种是相反。
后者软件总是在current追不上index,即软件更新描述符速度大于硬件处理描述符速度
一段时间后可能就是如下所示
而前者软件准备的慢则current会追上index此时DMA会停止,软件需要重新准备描述符并重新配置Tail来重启DMA。
具体的驱动代码后面再详讲。
1.5总结
以上介绍了描述符链表,尤其需要了解环形链表的工作模式何时启动,何时停止,软件和硬件如何配合使用环形链表。以及相关寄存器的值的含义和何时更新。了解以上机制后后面就可以开始编写驱动代码进行收发。下回再详讲。
审核编辑 黄宇
-
嵌入式
+关注
关注
5082文章
19107浏览量
304835 -
以太网
+关注
关注
40文章
5419浏览量
171609 -
寄存器
+关注
关注
31文章
5336浏览量
120235 -
驱动
+关注
关注
12文章
1838浏览量
85263 -
dma
+关注
关注
3文章
560浏览量
100551
发布评论请先 登录
相关推荐
评论