/** @file wlan_exp_transport.c * @brief Experiment Framework (Transport) * * Handles UDP transmissions and reception as well as process commands for the * transport group. * * @copyright Copyright 2013-2019, Mango Communications. All rights reserved. * Distributed under the Mango Communications Reference Design License * See LICENSE.txt included in the design archive or * at http://mangocomm.com/802.11/license * * This file is part of the Mango 802.11 Reference Design (https://mangocomm.com/802.11) */ /***************************** Include Files *********************************/ #include "wlan_mac_high_sw_config.h" #if WLAN_SW_CONFIG_ENABLE_WLAN_EXP #include #include #include #include #include #include #include "wlan_mac_common.h" #include "wlan_mac_high.h" #include "wlan_platform_high.h" #include "wlan_platform_common.h" #include "wlan_mac_dl_list.h" #include "wlan_mac_queue.h" #include "wlan_mac_network_info.h" #include "wlan_mac_station_info.h" #include "wlan_exp_common.h" #include "wlan_exp_node.h" #include "wlan_exp_transport.h" #include "wlan_exp_ip_udp_socket.h" // Internal structure to this file. It never is sent to a host OTW typedef struct _wlan_exp_transport_info_t{ u32 group_id; u32 unicast_port; u32 broadcast_port; u32 max_pkt_words; int socket_unicast; int socket_broadcast; } _wlan_exp_transport_info_t; // Transport information static _wlan_exp_transport_info_t wlan_exp_transport_info; // Defined in wlan_exp_node.c extern platform_high_dev_info_t platform_high_dev_info; extern wlan_exp_node_info_t wlan_exp_node_info; // Defined in wlan_mac_high.c extern wlan_mac_hw_info_t wlan_mac_hw_info; // Queues for wlan_exp Rx/Tx static int _wlan_exp_eth_rx_qid; static int _wlan_exp_eth_tx_qid; static sockaddr_in_t* gl_tx_to; void _wlan_exp_tx_queue_occupancy_change(int callback_arg, u32 queue_len); int _transport_config_socket(int* socket_index, u32 udp_port); /*****************************************************************************/ /** * @brief Transport subsystem initialization * * @return int - WLAN_SUCCESS or WLAN_FAILURE ******************************************************************************/ int transport_init() { u8 ip_addr[IP_ADDR_LEN]; int status = WLAN_SUCCESS; // Open a queue for Rx Ethernet frames _wlan_exp_eth_rx_qid = queue_open((void*)wlan_null_callback, 0, WLAN_EXP_MAX_QUEUE_LEN); // Open a queue for Tx Ethernet frames _wlan_exp_eth_tx_qid = queue_open((void*)_wlan_exp_tx_queue_occupancy_change, 0, WLAN_EXP_MAX_QUEUE_LEN); if((_wlan_exp_eth_rx_qid == -1) || (_wlan_exp_eth_tx_qid == -1)) xil_printf("Error in wlan_eth_util_init(), unable to open queue\n"); ip_addr[0] = (WLAN_EXP_DEFAULT_IP_ADDR >> 24) & 0xFF; ip_addr[1] = (WLAN_EXP_DEFAULT_IP_ADDR >> 16) & 0xFF; ip_addr[2] = (WLAN_EXP_DEFAULT_IP_ADDR >> 8) & 0xFF; ip_addr[3] = (WLAN_EXP_DEFAULT_IP_ADDR ) & 0xFF; // IP ADDR = w.x.y.z // Initialize the wlan_exp IP/UDP transport ip_udp_init(wlan_mac_hw_info.hw_addr_wlan_exp, ip_addr); // Print MAC address and IP address xil_printf(" wlan_exp MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n", wlan_mac_hw_info.hw_addr_wlan_exp[0], wlan_mac_hw_info.hw_addr_wlan_exp[1], wlan_mac_hw_info.hw_addr_wlan_exp[2], wlan_mac_hw_info.hw_addr_wlan_exp[3], wlan_mac_hw_info.hw_addr_wlan_exp[4], wlan_mac_hw_info.hw_addr_wlan_exp[5]); xil_printf(" wlan_exp IP Address: %d.%d.%d.%d\n", ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]); // Initialize transport info structure wlan_exp_transport_info.max_pkt_words = WLAN_EXP_DEFAULT_MAX_PACKET_WORDS; wlan_exp_transport_info.socket_unicast = SOCKET_INVALID_SOCKET; wlan_exp_transport_info.socket_broadcast = SOCKET_INVALID_SOCKET; wlan_exp_transport_info.group_id = 0; transport_set_ip_addr(ip_addr); // Configure the Sockets for each Ethernet Interface status = transport_config_sockets(WLAN_EXP_DEFAULT_UDP_UNICAST_PORT, WLAN_EXP_DEFAULT_UDP_MULTICAST_PORT); if (status != WLAN_SUCCESS) { wlan_exp_printf(WLAN_EXP_PRINT_ERROR, print_type_transport, "Cannot configure sockets for Ethernet\n"); } gl_tx_to = NULL; return status; } /*****************************************************************************/ /** * @brief Append a UDP reception for future processing * * This function accepts an Ethernet queue buffer and enqueues it for * later processing by wlan_exp. The framework uses this structure to defer * processing to outside of an interrupt context in platforms whose wlan_exp * Ethernet peripheral is interrupt-based. * * @return int - WLAN_SUCCESS or WLAN_FAILURE ******************************************************************************/ int wlan_exp_append_rx(dl_entry* queue_entry){ int status; status = queue_enqueue(_wlan_exp_eth_rx_qid, queue_entry); if( status == WLAN_FAILURE ){ queue_checkin(queue_entry); } return status; } /*****************************************************************************/ /** * @brief Append a UDP transmission for future processing * * This function accepts an Ethernet queue buffer and enqueues it for * later transmission by the platform's wlan_exp Ethernet peripheral. * * @return int - WLAN_SUCCESS or WLAN_FAILURE ******************************************************************************/ int wlan_exp_append_tx(dl_entry* queue_entry){ int status; interrupt_state_t prev_interrupt_state; prev_interrupt_state = wlan_platform_intc_stop(); status = queue_enqueue(_wlan_exp_eth_tx_qid, queue_entry); if( status == WLAN_FAILURE ){ queue_checkin(queue_entry); } wlan_platform_intc_set_state(prev_interrupt_state); return status; } /*****************************************************************************/ /** * @brief Get the current length of the wlan_exp Tx queue * * @return int - length of the Tx queue ******************************************************************************/ int wlan_exp_get_tx_queue_length(){ return queue_length(_wlan_exp_eth_tx_qid); } /*****************************************************************************/ /** * @brief Poll enqueued UDP receptions * * This function is called by outside of an interrupt context and is used to process * any received UDP frames enqueued by wlan_exp_append_rx(). By executing outside of * an ISR, critical 802.11 behaviors are able to take priority over wlan_exp * behaviors. * * @return None * *****************************************************************************/ void wlan_exp_transport_poll() { u32 i; u32 num_packets; wlan_exp_eth_rx_queue_buffer_t* wlan_exp_eth_rx_queue_buffer; dl_entry* wlan_exp_eth_rx_queue_entry; num_packets = WLAN_MIN(queue_length(_wlan_exp_eth_rx_qid), WLAN_EXP_MAX_RX_PROC_PER_POLL); for(i=0; idata); // The only way for this to fail is if there are no available queue entries in the free pool. // We are forced to skip processing this command if this is the case. // We will need socket details about this reception in order to form a response. Rather than pass this information // through a series of context switches as function arguments, we will hold on to it in a global that is only // non-null for the duration of wlan_exp_transport_receive(). gl_tx_to = &(wlan_exp_eth_rx_queue_buffer->rx_from); // Pass packet off to transport command processing wlan_exp_transport_receive(wlan_exp_eth_rx_queue_buffer); gl_tx_to = NULL; // Return the received packet to the free pool queue_checkin(wlan_exp_eth_rx_queue_entry); } } /*****************************************************************************/ /** * @brief Fill IP/UDP headers in response packet * * This function is the final step before a packet is ready to be sent. It fills * in the IP and UDP headers of the packet, which include a checksum that depends * on the total length of the frame. * * @return WLAN_SUCCESS or WLAN_FAILURE *****************************************************************************/ int wlan_exp_transport_fill_headers_response(eth_tx_queue_buffer_t* eth_tx_queue_buffer){ // This function will fill in the Ethernet, IP, UDP, and transport headers needed // to send a response packet. int length; u32 unicast_port; unicast_port = wlan_exp_transport_info.unicast_port; if(eth_tx_queue_buffer == NULL) return WLAN_FAILURE; // Fill in Ethernet, IP, and UDP headers using socket function length = ip_udp_template_init(eth_tx_queue_buffer->seg0, unicast_port, gl_tx_to); if(length == WLAN_FAILURE){ return WLAN_FAILURE; } // We do not need to update the length metadata in the packet queue buffer. We had already // set aside the necessary bytes of the headers in wlan_exp_transport_checkout_response() return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Handle wlan_exp Tx Queue * * This function is called in an interrupt context and is responsible for * dequeuing packets from the wlan_exp Tx queue. * * @return u32 - the remaining length of the wlan_exp Tx queue after transmissions * are complete *****************************************************************************/ u32 wlan_exp_handle_tx_queue(){ dl_entry* packet_to_process; u32 i; int status; for(i = 0; i < WLAN_MIN(queue_length(_wlan_exp_eth_tx_qid), WLAN_MAX_WLAN_EXP_ETH_TX_PROCESS_PER_ISR); i++){ packet_to_process = queue_dequeue(_wlan_exp_eth_tx_qid); if(packet_to_process == NULL) return 0; // should not be possible // Platform Ethernet Send functions are required to check in // packet_to_process when they are done with them if they were successful. status = wlan_platform_wlan_exp_eth_send((eth_tx_queue_buffer_t*)(packet_to_process->data)); if(status != 0){ // We were unable to transmit this packet. This is mostly likely due to an ongoing transmission // causing there to be no free BDs. Re-enqueue this packet back into the head of the _eth_tx_qid // queue; status = enqueue_head(_wlan_exp_eth_tx_qid, packet_to_process); if( status == -1 ){ queue_checkin(packet_to_process); } } } return queue_length(_wlan_exp_eth_rx_qid); } /*****************************************************************************/ /** * @brief Process the received UDP packet by the transport * * @param wlan_exp_eth_rx_queue_buffer* - Received Ethernet frame * * @return None * *****************************************************************************/ void wlan_exp_transport_receive(wlan_exp_eth_rx_queue_buffer_t* wlan_exp_eth_rx_queue_buffer){ int resp_sent; u16 dest_id; u16 src_id; u16 seq_num; u16 flags; u32 node_id; u32 group_id; u16 resp_template_length = 0; eth_tx_queue_buffer_t* eth_tx_queue_buffer = NULL; wlan_exp_transport_header* transport_header_rx = NULL; wlan_exp_transport_header* transport_header_tx = NULL; dl_entry* eth_tx_queue_entry; resp_sent = NO_RESP_SENT; transport_header_rx = (wlan_exp_transport_header*)( wlan_exp_eth_rx_queue_buffer->pkt + sizeof(ethernet_header_t) + sizeof(ipv4_header_t) + sizeof(udp_header_t) ); // Extract values from the received transport header // dest_id = Xil_Ntohs(transport_header_rx->dest_id); src_id = Xil_Ntohs(transport_header_rx->src_id); seq_num = Xil_Ntohs(transport_header_rx->seq_num); flags = Xil_Ntohs(transport_header_rx->flags); node_id = wlan_exp_node_info.node_id; group_id = wlan_exp_transport_info.group_id; // If this message is not for the given node, then ignore it if((dest_id != node_id) && (dest_id != TRANSPORT_BROADCAST_DEST_ID) && ((dest_id & (0xFF00 | group_id)) == 0)) { return; } // We can now construct an outgoing response template eth_tx_queue_entry = queue_checkout(); if(eth_tx_queue_entry == NULL){ xil_printf("Unable to checkout free queue entry for wlan_exp Eth response\n"); return; } eth_tx_queue_buffer = (eth_tx_queue_buffer_t*)eth_tx_queue_entry->data; // Update segment 0 length to account for Eth/IP/UDP headers eth_tx_queue_buffer->seg0_len = (sizeof(ethernet_header_t) + sizeof(ipv4_header_t) + sizeof(udp_header_t)); eth_tx_queue_buffer->seg1_len = 0; // The response template has already been filled in with an Ethernet, IP, and UDP header. // we will point to the bytes after these headers to place the transport header. transport_header_tx = (wlan_exp_transport_header*)(eth_tx_queue_buffer->seg0 + eth_tx_queue_buffer->seg0_len); eth_tx_queue_buffer->seg0_len += sizeof(wlan_exp_transport_header); resp_template_length = eth_tx_queue_buffer->seg0_len; // Form outgoing Transport header for any outgoing packet in response to this message // The u16/u32 fields here will be endian swapped in wlan_exp_transport_send // The length field of the header will be set in wlan_exp_transport_send // transport_header_tx->dest_id = src_id; transport_header_tx->src_id = node_id; transport_header_tx->seq_num = seq_num; transport_header_tx->flags = 0; // Call the callback to further process the recv_buffer resp_sent = process_msg_from_host(wlan_exp_eth_rx_queue_buffer, eth_tx_queue_buffer); // Based on the status, return a message to the host if(resp_sent == NO_RESP_SENT) { // Check if the host requires a response from the node if (flags & TRANSPORT_HDR_ROBUST_FLAG ) { if( (eth_tx_queue_buffer->seg0_len) > resp_template_length ){ // Check that the node has something to send to the host if ( (wlan_exp_transport_fill_headers_response(eth_tx_queue_buffer) == 0) ) { wlan_exp_transport_send(eth_tx_queue_buffer); return; } }else { wlan_exp_printf(WLAN_EXP_PRINT_WARNING, print_type_transport, "Host requires response but node has nothing to send.\n"); } } queue_checkin(eth_tx_queue_entry); } return; } /*****************************************************************************/ /** * @brief Finish response packet and enqueue for transmission * * @param eth_tx_queue_buffer_t* eth_tx_queue_buffer - packet queue buffer for outgoing Ethernet frame * * @return None * *****************************************************************************/ void wlan_exp_transport_send(eth_tx_queue_buffer_t* eth_tx_queue_buffer) { wlan_exp_transport_prepare_headers(eth_tx_queue_buffer); wlan_exp_append_tx(eth_tx_queue_buffer->pyld_queue_hdr.dle); } /*****************************************************************************/ /** * @brief Prepare headers in response packet for transmission * * This function will swap endianness of the wlan_exp_transport_header portion * of the provided packet queue buffer, set the IP checksum for the finalized * IP header contents, and enqueue the packet for transmission. * * @param eth_tx_queue_buffer_t* eth_tx_queue_buffer - packet queue buffer for outgoing Ethernet frame * * @return None * *****************************************************************************/ void wlan_exp_transport_prepare_headers(eth_tx_queue_buffer_t* eth_tx_queue_buffer){ wlan_exp_transport_header* transport_header_tx; transport_header_tx = (wlan_exp_transport_header*)(eth_tx_queue_buffer->seg0 + sizeof(ethernet_header_t) + sizeof(ipv4_header_t) + sizeof(udp_header_t)); transport_header_tx->dest_id = Xil_Htons(transport_header_tx->dest_id); transport_header_tx->src_id = Xil_Htons(transport_header_tx->src_id); // Transport header's length includes all data following the Eth/IP/UDP headers transport_header_tx->length = Xil_Htons(eth_tx_queue_buffer->seg0_len + eth_tx_queue_buffer->seg1_len - sizeof(ethernet_header_t) - sizeof(ipv4_header_t) - sizeof(udp_header_t)); transport_header_tx->seq_num = Xil_Htons(transport_header_tx->seq_num); transport_header_tx->flags = Xil_Htons(transport_header_tx->flags); // Set the IP/UDP header lengths and update checksum wlan_exp_ip_udp_set_length(eth_tx_queue_buffer->seg0, eth_tx_queue_buffer->seg0_len + eth_tx_queue_buffer->seg1_len); } /*****************************************************************************/ /** * @brief Process Transport Commands * * Process commands from a host meant for the transport group * * @param cmd_hdr - pointer to the command header * @param eth_tx_queue_buffer - pointer to a Ethernet queue buffer that should be * filled in with response arguments * * @return int - NO_RESP_SENT or RESP_SENT * *****************************************************************************/ int process_transport_cmd(cmd_resp_hdr_t* cmd_hdr, eth_tx_queue_buffer_t* eth_tx_queue_buffer) { // // IMPORTANT ENDIAN NOTES: // - command // - header - Already endian swapped by the framework (safe to access directly) // - args - Must be endian swapped as necessary by code (framework does not know the contents of the command) // - response // - header - Will be endian swapped by the framework (safe to write directly) // - args - Must be endian swapped as necessary by code (framework does not know the contents of the response) // // Standard variables u32 resp_sent = NO_RESP_SENT; u32 cmd_id = CMD_TO_CMDID(cmd_hdr->cmd); // Segment 0 length includes a fully formed command response header // because one was created with default values suitable for a responseless // acknowledgment. cmd_resp_hdr_t* resp_hdr = (cmd_resp_hdr_t*)(eth_tx_queue_buffer->seg0 + eth_tx_queue_buffer->seg0_len - sizeof(cmd_resp_hdr_t)); u32* cmd_args_32 = (u32*)((u8*)cmd_hdr + sizeof(cmd_resp_hdr_t)); // Process the command switch(cmd_id){ //--------------------------------------------------------------------- case CMDID_TRANSPORT_PING: { // // Nothing actually needs to be done when receiving the ping command. The framework is going // to respond regardless, which is all the host wants. // } break; //--------------------------------------------------------------------- case CMDID_TRANSPORT_TEST_MTU: { // Host requests a packet with a bogus payload with a specified length // Python already accounts for the transport and cmd-response headers // in computing the requested length to achieve the desired over-the-wire length u32 req_length = Xil_Ntohl(cmd_args_32[0]); // Copy the requested length as the only response argument // Python can compare this value to the requested length to confirm // this is a valid response, then examine the actual length of the // received packet to confirm the effective MTU wlan_exp_add_u32_resp_arg(eth_tx_queue_buffer, resp_hdr, req_length); // This command response header is unusual as it includes a single argument // (the requested response payload length) but a large payload that isn't // a "response argument". The large payload is bogus, only used to test MTU // along the node-to-host link. Other command/response code should *not* // mimic this unusual num_args/length mismatch // The seg1_addr value below can be any DMA-accessible memory area resp_hdr->length += req_length; eth_tx_queue_buffer->seg1_addr = (u8*)USER_SCRATCH_BASE; eth_tx_queue_buffer->seg1_len = req_length; } break; //--------------------------------------------------------------------- case CMDID_TRANSPORT_SET_MAX_RESP_WORDS: { u32 max_words = Xil_Ntohl(cmd_args_32[0]); // The wlan_exp host sets this node's maximum payload size for response packets // The host determines this maximum based on its own configuration (host NIC MTU), // the MTU reported by this node during init (node_info.wlan_exp_eth_mtu) and the // result of the MTU test run during init. // The C code keeps two MTU values at runtime: // - wlan_exp_node_info.wlan_exp_eth_mtu: the MTU in bytes of the node's Eth interface, set once at boot // Almost always 1500 (non-jumbo) or 9000 (jumbo) // // - wlan_exp_transport_info.max_pkt_words: the maximum number of u32 payload words the node may packet // into a response packet sent to the host. This value describes only the wlan_exp payload, not the // IP/UDP/wlan_exp headers that precede the payload on the wire // This is a blind setter - extra error checking here might be sensible, but this command // is currently only called after the MTU test, which itself has lots of error checking and printing transport_set_max_resp_pkt_words(max_words); } break; //--------------------------------------------------------------------- case CMDID_TRANSPORT_NODE_GROUP_ID_ADD: { wlan_exp_transport_info.group_id = (wlan_exp_transport_info.group_id | Xil_Htonl(cmd_args_32[0])); } break; //--------------------------------------------------------------------- case CMDID_TRANSPORT_NODE_GROUP_ID_CLEAR: { wlan_exp_transport_info.group_id = (wlan_exp_transport_info.group_id & ~Xil_Htonl(cmd_args_32[0])); } break; //--------------------------------------------------------------------- default: { wlan_exp_printf(WLAN_EXP_PRINT_ERROR, print_type_transport, "Unknown user command ID: %d\n", cmd_id); } break; } return resp_sent; } /*****************************************************************************/ /** * @brief Configure unicast and broadcast sockets * * @param unicast_port - Unicast port for the node * @param broadcast_port - Broadcast port for the node * * @return int - WLAN_SUCCESS or WLAN_FAILURE * *****************************************************************************/ int transport_config_sockets(u32 unicast_port, u32 broadcast_port) { int status = WLAN_SUCCESS; status = _transport_config_socket(&(wlan_exp_transport_info.socket_unicast), unicast_port); if (status == WLAN_FAILURE) { return status; } wlan_exp_transport_info.unicast_port = unicast_port; status = _transport_config_socket(&(wlan_exp_transport_info.socket_broadcast), broadcast_port); if (status == WLAN_FAILURE) { return status; } wlan_exp_transport_info.broadcast_port = broadcast_port; xil_printf(" Listening on UDP ports %d (unicast) and %d (broadcast)\n", unicast_port, broadcast_port); return status; } /*****************************************************************************/ /** * @brief Set the IP address of the node * * @return None * *****************************************************************************/ void transport_set_ip_addr(u8* ip_addr){ eth_set_ip_addr( ip_addr ); return; } /*****************************************************************************/ /** * @brief Get the IP address of the node * * @return None * *****************************************************************************/ void transport_get_ip_addr(u8 * ip_addr) { eth_get_ip_addr( ip_addr ); return; } /*****************************************************************************/ /** * @brief Get the maximum number of u32 words this transport is able to send * in a single packet * * @return u32 - number of u32 words * *****************************************************************************/ u32 wlan_exp_transport_get_max_pkt_words(){ return wlan_exp_transport_info.max_pkt_words; } /*****************************************************************************/ /** * @brief Set the maximum number of u32 words this transport is able to send * in a single packet * * @param max_words - maximum number of u32 words * @return None * *****************************************************************************/ void transport_set_max_resp_pkt_words(u32 max_words) { // Update the maximum size for response packet payloads // Used to reset the maximum on node init and to update // the maximum after the MTU test wlan_exp_transport_info.max_pkt_words = max_words; } // Local functions /*****************************************************************************/ /** * @brief Update software interrupt signal based on Tx queue occupancy * * @param queue_len - current length of the Tx queue * @return None * *****************************************************************************/ void _wlan_exp_tx_queue_occupancy_change(int callback_arg, u32 queue_len){ // callback_arg is not used. It was set to 0 when opening the queue if(queue_len == 0){ wlan_platform_clear_sw_intr(SW_INTR_ID_WLAN_EXP_ETH_TX); } else { wlan_platform_assert_sw_intr(SW_INTR_ID_WLAN_EXP_ETH_TX); } } /*****************************************************************************/ /** * @brief Create and bind a socket * * @param socket_index - Socket index (return value) * @param udp_port - UDP port number * * @return int - WLAN_SUCCESS or WLAN_FAILURE * *****************************************************************************/ int _transport_config_socket(int* socket_index, u32 udp_port) { int status; int tmp_socket = *socket_index; // Release socket if it is already bound if (tmp_socket != SOCKET_INVALID_SOCKET) { socket_close(tmp_socket); } // Create a new socket tmp_socket = socket_alloc(AF_INET, SOCK_DGRAM, 0); if (tmp_socket == SOCKET_INVALID_SOCKET) { wlan_exp_printf(WLAN_EXP_PRINT_ERROR, print_type_transport, "Could not create socket\n"); *socket_index = SOCKET_INVALID_SOCKET; return WLAN_FAILURE; } // Bind the socket status = socket_bind_eth(tmp_socket, udp_port); if (status == WLAN_FAILURE) { wlan_exp_printf(WLAN_EXP_PRINT_ERROR, print_type_transport, "Unable to bind socket on port: %d\n", udp_port); socket_close(tmp_socket); *socket_index = SOCKET_INVALID_SOCKET; return WLAN_FAILURE; } *socket_index = tmp_socket; return WLAN_SUCCESS; } #endif