LwIP——以太网描述符

目录

什么是以太网DMA描述符

TX DMA描述符成员变量简介

RX DMA描述符成员变量简介

以太网DMA描述符结构

 如何追踪描述符

 如何创建Tx/Rx描述符

以太网发送和接收数据流程

总结


在移植LwIP之前有必要了解一下以太网DMA描述符的相关知识,ST以太网模块中的接收/发送FIFO和内存之间的以太网数据包传输是以太网DMA描述符完成的。它们一共有两个描述符列表:一个用于接收,一个用于发送,这两个列表的基地址分别写入DMARDLAR寄存器和DMATDLAR寄存器。关于DMA的相关详细介绍可以参考之前写的一篇博客。

【STM32】DMA原理,配置步骤超详细,一文搞懂DMA_~Old的博客-CSDN博客_dma配置

什么是以太网DMA描述符

发送:不需要CPU参与下,把描述符指向的缓冲区数据传输到TX FIFO当中

接收:不需要CPU参与下,把RX FIFO中的数据传输到描述符指向的缓冲区当中。

其中,ST把描述符分为两种:

 对应于在HAL库中的头文件stm32f4xx_hal_eth.h中的结构体ETH_DMAInitTypeDef

typedef struct  
{
  __IO uint32_t   Status;           /*!<状态 */
  
  uint32_t   ControlBufferSize;     /*!< 缓冲区1和2的大小 */
  
  uint32_t   Buffer1Addr;           /*!< 缓冲区1的地址 */
  
  uint32_t   Buffer2NextDescAddr;   /*!< 缓冲区2的地址/保存下一个描述符的地址 */
  
  /*!< Enhanced ETHERNET DMA PTP Descriptors 增强描述符相关的内容,比如时间戳和IPV4校验和 */
  uint32_t   ExtendedStatus;        /*!<增强描述符状态 */
  
  uint32_t   Reserved1;             /*!<保留*/
  
  uint32_t   TimeStampLow;          /*!< 时间戳低位 */
  
  uint32_t   TimeStampHigh;         /*!< 时间戳高位 */

} ETH_DMADescTypeDef;

关于TX DMA描述符成员变量的介绍可以参考ST官方的中文参考手册以太网章节:

TX DMA描述符成员变量简介

TDES0[31]:置0:CPU可将数据拷贝到描述符中,拷贝完成之后把该位置1,告诉DMA可以发送数据

 TDES0[20]:置1,描述符中的第二个地址是下一个描述符地址,ST默认是把该位置1了

TDES1[28:16]:如果TDES0[20]位置1,则该字段无效

TDES3[31:0]:取决于TDES0[20]的值,为1,则指向下一个描述符地址

RX DMA描述符成员变量简介

RDES0[31]:置1,MAC将数据从RX FIFO 传输到RX描述符中,拷贝完成之后该位置0,告诉CPU可以接收数据

RDES0[14]:描述符中的第二个地址是下一个描述符地址,ST以太网驱动库是默认把该位置1

RSES1[28;16] ;如果RDES0[14]位置1,则该字段无效

RDES3[31:0]:取决于RDES0[14]的值,为1,则指向下一个描述符地址

以太网DMA描述符结构

有两种描述符结构,一种是环形结构,一种是链接结构。在ST提供的以太网驱动库stm32f4xx_hal_eth.c中初始化发送和接收描述符列表就使用的是链接结构,DMA描述符连接结构的具体描述如下图。

  上面第一幅图是对于HAL版本V1.26而言的,下面的是HAL版本V1.27而言。

上面的DMA描述符链接结构可以由一下的函数来实现,如下表所示:

HAL版本        函数
F4_V1.26.0HAL_ETH_DMATxDescListInit()
HAL_ETH_DMARxDescListInit()
F4_V1.27.0/F7_V1.17/H7_V1.10ETH_DMATxDescListInit()
ETH_DMARxDescListInit()

 这些函数应该注意以下几点:

  • 跨度性:一个以太网数据包可以跨越一个或者多个DMA描述符
  • 唯一性:一个DMA描述符只能用于一个以太网数据包
  • 循环性:DMA描述符列表中的最后一个描述符指向第一个,形成循环链式结构。

什么是环形单向链表

将一系列节点链接成链,并且单链表的最后一个节点指向链表的第一个节点,构成环状的链表

 如何追踪描述符

为了追踪Tx/Rx的DMA描述符,在下图结构体中定义了两个非常重要的指针变量,如下代码所示:

#if (USE_HAL_ETH_REGISTER_CALLBACKS == 1)
typedef struct __ETH_HandleTypeDef
#else
typedef struct
#endif /* USE_HAL_ETH_REGISTER_CALLBACKS */
{
  ETH_TypeDef                *Instance;     /*!< Register base address       */
  
  ETH_InitTypeDef            Init;          /*!< Ethernet Init Configuration */
  
  uint32_t                   LinkStatus;    /*!< Ethernet link status        */
  
  ETH_DMADescTypeDef         *RxDesc;       /*!< Rx descriptor to Get        */
  
  ETH_DMADescTypeDef         *TxDesc;       /*!< Tx descriptor to Set        */
  
  ETH_DMARxFrameInfos        RxFrameInfos;  /*!< last Rx frame infos         */
  
  __IO HAL_ETH_StateTypeDef  State;         /*!< ETH communication state     */
  
  HAL_LockTypeDef            Lock;          /*!< ETH Lock                    */

#if (USE_HAL_ETH_REGISTER_CALLBACKS == 1)

  void    (* TxCpltCallback)     ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Tx Complete Callback   */
  void    (* RxCpltCallback)     ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Rx  Complete Callback   */
  void    (* DMAErrorCallback)   ( struct __ETH_HandleTypeDef * heth);  /*!< DMA Error Callback      */
  void    (* MspInitCallback)    ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Msp Init callback       */
  void    (* MspDeInitCallback)  ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Msp DeInit callback     */

#endif  /* USE_HAL_ETH_REGISTER_CALLBACKS */

} ETH_HandleTypeDef;

其中的RxDesc和TxDesc两个指针变量指向ETH_DMADescTypeDef结构体,在使用过程中它们两个分别指向下一个要发送或者接收的描述符,如下图所示那样:

 如何创建Tx/Rx描述符

为发送/接收描述符和缓冲区申请内存

这里观看正点原子视频,其实发送/接收描述符本质上也是一个内存块,和缓冲区本质上都是内存。

方法1:使用算法实现的内存申请和释放函数来申请内存

/**
 * @breif       为ETH底层驱动申请内存
 * @param       无
 * @retval      0,正常
 *              1,失败
 */
uint8_t ethernet_mem_malloc(void)
{
    g_eth_dma_rx_dscr_tab = mymalloc(SRAMIN, ETH_RXBUFNB * sizeof(ETH_DMADescTypeDef)); /* 申请内存 */
    g_eth_dma_tx_dscr_tab = mymalloc(SRAMIN, ETH_TXBUFNB * sizeof(ETH_DMADescTypeDef)); /* 申请内存 */
    g_eth_rx_buf = mymalloc(SRAMIN, ETH_RX_BUF_SIZE * ETH_RXBUFNB); /* 申请内存 */
    g_eth_tx_buf = mymalloc(SRAMIN, ETH_TX_BUF_SIZE * ETH_TXBUFNB); /* 申请内存 */

    if (!(uint32_t)&g_eth_dma_rx_dscr_tab || !(uint32_t)&g_eth_dma_tx_dscr_tab || !(uint32_t)&g_eth_rx_buf || !(uint32_t)&g_eth_tx_buf)
    {
        ethernet_mem_free();
        return 1;   /* 申请失败 */
    }

    return 0;       /* 申请成功 */
}

方法2:ST官方给出的例程使用数组来实现内存申请

/* Private variables ---------------------------------------------------------*/
#if defined ( __ICCARM__ ) /*!< IAR Compiler */
  #pragma data_alignment=4
#endif
__ALIGN_BEGIN ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB] __ALIGN_END;/* Ethernet Rx MA Descriptor */

#if defined ( __ICCARM__ ) /*!< IAR Compiler */
  #pragma data_alignment=4
#endif
__ALIGN_BEGIN ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB] __ALIGN_END;/* Ethernet Tx DMA Descriptor */

#if defined ( __ICCARM__ ) /*!< IAR Compiler */
  #pragma data_alignment=4
#endif
__ALIGN_BEGIN uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __ALIGN_END; /* Ethernet Receive Buffer */

#if defined ( __ICCARM__ ) /*!< IAR Compiler */
  #pragma data_alignment=4
#endif
__ALIGN_BEGIN uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __ALIGN_END; /* Ethernet Transmit Buffer */

其中

/* 初始化发送描述符 */

  HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);


/* 初始化接收描述符  */

  HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

 

HAL_StatusTypeDef HAL_ETH_DMATxDescListInit(ETH_HandleTypeDef *heth, ETH_DMADescTypeDef *DMATxDescTab, uint8_t *TxBuff, uint32_t TxBuffCount)
{
  uint32_t i = 0U;
  ETH_DMADescTypeDef *dmatxdesc;
  
  /* Process Locked */
  __HAL_LOCK(heth);
  
  /*设置状态等于BUSY */
  heth->State = HAL_ETH_STATE_BUSY;
  
  /* 指向第一个描述符 */
  heth->TxDesc = DMATxDescTab;
  
  /* 通过for循环来添加每一个描述符 */   
  for(i=0U; i < TxBuffCount; i++)
  {
    /* 获取Tx Desc列表的第i个成员的指针 */
    dmatxdesc = DMATxDescTab + i;
    
    /*TDES0[20]置1:设置第二个地址链接位 */
    dmatxdesc->Status = ETH_DMATXDESC_TCH;  
    
    /* 给描述符的buffer赋值地址 */
    dmatxdesc->Buffer1Addr = (uint32_t)(&TxBuff[i*ETH_TX_BUF_SIZE]);
    
    if ((heth->Init).ChecksumMode == ETH_CHECKSUM_BY_HARDWARE)
    {
      /* Set the DMA Tx descriptors checksum insertion */
      dmatxdesc->Status |= ETH_DMATXDESC_CHECKSUMTCPUDPICMPFULL;
    }
    
    /* 如果小于最大值 */
    if(i < (TxBuffCount-1U))
    {
      /* 描述符的next就指向下一个,否则就指向第一个描述符 */
      dmatxdesc->Buffer2NextDescAddr = (uint32_t)(DMATxDescTab+i+1U);
    }
    else
    {
      /* 最后一个描述符,设置下一个描述符地址寄存器就等于第一个描述符基地址 */ 
      dmatxdesc->Buffer2NextDescAddr = (uint32_t) DMATxDescTab;  
    }
  }
  
  /* 把描述符地址赋值给DMA的寄存器 */
  (heth->Instance)->DMATDLAR = (uint32_t) DMATxDescTab;
  
  /* 设置状态等于Ready */
  heth->State= HAL_ETH_STATE_READY;
  
  /* Process Unlocked */
  __HAL_UNLOCK(heth);
  
  /* Return function status */
  return HAL_OK;
}

以太网发送和接收数据流程

发送:通过网络层下发数据包之后,通过内存拷贝函数,把pbuf数据包拷贝到Tx描述符管理的缓冲区中,缓冲区就保存了要发送的数据包,之后通过以太网的DMA实现存储器到存储器的传输,转移到TX FIFO中,转发到MAC内核,这时候通过介质独立接口发往到PHY设备中,转成电信号/光信号发送出去。

接收:PHY接收到电信号/光信号经过内部的解调和AD的转换,利用介质独立接口发往到MAC内核,再转发到RX FIFO中,通过DMA的存储器到存储器传输到Rx描述符管理的缓冲区中,再拷贝到Rx对应的pbuf中,再往上层网络层发送数据包。

总结

1、TxDesc用来追踪DMA描述符

2、DMA描述符是用来管理缓冲区的

3、缓存区是用来保存数据包的

  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值