主函數:

1 int main() 2 { 3 uint cycle = 0; 4 ip_addr_t ipaddr, netmask, gw; 5 RxBufNo = 0; 6 RxBufferPtr[0] = (u16 *)RX_BUFFER0_BASE; 7 RxBufferPtr[1] = (u16 *)RX_BUFFER1_BASE; 8 9 /* the mac address of the board. this should be unique per board */ 10 unsigned char mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 }; 11 12 XGpio_Initialize(&Gpio, XPAR_AXI_GPIO_0_DEVICE_ID);//初始化AXI-GPIO 13 XGpio_SetDataDirection(&Gpio, 1, 0); 14 15 16 /* Define this board specific macro in order perform PHY reset on ZCU102 */ 17 18 // dma configuration 19 int ret; 20 ret = dma_init(&AxiDma); 21 if(ret!=XST_SUCCESS) 22 xil_printf("dma init error!"); 23 ret = Timer_init(&Timer, TIMER_LOAD_VALUE, 0); 24 if(ret!=1) 25 xil_printf("timer init failed!\n"); 26 ret = dma_intr_init(&AxiDma); 27 if(ret!=XST_SUCCESS){ 28 xil_printf("dma interrup init failed!\n"); 29 } 30 Timer_Setup_Intr_System(&Intc,&Timer, XPAR_SCUTIMER_INTR); 31 32 echo_netif = &server_netif; 33 /* initliaze IP addresses to be used */ 34 IP4_ADDR(&ipaddr, 192, 168, 1, 10); 35 IP4_ADDR(&netmask, 255, 255, 255, 0); 36 IP4_ADDR(&gw, 192, 168, 1, 1); 37 38 lwip_init(); 39 40 /* Add network interface to the netif_list, and set it as default */ 41 if (!xemac_add(echo_netif, &ipaddr, &netmask, 42 &gw, mac_ethernet_address, 43 PLATFORM_EMAC_BASEADDR)) { 44 xil_printf("Error adding N/W interface\n\r"); 45 return -1; 46 } 47 netif_set_default(echo_netif); 48 49 /* specify that the network if is up */ 50 netif_set_up(echo_netif); 51 52 /* start the application (web server, rxtest, txtest, etc..) */ 53 client_application(); 54 // start data generation progress in PL side by AXI4-Lite bus 55 XGpio_DiscreteWrite(&Gpio, 1, 1); 56 Timer_start(&Timer); 57 58 /* receive and process packets */ 59 while (1) { 60 if (TcpFastTmrFlag) { 61 tcp_fasttmr(); 62 TcpFastTmrFlag = 0; 63 } 64 if (TcpSlowTmrFlag) { 65 tcp_slowtmr(); 66 TcpSlowTmrFlag = 0; 67 } 68 xemacif_input(echo_netif); 69 if(connected_pcb!=NULL){ 70 send_data(); 71 } 72 } 73 return 0; 74 }
首先是各種初始化,包括GPIO初始化,DMA初始化,定時器初始化;其次是DMA中斷和定時器中斷的初始化;然后是LWIP的初始化(IP地址、端口號、網關),添加PS側的MAC,設置回調函數;通過GPIO開啟PL側數據的產生。
主循環中先判斷是否已經建立好連接,如果已經建立了連接,那么就啟動send_data函數:

1 int send_data() 2 { 3 err_t err; 4 int ret; 5 struct tcp_pcb *tpcb = connected_pcb; 6 7 if (!tpcb){ 8 xil_printf("tpcb created error!\r\n"); 9 return -1; 10 } 11 if(!first_dma_start){ 12 ret = XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)RxBufferPtr[RxBufNo&1],MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA); 13 if(ret!=XST_SUCCESS) 14 xil_printf("first dma start failed!\n"); 15 first_dma_start = 1; 16 } 17 if(dma_com_flag){ 18 //判斷發送數據長度是否小於發送緩沖區剩余可用長度 19 if (TX_SIZE < tcp_sndbuf(tpcb)) { 20 //Write data for sending (but does not send it immediately). 21 // err = tcp_write(tpcb, data, TX_SIZE, 1); 22 err = tcp_write(tpcb, RxBufferPtr[RxBufNo&1], TX_SIZE, 1); 23 if (err != ERR_OK) { 24 xil_printf("txperf: Error on tcp_write: %d\r\n", err); 25 connected_pcb = NULL; 26 return -1; 27 } 28 29 //Find out what we can send and send it 30 err = tcp_output(tpcb); 31 if (err != ERR_OK) { 32 xil_printf("txperf: Error on tcp_output: %d\r\n",err); 33 return -1; 34 } 35 dma_com_flag = 0; 36 RxBufNo++; 37 XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)RxBufferPtr[(RxBufNo)&1], MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA); 38 } 39 } 40 return 0; 41 }
首先判斷是否已經啟動了第一次DMA傳輸,如果還沒有,就先啟動一次DMA傳輸;如果已經啟動了,那么就判斷DMA傳輸是否已經完成,如果已經完成一次DAM傳輸,那么就啟動TCP傳輸對應緩沖的數據,同時再開啟另一塊緩沖的DMA傳輸。其中DMA傳輸完成的標志是在DMA傳輸中斷處理函數RxIntrHandler中設置的:

1 static void RxIntrHandler(void *Callback) 2 { 3 u32 IrqStatus; 4 int TimeOut; 5 XAxiDma *AxiDmaInst = (XAxiDma *)Callback; 6 7 /* Read pending interrupts */ 8 IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA); 9 10 /* Acknowledge pending interrupts */ 11 XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA); 12 13 /* 14 * If no interrupt is asserted, we do not do anything 15 */ 16 if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) { 17 return; 18 } 19 20 /* 21 * If error interrupt is asserted, raise error flag, reset the 22 * hardware to recover from the error, and return with no further 23 * processing. 24 */ 25 if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) { 26 27 Error = 1; 28 29 /* Reset could fail and hang 30 * NEED a way to handle this or do not call it?? 31 */ 32 XAxiDma_Reset(AxiDmaInst); 33 34 TimeOut = RESET_TIMEOUT_COUNTER; 35 36 while (TimeOut) { 37 if(XAxiDma_ResetIsDone(AxiDmaInst)) { 38 break; 39 } 40 41 TimeOut -= 1; 42 } 43 44 return; 45 } 46 47 /* 48 * If completion interrupt is asserted, then set RxDone flag 49 */ 50 if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) { 51 if(!dma_com_flag) 52 dma_com_flag = 1; 53 } 54 }
send_data函數中的tcp_write函數和tcp_output函數是真正實現TCP數據傳輸功能的函數。若處於連接狀態,且發送緩沖區容量比帶發送數據量大,則調用tcp_write將待發送數據寫入發送緩沖區,之后調用tcp_output函數立即傳輸發送緩沖區內容。如果不調用tcp_output,LWIP會等待數據量達到一定值時一起發送來提高效率,是否調用tcp_output函數可根據具體需求而定。
client_application函數:

1 int client_application() 2 { 3 struct tcp_pcb *pcb; 4 ip_addr_t ipaddr; 5 err_t err; 6 unsigned port = 7; 7 8 /* create new TCP PCB structure */ 9 pcb = tcp_new(); 10 if (!pcb) { 11 xil_printf("Error creating PCB. Out of Memory\n\r"); 12 return -1; 13 } 14 15 /* connect to iperf tcp server */ 16 IP4_ADDR(&ipaddr, 192, 168, 1, 209);//設置要連接的主機的地址 17 18 //當連接到主機時,調用tcp_connected_callback 19 err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback); 20 if (err != ERR_OK) { 21 xil_printf("txperf: tcp_connect returned error: %d\r\n", err); 22 return err; 23 } 24 25 return 0; 26 }
在本例程中, zynq 作為客戶端, PC 作為服務器。 由 zynq 向 PC 主動發起 TCP 連接請求,通過 tcp_connect 函數便可以完成這個過程。 該函數的參數包含了一個回調函數指針tcp_connected_fn, 該回調函數將在 TCP 連接請求三次握手完成后被自動調用。 該回調函數被調用時代表客戶端和服務器之間的 TCP 連接建立完成。 在本例程中, 該回調函數被定義為 tcp_connected_callback。

1 static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err) 2 { 3 /* store state */ 4 connected_pcb = tpcb; 5 6 /* set callback values & functions */ 7 tcp_arg(tpcb, NULL); 8 9 //發送到遠程主機后調用tcp_sent_callback 10 tcp_sent(tpcb, tcp_sent_callback); 11 12 client_connected = 1; 13 14 /* initiate data transfer */ 15 return ERR_OK; 16 }
在該函數中,通過 tcp_sent 函數配置另一個 TCP 發送完成的回調函數tcp_sent_callback。

1 static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb,u16_t len) 2 { 3 tcp_trans_done ++; 4 return ERR_OK; 5 }
該回調函數在每個 TCP 包發送完成后會被自動調用, 代表 TCP 包發送完成。 該回調函數在本例程中僅作發送完成數據包的計數。
參考:
1. 米聯客視頻教程及《Zynq SOC修煉秘籍》
2. https://www.cnblogs.com/moluoqishi/p/9865480.html