LWIP的移植笔记
第一次发表博客,文章摘录于还不懂同学的专栏
lwIp的作者做了大量的工作以方便像我这种懒人移植该协议栈,基本上只需修改一个配置头文件和改写3个函数即可完成lwIP的移植。要改写的函数位于lwIP-1.3.0/src/netif/ethernetif.c中,你也可以用自己更合适的网络接口名来代替“ethernetif”。另外还有一个配置头文件,叫做lwipopts.h文件,它要放在工程目录下。这个文件来源于lwIP-1.3.0/src/include/lwip/opt.h头文件,是对整个协议栈的一个配置,比如是否使用TCP/UDP/DHCP协议等等。
第一个函数为ethernerif_init()函数,这个函数先是设置与协议栈有关的底层操作,指定底层接收回调函数等,接着对实际网络接口芯片进行初始化,设置硬件的工作方式,开放中断等。
第二个函数为low_level_output函数,主要目的是将要发送的数据包分解成网络接口芯片能识别的书籍结构并将数据发送到链路上。
第三个函数为low_level_input()函数,主要接收以太网数据并将数据打包成lwIP能识别的数据结构。
第四个头文件lwipopts.h内容较多,按照具体应用进行配置。
1、lwip协议栈的cc.h文件,该文件中定义了与处理器相关的数据类型。
这个文件主要设置lwIP内部使用的数据类型,比如u8_t、u32_t等。lwIP可以移植到32位、16位甚至是8位构架的微控制器,由于移植的硬件平台以及编译器的不同,这些数据类型是要移植者根据自己的硬件和编译器特性来自行设置的。比如int类型变量,在8位和16位控制器中多表示2字节,但在32位微处理器中却表示4个字节,若是连这些基本数据类型都没有设置正确的话,就谈不上移植了。下面看cc.h的源代码:
1 #ifndef __CC_H__
2 #define __CC_H__
3
4 typedef unsigned char u8_t; //基本数据类型设置
5 typedef signed char s8_t;
6 typedef unsigned short u16_t;
7 typedef signed short s16_t;
8 typedef unsigned long u32_t;
9 typedef signed long s32_t;
10 typedef u32_t mem_ptr_t;
11
12 #ifndef BYTE_ORDER
13 #define BYTE_ORDER LITTLE_ENDIAN
14 #endif
15
16 #if defined(__arm__) && defined(__ARMCC_VERSION) //以下主要设置不同编译器的结构体数据的对齐,lwIP需要
17 //
18 // Setup PACKing macros for KEIL/RVMDK Tools
19 //
20 #define PACK_STRUCT_BEGIN __packed
21 #define PACK_STRUCT_STRUCT
22 #define PACK_STRUCT_END
23 #define PACK_STRUCT_FIELD(x) x
24 #elif defined (__IAR_SYSTEMS_ICC__)
25 //
26 // Setup PACKing macros for IAR Tools
27 //
28 #define PACK_STRUCT_BEGIN
29 #define PACK_STRUCT_STRUCT
30 #define PACK_STRUCT_END
31 #define PACK_STRUCT_FIELD(x) x
32 #define PACK_STRUCT_USE_INCLUDES
33 #else
34 //
35 // Setup PACKing macros for GCC Tools
36 //
37 #define PACK_STRUCT_BEGIN
38 #define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
39 #define PACK_STRUCT_END
40 #define PACK_STRUCT_FIELD(x) x
41 #endif
42
43 #ifdef DEBUG
44 extern void __error__(char *pcFilename, unsigned long ulLine);
45 #define LWIP_PLATFORM_ASSERT(expr) \
46 { \
47 if(!(expr)) \
48 { \
49 __error__(__FILE__, __LINE__); \
50 } \
51 }
52 #else
53 #define LWIP_PLATFORM_ASSERT(expr)
54 #endif
55
56 #endif /* __CC_H__ */
2、以太网硬件初始化、与硬件密切相关的数据接收、发送函数
Stellais串口转以太网模块与硬件密切相关的移植代码位于stellarisif.c中。这里面的代码主要是三部分:lwIP协议栈和以太网硬件初始化函数、lwIP协议栈将数据发送到网络接口上的输出函数以及从Stellaris以太网硬件读取数据并送给lwIP协议栈的输入函数。
2.1 lwIP协议栈和以太网硬件初始化
在移植代码stellarisif.c中,对lwIP协议栈和以太网硬件初始化的函数是:
err_t stellarisif_init(structnetif *netif)
这个函数先是设置与协议栈有关的底层操作,指定底层接收回调函数等,接着对实际网络接口芯片进行初始化,设置硬件的工作方式,开放中断等。源代码如下所示:
1 /**
2 * Should be called at the beginning of the program to set up the
3 * network interface. It calls the function stellarisif_hwinit() to do the
4 * actual setup of the hardware.
5 * 此在程序开始的时候被调用,用来设置网络接口.他调用stellarisif_hwinit()函数
6 * 来完成以太网硬件的设置.
7 * This function should be passed as a parameter to netif_add().
8 * 这个函数作为一个参数传递给netif_add()函数.
9 * @param netif the lwip network interface structure for this ethernetif
10 * @return ERR_OK if the loopif is initialized
11 * ERR_MEM if private data couldn't be allocated
12 * any other err_t on error
13 */
14 err_t
15 stellarisif_init(struct netif *netif)
16 {
17 LWIP_ASSERT("netif != NULL", (netif != NULL));
18
19 #if LWIP_NETIF_HOSTNAME
20 /* Initialize interface hostname */
21 netif->hostname = "lwip"; //初始化接口主机名字
22 #endif /* LWIP_NETIF_HOSTNAME */
23
24 /*
25 * Initialize the snmp variables and counters inside the struct netif.
26 * The last argument should be replaced with your link speed, in units
27 * of bits per second.
28 */
29 NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, 1000000); //初始化snmp变量
30
31 netif->state = &stellarisif_data; //指向以太网接口的私有数据,包括pbuf数据链和MAC地址
32 netif->name[0] = IFNAME0;
33 netif->name[1] = IFNAME1;
34 /* We directly use etharp_output() here to save a function call.
35 * You can instead declare your own function an call etharp_output()
36 * from it if you have to do some checks before sending (e.g. if link
37 * is available...) */
38 netif->output = etharp_output; //IP层将一包数据发往网络接口时调用此函数
39 netif->linkoutput = stellarisif_output; //ARP模块将一包数据发往网络接口时调用此函数
40
41 stellarisif_data.ethaddr = (struct eth_addr *)&(netif->hwaddr[0]); //初始化MAC地址
42 stellarisif_data.txq.qread = stellarisif_data.txq.qwrite = 0; //初始化pbuf数据链
43 stellarisif_data.txq.overflow = 0;
44
45 /* initialize the hardware */
46 stellarisif_hwinit(netif); //初始化Stellaris以太网硬件
47
48 return ERR_OK;
49 }
1. netif->output = etharp_output;用于将一包数据发送到网络接口,由IP层调用。这个函数最终会调用netif->linkoutput来将数据发送到网络接口。
2. netif->linkoutput = stellarisif_output;用于将一包数据发送到网络接口,有ARP模块调用。程序员需根据自己的硬件平台来编写该函数。后面会讲到该函数。
3.stellarisif_hwinit(netif):初始化以太网硬件,还是有必要看看该函数的,代码如下所示,不再单独解释,可以看注释。
1 /**
2 * In this function, the hardware should be initialized.
3 * Called from stellarisif_init().
4 *
5 * @param netif the already initialized lwip network interface structure
6 * for this ethernetif
7 */
8 static void
9 stellarisif_hwinit(struct netif *netif)
10 {
11 u32_t temp;
12 //struct stellarisif *stellarisif = netif->state;
13
14 /* 设置以太网硬件MAC地址长度 */
15 netif->hwaddr_len = ETHARP_HWADDR_LEN;
16
17 /* 设置以太网硬件地址 */
18 EthernetMACAddrGet(ETH_BASE, &(netif->hwaddr[0]));
19
20 /* 最大发送单元 */
21 netif->mtu = 1500;
22
23 /* 使能网卡的功能,允许网卡广播、ARP功能和允许硬件链路连接该网卡*/
24 /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
25 netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
26
27 /* Do whatever else is needed to initialize interface. */
28 /* 禁止所有以太网中断 */
29 EthernetIntDisable(ETH_BASE, (ETH_INT_PHY | ETH_INT_MDIO | ETH_INT_RXER |
30 ETH_INT_RXOF | ETH_INT_TX | ETH_INT_TXER | ETH_INT_RX));
31 temp = EthernetIntStatus(ETH_BASE, false);
32 EthernetIntClear(ETH_BASE, temp);
33
34 /* 初始化以太网控制器 */
35 EthernetInitExpClk(ETH_BASE, SysCtlClockGet());
36
37 /*
38 * 配置以太网控制器正常运行.
39 * - 使能 TX 全双工模式
40 * - 使能 TX 填充
41 * - 使能 TX CRC 生成
42 * - 使能 RX 组播接收
43 */
44 EthernetConfigSet(ETH_BASE, (ETH_CFG_TX_DPLXEN |ETH_CFG_TX_CRCEN |
45 ETH_CFG_TX_PADEN | ETH_CFG_RX_AMULEN));
46
47 /* 使能以太网控制器的发送器和接收器 */
48 EthernetEnable(ETH_BASE);
49
50 /* 使能以太网中断 */
51 IntEnable(INT_ETH);
52
53 /* 使能以太网TX和RX数据包中断 */
54 EthernetIntEnable(ETH_BASE, ETH_INT_RX | ETH_INT_TX);
55 }
2.2 Stellaris以太网硬件底层数据发送函数
上面也已经讲到了,ARP模块发送数据到网络接口会调用netif->linkoutput函数;IP层发送数据到网络接口会调用netif->output函数,但IP层实际的数据发送函数任然是netif->linkoutput,在以太网初始化中,已经为linkoutput指针赋值:netif->linkoutput=stellarisif_output;因此,移植lwIP时程序员需要编写的Stellaris以太网硬件底层数据发送函数正是stellarisif_output()函数。先来看以下它的源码:
1 /**
2 * This function with either place the packet into the Stellaris transmit fifo,
3 * or will place the packet in the interface PBUF Queue for subsequent
4 * transmission when the transmitter becomes idle.
5 * 这个函数要么将数据包放到Stellaris发送缓存FIFO中,要么把数据包放到PBUF队列,等待
6 * 发送器变空之后载发送。
7 *
8 * @param netif the lwip network interface structure for this ethernetif
9 * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
10 * @return ERR_OK if the packet could be sent
11 * an err_t value if the packet couldn't be sent
12 *
13 */
14 static err_t
15 stellarisif_output(struct netif *netif, struct pbuf *p)
16 {
17 struct stellarisif *stellarisif = netif->state;
18 SYS_ARCH_DECL_PROTECT(lev);
19
20 /**
21 * This entire function must run within a "critical section" to preserve
22 * the integrity of the transmit pbuf queue.
23 *
24 */
25 SYS_ARCH_PROTECT(lev);
26
27 /**
28 * Bump the reference count on the pbuf to prevent it from being
29 * freed till we are done with it.
30 *
31 */
32 pbuf_ref(p);
33
34 /**
35 * If the transmitter is idle, and there is nothing on the queue,
36 * send the pbuf now.
37 *
38 */
39 if(PBUF_QUEUE_EMPTY(&stellarisif->txq) &&
40 ((HWREG(ETH_BASE + MAC_O_TR) & MAC_TR_NEWTX) == 0)) {
41 stellarisif_transmit(netif, p);
42 }
43
44 /* Otherwise place the pbuf on the transmit queue. */
45 else {
46 /* Add to transmit packet queue */
47 if(!enqueue_packet(p, &(stellarisif->txq))) {
48 /* if no room on the queue, free the pbuf reference and return error. */
49 pbuf_free(p);
50 SYS_ARCH_UNPROTECT(lev);
51 return (ERR_MEM);
52 }
53 }
54
55 /* Return to prior interrupt state and return. */
56 SYS_ARCH_UNPROTECT(lev);
57 return ERR_OK;
58 }
1、 SYS_ARCH_DECL_PROTECT(lev);、SYS_ARCH_PROTECT(lev);、SYS_ARCH_UNPROTECT(lev);由于Stellaris以太网在发送数据的时候必须处在临界区,既不能被中断打扰,因此,上面几个宏是用来关闭和打开总中断的。SYS_ARCH_DECL_PROTECT(lev)用来定义一个32位变量lev,这个变量用来保存当前中断使能信息;SYS_ARCH_PROTECT(lev)用来关闭中断;SYS_ARCH_UNPROTECT(lev)用来打开中断。
2、pbuf_ref(p);将参数结构体pbuf的ref域加一。这个域统计有多少个指针指向这个pbuf,这些指针可能是应用程序的指针、协议栈的指针或者数据链中的pbuf- >next指针,只有ref为0时,才可以释放这个pbuf。关于结构体pbuf的详细介绍参见博客:http://blog.csdn.net/zhzht19861011/article/details/6591252
3、stellarisif_transmit(netif, p):发送数据。
2.3 Stellaris以太网硬件底层数据过程
当网络上一包数据到达以太网控制器后,以太网控制器会置为接收中断,在中断服务函数中,调用
stellarisif_receive函数(需程序员根据具体硬件自行编写)来接收一个数据包,再通过
ethernet_input函数分析接收到的数据包的类型,比如类型为0x8000,则为IP帧,会调用ip_input
函数来将收到的这个IP数据包送往lwIP协议栈的高层。Stellaris以太网硬件具体中断函数分析见博客:http://blog.csdn.net/zhzht19861011/article/details/6221699