利用ZYNQ SOC快速打开算法验证通路(6)——LWIP实现千兆TCP/IP网络传输
一、前言
之前zynq与pc之间的网络连接依赖于外接硬件协议栈芯片,虽然c驱动非常简单,但网络带宽受限。现采用lwip+ps端mac控制器+phy芯片的通用架构。关于lwip库,已经有很多现成的资料和书籍。其有两套api,一个是socket,另一个是本例中要用到的raw。raw api理解起来较为复杂,整个程序基于中断机制运行,通过函数指针完成多层回调函数的执行。socket api需要支持多线程操作系统的支持,也牺牲了效率,但理解和编程都较为容易。实际上socket api是对raw api的进一步封装。
二、lwip echo server demo解读
首先打开xilinx sdk自带的lwip echo server demo.
1 /****************************************************************************** 2 * 3 * copyright (c) 2009 - 2014 xilinx, inc. all rights reserved. 4 * 5 * permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "software"), to deal 7 * in the software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the software, and to permit persons to whom the software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * the above copyright notice and this permission notice shall be included in 13 * all copies or substantial portions of the software. 14 * 15 * use of the software is limited solely to applications: 16 * (a) running on a xilinx device, or 17 * (b) that interact with a xilinx device through a bus or interconnect. 18 * 19 * the software is provided "as is", without warranty of any kind, express or 20 * implied, including but not limited to the warranties of merchantability, 21 * fitness for a particular purpose and noninfringement. in no event shall 22 * xilinx be liable for any claim, damages or other liability, 23 * whether in an action of contract, tort or otherwise, arising from, out of 24 * or in connection with the software or the use or other dealings in the 25 * software. 26 * 27 * except as contained in this notice, the name of the xilinx shall not be used 28 * in advertising or otherwise to promote the sale, use or other dealings in 29 * this software without prior written authorization from xilinx. 30 * 31 ******************************************************************************/ 32 33 #include <stdio.h> 34 35 #include "xparameters.h" 36 37 #include "netif/xadapter.h" 38 39 #include "platform.h" 40 #include "platform_config.h" 41 #if defined (__arm__) || defined(__aarch64__) 42 #include "xil_printf.h" 43 #endif 44 45 #include "lwip/tcp.h" 46 #include "xil_cache.h" 47 48 #if lwip_dhcp==1 49 #include "lwip/dhcp.h" 50 #endif 51 52 /* defined by each raw mode application */ 53 void print_app_header(); 54 int start_application(); 55 int transfer_data(); 56 void tcp_fasttmr(void); 57 void tcp_slowtmr(void); 58 59 /* missing declaration in lwip */ 60 void lwip_init(); 61 62 #if lwip_dhcp==1 63 extern volatile int dhcp_timoutcntr; 64 err_t dhcp_start(struct netif *netif); 65 #endif 66 67 extern volatile int tcpfasttmrflag; 68 extern volatile int tcpslowtmrflag; 69 static struct netif server_netif; 70 struct netif *echo_netif; 71 72 void 73 print_ip(char *msg, struct ip_addr *ip) 74 { 75 print(msg); 76 xil_printf("%d.%d.%d.%d\n\r", ip4_addr1(ip), ip4_addr2(ip), 77 ip4_addr3(ip), ip4_addr4(ip)); 78 } 79 80 void 81 print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw) 82 { 83 84 print_ip("board ip: ", ip); 85 print_ip("netmask : ", mask); 86 print_ip("gateway : ", gw); 87 } 88 89 #if defined (__arm__) && !defined (armr5) 90 #if xpar_gige_pcs_pma_sgmii_core_present == 1 || xpar_gige_pcs_pma_1000basex_core_present == 1 91 int programsi5324(void); 92 int programsfpphy(void); 93 #endif 94 #endif 95 96 #ifdef xps_board_zcu102 97 #ifdef xpar_xiicps_0_device_id 98 int iicphyreset(void); 99 #endif 100 #endif 101 102 int main() 103 { 104 struct ip_addr ipaddr, netmask, gw; 105 106 /* the mac address of the board. this should be unique per board */ 107 unsigned char mac_ethernet_address[] = 108 { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 }; 109 110 echo_netif = &server_netif; 111 #if defined (__arm__) && !defined (armr5) 112 #if xpar_gige_pcs_pma_sgmii_core_present == 1 || xpar_gige_pcs_pma_1000basex_core_present == 1 113 programsi5324(); 114 programsfpphy(); 115 #endif 116 #endif 117 118 /* define this board specific macro in order perform phy reset on zcu102 */ 119 #ifdef xps_board_zcu102 120 iicphyreset(); 121 #endif 122 123 init_platform(); 124 125 #if lwip_dhcp==1 126 ipaddr.addr = 0; 127 gw.addr = 0; 128 netmask.addr = 0; 129 #else 130 /* initliaze ip addresses to be used */ 131 ip4_addr(&ipaddr, 192, 168, 1, 10); 132 ip4_addr(&netmask, 255, 255, 255, 0); 133 ip4_addr(&gw, 192, 168, 1, 1); 134 #endif 135 print_app_header(); 136 137 lwip_init();//网络参数初始化 138 139 /* add network interface to the netif_list, and set it as default */ 140 if (!xemac_add(echo_netif, &ipaddr, &netmask, 141 &gw, mac_ethernet_address, 142 platform_emac_baseaddr)) { 143 xil_printf("error adding n/w interface\n\r"); 144 return -1; 145 } 146 netif_set_default(echo_netif); 147 148 /* now enable interrupts */ 149 platform_enable_interrupts(); 150 151 /* specify that the network if is up */ 152 netif_set_up(echo_netif); 153 154 #if (lwip_dhcp==1) 155 /* create a new dhcp client for this interface. 156 * note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at 157 * the predefined regular intervals after starting the client. 158 */ 159 dhcp_start(echo_netif); 160 dhcp_timoutcntr = 24; 161 162 while(((echo_netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0)) 163 xemacif_input(echo_netif); 164 165 if (dhcp_timoutcntr <= 0) { 166 if ((echo_netif->ip_addr.addr) == 0) { 167 xil_printf("dhcp timeout\r\n"); 168 xil_printf("configuring default ip of 192.168.1.10\r\n"); 169 ip4_addr(&(echo_netif->ip_addr), 192, 168, 1, 10); 170 ip4_addr(&(echo_netif->netmask), 255, 255, 255, 0); 171 ip4_addr(&(echo_netif->gw), 192, 168, 1, 1); 172 } 173 } 174 175 ipaddr.addr = echo_netif->ip_addr.addr; 176 gw.addr = echo_netif->gw.addr; 177 netmask.addr = echo_netif->netmask.addr; 178 #endif 179 180 print_ip_settings(&ipaddr, &netmask, &gw);//打印关键网络参数 181 182 /* start the application (web server, rxtest, txtest, etc..) */ 183 start_application();//设置回调函数,这些函数在特定事件发生时以函数指针的方式被调用 184 185 /* receive and process packets */ 186 while (1) { 187 if (tcpfasttmrflag) {//发送处理,如差错重传,通过定时器置位标志位 188 tcp_fasttmr(); 189 tcpfasttmrflag = 0; 190 } 191 if (tcpslowtmrflag) { 192 tcp_slowtmr(); 193 tcpslowtmrflag = 0; 194 } 195 xemacif_input(echo_netif);//连续接收数据包,并将数据包存入lwip 196 transfer_data();//空函数 197 } 198 199 /* never reached */ 200 cleanup_platform(); 201 202 return 0; 203 }
整体流程为:初始化lwip、添加网络接口(mac)、使能中断、设置回调函数。最终进入主循环,内部不断检测定时器中断标志位,当标志位tcpfasttmrflag或tcpslowtmrflag为1则调用相应的处理函数,完成超时重传等任务。接下来查看回调函数的设置:
int start_application() { struct tcp_pcb *pcb;//protocol control block 简称pcb err_t err; unsigned port = 7; /* create new tcp pcb structure */ pcb = tcp_new(); if (!pcb) { xil_printf("error creating pcb. out of memory\n\r"); return -1; } /* bind to specified @port */ err = tcp_bind(pcb, ip_addr_any, port); if (err != err_ok) { xil_printf("unable to bind to port %d: err = %d\n\r", port, err); return -2; } /* we do not need any arguments to callback functions */ tcp_arg(pcb, null); /* listen for connections */ pcb = tcp_listen(pcb); if (!pcb) { xil_printf("out of memory while tcp_listen\n\r"); return -3; } /* specify callback to use for incoming connections */ tcp_accept(pcb, accept_callback); xil_printf("tcp echo server started @ port %d\n\r", port); return 0; }
创建pcb(protocol control block)建立连接、绑定ip地址和端口号、监听请求,最后tcp_accept函数用于指定当监听到连接请求时调用的函数accept_callback。进入该函数内部查看:
1 err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) 2 { 3 static int connection = 1; 4 5 /* set the receive callback for this connection */ 6 tcp_recv(newpcb, recv_callback); 7 8 /* just use an integer number indicating the connection id as the 9 callback argument */ 10 tcp_arg(newpcb, (void*)(uintptr)connection); 11 12 /* increment for subsequent accepted connections */ 13 connection++; 14 15 return err_ok; 16 }
内部主要通过tcp_recv函数来指定当收到tcp包后调用的函数recv_callback。我们再次观察其内容:
1 err_t recv_callback(void *arg, struct tcp_pcb *tpcb, 2 struct pbuf *p, err_t err) 3 { 4 /* do not read the packet if we are not in established state */ 5 if (!p) { 6 tcp_close(tpcb); 7 tcp_recv(tpcb, null); 8 return err_ok; 9 } 10 11 /* indicate that the packet has been received */ 12 tcp_recved(tpcb, p->len); 13 14 /* echo back the payload */ 15 /* in this case, we assume that the payload is < tcp_snd_buf */ 16 if (tcp_sndbuf(tpcb) > p->len) { 17 err = tcp_write(tpcb, p->payload, p->len, 1); 18 } else 19 xil_printf("no space in tcp_sndbuf\n\r"); 20 21 /* free the received pbuf */ 22 pbuf_free(p); 23 24 return err_ok; 25 }
tcp_recved函数指示用来告知lwip接收数据量,然后检测发送缓冲区是否足够容纳接收内容,若大于则调用tcp_write函数将接收数据写入发送缓冲区等待发送。综上,整体的调用流程为:tcp_accept -> accept_callback -> tcp_recv -> recv_callback -> tcp_recved和tcp_write。前四个用于接收,后两个用于发送。
函数解析完毕,之后改动上位机网络参数,使pc机ip地址与board在同一网段内,这里设置为192.168.1.11.打开网络调试助手,设置pc为tcp client。以下是zynq串口打印及网络调试结果。
三、tcp client send data
现在我们来改动demo,设计一个客户端发送数据包的示例工程,功能是循环发送一个常数数组中数据到远程服务器。该工程参考米联客教程中相关章节内容。代码如下:
/****************************************************************************** * * copyright (c) 2009 - 2014 xilinx, inc. all rights reserved. * * permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "software"), to deal * in the software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the software, and to permit persons to whom the software is * furnished to do so, subject to the following conditions: * * the above copyright notice and this permission notice shall be included in * all copies or substantial portions of the software. * * use of the software is limited solely to applications: * (a) running on a xilinx device, or * (b) that interact with a xilinx device through a bus or interconnect. * * the software is provided "as is", without warranty of any kind, express or * implied, including but not limited to the warranties of merchantability, * fitness for a particular purpose and noninfringement. in no event shall * xilinx be liable for any claim, damages or other liability, * whether in an action of contract, tort or otherwise, arising from, out of * or in connection with the software or the use or other dealings in the * software. * * except as contained in this notice, the name of the xilinx shall not be used * in advertising or otherwise to promote the sale, use or other dealings in * this software without prior written authorization from xilinx. * ******************************************************************************/ #include <stdio.h> #include "xparameters.h" #include "netif/xadapter.h" #include "platform.h" #include "platform_config.h" #if defined (__arm__) || defined(__aarch64__) #include "xil_printf.h" #endif #include "lwip/tcp.h" #include "xil_cache.h" #if lwip_dhcp==1 #include "lwip/dhcp.h" #endif /* defined by each raw mode application */ void print_app_header(); int client_application(); //int start_application(); //int transfer_data(); int send_data(); void tcp_fasttmr(void); void tcp_slowtmr(void); /* missing declaration in lwip */ void lwip_init(); #if lwip_dhcp==1 extern volatile int dhcp_timoutcntr; err_t dhcp_start(struct netif *netif); #endif extern volatile int tcpfasttmrflag; extern volatile int tcpslowtmrflag; static struct netif server_netif; struct netif *echo_netif; void print_ip(char *msg, struct ip_addr *ip) { print(msg); xil_printf("%d.%d.%d.%d\n\r", ip4_addr1(ip), ip4_addr2(ip), ip4_addr3(ip), ip4_addr4(ip)); } void print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw) { print_ip("board ip: ", ip); print_ip("netmask : ", mask); print_ip("gateway : ", gw); } int main() { uint cycle = 0; struct ip_addr ipaddr, netmask, gw; /* the mac address of the board. this should be unique per board */ unsigned char mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 }; echo_netif = &server_netif; /* define this board specific macro in order perform phy reset on zcu102 */ init_platform(); /* initliaze ip addresses to be used */ ip4_addr(&ipaddr, 192, 168, 1, 10); ip4_addr(&netmask, 255, 255, 255, 0); ip4_addr(&gw, 192, 168, 1, 1); print_app_header(); lwip_init(); /* add network interface to the netif_list, and set it as default */ if (!xemac_add(echo_netif, &ipaddr, &netmask, &gw, mac_ethernet_address, platform_emac_baseaddr)) { xil_printf("error adding n/w interface\n\r"); return -1; } netif_set_default(echo_netif); /* now enable interrupts */ platform_enable_interrupts(); /* specify that the network if is up */ netif_set_up(echo_netif); print_ip_settings(&ipaddr, &netmask, &gw); /* start the application (web server, rxtest, txtest, etc..) */ //start_application(); client_application(); /* receive and process packets */ while (1) { if (tcpfasttmrflag) { tcp_fasttmr(); tcpfasttmrflag = 0; } if (tcpslowtmrflag) { tcp_slowtmr(); tcpslowtmrflag = 0; } xemacif_input(echo_netif); //transfer_data(); if(cycle == 9999){ cycle = 0; send_data(); } else cycle++; } return 0; }
函数定义:
1 /* 2 * tcp_trans.c 3 * 4 * created on: 2018年10月18日 5 * author: s 6 */ 7 8 9 #include <stdio.h> 10 #include <string.h> 11 12 #include "lwip/err.h" 13 #include "lwip/tcp.h" 14 #include "lwipopts.h" 15 #include "xil_cache.h" 16 #include "xil_printf.h" 17 #include "sleep.h" 18 19 #define tx_size 10 20 21 static struct tcp_pcb*connected_pcb = null; 22 unsigned client_connected = 0; 23 //静态全局函数 外部文件不可见 24 uint tcp_trans_done = 0; 25 26 u_char data[tx_size] = {0,1,2,3,4,5,6,7,8,9}; 27 28 int send_data() 29 { 30 err_t err; 31 struct tcp_pcb *tpcb = connected_pcb; 32 33 if (!tpcb) 34 return -1; 35 36 //判断发送数据长度是否小于发送缓冲区剩余可用长度 37 if (tx_size < tcp_sndbuf(tpcb)) { 38 //write data for sending (but does not send it immediately). 39 err = tcp_write(tpcb, data, tx_size, 1); 40 if (err != err_ok) { 41 xil_printf("txperf: error on tcp_write: %d\r\n", err); 42 connected_pcb = null; 43 return -1; 44 } 45 46 //find out what we can send and send it 47 err = tcp_output(tpcb); 48 if (err != err_ok) { 49 xil_printf("txperf: error on tcp_output: %d\r\n",err); 50 return -1; 51 } 52 } 53 else 54 xil_printf("no space in tcp_sndbuf\n\r"); 55 56 return 0; 57 } 58 59 static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb,u16_t len) 60 { 61 tcp_trans_done ++; 62 return err_ok; 63 } 64 65 //tcp连接回调函数 设置为静态函数,外部文件不可见 66 static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err) 67 { 68 /* store state */ 69 connected_pcb = tpcb; 70 71 /* set callback values & functions */ 72 tcp_arg(tpcb, null); 73 74 //发送到远程主机后调用tcp_sent_callback 75 tcp_sent(tpcb, tcp_sent_callback); 76 77 client_connected = 1; 78 79 /* initiate data transfer */ 80 return err_ok; 81 } 82 83 int client_application() 84 { 85 struct tcp_pcb *pcb; 86 struct ip_addr ipaddr; 87 err_t err; 88 unsigned port = 7; 89 90 /* create new tcp pcb structure */ 91 pcb = tcp_new(); 92 if (!pcb) { 93 xil_printf("error creating pcb. out of memory\n\r"); 94 return -1; 95 } 96 97 /* connect to iperf tcp server */ 98 ip4_addr(&ipaddr, 192, 168, 1, 209);//设置要连接的主机的地址 99 100 //当连接到主机时,调用tcp_connected_callback 101 err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback); 102 if (err != err_ok) { 103 xil_printf("txperf: tcp_connect returned error: %d\r\n", err); 104 return err; 105 } 106 107 return 0; 108 }
可以看出还是一样的套路,在client_application函数中设置回调函数。首先新建pcb,tcp_connect函数设定要连接远程服务器的ip地址和端口号,连接建立时将调用回调函数tcp_connected_callback。tcp_connected_callback内部tcp_sent函数用于指定当发送数据包完成后执行的tcp_sent_callback。tcp_sent_callback内部只利用tcp_trans_done变量计数发送次数。而真正的发送处理任务则交给主循环中的send_data。若处于连接状态,且发送缓冲区容量比带发送数据量大,则调用tcp_write将待发送数据写入发送缓冲区,之后调用tcp_output函数立即传输发送缓冲区内容。如果不调用tcp_output,lwip会等待数据量达到一定值时一起发送来提高效率,是否调用tcp_output函数可根据具体需求而定。
接下来看下实验结果:
pc端正确接收到常数数组,实验无误。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
参考文献:
1 lwip 无os raw-api 函数 - 专注的力量 - csdn博客 https://blog.csdn.net/liang890319/article/details/8574603
2 解读tcp 四种定时器 - xiaofei0859的专栏 - csdn博客 https://blog.csdn.net/xiaofei0859/article/details/52794576
3 米联 《zynq soc修炼秘籍》