LWIP是嵌入式设备的网络微协议,基本上实现了标准的TCP/IP的功能,它没有项标准的TCP/IP协议那样有很严格的分层。主要原因是由于嵌入式设别的资源有限,所以避免了每层的COPY动作,在不同层之间是之间共用同一内存操作。那么下面我们来介绍下数据到底是怎么从网卡接收,然后吧数据交给协议处理的:
首先说明下,我用的平台是STM32F207+DP83848平台的,lwip1.4.1。
1、首先我们初始化网卡,这里涉及的是硬件上面的配置,在这里就不做详细说明。在配置好了后,我们需要初始化相关参数。由于我们的STM32F207用的是DMA从PHY那里获取数据,然后也是通过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了
}
我们再摘用正点原子的图片说明一下这个流程是怎么实现的:
而接收缓冲区也是这么实现的:
具体看代码实现:
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:数据是怎么传到LWIP的pbuf的?
还是看代码实现:
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层处理的。