基于DWC_ether_qos的以太网驱动开发-收发驱动编写与调试
一.
前言
前面已经介绍了环形描述符的工作方式和描述符的具体格式。收发的驱动实际就是围绕着准备描述符来进行的,这种机制使得驱动代码的编写比较简单了,软件通过描述符高速硬件
DMA
怎么做即可,剩下的就交给硬件
DMA
了,这样效率非常高。
二.
收发驱动
2.1
发送
Current
是硬件维护的指针
,Current
表示硬件当前操作的描述符,如果该描述符不是
OWN BY DMA
则停止。
Inx
是软件维护的指针,软件写完一个描述符
Inx
递增,如果
Inx
追赶上了
Current
,即遇到了
OWN BY DMA
的描述符也停止。
开始时指针如下,
Current
,
Inx
都从
Base
开始,注意
current
需要启动后才能读到值,初始化后是
0
,所以这里编程需要注意
软件准备
n
个描述待发送,黄色部分,并设置这些描述符
OWN BY DMA
,启动
DMA
,硬件开始处理这些描述符
比如硬件处理完一个描述符后,继续处理后面的
硬件处理完
,Current
追上了
Inx
,停止
DMA
当然 Current 在发送的时候,软件又可以从 Inx 往后面准备描述符,相当于 Inx 是生产者,生产描述符, Current 是消费者,消费描述符,这样就可以不间断的进行发送。
/**
* 见手册P1087
* inx 软件当前操作指针
* current DMA当前操作指针
* cnt 描述符个数
* tail
* 0 .... (cnt-1) | tail
* ^ ^
* current
* inx
*
* 0 .... 3 (cnt-1) | tail
* ^ ^
* current inx
* |---own by dma- |
* 1)tail设置在描述符个数cnt以外,这样永远不会因为current==tail而停止,
* 停止条件只有一个即current处的描述符不是own by dma。
* 2)初始化时,inx为0,current寄存器值为0,值为0时认为其指向索引0.
* 3)软件需要发送3个包,修改了3个包0,1,2为own by dma,此时inx变为3,软件同时设置tail触发DMA去重启,并检查current处
* 是否own by dma
* 4)DMA检测到0处 own by dma于是处理0处的描述符,直到处理到描述符3处,发现不是own by dma于是停止
* 5)在硬件DMA处理0,1,2这三个描述符的过程中,软件又可以继续从3开始准备描述符设置为own by dma。
* 所以过程如下:
* [current,inx)注意不包括inx处 own by dma,是硬件待处理的部分。
* 其他部分own by 软件,空闲描述符,软件可以使用的。
* current追赶inx,current追赶到inx后就停止,current和inx都是到了cnt-1后绕回到0.
* inx也是不断追赶current,inx追赶到current说明软件发的快。
*/
int iot_eth_send_frame(eth_ctrl_t *p_ctrl, uint32_t* buffer, uint32_t desc_cnt)
{
uint32_t inx;
uint32_t desc;
uint32_t status;
uint32_t first_seg = 1;
uint32_t sparenum = 0;
if((desc_cnt==0) || (buffer == 0) || (p_ctrl == 0) || (desc_cnt > p_ctrl->txd.buf_cnt))
{
return -1;
}
inx = p_ctrl->txd.inx;
/* 查询空闲描述符个数 */
for(uint32_t i=0; i
{
desc = (uint32_t)(p_ctrl->txd.desc_base) + inx*sizeof(eth_dma_t);
status = DWC_DESC_DESC3(desc);
if (DWC_DESC_STATUS_OWNER_DMA & status)
{
/* 到了own by dma的地方,停止*/
break;
}
sparenum++;
inx++;
inx %= p_ctrl->txd.buf_cnt;
}
DWC_ETH_LOG(("sparenum %d\r\n",sparenum));
if(sparenum < desc_cnt)
{
return -2;
}
/* 填充描述符 */
inx = p_ctrl->txd.inx;
for(uint32_t i=0; i
{
status = 0;
desc = (uint32_t)(p_ctrl->txd.desc_base) + inx*sizeof(eth_dma_t);
if (first_seg)
{
status |= (DWC_DESC_STATUS_FIRST_SEG |
DWC_DESC_STATUS_CRC_PAD_INS |
DWC_DESC_STATUS_SA_REP_R1);
first_seg = 0;
}
DWC_DESC_DESC0(desc) = (uint32_t)buffer[2*i]; /* 待发送的数据的缓冲区 */
DWC_DESC_DESC1(desc) = 0;
if (i == (desc_cnt-1))
{
status |= DWC_DESC_STATUS_LAST_SEG;
DWC_DESC_DESC2(desc) = DWC_DESC_CTRL_BUF1_LEN(buffer[2*i+1]) | DWC_DESC_CTRL_INT_ENA; /* 最后一个描述符使能IOC 发送完中断 */
}
else
{
DWC_DESC_DESC2(desc) = DWC_DESC_CTRL_BUF1_LEN(buffer[2*i+1]); /* 待发送的数据的长度 */
}
DWC_DESC_DESC3(desc) = status | DWC_DESC_STATUS_OWNER_DMA | DWC_DESC_CTRL_BUF1_LEN(buffer[2*i+1]);
inx++;
inx %= p_ctrl->txd.buf_cnt;
}
p_ctrl->txd.inx = inx;
/* 理论上这里只需要停止之后才设置tail重启
* 为了简单不判断是否停止总是写tail重启
*/
desc = (uint32_t)(p_ctrl->txd.desc_base) + p_ctrl->txd.buf_cnt*sizeof(eth_dma_t);
gmac_start_txdsc(p_ctrl->unit,desc);
DWC_ETH_LOG(("Tx Current %x,idx %d\r\n",gmac_get_currenttxdsc(0),inx));
(void)desc_cnt;
uint32_t desc = gmac_get_currenttxdsc(p_ctrl->unit);
if(desc == 0)
{
desc = (uint32_t)(p_ctrl->txd.desc_base);
}
DWC_DESC_DESC0(desc) = (uint32_t)buffer[2*0]; /* 待发送的数据的缓冲区 */
DWC_DESC_DESC1(desc) = 0;
DWC_DESC_DESC2(desc) = DWC_DESC_CTRL_BUF1_LEN(buffer[2*0+1]) | DWC_DESC_CTRL_INT_ENA; /* 待发送的数据的长度 最后一个描述符使能IOC 发送完中断 */
uint32_t status = DWC_DESC_STATUS_OWNER_DMA;
status |= (DWC_DESC_STATUS_FIRST_SEG |
DWC_DESC_STATUS_CRC_PAD_INS |
DWC_DESC_STATUS_SA_REP_R1);
status |= DWC_DESC_STATUS_LAST_SEG | DWC_DESC_CTRL_BUF1_LEN(buffer[2*0+1]);
DWC_DESC_DESC3(desc) = status;
DWC_ETH_LOG(("tx desc %x tail %x\r\n",desc,gmac_get_txtail(p_ctrl->unit)));
//gmac_start_tx(p_ctrl->unit);
gmac_start_txdsc(p_ctrl->unit, desc+0x10);
return 0;
}
2.2
接收
接收和发送类似
初始化时先将所有描述符设置为接收状态,且设置产生
IOC
中断,并启动
DMA
。
如下所示黄色描述符都是表示准备接收数据。
在收到一包数据后,产生中断,如下描述符
0
即接收到数据的描述符,变为非
OWN BY DMA
,软件查询该描述符可以获取到接收数据长度等信息,进行处理。
软件处理完描述符
0
之后,又可以将其设置为接收状态
OWN BY DMA
以便接收继续接收。
以上是接收比软件处理的慢,所以
Current
一直追不上
Inx
,如果接收比软件处理的快,
则
Current
会追上
Inx
而停止。
比如如下
int iot_eth_receive_handle(eth_ctrl_t *p_ctrl,eth_rcv_data_pf cb)
{
int res = 0;
eth_buf_t *p_rxd = &p_ctrl->rxd;
uint32_t status;
uint32_t desc;
uint32_t index = p_rxd->inx;
uint32_t len;
uint8_t *data;
uint8_t dataflag = 0;
do
{
dataflag = 0;
desc = (uint32_t)(p_ctrl->rxd.desc_base) + index*sizeof(eth_dma_t);
status = DWC_DESC_DESC3(desc);
if (DWC_DESC_STATUS_OWNER_DMA & status)
{
/* 到了OWN为DMA的描述符,说明后面部分属于DMA了,不需要继续处理了 */
DWC_ETH_LOG(("eth rx own dma\r\n"));
break;
}
if (DWC_DESC_STATUS_ERROR_SUM & status)
{
DWC_ETH_WARN(("eth rx fcs err\r\n"));
dataflag = 1;
//continue; /* 还是需要继续重新配置描述符接收,否则该描述符将会浪费 */
}
len = DWC_DESC_GET_DATA_LEN(status) - ETH_CRC_LEN;
if (ETH_INVALID_LEN(len))
{
DWC_ETH_WARN(("eth rx len err:%d\r\n",len));
dataflag = 1;
//continue; /* 还是需要继续重新配置描述符接收,否则该描述符将会浪费 */
}
data = (uint8_t *)DWC_DESC_DESC0(desc);
if (NULL == data)
{
DWC_ETH_WARN(("eth rx buf err\r\n"));
dataflag = 1;
//continue; /* 还是需要继续重新配置描述符接收,否则该描述符将会浪费 */
}
/* 数据回调处理:回调存在且帧有效才处理 */
if((cb != 0) && (dataflag==0))
{
cb(data, len);
}
else
{
DWC_ETH_WARN(("eth rx no cb or err\r\n"));
}
DWC_ETH_LOG(("eth rx desc %x %x\r\n",desc,gmac_get_currentrxdsc(0)));
res++;
/* 重新初始化描述符以便继续接收,这里四个描述符都可能被回写,所以都要重新设置 */
DWC_DESC_DESC0(desc) = (uint32_t)(p_ctrl->rxd.buf_base) + ETH_BUFFER_SIZE * index;
DWC_DESC_DESC1(desc) = 0;
DWC_DESC_DESC2(desc) = 0;
DWC_DESC_DESC3(desc) = DWC_DESC_STATUS_INT_ENA | DWC_DESC_STATUS_OWNER_DMA | DWC_DESC_STATUS_BUF_1_ENA;
p_rxd->last_inx = index; /* 逐步推进,处理完了的就都可以作为tai了 */
index++;
if (index >= p_rxd->buf_cnt)
{
index = 0;
}
} while (p_rxd->inx != index); /* 如果p_rxd->dinx和index相等说明整个环形缓冲区都处理了,也要停止继续. */
p_rxd->inx = index; /* 记录下一次从这里继续扫描 */
/* 重新设置Tail
* Tail设置好后,不能再修改,
* Current追赶到Tail之后,并且Current达到Ring_Length之后,会自动绕回到0,
* 此时Current还是小于Tail,继续处理。
* 如果Current追赶到Tail之后,并且Current没有达到Ring_Length,则需要移动Tail来
* 使得Current
*
* 这里理论上需要判断停止才需要重启,为了简单总是进行重启操作。
*/
desc = (uint32_t)(p_ctrl->rxd.desc_base) + (p_rxd->last_inx+1)*sizeof(eth_dma_t);
gmac_start_rxdsc(p_ctrl->unit,desc);
return 0;
}
接收由于要处理包数据所以花费时间比较大,一般不在中断中处理数据,也就是不在中断中调用
iot_eth_receive_handle
,而是查询方式调用
iot_eth_receive_handle
,或者接收
IOC
中断时中断中发送信号量或者设置标志,主线程主循环中接收信号量或标志才调用该函数进行接收处理。
三.
接收调试
收发数据流参考
https://mp.weixin.qq.com/s/klrHhaLMM_0W3FGVwHXFkA
的验证过程吗,按照
MAC
回环,
PHY
回环,和
PC
通讯的过程测试。
3.1
检查描述符相关
接收调试主要关注以下描述符相关寄存器
检查
RxDesc_List
,
RxDesc_Tail
是否设置正确,检查
RxDesc_Ring_Length
描述符个数是否设置正确。检查
RxDesc
和
RxBuffer
是否递增变化。
(gdb) x /28xw 0x01161100
0x1161100: 0x00000000 0x00100001 0x00100be1 0x00000000
0x1161110: 0x00000000 0x0200c120 0x00000000
0x0200bd00
RxDesc_List
0x1161120: 0x0200c510 0x00000000
0x0200bd00
0x0000003f
RxDesc_Tail
0x1161130:
0x00000001
0x0000c8c3 0x00000000 0x00000000
RxDesc_Ring_Length
0x1161140: 0x00000000 0x0200c510 0x00000000
0x0200bd10
RxDesc
0x1161150: 0x00000000 0x0200bd20 0x00000000
0x0200b710
RxBuffer
0x1161160: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
0x111C RxDesc_List
0x1128 RxDesc_Tail
0x1130 RxDesc_Ring_Length
0x114C
RxDesc
0x115C RxBuffer
3.2
确认回写状态和缓存数据是否有
(gdb) p rx_desc
$5 = {{
des0 = 0x200b120,
des1 = 0x0,
des2 = 0x0,
des3 = 0xc1000000
}, {
des0 = 0x200b710,
des1 = 0x0,
des2 = 0x0,
des3 = 0xc1000000
}}
(gdb) p /x rx_buffer
$6 = {
0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0x0, 0x42, 0xaa, 0x55
(gdb)
3.3
确认接收状态
如果以上描述符都正确但是还是没有数据,确认接收是否正在工作
查看
DMA_Debug_Status0
寄存器
RPS0
是
3
表示
Running
(gdb) x /20xw 0x01161000
0x1161000: 0x00003000 0x00000001 0x00000000
0x00006300
DMA_Debug_Status0
0x1161010: 0x00000000 0x00000000 0x00000000 0x00000000
0x1161020: 0x00000000 0x00000000 0x00000000 0x00000000
0x1161030: 0x00000000 0x00000000 0x00000000 0x00000000
0x1161040: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
3.4
检查过滤等其他设置
如果以上还未收到,检查过两次设置,最好设置为
RA
接收所有包,
确认
DMA
的
SR
是否使能等。
四
.
发送调试
和接收类似
4.1
检查描述符
检查如下寄存器设置,看
TxDesc
是否递增
(gdb) x /28xw 0x01161100
0x1161100: 0x00000000 0x00100001 0x00100be1 0x00000000
0x1161110: 0x00000000
0x0200c120
0x00000000 0x0200bd00
TxDesc_List
0x1161120:
0x0200c3d0
0x00000000 0x0200bd20
0x0000003f
TxDesc_Tail
Ring_Length
0x1161130: 0x00000001 0x0000c8c3 0x00000000 0x00000000
0x1161140: 0x00000000
0x0200c3d0
0x00000000 0x0200bd10
TxDesc
0x1161150: 0x00000000
0x0200bd20
0x00000000 0x0200b710
TxBuffer
0x1161160: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
4.2
确认回写状态
(gdb) p /x tx_desc
$2 = {{des0 = 0x200bdc0, des1 = 0x0, des2 = 0x8000002a, des3 = 0x30000000}
(gdb)
4.3
确认发送状态
如果以上描述符都正确但是还是没有数据,确认接收是否正在工作
查看
DMA_Debug_Status0
寄存器
(gdb) x /20xw 0x01161000
0x1161000: 0x00002000 0x00000001 0x00000001
0x00006400
DMA_Debug_Status0
0x1161010: 0x00000000 0x00000000 0x00000000 0x00000000
0x1161020: 0x00000000 0x00000000 0x00000000 0x00000000
0x1161030: 0x00000000 0x00000000 0x00000000 0x00000000
0x1161040: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
4.4
检查发送数据等
检查发送数据是否又
64
字节以上,可以配置
CPC
自动添加
CRC
和填充。
确认
DMA
的
ST
使能了发送等。
五
.
总结
收发驱动实际就是对描述符的操作,需要了解描述符环形结构,软件硬件分别是怎么实用描述符的。另外也需要了解相关寄存器,描述符格式等,了解如何去调试收发过程。