基于rt-thread+lwip分析数据是怎么从网卡芯片接收数据到pbuf的(lwip源码解析一)

LWIP是嵌入式设备的网络微协议,基本上实现了标准的TCP/IP的功能,它没有项标准的TCP/IP协议那样有很严格的分层。主要原因是由于嵌入式设别的资源有限,所以避免了每层的COPY动作,在不同层之间是之间共用同一内存操作。那么下面我们来介绍下数据到底是怎么从网卡接收,然后吧数据交给协议处理的:

首先说明下,我用的平台是STM32F207+DP83848平台的,lwip1.4.1

1、首先我们初始化网卡,这里涉及的是硬件上面的配置,在这里就不做详细说明。在配置好了后,我们需要初始化相关参数。由于我们的STM32F207用的是DMAPHY那里获取数据,然后也是通过DMA把发送的数据送到PHY。所以在这里我们首先需要初始化好DMA的数据接收缓存和发送缓存。

1-1.在这里我们需要知道这两个全局变量:

ETH_DMADESCTypeDef  *DMATxDescToSet;//发送缓冲区指针,指向当前发送数据的首地址。

ETH_DMADESCTypeDef  *DMARxDescToGet;//接收缓冲区指针,指向当前接收缓冲区的首地址。

结构体成员变量如下:

typedef struct  {
  __IO uint32_t   Status;                /*!< Status */
  uint32_t   ControlBufferSize;     /*!< Control and Buffer1, Buffer2 lengths */
  uint32_t   Buffer1Addr;           /*!< Buffer1 address pointer */
  uint32_t   Buffer2NextDescAddr;   /*!< Buffer2 or next descriptor address pointer */
/* Enhanced ETHERNET DMA PTP Descriptors */
#ifdef USE_ENHANCED_DMA_DESCRIPTORS//这个红没有打开,所以我们只关心上面几个变量
  uint32_t   ExtendedStatus;        /* Extended status for PTP receive descriptor */
  uint32_t   Reserved1;             /* Reserved */
  uint32_t   TimeStampLow;          /* Time Stamp Low value for transmit and receive */
  uint32_t   TimeStampHigh;         /* Time Stamp High value for transmit and receive */
#endif /* USE_ENHANCED_DMA_DESCRIPTORS */
} ETH_DMADESCTypeDef;

首先来讲解下发送缓冲区:由于rt-thread用到的是一个两维数组做为发送数据的缓冲区,而且是把这个二维数组做成了一个环形的链表,这个就是在初始化需要完成的事情。

下面具体看代码实现:

void ETH_DMATxDescChainInit(ETH_DMADESCTypeDef *DMATxDescTab, uint8_t* TxBuff, uint32_t TxBuffCount)
{
  uint32_t i = 0;
  ETH_DMADESCTypeDef *DMATxDesc;
  /* Set the DMATxDescToSet pointer with the first one of the DMATxDescTab list */
  DMATxDescToSet = DMATxDescTab;//首先是把数组DMATxDescTab的首地址传入
  /* Fill each DMATxDesc descriptor with the right values */   
  for(i=0; i < TxBuffCount; i++)//这个for循环就是把数据缓冲区形成一个单项链表
  {
    /* Get the pointer on the ith member of the Tx Desc list */
    DMATxDesc = DMATxDescTab + i;
    /* Set Second Address Chained bit */
    DMATxDesc->Status = ETH_DMATxDesc_TCH;  
       
    /* Set Buffer1 address pointer */
    DMATxDesc->Buffer1Addr = (uint32_t)(&TxBuff[i*ETH_TX_BUF_SIZE]);
    
    /* Initialize the next descriptor with the Next Descriptor Polling Enable */
    if(i < (TxBuffCount-1))//这里是连接TxBuffCount-1个接收缓冲区,形成的只是一个单项链表
    {
      /* Set next descriptor address register with next descriptor base address */
      DMATxDesc->Buffer2NextDescAddr = (uint32_t)(DMATxDescTab+i+1);//每个结构体
    }
    else //这里才是形成一个环形链表,具体操作是把最后一个最后一个描述符的Buffer2NextDescAddr 指向了第一个描述符,这样就形成了一个环形链表
    {
      /* For last descriptor, set next descriptor address register equal to the first descriptor base address */ 
      DMATxDesc->Buffer2NextDescAddr = (uint32_t) DMATxDescTab;  
    }
  }
   
  /* Set Transmit Desciptor List Address Register */
  ETH->DMATDLAR = (uint32_t) DMATxDescTab;//这步就是把描述符的首地址赋给DMA的发送寄存器,这样我们就可以通过DMA发送数据给PHY了
}

我们再摘用正点原子的图片说明一下这个流程是怎么实现的:

基于rt-thread+lwip分析数据是怎么从网卡芯片接收数据到pbuf的(lwip源码解析一)_第1张图片

而接收缓冲区也是这么实现的:

具体看代码实现:

void ETH_DMARxDescChainInit(ETH_DMADESCTypeDef *DMARxDescTab, uint8_t *RxBuff, uint32_t RxBuffCount)
{
  uint32_t i = 0;
  ETH_DMADESCTypeDef *DMARxDesc;
  
  /* Set the DMARxDescToGet pointer with the first one of the DMARxDescTab list */
  DMARxDescToGet = DMARxDescTab; 
  /* Fill each DMARxDesc descriptor with the right values */
  for(i=0; i < RxBuffCount; i++)//也是形成一个简单的单向链表
  {
    /* Get the pointer on the ith member of the Rx Desc list */
    DMARxDesc = DMARxDescTab+i;
    /* Set Own bit of the Rx descriptor Status */
    DMARxDesc->Status = ETH_DMARxDesc_OWN;

    /* Set Buffer1 size and Second Address Chained bit */
    DMARxDesc->ControlBufferSize = ETH_DMARxDesc_RCH | (uint32_t)ETH_RX_BUF_SIZE;  
    /* Set Buffer1 address pointer */
    DMARxDesc->Buffer1Addr = (uint32_t)(&RxBuff[i*ETH_RX_BUF_SIZE]);
    
    /* Initialize the next descriptor with the Next Descriptor Polling Enable */
    if(i < (RxBuffCount-1))
    {
      /* Set next descriptor address register with next descriptor base address */
      DMARxDesc->Buffer2NextDescAddr = (uint32_t)(DMARxDescTab+i+1); 
    }
    Else //这里才是把单向链表形成一个环形链表
    {
      /* For last descriptor, set next descriptor address register equal to the first descriptor base address */ 
      DMARxDesc->Buffer2NextDescAddr = (uint32_t)(DMARxDescTab); 
    }
  }
  /* Set Receive Descriptor List Address Register */
  ETH->DMARDLAR = (uint32_t) DMARxDescTab; //把接收描述符地址赋给DMA接收寄存器,这样我们就可以通过DMA接收来之PHY的数据了。
  DMA_RX_FRAME_infos = &RX_Frame_Descriptor;//这个是有关帧信息的
}

上面已经把DMA初始化完成了,那么网络数据怎么从网卡接收到数据的呢?

其实在rt-thread里面有一个接收网络数据的邮箱:eth_rx_thread_mb(发送也是有一个邮箱eth_tx_thread_mb),我们主要是讲解下网络数据接收邮箱。我们在初始化的时候配置了DMA中断接收网络数据的。所以在网络数据到来的时候是首先进入接收中断:

void ETH_IRQHandler(void)
{
   rt_uint32_t status;
	status = ETH->DMASR;
	/* Frame received */
	if ( ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R) == SET) 
	{
		rt_err_t result;
		//rt_kprintf("Frame comming\n");
		/* Clear the interrupt flags. */
		/* Clear the Eth DMA Rx IT pending bits */  
		ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
		/* a frame has been received */
		result = eth_device_ready(&(stm32_eth_device.parent));//发送接收数据消息邮箱
		if( result != RT_EOK ) rt_kprintf("RX err =%d\n", result );
		//RT_ASSERT(result == RT_EOK); 
	}
	if (ETH_GetDMAITStatus(ETH_DMA_IT_T) == SET) /* packet transmission */
	{
		ETH_DMAClearITPendingBit(ETH_DMA_IT_T);
	}
	ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
}
//发送网络接收数据的消息邮箱函数如下:
rt_err_t eth_device_ready(struct eth_device* dev)
{
    if (dev->netif)
        /* post message to Ethernet thread */
        return rt_mb_send(ð_rx_thread_mb, (rt_uint32_t)dev);
    else
        return ERR_OK; /* netif is not initialized yet, just return. */
}

在网络接收中断里面发送一个接收数据的消息邮箱,在接收数据的线程去接收消息邮箱的数据,接收数据后就把数据传输到ip_input()函数,这样就是到lwip的内核当中了,交给lwip的内核去处理解析相关数据了,这个我们在下一章节介绍。

接收网络数据的消息邮箱代码:

/* Ethernet Rx Thread */
static void eth_rx_thread_entry(void* parameter)
{
    struct eth_device* device;

    while (1)
    {
        if (rt_mb_recv(ð_rx_thread_mb, (rt_uint32_t*)&device, RT_WAITING_FOREVER) == RT_EOK)//接收网络数据的消息邮箱
        {
            struct pbuf *p;

            /* check link status */
            if (device->link_changed)
            {
                int status;
                rt_uint32_t level;

                level = rt_hw_interrupt_disable();
                status = device->link_status;
                device->link_changed = 0x00;
                rt_hw_interrupt_enable(level);

                if (status)
                    netifapi_netif_set_link_up(device->netif);
                else
                    netifapi_netif_set_link_down(device->netif);
            }

            /* receive all of buffer */
            while (1)
            {
                p = device->eth_rx(&(device->parent));//这个就是调用struct pbuf *rt_stm32_eth_rx(rt_device_t dev)函数接收数据,并把数据存入pbuf链表。
                if (p != RT_NULL)
                {
                    /* notify to upper layer */
                    if( device->netif->input(p, device->netif) != ERR_OK )//这个是调用ip_input()函数,内核IP层去处理解析接收到的数据,然后在丢给上一层(TCP/DDP...协议处理数据),这个函数在下一章节在详细介绍。
                    {
                        LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: Input error\n"));
                        pbuf_free(p);
                        p = NULL;
                    }
                }
                else break;
            }
        }
        else
        {
            LWIP_ASSERT("Should not happen!\n",0);
        }
    }
}

2-1:数据是怎么传到LWIPpbuf的?

还是看代码实现:

struct pbuf *rt_stm32_eth_rx(rt_device_t dev)
{
	struct pbuf *p, *q;
	u16_t len;
	uint32_t l=0,i =0;
	FrameTypeDef frame;
	static framecnt = 1;
	u8 *buffer;
	__IO ETH_DMADESCTypeDef *DMARxNextDesc;
	
	p = RT_NULL;
	
	//rt_kprintf("ETH rx\n");
	/* Get received frame */
	frame = ETH_Get_Received_Frame_interrupt(); //这个代码是查询DMA接收的数据,里面具体的实现大家可以自己看看,主要是查看DMA接收的数据,并把接收数据的	//指针传给frame帧结构。以及移动全局变量描述符,以便接收后面的数据。
	if( frame.length > 0 )
	{
		/* check that frame has no error */
		if ((frame.descriptor->Status & ETH_DMARxDesc_ES) == (uint32_t)RESET)
		{
			//rt_kprintf("Get a frame %d buf = 0x%X, len= %d\n", framecnt++, frame.buffer, frame.length);
			/* Obtain the size of the packet and put it into the "len" variable. */
			len = frame.length;
			buffer = (u8 *)frame.buffer;
			
			/* We allocate a pbuf chain of pbufs from the pool. */
			p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); //这就是申请pbuf的空间,用来存储接收到的网络数据。这个就需要升入进去看看他到底是怎么申请//空间的。可以先说明下,它是根据接收到数据的总长度来申请空间的。写得比较乱,详解代码在后面附上。注意:这里只是负责pbuf的存储空间,而没有具体的把接收到的数//据存入到里面。
			//p = pbuf_alloc(PBUF_LINK, len, PBUF_RAM);

			/* Copy received frame from ethernet driver buffer to stack buffer */
			if (p != NULL)
			{ 
			  for (q = p; q != NULL; q = q->next)
			  {
			    rt_memcpy((u8_t*)q->payload, (u8_t*)&buffer[l], q->len);//这里才是把接收到的数据存入pbuf里面。
			    l = l + q->len;
			  } 
			}
		}
		//上面已经把数据存入到了pbuf里面,下面的代码就是释放刚刚使用的描述符给DMA再次接收数据使用。
		/* Release descriptors to DMA */
		/* Check if received frame with multiple DMA buffer segments */
		if (DMA_RX_FRAME_infos->Seg_Count > 1)
		{
			DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
		}
		else
		{
			DMARxNextDesc = frame.descriptor;
		}
		/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
		for (i=0; iSeg_Count; i++)
		{  
			DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
			DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
		}
		/* Clear Segment_Count */
		DMA_RX_FRAME_infos->Seg_Count =0;
		
		
		/* When Rx Buffer unavailable flag is set: clear it and resume reception */
		if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
		{
			/* Clear RBUS ETHERNET DMA flag */
			ETH->DMASR = ETH_DMASR_RBUS;
			  
			/* Resume DMA reception */
			ETH->DMARPDR = 0;
		}
	}
	return p;
}

下面讲解如何根据接收到的网络数据长度来申请pbuf的,也就是到底需要申请多少个pbuf来存储接收到的数据。

还是上代码:

struct pbuf *
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
  struct pbuf *p, *q, *r;
  u16_t offset;
  s32_t rem_len; /* remaining length */
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));

  /* determine header offset */
  switch (layer) {
  case PBUF_TRANSPORT:
    /* add room for transport (often TCP) layer header */
    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN;
    break;
  case PBUF_IP:
    /* add room for IP layer header */
    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN;
    break;
  case PBUF_LINK:
    /* add room for link layer header */
    offset = PBUF_LINK_HLEN;
    break;
  case PBUF_RAW:
    offset = 0;
    break;
  default:
    LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0);
    return NULL;
  }

  switch (type) {//这里只讲解PBUF_POOL类型的,其他的自己根据代码分析
  case PBUF_POOL:
    /* allocate head of pbuf chain into p */
    p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);//从内存申请一个pbuf
    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc: allocated pbuf %p\n", (void *)p));
    if (p == NULL) {
      PBUF_POOL_IS_EMPTY();
      return NULL;
    }
    p->type = type;
    p->next = NULL;

    /* make the payload pointer point 'offset' bytes into pbuf data memory */
    p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + (SIZEOF_STRUCT_PBUF + offset)));
    LWIP_ASSERT("pbuf_alloc: pbuf p->payload properly aligned",
            ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
    /* the total length of the pbuf chain is the requested size */
    p->tot_len = length; //把接收到的网络数据总长度赋给p->tot_len,给后面循环申请pbuf使用。
    /* set the length of the first pbuf in the chain */
    p->len = LWIP_MIN(length, PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset));
    LWIP_ASSERT("check p->payload + p->len does not overflow pbuf",
                ((u8_t*)p->payload + p->len <=
                 (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED));
    LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT",
      (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0 );
    /* set reference count (needed here in case we fail) */
    p->ref = 1; //这个是pbuf使用次数
    /* now allocate the tail of the pbuf chain */

    /* remember first pbuf for linkage in next iteration */
    r = p;
    /* remaining length to be allocated */
    rem_len = length - p->len; 
    /* any remaining pbufs to be allocated? */
    while (rem_len > 0) {//这里就是循环分配pbuf空间来存储接收到的网络数据,没分配一个,rem_len减去一个pbuf可以存储数据的长度,直到存储完接收到的数据为止。
      q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
      if (q == NULL) {
        PBUF_POOL_IS_EMPTY();
        /* free chain so far allocated */
        pbuf_free(p);
        /* bail out unsuccesfully */
        return NULL;
      }
      q->type = type;
      q->flags = 0;
      q->next = NULL;
      /* make previous pbuf point to this pbuf */
      r->next = q;//上一个pbuf指向刚刚申请的pbuf,形成一个单向链表。
      /* set total length of this pbuf and next in chain */
      LWIP_ASSERT("rem_len < max_u16_t", rem_len < 0xffff);
      q->tot_len = (u16_t)rem_len;//每个pbuf的tot_len记录自己后面还有多长的数据没有存储完。
      /* this pbuf length is pool size, unless smaller sized tail */
      q->len = LWIP_MIN((u16_t)rem_len, PBUF_POOL_BUFSIZE_ALIGNED);
      q->payload = (void *)((u8_t *)q + SIZEOF_STRUCT_PBUF);
      LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned",
              ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
      LWIP_ASSERT("check p->payload + p->len does not overflow pbuf",
                  ((u8_t*)p->payload + p->len <=
                   (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED));
      q->ref = 1;
      /* calculate remaining length to be allocated */
      rem_len -= q->len;
      /* remember this pbuf for linkage in next iteration */
      r = q;
    }
    /* end of chain */
    /*r->next = NULL;*/

    break;
  case PBUF_RAM:
    /* If pbuf is to be allocated in RAM, allocate memory for it. */
    p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));
    if (p == NULL) {
      return NULL;
    }
    /* Set up internal structure of the pbuf. */
    p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset));
    p->len = p->tot_len = length;
    p->next = NULL;
    p->type = type;

    LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned",
           ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
    break;
  /* pbuf references existing (non-volatile static constant) ROM payload? */
  case PBUF_ROM:
  /* pbuf references existing (externally allocated) RAM payload? */
  case PBUF_REF:
    /* only allocate memory for the pbuf structure */
    p = (struct pbuf *)memp_malloc(MEMP_PBUF);
    if (p == NULL) {
      LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
                  ("pbuf_alloc: Could not allocate MEMP_PBUF for PBUF_%s.\n",
                  (type == PBUF_ROM) ? "ROM" : "REF"));
      return NULL;
    }
    /* caller must set this field properly, afterwards */
    p->payload = NULL;
    p->len = p->tot_len = length;
    p->next = NULL;
    p->type = type;
    break;
  default:
    LWIP_ASSERT("pbuf_alloc: erroneous type", 0);
    return NULL;
  }
  /* set reference count */
  p->ref = 1;
  /* set flags */
  p->flags = 0;
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p));
  return p;
}

总结下,从PHY->stm32f207->pbuf的接收流程就是这样,先是初始化DMA,然后从描述符指向的数据缓冲区取数据,然后根据数据长度分配若干个(不能大于最大值)pbuf存数据,然后把数据存入到pbuf->payload。这样lwip就可以解析数据了。好了接收的流程就是这样。自己水平有限,这个只是个人理解,有错误的地方希望指正。

好了,写文档确实是很费时间,最近比较忙,但是还是想写下来,帮助下像我这种菜鸟更快的熟悉lwip,好到此为止,睡觉。。。。。。。。

下一章节详细介绍下数据是怎么传入到lwip内核的IP层处理的。




你可能感兴趣的:(lwip,STM32,rt-thread学习)