U-Boot是嵌入式系統上被廣為運用的boot loader,它擁有極為活躍的開發社群,也支援許多不同類型的CPU核心架構。U-Boot目前並不支援完整的TCP/IP,僅有限度地支援TFTP,方便開發者上傳韌體或是網路開機(BOOTP)。U-Boot身為boot loader非嵌入式作業系統,依據開放原始碼社群秉持地「精簡就是美」原則,不支援TCP/IP是可以理解的。萬一真的需要網路功能,移植一套完整的TCP/IP協定堆疊的任務雖然富有挑戰性,實務上亦是可行之道。
麻雀雖小五臟俱全的「lwIP」
lightweight IP(lwIP)是一個輕巧的開放原始碼之TCP/IP通訊協定之實作。lwIP首先由Adam Dunkels發表,目前則是以Kieran Mansley為計畫主持人,帶領開放原始碼社群進行該專案的維護與開發。
目前lwIP已實做的協定如下:
- IP (Internet Protocol) including packet forwarding over multiple network interfaces
- ICMP (Internet Control Message Protocol) for network maintenance and debugging
- IGMP (Internet Group Management Protocol) for multicast traffic management
- UDP (User Datagram Protocol) including experimental UDP-lite extensions
- TCP (Transmission Control Protocol) with congestion control, RTT estimation and fast recovery/fast retransmit
- Raw/native API for enhanced performance
- DNS (Domain names resolver)
- SNMP (Simple Network Management Protocol)
- DHCP (Dynamic Host Configuration Protocol)
- AUTOIP (for IPv4, conform with RFC 3927)
- PPP (Point-to-Point Protocol)
- ARP (Address Resolution Protocol) for Ethernet
- Optional Berkeley-like socket API
lwIP設計理念主要是希望於支援TCP/IP協定的同時,於執行時期也能精簡記憶體之運用,以達成支援小型的嵌入式裝置、平台或即時作業系統(RTOS)的目標;除此之外,lwIP也已經移植到眾多RTOS平台上,例如:eCos…等等。由此這些特性來看,使用lwIP搭配U-Boot是一個好的選擇。
lwIP移植步驟
lwIP的的核心部份由ANSI C撰寫,使用目前的GCC應該可以直接交叉編譯(cross compiling)。整體來說要將lwIP移植到不同平台上,只要完成三件工作即可。首先要決定lwIP的組態;接著,補完lwIP需要之OS抽象層的實作;最後,撰寫lwIP之網路卡驅動程式。
一、選擇你要的lwIP組態
移植lwIP的第一步,要先決定將要啟用之lwIP的功能。由於lwIP包含許多通訊協定堆疊的實作以及許多系統開發除錯(debug)相關的功能,有些功能可能暫時使用不到,我們可以選擇將之關閉。
首先,建立並編輯「lwipopts.h」。以下為範例:
lwipopts.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
#ifndef __LWIPOPTS_H__ #define __LWIPOPTS_H__ #define NO_SYS 1 #define MEM_LIBC_MALLOC 1 #define MEMP_MEM_MALLOC 0 #define MEMP_SEPARATE_POOLS 0 #define MEM_ALIGNMENT 4 #define MEM_SIZE (4 * 1024 * 1024) #define MEMP_NUM_PBUF 1024 #define MEMP_NUM_UDP_PCB 20 #define MEMP_NUM_TCP_PCB 20 #define MEMP_NUM_TCP_PCB_LISTEN 16 #define MEMP_NUM_TCP_SEG 128 #define MEMP_NUM_REASSDATA 32 #define MEMP_NUM_ARP_QUEUE 10 #define PBUF_POOL_SIZE 512 #define LWIP_ARP 1 #define LWIP_TCP 1 #define LWIP_UDP 1 #define LWIP_DHCP 1 #define IP_REASS_MAX_PBUFS 64 #define IP_FRAG_USES_STATIC_BUF 0 #define IP_DEFAULT_TTL 255 #define IP_SOF_BROADCAST 1 #define IP_SOF_BROADCAST_RECV 1 #define LWIP_ICMP 1 #define LWIP_BROADCAST_PING 1 #define LWIP_MULTICAST_PING 1 #define LWIP_RAW 1 #define TCP_WND (4 * TCP_MSS) #define TCP_MSS 1460 #define TCP_SND_BUF (8 * TCP_MSS) #define TCP_LISTEN_BACKLOG 1 #define LWIP_NETIF_STATUS_CALLBACK 1 #define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_HWADDRHINT 1 #define LWIP_NETCONN 0 #define LWIP_SOCKET 0 #define LWIP_STATS_DISPLAY 1 #define MEM_STATS 0 #define SYS_STATS 0 #define MEMP_STATS 0 #define LINK_STATS 0 #define ETHARP_TRUST_IP_MAC 0 #define ETH_PAD_SIZE 0 #define LWIP_CHKSUM_ALGORITHM 2 #define LWIP_TCP_KEEPALIVE 1 #define MEMP_NUM_SYS_TIMEOUT 5 // Keepalive values, compliant with RFC 1122. Don't change this unless you know what you're doing #define TCP_KEEPIDLE_DEFAULT 10000UL // Default KEEPALIVE timer in milliseconds #define TCP_KEEPINTVL_DEFAULT 2000UL // Default Time between KEEPALIVE probes in milliseconds #define TCP_KEEPCNT_DEFAULT 9U // Default Counter for KEEPALIVE probes extern void* dma_memory_alloc(unsigned int size); extern void* dma_memory_calloc(unsigned int size, unsigned int number); extern void dma_memory_free(void* ptr); #define mem_init() #define mem_free dma_memory_free #define mem_malloc dma_memory_alloc #define mem_calloc(c, n) dma_memory_calloc((c) * (n)) #define mem_realloc(p, sz) (p) #define LWIP_MEM_ALIGN(addr) ((void *)(((u32_t)(addr) + MEM_ALIGNMENT - 1) & ~(MEM_ALIGNMENT - 1))) #define LWIP_DEBUG 0 #define ETHARP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_OFF #define PBUF_DEBUG LWIP_DBG_OFF #define API_LIB_DEBUG LWIP_DBG_OFF #define API_MSG_DEBUG LWIP_DBG_OFF #define SOCKETS_DEBUG LWIP_DBG_OFF #define ICMP_DEBUG LWIP_DBG_OFF #define INET_DEBUG LWIP_DBG_OFF #define IP_DEBUG LWIP_DBG_OFF #define IP_REASS_DEBUG LWIP_DBG_OFF #define RAW_DEBUG LWIP_DBG_OFF #define MEM_DEBUG LWIP_DBG_OFF #define MEMP_DEBUG LWIP_DBG_OFF #define SYS_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF #define TCP_INPUT_DEBUG LWIP_DBG_OFF #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF #define TCP_RTO_DEBUG LWIP_DBG_OFF #define TCP_CWND_DEBUG LWIP_DBG_OFF #define TCP_WND_DEBUG LWIP_DBG_OFF #define TCP_FR_DEBUG LWIP_DBG_OFF #define TCP_QLEN_DEBUG LWIP_DBG_OFF #define TCP_RST_DEBUG LWIP_DBG_OFF #define UDP_DEBUG LWIP_DBG_OFF #define TCPIP_DEBUG LWIP_DBG_OFF #define PPP_DEBUG LWIP_DBG_OFF #define SLIP_DEBUG LWIP_DBG_OFF #define DHCP_DEBUG LWIP_DBG_OFF #endif /* __LWIPOPTS_H__ */ |
在lwipopts.h中,我們可以選擇開啟或關閉某些lwIP的模組、決定debug訊息的層級以及lwIP執行時的記憶體配置模型。尤有甚者,U-Boot缺乏多工、多執行緒能力,僅能支援lwIP提供之Raw API,所以需要關閉BSD socket支援。關於每個選項的意義,可以交叉參考「lwip/src/include/lwip/opt.h」裡的說明。
二、移植lwIP之OS抽象層
移植之抽象層至U-Boot並不會太複雜,且U-Boot並沒有多執行緒及其衍生之關於資源競爭(race condition)的議題,移植工程的複雜度可以大幅減輕。所以在這個階段,基本上我們只要解決C compiler的互通性問題即可。lwIP將與此議題相關的選項歸納於cc.h。
cc.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#ifndef __ARCH_CC_H__ #define __ARCH_CC_H__ // Includes definition of macro to do printf extern int printf(const char *fmt, ...); #define BYTE_ORDER LITTLE_ENDIAN #define LWIP_PLATFORM_BYTESWAP 0 typedef unsigned char u8_t; typedef char s8_t; typedef unsigned short u16_t; typedef short s16_t; typedef unsigned int u32_t; typedef int s32_t; typedef unsigned int* mem_ptr_t; #define LWIP_ERR_T int /* Define (sn)printf formatters for these lwIP types */ #define U16_F "u" #define S16_F "d" #define X16_F "x" #define U32_F "u" #define S32_F "d" #define X32_F "x" /* Compiler hints for packing structures */ #define PACK_STRUCT_FIELD(x) x #define PACK_STRUCT_STRUCT __attribute__((packed)) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_END /* Plaform specific diagnostic output */ #define LWIP_PLATFORM_DIAG(x, ...) do { \ printf(x, ##__VA_ARGS__); \ } while (0) #define LWIP_PLATFORM_ASSERT(x) do { \ printf("Assert \"%s\" failed at line %d in %s\n", \ x, __LINE__, __FILE__); \ } while (0) #endif /* __ARCH_CC_H__ */ |
移植時,需指定平台的「BYTE_ORDER」,可為”LITTLE_ENDIAN”或”BIG_ENDIAN”,與此選項相依的有「LWIP_PLATFORM_BYTESWAP」。若移植平台為32-bits little-endian ARM,將BYTE_ORDER設為LITTLE_ENDIAN,LWIP_PLATFORM_BYTESWAP指定為0即可。相關設定,可以參考範例。
三、移植timer相關函式
移植工作至此,除了驅動程式的撰寫外,僅剩下timer的移植。由於lwIP之通訊協定堆疊大多仰賴timer,因此timer的移植於lwIP重要性不言而喻。以U-Boot來說,關於timer的運用並無標準之API,彈性很大,移植者可以視平台狀況啟動timer,並撰寫合適的函式。有關此類函式,可集中於sys_arch.c
sys_arch.c
1
2
3
4
5
6
7
8
9
|
#include "lwip/sys.h" extern unsigned int timer2_now(); extern unsigned int timer2_interval(); u32_t sys_now(void) { return timer2_now() * timer2_interval(); } |
四、撰寫網路卡驅動程式
lwIP透過網路卡驅動程式由實體層收送封包,關於驅動程式的撰寫方式,可參考lwIP原始碼提供之「netif/ethernetif.c」。
結論
整體來說,移植lwIP至U-Boot並不複雜,但是相關文件不多,需要些許耐心地閱讀甚至追蹤、觀察原始碼的運作。但由於lwIP設計完備,些許心力即可換來與U-Boot的良好搭配,在在顯示open source的彈性與優點。