11/04/2011

移植lwIP至U-Boot

U-Boot是嵌入式系統上被廣為運用的boot loader,它擁有極為活躍的開發社群,也支援許多不同類型的CPU核心架構。U-Boot目前並不支援完整的TCP/IP,僅有限度地支援TFTP,方便開發者上傳韌體或是網路開機(BOOTP)。U-Boot身為boot loader非嵌入式作業系統,依據開放原始碼社群秉持地「精簡就是美」原則,不支援TCP/IP是可以理解的。萬一真的需要網路功能,移植一套完整的TCP/IP協定堆疊的任務雖然富有挑戰性,實務上亦是可行之道。

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」。以下為範例:
#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。
#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
#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的彈性與優點。

No comments:

Post a Comment