Lwip移植:
数据流向:
如图,Remote为远端主机,通过网线连接。
- 本地机器接收,数据流上依次是 PHY芯片、以太网MAC、在到软件层的协议栈LWIP、最后到应用程序。
- 本地机器发送,数据流上依次是软件层应用程序、协议栈LWIP、硬件以太网MAC层、通过网络PHY芯片发送以太网帧。
- PHY与MAC之间数据接口可能是MII\RMII\GMII\RGMII。
无系统移植LWIP需要做的工作:
1、网卡驱动移植。实现上图中的 Input、Output以及一个初始化函数。
2、Lwip 时钟移植(超时处理用到的)。
带FreeRTOS移植LWIP需要做的工作:
1、网卡驱动移植。实现上图中的 Input、Output以及一个初始化函数。
2、系统接口相关移植。
一、网卡驱动移植。
1、Input 函数
接口原型参考:lwip\contrib\examples\ethernetif\ethernetif.c
/**
* This function should be called when a packet is ready to be read
* from the interface. It uses the function low_level_input() that
* should handle the actual reception of bytes from the network
* interface. Then the type of the received packet is determined and
* the appropriate input function is called.
*
* @param netif the lwip network interface structure for this ethernetif
*/
/* 当网络接口中有数据包时调用这个函数,这个函数使用low_level_input()去接收网络接口中的数据。
* 判断数据包的不同类型,调用不同的输入接口。
*/
void ethernetif_input(struct netif *netif)
{
struct ethernetif *ethernetif;
struct eth_hdr *ethhdr;
struct pbuf *p;
ethernetif = netif->state;
/* move received packet into a new pbuf */
p = low_level_input(netif);
/* if no packet could be read, silently ignore this */
if (p != NULL) {
/* pass all packets to ethernet_input, which decides what packets it supports */
if (netif->input(p, netif) != ERR_OK) {
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
}
}
/* 不同的 low_level_input 函数即可。
* low_level_input 函数中把驱动的char buf数据拷贝到pbuf格式。
* netif->input(p, netif) 把转换后的pbuf数据输入到协议栈中。
* 接收动作就完成了。
* /
1.1、low_level_input 函数原型
/**
* Should allocate a pbuf and transfer the bytes of the incoming
* packet from the interface into the pbuf.
*
* @param netif the lwip network interface structure for this ethernetif
* @return a pbuf filled with the received packet (including MAC header)
* NULL on memory error
*/
static struct pbuf *
low_level_input(struct netif *netif)
{
struct ethernetif *ethernetif = netif->state;
struct pbuf *p, *q;
u16_t len;
/* Obtain the size of the packet and put it into the "len"
variable. */
len = ;
#if ETH_PAD_SIZE
len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif
/* We allocate a pbuf chain of pbufs from the pool. */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
#if ETH_PAD_SIZE
pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif
/* We iterate over the pbuf chain until we have read the entire
* packet into the pbuf. */
for (q = p; q != NULL; q = q->next) {
/* Read enough bytes to fill this pbuf in the chain. The
* available data in the pbuf is given by the q->len
* variable.
* This does not necessarily have to be a memcpy, you can also preallocate
* pbufs for a DMA-enabled MAC and after receiving truncate it to the
* actually received size. In this case, ensure the tot_len member of the
* pbuf is the sum of the chained pbuf len members.
*/
read data into(q->payload, q->len);
}
acknowledge that packet has been read();
MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);
if (((u8_t *)p->payload)[0] & 1) {
/* broadcast or multicast packet*/
MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);
} else {
/* unicast packet*/
MIB2_STATS_NETIF_INC(netif, ifinucastpkts);
}
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
LINK_STATS_INC(link.recv);
} else {
drop packet();
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
MIB2_STATS_NETIF_INC(netif, ifindiscards);
}
return p;
}
/* 申请pbuf,层参数为PBUF_RAW, 类型为PBUF_POOL。
* 然后调用网卡驱动把char buf数据拷贝到pbuf中。
* 怎么不同情况增加计数。
*/
2、Output函数
/**
* This function should do the actual transmission of the packet. The packet is
* contained in the pbuf that is passed to the function. This pbuf
* might be chained.
*
* @param netif the lwip network interface structure for this ethernetif
* @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
* @return ERR_OK if the packet could be sent
* an err_t value if the packet couldn't be sent
*
* @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
* strange results. You might consider waiting for space in the DMA queue
* to become available since the stack doesn't retry to send a packet
* dropped because of memory failure (except for the TCP timers).
*/
/* 这个函数是真正执行发送动作的,以太网包存放在pbuf中。这里的pbuf有可能是一条链。
*/
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
struct ethernetif *ethernetif = netif->state;
struct pbuf *q;
initiate transfer();
#if ETH_PAD_SIZE
pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif
for (q = p; q != NULL; q = q->next) {
/* Send the data from the pbuf to the interface, one pbuf at a
time. The size of the data in each pbuf is kept in the ->len
variable. */
send data from(q->payload, q->len);
}
signal that packet should be sent();
MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len);
if (((u8_t *)p->payload)[0] & 1) {
/* broadcast or multicast packet*/
MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts);
} else {
/* unicast packet */
MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);
}
/* increase ifoutdiscards or ifouterrors on error */
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
LINK_STATS_INC(link.xmit);
return ERR_OK;
}
/* low_level_output 函数就是把pbuf数据发送出去,注意pbuf可能是一条链。
*/
3、Init函数
/**
* Should be called at the beginning of the program to set up the
* network interface. It calls the function low_level_init() to do the
* actual setup of the hardware.
*
* This function should be passed as a parameter to netif_add().
*
* @param netif the lwip network interface structure for this ethernetif
* @return ERR_OK if the loopif is initialized
* ERR_MEM if private data couldn't be allocated
* any other err_t on error
*/
err_t
ethernetif_init(struct netif *netif)
{
struct ethernetif *ethernetif;
LWIP_ASSERT("netif != NULL", (netif != NULL));
ethernetif = mem_malloc(sizeof(struct ethernetif));
if (ethernetif == NULL) {
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
return ERR_MEM;
}
#if LWIP_NETIF_HOSTNAME
/* Initialize interface hostname */
netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */
/*
* Initialize the snmp variables and counters inside the struct netif.
* The last argument should be replaced with your link speed, in units
* of bits per second.
*/
MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);
netif->state = ethernetif;
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
/* We directly use etharp_output() here to save a function call.
* You can instead declare your own function an call etharp_output()
* from it if you have to do some checks before sending (e.g. if link
* is available...) */
#if LWIP_IPV4
netif->output = etharp_output;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
netif->linkoutput = low_level_output;
ethernetif->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]);
/* initialize the hardware */
low_level_init(netif);
return ERR_OK;
}
/* Init函数很简单,就是创建一个网卡状态结构体,注册上面写的output函数, input函数则在中断中调用或者轮询。
* 设置MAC地址等一些软件属性。
* 最后调用low_level_init 函数,初始化网卡。low_level_init根据不同的网络芯片实现。
*/
二、无操作系统Lwip 时钟移植 – 实现sys_now 函数。
static volatile uint32_t time_now = 0;
void time_isr(void)
{
time_now++;
}
uint32_t sys_now(void)
{
return (uint32_t)time_now;
}
三、带FreeRTOS,系统相关接口移植。
如果使用FreeRTOS的话需要添加一些接口文件。因为带FreeRTOS的协议栈会创建一个协议栈Task。
以太网数据通过中断接收进来,在通过邮箱发送消息到协议栈中。协议栈Task收到消息,根据消息类型调用对应的协议解析函数。
这中间的Task创建,消息的传递等都需要FreeRTOS相关的系统调用接口。
参考:
lwip\contrib\ports\freertos\sys_arch.c
lwip\contrib\ports\freertos\include\arch\sys_arch.h
一般可直接拿来用。
四、应用接口。
Lwip应用编程接口有3种:
1、Raw/Callback API
2、NETCONN API
3、Socket API
- 如果没有操作系统,则只能使用第一种API做应用编程。
- 第二种和第三种API接口都是依赖操作系统的。
五、协议栈的初始化。
1、带FreeRTOS的初始化。
void Lwip_stack_init(void)
{
ip4_addr_t netif_ipaddr, netif_netmask, netif_gw;
tcpip_init(NULL, NULL);
IP4_ADDR(&netif_ipaddr, configIP_ADDR0, configIP_ADDR1, configIP_ADDR2, configIP_ADDR3);
IP4_ADDR(&netif_netmask, configNET_MASK0, configNET_MASK1, configNET_MASK2, configNET_MASK3);
IP4_ADDR(&netif_gw, configGW_ADDR0, configGW_ADDR1, configGW_ADDR2, configGW_ADDR3);
//ethernetif
netifapi_netif_add(&netif, &netif_ipaddr, &netif_netmask, &netif_gw, ðernetif, ethernetif_init, tcpip_input);
netifapi_netif_set_default(&netif);
netifapi_netif_set_up(&netif);
LWIP_PLATFORM_DIAG(("************************************************"));
LWIP_PLATFORM_DIAG((" IPv4 Address : %u.%u.%u.%u", ((u8_t *)&netif_ipaddr)[0], ((u8_t *)&netif_ipaddr)[1],
((u8_t *)&netif_ipaddr)[2], ((u8_t *)&netif_ipaddr)[3]));
LWIP_PLATFORM_DIAG((" IPv4 Subnet mask : %u.%u.%u.%u", ((u8_t *)&netif_netmask)[0], ((u8_t *)&netif_netmask)[1],
((u8_t *)&netif_netmask)[2], ((u8_t *)&netif_netmask)[3]));
LWIP_PLATFORM_DIAG((" IPv4 Gateway : %u.%u.%u.%u", ((u8_t *)&netif_gw)[0], ((u8_t *)&netif_gw)[1],
((u8_t *)&netif_gw)[2], ((u8_t *)&netif_gw)[3]));
LWIP_PLATFORM_DIAG(("************************************************"));
}
/* 第一步写的input函数,需要在网络中断中调用。不同平台的网络中断函数实现不同,需要自己调整。
* 到这里带有FreeRTOS的Lwip协议栈移植完成,下一步就是写应用程序。
*/
有FreeRTOS的话,netifapi_netif_add函数的最后一个参数应该是tcpip_input。Lwip 协议栈会创建一个任务,接收从 ethernetif_input 输入的以太网帧并解析。
2、不带操作系统的初始化。
// 在 main 函数中添加如下:
IP4_ADDR(&netif_ipaddr, configIP_ADDR0, configIP_ADDR1, configIP_ADDR2, configIP_ADDR3);
IP4_ADDR(&netif_netmask, configNET_MASK0, configNET_MASK1, configNET_MASK2, configNET_MASK3);
IP4_ADDR(&netif_gw, configGW_ADDR0, configGW_ADDR1, configGW_ADDR2, configGW_ADDR3);
//ethernetif
netifapi_netif_add(&netif, &netif_ipaddr, &netif_netmask, &netif_gw, ðernetif, ethernetif_init, ethernet_input);
netifapi_netif_set_default(&netif);
netifapi_netif_set_up(&netif);
LWIP_PLATFORM_DIAG(("************************************************"));
LWIP_PLATFORM_DIAG((" IPv4 Address : %u.%u.%u.%u", ((u8_t *)&netif_ipaddr)[0], ((u8_t *)&netif_ipaddr)[1],
((u8_t *)&netif_ipaddr)[2], ((u8_t *)&netif_ipaddr)[3]));
LWIP_PLATFORM_DIAG((" IPv4 Subnet mask : %u.%u.%u.%u", ((u8_t *)&netif_netmask)[0], ((u8_t *)&netif_netmask)[1],
((u8_t *)&netif_netmask)[2], ((u8_t *)&netif_netmask)[3]));
LWIP_PLATFORM_DIAG((" IPv4 Gateway : %u.%u.%u.%u", ((u8_t *)&netif_gw)[0], ((u8_t *)&netif_gw)[1],
((u8_t *)&netif_gw)[2], ((u8_t *)&netif_gw)[3]));
LWIP_PLATFORM_DIAG(("************************************************"));
while (1)
{
//调用网卡接收函数
ethernetif_input(&gnetif);
//处理 LwIP 中超时事件
sys_check_timeouts();
}
3、Lwip还需要一个配置文件,lwipopts.h
参考在 lwip\contrib\examples\example_app。
#define NO_SYS 0
// NO_SYS 为0时使用FreeRTOS, 为1时不带操作系统
#define LWIP_SOCKET (NO_SYS==0)
#define LWIP_NETCONN (NO_SYS==0)
#define LWIP_NETIF_API (NO_SYS==0)
// 如果没有操作系统,则无法使用Socket API、NETCONN API、NETIF_API。
#define MEM_SIZE 10240
// 协议栈使用的堆的大小
这里仅列出一小部分配置选项,Lwip可高度配置。