/** @file wlan_mac_eth_util.c * @brief Ethernet Framework * * Contains code for using Ethernet, including encapsulation and de-encapsulation. * * @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 "stdlib.h" #include "stddef.h" #include "xil_types.h" #include "string.h" #include "wlan_platform_high.h" #include "wlan_mac_high.h" #include "wlan_mac_common.h" #include "wlan_mac_eth_util.h" #include "wlan_mac_dl_list.h" #include "wlan_mac_queue.h" #include "wlan_mac_station_info.h" #include "wlan_mac_802_11_defs.h" #include "wlan_mac_pkt_buf_util.h" #include "wlan_mac_high_sw_config.h" /*************************** Variable Definitions ****************************/ #if WLAN_SW_CONFIG_ENABLE_ETH_BRIDGE // Ethernet Station MAC Address // // The station code's implementation of encapsulation and de-encapsulation has an important // limitation: only one device may be plugged into the station's Ethernet port. The station // does not provide NAT. It assumes that the last received Ethernet src MAC address is used // as the destination MAC address on any Ethernet transmissions. This is fine when there is // only one device on the station's Ethernet port, but will definitely not work if the station // is plugged into a switch with more than one device. // static u8 mac_addr_to_overwrite[6]; /*********************** Global Variable Definitions *************************/ // Controls whether or not portal behaviors will be enabled static u8 gl_portal_en; static int _portal_eth_rx_qid; static int _portal_eth_tx_qid; // Callback for top-level processing of Ethernet received packets static function_ptr_t eth_rx_callback; /*************************** Functions Prototypes ****************************/ void _portal_eth_rx_queue_occupancy_change(int callback_arg, u32 queue_len); void _portal_eth_tx_queue_occupancy_change(int callback_arg, u32 queue_len); #if PERF_MON_ETH_BD void print_bd_high_water_mark() { xil_printf("BD HWM = %d\n", bd_high_water_mark); } #endif /*****************************************************************************/ /** * @brief Initialize the Ethernet utility sub-system * * Initialize Ethernet A hardware and framework for handling Ethernet Tx/Rx via * the DMA. This function must be called once at boot, before any Ethernet * configuration or Tx/Rx operations. * * @return WLAN_SUCCESS */ int wlan_eth_util_init() { // Open a queue for Rx Ethernet frames _portal_eth_rx_qid = queue_open((void*)_portal_eth_rx_queue_occupancy_change, 0, PORTAL_MAX_QUEUE_LEN); // Open a queue for Tx Ethernet frames _portal_eth_tx_qid = queue_open((void*)_portal_eth_tx_queue_occupancy_change, 0, PORTAL_MAX_QUEUE_LEN); if((_portal_eth_rx_qid == -1) || (_portal_eth_tx_qid == -1)) xil_printf("Error in wlan_eth_util_init(), unable to open queue\n"); // Initialize Callback eth_rx_callback = (function_ptr_t)wlan_null_callback; // Enable bridging of the wired-wireless networks (a.k.a. the "portal" between networks) wlan_eth_portal_en(1); return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Sets the MAC callback to process Ethernet receptions * * The framework will call the MAC's callback for each Ethernet reception that is a candidate * for wireless transmission. The framework may reject some packets for malformed or unrecognized * Ethernet headers. The MAC may reject more, based on Ethernet address or packet contents. * * @param void(*callback) * -Function pointer to the MAC's Ethernet Rx callback */ void wlan_mac_util_set_eth_rx_callback(void(*callback)()) { eth_rx_callback = (function_ptr_t)callback; } int wlan_enqueue_eth_rx(eth_rx_queue_buffer_t* eth_rx_queue_buffer){ u8 is_claimed = 0; int status; status = queue_enqueue(_portal_eth_rx_qid, eth_rx_queue_buffer->pyld_queue_hdr.dle); if(status == 0) is_claimed = 1; return is_claimed; } int wlan_enqueue_eth_tx(dl_entry* queue_entry){ int status; status = queue_enqueue(_portal_eth_tx_qid, queue_entry); return status; } u32 wlan_poll_eth_rx_queue(){ dl_entry* packet_to_process; int ret; u32 i; for(i = 0; i < WLAN_MIN(queue_length(_portal_eth_rx_qid), WLAN_MAX_ETH_RX_PROCESS_PER_ISR); i++){ packet_to_process = queue_dequeue(_portal_eth_rx_qid); if(packet_to_process == NULL) return WLAN_SUCCESS; // should not be possible ret = wlan_process_eth_rx((eth_rx_queue_buffer_t*)(packet_to_process->data)); if( ret != 0 ){ // This Ethernet packet was rejected so we must return // the entry back to the free list. queue_checkin(packet_to_process); } } return queue_length(_portal_eth_rx_qid); } u32 wlan_poll_eth_tx_queue(){ dl_entry* packet_to_process; u32 i; int status; for(i = 0; i < WLAN_MIN(queue_length(_portal_eth_tx_qid), WLAN_MAX_ETH_TX_PROCESS_PER_ISR); i++){ packet_to_process = queue_dequeue(_portal_eth_tx_qid); if(packet_to_process == NULL) return WLAN_SUCCESS; // 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_portal_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(_portal_eth_tx_qid, packet_to_process); if( status == -1 ){ queue_checkin(packet_to_process); } } } return queue_length(_portal_eth_tx_qid); } /*****************************************************************************/ /** * @brief Process an Ethernet packet that has been received by the ETH DMA * * This function processes an Ethernet DMA buffer descriptor. This design assumes * a 1-to-1 correspondence between buffer descriptors and Ethernet packets. For * each packet, this function encapsulates the Ethernet packet and calls the * MAC's callback to either enqueue (for eventual wireless Tx) or reject the packet. * * NOTE: Processed ETH DMA buffer descriptors are freed but not resubmitted to * hardware for use by future Ethernet receptions. This is the responsibility of * higher level code (see comment at the end of the function if this behavior needs * to change). * * This function requires the MAC implement a function (assigned to the eth_rx_callback * function pointer) that returns 0 or 1, indicating the MAC's handling of the packet: * 0: Packet was not enqueued and will not be processed by wireless MAC; framework * should immediately discard * 1: Packet was enqueued for eventual wireless transmission; MAC will check in * occupied queue entry when finished * */ int wlan_process_eth_rx(eth_rx_queue_buffer_t* eth_rx_queue_buffer) { int return_value = -1; int packet_claimed = 0; #if PERF_MON_ETH_PROCESS_RX wlan_mac_set_dbg_hdr_out(0x4); #endif // Check arguments if( eth_rx_queue_buffer == NULL ) { xil_printf("ERROR: Tried to process NULL Ethernet packet\n"); return return_value; } //////////////////// // 1. The first potential claimant for this packet is wlan_exp. It is expected that wlan_exp // processing will be light and will simply save the packet away for future processing if // it decides to claim it. // TODO: // wlan_exp_process_this_packet(pyld_queue_eth) packet_claimed = 0; //wlan_exp should return 1 if it is holding on to this packet if(packet_claimed == 0){ //////////////////// // 2. The second potential claimant for this packet is the 802.11 portal. packet_claimed = eth_rx_callback(eth_rx_queue_buffer); if (packet_claimed) { return_value = 0; } } #if PERF_MON_ETH_PROCESS_RX wlan_mac_clear_dbg_hdr_out(0x4); #endif return return_value; } void wlan_eth_portal_en(u8 enable){ gl_portal_en = enable; } #endif /* WLAN_SW_CONFIG_ENABLE_ETH_BRIDGE */ /*****************************************************************************/ /** * @brief Encapsulates Ethernet packets for wireless transmission * * This function implements the encapsulation process for 802.11 transmission of * Ethernet packets * * The encapsulation process depends on the node's role: * * AP: * - Copy original packet's source and destination addresses to temporary space * - Add an LLC header (8 bytes) in front of the Ethernet payload * - LLC header includes original packet's ETHER_TYPE field; only IPV4 and ARP * are currently supported * * STA: * - Copy original packet's source and destination addresses to temporary space * - Add an LLC header (8 bytes) in front of the Ethernet payload * - LLC header includes original packet's ETHER_TYPE field; only IPV4 and ARP * are currently supported * - If packet is ARP Request, overwrite ARP header's source address with STA * wireless MAC address * - If packet is UDP packet containing a DHCP request * - Assert DHCP header's BROADCAST flag * - Disable the UDP packet checksum (otherwise it would be invliad after * modifying the BROADCAST flag) * */ tx_80211_queue_buffer_t* wlan_eth_encap( eth_rx_queue_buffer_t* eth_rx_queue_buffer, u32 flags ) { #if WLAN_SW_CONFIG_ENABLE_ETH_BRIDGE ethernet_header_t* eth_hdr; llc_header_t* llc_hdr; ipv4_header_t* ip_hdr; arp_ipv4_packet_t* arp; udp_header_t* udp; dhcp_packet* dhcp; //Cast the Rx Ethernet Stencil as a Tx 802.11 Stencil tx_80211_queue_buffer_t* tx_queue_buffer = (tx_80211_queue_buffer_t*)eth_rx_queue_buffer; // Calculate actual wireless Tx len (eth payload - eth header + wireless header) tx_queue_buffer->length = eth_rx_queue_buffer->length - sizeof(ethernet_header_t) + sizeof(llc_header_t) + sizeof(mac_header_80211) + WLAN_PHY_FCS_NBYTES; // Update metadata so dequeue operation knows how to construct contiguous series of bytes that form the MPDU // 1) The C0 section starting at tx_queue_buffer->pkt contains an 802.11 header and an LLC header. tx_queue_buffer->seg0_len = sizeof(mac_header_80211) + sizeof(llc_header_t); // 2) The C1 section starts after both the C0 section and an Ethernet header. The Ethernet header must remain // in this buffer for other processes in this framework, but the bytes themselves are not copied to CPU_LOW // on dequeue. This offset allows the dequeue operation to skip these bytes. tx_queue_buffer->seg1_offset = tx_queue_buffer->seg0_len + sizeof(ethernet_header_t); // 3) Together, the C0 and C1 section lengths total the wireless Tx length calculated above in tx_queue_buffer->pyld_queue_hdr.length tx_queue_buffer->seg1_len = tx_queue_buffer->length - tx_queue_buffer->seg0_len; // Read pointers to interpret fields in the new MPDU eth_hdr = (ethernet_header_t*)eth_rx_queue_buffer->pkt; // Write pointers to fill fields in the new MPDU llc_hdr = (llc_header_t*)(tx_queue_buffer->pkt + sizeof(mac_header_80211)); // Prepare the MPDU LLC header llc_hdr->dsap = LLC_SNAP; llc_hdr->ssap = LLC_SNAP; llc_hdr->control_field = LLC_CNTRL_UNNUMBERED; bzero((void *)(llc_hdr->org_code), 3); //Org Code 0x000000: Encapsulated Ethernet if( flags & WLAN_ETH_ENCAP_FLAGS_OVERWRITE_PYLD_ADDRS ){ memcpy(mac_addr_to_overwrite, eth_hdr->src_mac_addr, 6); } switch(eth_hdr->ethertype){ case ETH_TYPE_ARP: llc_hdr->type = LLC_TYPE_ARP; if( flags & WLAN_ETH_ENCAP_FLAGS_OVERWRITE_PYLD_ADDRS ){ arp = (arp_ipv4_packet_t*)((void*)eth_hdr + sizeof(ethernet_header_t)); memcpy(arp->sender_haddr, get_mac_hw_addr_wlan(), 6); } break; case ETH_TYPE_IP: llc_hdr->type = LLC_TYPE_IP; if( flags & WLAN_ETH_ENCAP_FLAGS_OVERWRITE_PYLD_ADDRS ){ // Check if IPv4 packet is a DHCP Discover in a UDP frame ip_hdr = (ipv4_header_t*)((void*)eth_hdr + sizeof(ethernet_header_t)); if (ip_hdr->protocol == IPV4_PROT_UDP) { udp = (udp_header_t*)((void*)ip_hdr + 4*((u8)(ip_hdr->version_ihl) & 0xF)); // All Bootstrap Protocol packets contain the client MAC address as part of the // payload. For STA encapsulation, we need to replace the wired MAC address of // the client with the wireless MAC address of the STA. if ((Xil_Ntohs(udp->src_port) == UDP_SRC_PORT_BOOTPC) || (Xil_Ntohs(udp->src_port) == UDP_SRC_PORT_BOOTPS)) { // Disable the checksum since this will change the bytes in the packet udp->checksum = 0; dhcp = (dhcp_packet*)((void*)udp + sizeof(udp_header_t)); if (Xil_Ntohl(dhcp->magic_cookie) == DHCP_MAGIC_COOKIE) { // Overwrite DHCP client MAC address with the station's wireless MAC address memcpy(dhcp->chaddr, get_mac_hw_addr_wlan(), 6); } // END is DHCP valid } // END is DHCP } // END is UDP } break; default: return NULL; break; } return tx_queue_buffer; #else return NULL; #endif } int wlan_eth_decap_and_send( u8* rx_mac_payload, u8* addr_da, u8* addr_sa, u16 rx_length, u32 flags ) { #if WLAN_SW_CONFIG_ENABLE_ETH_BRIDGE eth_tx_queue_buffer_t* eth_tx_queue_buffer; dl_entry* eth_tx_queue_entry; mac_header_80211* rx80211_hdr; u32 eth_length; llc_header_t* llc_hdr; u16 pre_llc_offset; int status; // Read pointers -- these point to Rx packet buffers u8* eth_payload_read; arp_ipv4_packet_t* arp_read; ipv4_header_t* ip_hdr_read; udp_header_t* udp_read; dhcp_packet* dhcp_read; // Write pointers -- these point into Queue buffer in DRAM ethernet_header_t* eth_hdr_write; arp_ipv4_packet_t* arp_write; ipv4_header_t* ip_hdr_write; udp_header_t* udp_write; dhcp_packet* dhcp_write; u8 addr_da_localcopy[MAC_ADDR_LEN]; u8 addr_sa_localcopy[MAC_ADDR_LEN]; // Because the decapsulation procedure is destructive to the 802.11 header, // we need to make copies of the addr_da and addr_sa arguments if( flags & WLAN_ETH_ENCAP_FLAGS_OVERWRITE_PYLD_ADDRS ){ memcpy(addr_da_localcopy, mac_addr_to_overwrite, MAC_ADDR_LEN); } else { memcpy(addr_da_localcopy, addr_da, MAC_ADDR_LEN); } memcpy(addr_sa_localcopy, addr_sa, MAC_ADDR_LEN); // Get helper pointers to various byte offsets in the packet payload rx80211_hdr = (mac_header_80211*)(rx_mac_payload); switch(rx80211_hdr->frame_control_1){ case MAC_FRAME_CTRL1_SUBTYPE_QOSDATA: pre_llc_offset = sizeof(qos_control); break; case MAC_FRAME_CTRL1_SUBTYPE_DATA: pre_llc_offset = 0; break; default: // Unrecognized type -- cannot decapsulate return WLAN_FAILURE; break; } llc_hdr = (llc_header_t*)(rx_mac_payload + sizeof(mac_header_80211) + pre_llc_offset); eth_payload_read = (u8*)(rx_mac_payload + sizeof(mac_header_80211) + sizeof(llc_header_t) + pre_llc_offset); eth_length = rx_length - sizeof(mac_header_80211) - sizeof(llc_header_t) - pre_llc_offset - WLAN_PHY_FCS_NBYTES + sizeof(ethernet_header_t); if(eth_length <= sizeof(ethernet_header_t)){ //This packet has no payload, we should quit now. return WLAN_FAILURE; } // Speculatively check out a queue buffer and begin CDMA of the payload. This enables decapsulation // processing to be pipelined with the bulk of the copy eth_tx_queue_entry = queue_checkout(); if(eth_tx_queue_entry == NULL){ return WLAN_FAILURE; } eth_tx_queue_buffer = (eth_tx_queue_buffer_t*)(eth_tx_queue_entry->data); eth_hdr_write = (ethernet_header_t*)(eth_tx_queue_buffer->seg0); eth_tx_queue_buffer->seg0_len = eth_length; eth_tx_queue_buffer->seg1_len = 0; // Start the transfer wlan_mac_high_cdma_start_transfer((u8*)eth_hdr_write + sizeof(ethernet_header_t), eth_payload_read, eth_length - sizeof(ethernet_header_t)); // Create Ethernet Header switch(llc_hdr->type){ case LLC_TYPE_ARP: eth_hdr_write->ethertype = ETH_TYPE_ARP; break; case LLC_TYPE_IP: eth_hdr_write->ethertype = ETH_TYPE_IP; break; default: // Unrecognized type -- cannot decapsulate wlan_mac_high_cdma_finish_transfer(); queue_checkin(eth_tx_queue_entry); return WLAN_FAILURE; break; } memcpy(eth_hdr_write->dest_mac_addr, addr_da_localcopy, MAC_ADDR_LEN); memcpy(eth_hdr_write->src_mac_addr, addr_sa_localcopy, MAC_ADDR_LEN); // (Optional) -- overwrite MAC address present in the payload of the Ethernet frame // with one stored previously during encapsulation if( flags & WLAN_ETH_ENCAP_FLAGS_OVERWRITE_PYLD_ADDRS ){ // We are about to start modifying bytes deep into the payload of the packet. We should // wait for the CDMA operation to finish so we avoid a race. wlan_mac_high_cdma_finish_transfer(); switch(llc_hdr->type){ case LLC_TYPE_ARP: // If the ARP packet is addressed to this wireless address, replace the ARP dest address // with the connected wired device's MAC address arp_read = (arp_ipv4_packet_t *)(eth_payload_read); //eth_payload_read arp_write = (arp_ipv4_packet_t *)((u8*)eth_hdr_write + sizeof(ethernet_header_t)); if (wlan_addr_eq(arp_read->target_haddr, get_mac_hw_addr_wlan())) { memcpy(arp_write->target_haddr, mac_addr_to_overwrite, MAC_ADDR_LEN); } break; case ETH_TYPE_IP: ip_hdr_read = (ipv4_header_t*)(eth_payload_read); ip_hdr_write = (ipv4_header_t*)((u8*)eth_hdr_write + sizeof(ethernet_header_t)); if (ip_hdr_read->protocol == IPV4_PROT_UDP) { udp_read = (udp_header_t*)((void*)ip_hdr_read + 4*((u8)(ip_hdr_read->version_ihl) & 0xF)); udp_write = (udp_header_t*)((void*)ip_hdr_write + 4*((u8)(ip_hdr_read->version_ihl) & 0xF)); // All Bootstrap Protocol packets contain the client MAC address as part of the // payload. For STA de-encapsulation, we need to replace the wireless MAC address // of the STA with the wired MAC address of the client if ((Xil_Ntohs(udp_read->src_port) == UDP_SRC_PORT_BOOTPC) || (Xil_Ntohs(udp_read->src_port) == UDP_SRC_PORT_BOOTPS)) { // Disable the checksum since this will change the bytes in the packet udp_write->checksum = 0; dhcp_read = (dhcp_packet*)((u8*)udp_read + sizeof(udp_header_t)); dhcp_write = (dhcp_packet*)((u8*)udp_write + sizeof(udp_header_t)); if (Xil_Ntohl(dhcp_read->magic_cookie) == DHCP_MAGIC_COOKIE) { // Overwrite DHCP client MAC address with the station's wireless MAC address memcpy(dhcp_write->chaddr, mac_addr_to_overwrite, MAC_ADDR_LEN); } // END is DHCP valid } // END is DHCP } // END is UDP break; default: break; } } // Wait for CDMA to finish. If ~WLAN_ETH_ENCAP_FLAGS_OVERWRITE_PYLD_ADDRS, we have not called this yet wlan_mac_high_cdma_finish_transfer(); // Add it to the Tx queue status = wlan_enqueue_eth_tx(eth_tx_queue_entry); if(status == -1){ queue_checkin(eth_tx_queue_entry); return WLAN_FAILURE; } else { return WLAN_SUCCESS; } #else return WLAN_FAILURE; #endif } #if WLAN_SW_CONFIG_ENABLE_ETH_BRIDGE // Local functions void _portal_eth_rx_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_PORTAL_ETH_RX); } else { wlan_platform_assert_sw_intr(SW_INTR_ID_PORTAL_ETH_RX); } } void _portal_eth_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_PORTAL_ETH_TX); } else { wlan_platform_assert_sw_intr(SW_INTR_ID_PORTAL_ETH_TX); } } #endif