學會Zynq(12)lwIP 1.4.1庫的配置與使用

FPGADesigner發表於2019-03-20

lwIP概述

lwIP是一個用於嵌入式系統的開源TCP/IP協議集,是一套可以獨立執行的棧,無需依賴作業系統,但也可以與作業系統同時使用。lwIP提供了兩套API(術語為A05PI),供使用者選擇:

  • RAW API:直接訪問核心的lwIP棧;
  • Socket API:通過BSD socket風格的介面訪問lwIP棧。

基於lwIP 1.4.1庫版本,SDK提供了相應適配的庫,稱作lwip 141_v1_x。這個庫為Ethernetlite、TEMAC、GigE、MAC核提供了介面卡(adapter)。Ethernetlite和TEMAC核用於MicroBlaze系統;GigE控制器和MAC核用於Zynq。想在Xilinx FPGA環境下熟練使用lwIP,不僅要了解lwIP的API用法,還要掌握xilinx介面卡的一些知識。

Xilinx中lwIP的使用可以參考xapp1026和UG650;lwIP的開發者主頁為 http://savannah.nongnu.org/projects/lwip/ ;查閱lwip的相關知識:https://lwip.fandom.com/wiki/LwIP_Wiki。


設定硬體系統

lwIP支援的硬體系統包含的關鍵元件如下:

  • 處理器:MicroBlaze、Zynq中的Cortex-A9、Zynq UltraScale+ MPSoC系統中的Cortex-A53和Cortex-R5;
  • MAC:lwIP支援axi_ethernetlite、axi_ethernet、GigE控制器和MAC核;
  • 定時器:基於lwIP RAW API的應用需要按週期間隔呼叫某些函式,可通過一個帶定時器的中斷處理器來實現。
  • DMA:對於MicroBlaze,axi_ethernet核可以配置一個軟DMA引擎或一個FIFO介面。對於Zynq,已經有嵌入的DMA,因此無需額外配置。

在這裡插入圖片描述
上圖是一個MicroBlaze系統架構的示例,使用了帶DMA的axi_ethernet核。


設定軟體系統

把Vivado的硬體平臺匯入到SDK中時,預設是不包含lwIP庫的,因此必須先做相應配置,編譯lwIP庫到應用程式中。步驟如下:

1.SDK中選擇File->New->Xilinx Board Support Package,建立新的板級支援包。
在這裡插入圖片描述
2.設定工程名稱和目錄。Zynq系列選擇FreeRTOS或裸機;MicroBlaze還可以選擇XilKernel。點選Finish,彈出配置視窗,配置BSP。
在這裡插入圖片描述
3.選中lwip 141,視窗左側會出現對lwip庫做更詳細配置的視窗。
在這裡插入圖片描述
4.配置完成後點選OK,SDK會自動構建包含lwIP的板級支援包。


lwIP的詳細配置

lwIP提供了可配置的引數,SDK中可以改變這些引數值。可配置的選項可以分為兩類:

  • Xilinx介面卡的相關選項:Xilinx介面卡把這些控制設定用於乙太網核;
  • 基本lwIP選項:這些選項是lwIP庫本身的一部分,包括用於TCP、UDP、IP等其它協議的引數。

1.定製lwIP API模式

lwip141_v1_x支援RAM API和Socket API。RAW API有更好的效能和更低的記憶體佔用,但由於是基於回撥機制的,因此不能與其它TCP 棧相容;Socket API提供了一個BSD socket風格的介面,因此移植性很強,但在效能和記憶體需求方面沒有RAW API效率高。

選項名稱 說明 預設值
api_mode{RAW_API|SOCKET_API} lwIP庫的操作模式 RAW_API
socket_mode_thread_prio lwIP執行緒的優先順序,僅當使用Xilkernel的優先順序模式時才有效 1
use_axieth_on_zynq 如果要在Zynq中使用AxiEthernet軟核IP,需要設定此選項來關閉MAC核(GigE) 0表示使用GigE控制器;1表示使用AxiEthernet

2.配置Xilinx介面卡選項

axi_ethernetlite介面卡(ethernetlite_adapter_options)相關的配置引數如下:

屬性 說明 預設值
sw_rx_fifo_size 軟體緩衝區大小,以EMAC和處理器之間接收資料的位元組為單位 8192
sw_tx_fifo_size 軟體緩衝區大小,以處理器和EMAC之間傳送資料的位元組為單位 8192

axi_ethernet和GigE介面卡(temac_adapter_options)的相關配置引數如下:

屬性 預設值 說明
n_tx_descriptors 64 使用Tx描述符的數量,高效能系統增大此值
n_rx_descriptors 64 使用Rx描述符的數量,高效能系統增大此值
n_tx_coalesce 1 設定Tx中斷合併
n_rx_coalesce 1 設定Rx中斷合併
tcp_rx_checksum_offload false 解除安裝(offload)TCP接收校驗和計算
tcp_tx_checksum_offload false 解除安裝(offload)TCP傳送校驗和計算
tcp_ip_rx_checksum_ofload false 解除安裝(offload)TCP和IP接收校驗和計算
tcp_ip_tx_checksum_ofload false 解除安裝(offload)TCP和IP傳送校驗和計算
phy_link_speed AUTO 由物理層自動協商鏈路速度,lwIP據此配置TEMAC/GigE,某些PHY可能不支援自動檢測,此時這個值必須設定正確
temac_use_jumbo_frames_experimental false 使用TEMAC巨型幀(可達9k位元組)。設定為true,TEMAC將允許傳輸和接收巨型幀。

3.配置記憶體選項

lwIP棧提供了不同種類的記憶體。當應用程式使用socket模式時,將使用不同的記憶體選項。所有可配置的記憶體選項都作為單獨的類別提供。

屬性 預設值 說明
mem _size 131072 可用堆記憶體的總大小(位元組),如果應用程式需要從堆中使用大量記憶體應考慮增大此值
memp_n_pbuf 16 memp結構pbuf的數量,如果應用程式需要從ROM等靜態記憶體中傳送大量資料,考慮增大此值
memp_n_udp_pcb 4 UDP協議控制塊的數量,每個活躍的UDP連線佔用一個控制塊
memp_n_tcp_pcb 32 同上,同時活躍的TCP連線數
memp_n_tcp_pcb_listen 8 監聽TCP連線的數量
memp_n_tcp_seg 256 同時排隊的TCP段的數量
memp_n_sys_timeout 8 同時活躍的超時(timeout)數量
memp_num_netbuf 8 netbufs型別的結構體例項的數量,僅在socket模式下可用
memp_num_netconn 16 netconns型別的結構體例項的數量,僅在socket模式下可用
memp_num_api_msg 16 api_msg型別的結構體例項的數量,僅在socket模式下可用
memp_num_tcpip_msg 64 TCP/IP msg結構體的數量,僅在socket模式下可用

4.pbuf_options

包緩衝區(Pbuf)跨TCP/IP棧的不同層,下面是lwip棧提供的pbuf記憶體選項,一般情況下無需修改該選項的預設值。

屬性 預設值 說明
pbuf_pool_size 256 pbuf池中的緩衝區數目,對於高效能系統,可以考慮設定更大的值
pbuf_pool_bufsize 1700 pbuf池中每個pbuf的大小,對於支援巨型幀的系統,要設定比巨型幀更大的值
pbuf_link_hlen 16 分配給鏈路級header的位元組數

5.arp_options

一般情況下無需修改該選項的預設值。

屬性 預設值 說明
arp_table_size 10 快取的硬體地址IP地址對數目
arp_queueing 1 出站資料包在硬體地址解析期間會排隊

6.lwip_ip_options

下表是下級選單中的IP引數選項,一般情況下無需修改該選項的預設值。

屬性 預設值 說明
ip_forward 0 1表示啟用跨網路介面轉發IP資料包的功能在單個網路介面上執行lwIP時設定為0
ip_options 0 1表示允許使用IP選項0表示丟棄所有帶IP選項的包
ip_reassembly 1 重新組裝傳入的碎片IP資料包
ip_flag 1 傳送的IP資料包大小超過MTU時對其分段
ip_reass_max_pbufs 128 重組的pbuf佇列長度
ip_frag_max_mtu 1500 任意介面的IP碎片緩衝區的的最大MTU
ip_default_ttl 255 傳輸層使用的全域性預設TTL值

7.icmp_options

該選項只可以設定ICMP的TTL值。Zynq中的GigE核不支援使用ICMP。

8.igmp_options

lwIP支援IGMP協議,該選項沒有下級選單,設定為true可以啟用IGMP協議。

9.udp_options

lwIP支援UDP協議,一般情況下無需修改該選項的預設值。

屬性 預設值 說明
lwip_udp true 設定是否需要UDP
udp_ttl 255 UDP TTL值

10.tcp_options

lwIP支援TCP協議,一般情況下無需修改該選項的預設值。

屬性 預設值 說明
lwip_tcp true 設定是否需要TCP
tcp_ttl 255 TCP
tcp_wnd 2048 TCP視窗大小(位元組)
tcp_maxrtx 12 TCP最大重傳值
tcp_synmaxrtx 4 TCP最大SYN重傳值
tcp_queue_ooseq 1 接收順序錯誤的TCP佇列段在記憶體不足的情況下可設為0
tcp_mss 1460 TCP最大段的大小
tcp_snd_buf 8192 TCP傳送緩衝空間(位元組)

11.dhcp_options

lwIP支援DHCP協議,一般情況下無需修改該選項的預設值。

屬性 預設值 說明
lwip_dhcp false 設定是否需要DHCP
Dhcp_does_arp_check false 設定ARP是否檢查提供的地址

12.stats_options

lwIP棧在設計上可以收集一些統計資訊,比如使用的連線數、記憶體使用量、應用程式使用的訊號量的數量。lwIP庫提供了函式stats_display()來顯示統計值。是否啟用該功能在stats選項中設定。該選項下只有一個boolean型別的lwip_stats,預設為false。

13.debug_options

lwIP可以提供除錯資訊,debug_options下包括lwip_debug、ip_debug、tcp_debug、udp_debug、icmp_debug、igmp_debug、netif_debug、sys_debug、pbuf_debug幾個選項,都是boolean型別,設定true/false來開啟/關閉對應的除錯功能。


軟體API

lwIP庫提供了兩種不同的API:RAW mode和Socket mode。

RAW API基於回撥機制,應用程式與TCP棧之間可以直接訪問。因此沒有額外的socket層,RAW API有優秀的效能表現,但不能與其它TCP棧相容。此外,Xilinx介面卡還為接收資料包提供了xemacif_input程式函式。必須經常呼叫此函式,將接收到的資料包從中斷處理程式移動到lwIP棧。根據接受包的型別,lwIP回撥相應的程式。

Socket API是一套BSD socket風格的API。該API提供了一個執行模型,它是一個阻塞的、“開啟-讀-寫-關閉(open-read-write-close)”模型。使用Socket API和Xilinx介面卡的應用程式需要生成一個單獨的執行緒xemacif_input_thread。這個執行緒將接收到的資料包從中斷處理程式移動到lwIP的tcpip_thread。必須使用lwIP的sys_thread_new API建立使用lwIP的應用程式執行緒。

Xilinx介面卡提供了一些輔助函式,以簡化lwIP API的使用,各函式簡要說明如下:

1.lwip_init

void lwip_init()

這個函式為lwIP資料結構做了初始化,會替換對初始化狀態、系統、記憶體、pbuf、ARP、IP、UDP、TCP的特定呼叫。

2.xemac_add

struct netif *xemac_add (struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask, struct ip_addr *gw, unsigned char *mac_ethernet_address, unsigned mac_baseaddr)

這個函式為新增任何Xilinx EMAC IP和GigE核提供了一個統一的介面。這個函式在lwIP的netif_add函式基礎上封裝的,用於初始化網路介面‘netif’,給定它的IP地址、網路掩碼、閘道器的IP地址、6位元組的乙太網地址(MAC地址),以及axi_ethernetlite或axi_ethernet MAC核的基地址。

3.xemacif_input

void xemacif_input(struct netif *netif)

該函式只在RAW模式下可用。Xlinx lwIP介面卡在中斷模式下工作。接收中斷處理程式從EMAC/GigE中將包資料移動並儲存在佇列中。xemacif_input函式從佇列中取出這些包,傳遞給lwIP。因此在RAW mode下,需要使用這個程式。下面是一個簡單示例:

while (1) { 
	/* receive packets */ 
	xemacif_input(netif);

    /* do application specific processing */
}

該程式會通過回撥通知已經接收到的資料。

4.xemacif_input_thread

void xemacif_input_thread(struct netif *netif)

該函式只在Socket模式下可用。Socket模式中,應用程式必須啟動一個單獨的執行緒來接收輸入包。這與RAW模式下的xemacif_input函式功能相同,只不過它駐留在獨立的執行緒中。因此,任何lwIP socket模式下的應用程式都需要有類似如下的程式碼:

sys_thread_new(“xemacif_input_thread”, xemacif_input_thread, netif,
                     THREAD_STACK_SIZE, DEFAULT_THREAD_PRIO);

然後,應用程式可以啟動單獨的執行緒來完成應用程式中特定的任務。xemacif_input_thread會接收中斷處理程式處理的資料,將其傳遞給lwIP tcpip_thread。

5.xemacpsif_resetrx_on_no_rxdata

void xemacpsif_resetrx_on_no_rxdata(struct netif *netif)

該函式在Raw模式和Socket模式下都可用,但只能用於Zynq系列的GigE控制器。GigE控制器上有一個與Rx路徑有關的勘誤表(errata)。該勘誤表描述了當小資料包的Rx流量過大時,GigE的Rx路徑完全沒有響應的情況。這種情況很少發生,但發生時需要對控制器中的Rx邏輯進行軟體重置。使用者的應用程式必須週期性地呼叫這個函式,以確保Rx路徑不會再超過100ms的時間內停止響應。


RAW API程式架構

使用RAW API的應用程式是單執行緒的,一般具有與下面虛擬碼類似的主架構:

int main() 
{
	struct netif *netif, server_netif; 
	struct ip_addr ipaddr, netmask, gw;
	//板子的MAC地址,每個PHY都不同
	unsigned char mac_ethernet_address[] = 
		{0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};
	
	lwip_init();
	
	//把網路介面新增到netif_list, 並設為預設
	if (!xemac_add(netif, &ipaddr, &netmask, 
		&gw, mac_ethernet_address, EMAC_BASEADDR)) {
		printf(“Error adding N/W interface\n\r”); 
		return -1;
	}
	netif_set_default(netif);

	platform_enable_interrupts();   //使能中斷
	netif_set_up(netif);  //指定網路是否開啟
	start_application();  //啟動應用程式,設定回撥
	
	//接收並處理包
	while (1) {
		xemacif_input(netif); 
		transfer_data();  //執行應用程式的特定功能
	}
}

RAW API主要通過非同步呼叫傳送和接收的回撥函式來工作。


Socket API程式架構

Socket模式下,基於Xilkernel的應用程式可以在Xilkernel軟體平臺的設定對話方塊中指定一個靜態執行緒列表,這些執行緒會在Xilkernel啟動時生成。假設main_thread()是一個設定的由Xilkernel啟動的執行緒,在啟動Xilkernel排程後,控制權會從應用程式中的“main”轉移到這個執行緒。在main執行緒中,再建立一個執行緒(network_thread)來初始化MAC層。

對於基於FreeRTOS(Zynq-7000處理器系統)的應用程式,一旦控制權到達“main”,就會在啟動排程程式之前建立一個帶有main_thread()入口函式的任務。在FreeRTOS排程程式啟動之後,控制權到達main_thread(),在這裡進行lwIP的初始化。然後,應用程式再建立一個執行緒(network_thread)來初始化MAC層。

下面的虛擬碼展示了一個典型的Socket模式下的程式架構:

void network_thread(void *p) 
{
	struct netif *netif, server_netif; 
	struct ip_addr ipaddr, netmask, gw;
	//板子的MAC地址,每個PHY都不同
	unsigned char mac_ethernet_address[] = 
		{0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};
	netif = &server_netif;

	//初始化使用的IP地址
	IP4_ADDR(&ipaddr,192,168,1,10); 
	IP4_ADDR(&netmask,255,255,255,0); 
	IP4_ADDR(&gw,192,168,1,1);
	//把網路介面新增到netif_list, 並設為預設
	if (!xemac_add(netif, &ipaddr, &netmask, 
		&gw, mac_ethernet_address, EMAC_BASEADDR)) { 
		printf(“Error adding N/W interface\n\r”); 
		return;
	}
	netif_set_default(netif); 

	netif_set_up(netif); //指定網路是否開啟	
	//啟動包接收執行緒
	sys_thread_new(“xemacif_input_thread”, xemacif_input_thread, 
				netif, THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
	//啟動應用程式執行緒
	sys_thread_new(“httpd” web_application_thread, 0, 
				THREAD_STACKSIZE DEFAULT_THREAD_PRIO);
}

int main_thread() 
{
	//呼叫sys_thread_new前初始化lwIP 
	lwip_init();
	//使用lwIP的所有執行緒都要用sys_thread_new()建立 
	sys_thread_new(“network_thread” network_thread, 
			NULL, THREAD_STACKSIZE DEFAULT_THREAD_PRIO);
	return 0;
}

相關文章