第一次發表博客,文章摘錄於還不懂同學的專欄
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
