| /* |
| * Copyright (c) 2014, 2016-2017 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| /*======================================================================== |
| |
| \file epping_main.c |
| |
| \brief WLAN End Point Ping test tool implementation |
| |
| ========================================================================*/ |
| |
| /*-------------------------------------------------------------------------- |
| Include Files |
| ------------------------------------------------------------------------*/ |
| #include <wlan_hdd_includes.h> |
| #include <vos_api.h> |
| #include <vos_sched.h> |
| #include <linux/etherdevice.h> |
| #include <linux/firmware.h> |
| #include <wcnss_api.h> |
| #include <wlan_hdd_tx_rx.h> |
| #include <wniApi.h> |
| #include <wlan_nlink_srv.h> |
| #include <wlan_hdd_cfg.h> |
| #include <wlan_ptt_sock_svc.h> |
| #include <wlan_hdd_wowl.h> |
| #include <wlan_hdd_misc.h> |
| #include <wlan_hdd_wext.h> |
| #include <linux/wireless.h> |
| #include <net/cfg80211.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/semaphore.h> |
| #include <linux/ctype.h> |
| #include <wlan_hdd_hostapd.h> |
| #include <wlan_hdd_softap_tx_rx.h> |
| #include "epping_main.h" |
| #include "epping_internal.h" |
| #include "epping_test.h" |
| |
| #define TX_RETRY_TIMEOUT_IN_MS 1 |
| |
| static bool enb_tx_dump = 0; |
| |
| void epping_tx_dup_pkt(epping_adapter_t *pAdapter, |
| HTC_ENDPOINT_ID eid, adf_nbuf_t skb) |
| { |
| struct epping_cookie * cookie = NULL; |
| int skb_len, ret; |
| adf_nbuf_t new_skb; |
| |
| cookie = epping_alloc_cookie(pAdapter->pEpping_ctx); |
| if (cookie == NULL) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: epping_alloc_cookie returns no resource\n", __func__); |
| return; |
| } |
| new_skb = adf_nbuf_copy(skb); |
| if (!new_skb) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: adf_nbuf_copy returns no resource\n", __func__); |
| epping_free_cookie(pAdapter->pEpping_ctx, cookie); |
| return; |
| } |
| SET_HTC_PACKET_INFO_TX(&cookie->HtcPkt, |
| cookie, adf_nbuf_data(skb), adf_nbuf_len(new_skb), eid, 0); |
| SET_HTC_PACKET_NET_BUF_CONTEXT(&cookie->HtcPkt, new_skb); |
| skb_len = (int)adf_nbuf_len(new_skb); |
| /* send the packet */ |
| ret = HTCSendPkt(pAdapter->pEpping_ctx->HTCHandle, &cookie->HtcPkt); |
| if (ret != A_OK) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: HTCSendPkt failed, ret = %d\n", __func__, ret); |
| epping_free_cookie(pAdapter->pEpping_ctx, cookie); |
| adf_nbuf_free(new_skb); |
| return; |
| } |
| pAdapter->stats.tx_bytes += skb_len; |
| ++pAdapter->stats.tx_packets; |
| if (((pAdapter->stats.tx_packets + |
| pAdapter->stats.tx_dropped) % EPPING_STATS_LOG_COUNT) == 0 && |
| (pAdapter->stats.tx_packets || pAdapter->stats.tx_dropped)) { |
| epping_log_stats(pAdapter, __func__); |
| } |
| } |
| static int epping_tx_send_int(adf_nbuf_t skb, |
| epping_adapter_t *pAdapter) |
| { |
| EPPING_HEADER *eppingHdr = (EPPING_HEADER *)adf_nbuf_data(skb); |
| HTC_ENDPOINT_ID eid = ENDPOINT_UNUSED; |
| struct epping_cookie * cookie = NULL; |
| A_UINT8 ac = 0; |
| A_STATUS ret = A_OK; |
| int skb_len; |
| EPPING_HEADER tmpHdr = *eppingHdr; |
| |
| /* allocate resource for this packet */ |
| cookie = epping_alloc_cookie(pAdapter->pEpping_ctx); |
| /* no resource */ |
| if (cookie == NULL) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: epping_alloc_cookie returns no resource\n", __func__); |
| return -1; |
| } |
| |
| if (enb_tx_dump) |
| epping_hex_dump((void *)eppingHdr, skb->len, __func__); |
| /* |
| * a quirk of linux, the payload of the frame is 32-bit aligned and thus |
| * the addition of the HTC header will mis-align the start of the HTC |
| * frame, so we add some padding which will be stripped off in the target |
| */ |
| if (EPPING_ALIGNMENT_PAD > 0) { |
| A_NETBUF_PUSH(skb, EPPING_ALIGNMENT_PAD); |
| } |
| /* prepare ep/HTC information */ |
| ac = eppingHdr->StreamNo_h; |
| eid = pAdapter->pEpping_ctx->EppingEndpoint[ac]; |
| if (eid < 0 || eid >= EPPING_MAX_NUM_EPIDS) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: invalid eid = %d, ac = %d\n", __func__, eid, ac); |
| return -1; |
| } |
| if (tmpHdr.Cmd_h == EPPING_CMD_RESET_RECV_CNT || |
| tmpHdr.Cmd_h == EPPING_CMD_CONT_RX_START) { |
| epping_set_kperf_flag(pAdapter, eid, tmpHdr.CmdBuffer_t[0]); |
| } |
| if (pAdapter->pEpping_ctx->kperf[eid]) { |
| switch (tmpHdr.Cmd_h) { |
| case EPPING_CMD_NO_ECHO: |
| #ifdef HIF_PCI |
| epping_tx_copier_schedule(pAdapter->pEpping_ctx, eid, skb); |
| #endif /* HIF_PCI */ |
| break; |
| default: |
| break; |
| } |
| } |
| if (pAdapter->pEpping_ctx->kperf[eid] && |
| tmpHdr.Cmd_h == EPPING_CMD_NO_ECHO) { |
| epping_tx_dup_pkt(pAdapter, eid, skb); |
| } |
| SET_HTC_PACKET_INFO_TX(&cookie->HtcPkt, |
| cookie, adf_nbuf_data(skb), adf_nbuf_len(skb), eid, 0); |
| SET_HTC_PACKET_NET_BUF_CONTEXT(&cookie->HtcPkt, skb); |
| skb_len = skb->len; |
| /* send the packet */ |
| ret = HTCSendPkt(pAdapter->pEpping_ctx->HTCHandle, &cookie->HtcPkt); |
| epping_log_packet(pAdapter, &tmpHdr, ret, __func__); |
| if (ret != A_OK) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: HTCSendPkt failed, status = %d\n", __func__, ret); |
| epping_free_cookie(pAdapter->pEpping_ctx, cookie); |
| return -1; |
| } |
| pAdapter->stats.tx_bytes += skb_len; |
| ++pAdapter->stats.tx_packets; |
| if (((pAdapter->stats.tx_packets + |
| pAdapter->stats.tx_dropped) % EPPING_STATS_LOG_COUNT) == 0 && |
| (pAdapter->stats.tx_packets || pAdapter->stats.tx_dropped)) { |
| epping_log_stats(pAdapter, __func__); |
| } |
| |
| return 0; |
| } |
| |
| void epping_tx_timer_expire(epping_adapter_t *pAdapter) |
| { |
| adf_nbuf_t nodrop_skb; |
| |
| EPPING_LOG(VOS_TRACE_LEVEL_INFO, "%s: queue len: %d\n", __func__, |
| adf_nbuf_queue_len(&pAdapter->nodrop_queue)); |
| |
| if (!adf_nbuf_queue_len(&pAdapter->nodrop_queue)) { |
| /* nodrop queue is empty so no need to arm timer */ |
| pAdapter->epping_timer_state = EPPING_TX_TIMER_STOPPED; |
| return; |
| } |
| |
| /* try to flush nodrop queue */ |
| while ((nodrop_skb = adf_nbuf_queue_remove(&pAdapter->nodrop_queue))) { |
| HTCSetNodropPkt(pAdapter->pEpping_ctx->HTCHandle, TRUE); |
| if (epping_tx_send_int(nodrop_skb, pAdapter)) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: nodrop: %pK xmit fail in timer\n", __func__, nodrop_skb); |
| /* fail to xmit so put the nodrop packet to the nodrop queue */ |
| adf_nbuf_queue_insert_head(&pAdapter->nodrop_queue, nodrop_skb); |
| break; |
| } else { |
| HTCSetNodropPkt(pAdapter->pEpping_ctx->HTCHandle, FALSE); |
| EPPING_LOG(VOS_TRACE_LEVEL_INFO, |
| "%s: nodrop: %pK xmit ok in timer\n", __func__, nodrop_skb); |
| } |
| } |
| |
| /* if nodrop queue is not empty, continue to arm timer */ |
| if (nodrop_skb) { |
| adf_os_spin_lock_bh(&pAdapter->data_lock); |
| /* if nodrop queue is not empty, continue to arm timer */ |
| if (pAdapter->epping_timer_state != EPPING_TX_TIMER_RUNNING) { |
| pAdapter->epping_timer_state = EPPING_TX_TIMER_RUNNING; |
| adf_os_timer_mod(&pAdapter->epping_timer, TX_RETRY_TIMEOUT_IN_MS); |
| } |
| adf_os_spin_unlock_bh(&pAdapter->data_lock); |
| } else { |
| pAdapter->epping_timer_state = EPPING_TX_TIMER_STOPPED; |
| } |
| } |
| |
| int epping_tx_send(adf_nbuf_t skb, epping_adapter_t *pAdapter) |
| { |
| adf_nbuf_t nodrop_skb; |
| EPPING_HEADER *eppingHdr; |
| A_UINT8 ac = 0; |
| |
| eppingHdr = (EPPING_HEADER *)adf_nbuf_data(skb); |
| |
| if (!IS_EPPING_PACKET(eppingHdr)) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: Recived non endpoint ping packets\n", __func__); |
| /* no packet to send, cleanup */ |
| adf_nbuf_free(skb); |
| return -ENOMEM; |
| } |
| |
| /* the stream ID is mapped to an access class */ |
| ac = eppingHdr->StreamNo_h; |
| /* hard coded two ep ids */ |
| if (ac != 0 && ac != 1) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: ac %d is not mapped to mboxping service\n", __func__, ac); |
| adf_nbuf_free(skb); |
| return -ENOMEM; |
| } |
| |
| /* |
| * some EPPING packets cannot be dropped no matter what access class |
| * it was sent on. A special care has been taken: |
| * 1. when there is no TX resource, queue the control packets to |
| * a special queue |
| * 2. when there is TX resource, send the queued control packets first |
| * and then other packets |
| * 3. a timer launches to check if there is queued control packets and |
| * flush them |
| */ |
| |
| /* check the nodrop queue first */ |
| while ((nodrop_skb = adf_nbuf_queue_remove(&pAdapter->nodrop_queue))) { |
| HTCSetNodropPkt(pAdapter->pEpping_ctx->HTCHandle, TRUE); |
| if (epping_tx_send_int(nodrop_skb, pAdapter)) { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: nodrop: %pK xmit fail\n", __func__, nodrop_skb); |
| /* fail to xmit so put the nodrop packet to the nodrop queue */ |
| adf_nbuf_queue_insert_head(&pAdapter->nodrop_queue, nodrop_skb); |
| /* no cookie so free the current skb */ |
| goto tx_fail; |
| } else { |
| HTCSetNodropPkt(pAdapter->pEpping_ctx->HTCHandle, FALSE); |
| EPPING_LOG(VOS_TRACE_LEVEL_INFO, |
| "%s: nodrop: %pK xmit ok\n", __func__, nodrop_skb); |
| } |
| } |
| |
| /* send the original packet */ |
| if (epping_tx_send_int(skb, pAdapter)) |
| goto tx_fail; |
| |
| return 0; |
| |
| tx_fail: |
| if (!IS_EPING_PACKET_NO_DROP(eppingHdr)) { |
| /* allow to drop the skb so drop it */ |
| adf_nbuf_free(skb); |
| ++pAdapter->stats.tx_dropped; |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: Tx skb %pK dropped, stats.tx_dropped = %ld\n", |
| __func__, skb, pAdapter->stats.tx_dropped); |
| return -ENOMEM; |
| } else { |
| EPPING_LOG(VOS_TRACE_LEVEL_FATAL, |
| "%s: nodrop: %pK queued\n", __func__, skb); |
| adf_nbuf_queue_add(&pAdapter->nodrop_queue, skb); |
| adf_os_spin_lock_bh(&pAdapter->data_lock); |
| if (pAdapter->epping_timer_state != EPPING_TX_TIMER_RUNNING) { |
| pAdapter->epping_timer_state = EPPING_TX_TIMER_RUNNING; |
| adf_os_timer_mod(&pAdapter->epping_timer, TX_RETRY_TIMEOUT_IN_MS); |
| } |
| adf_os_spin_unlock_bh(&pAdapter->data_lock); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef HIF_SDIO |
| HTC_SEND_FULL_ACTION epping_tx_queue_full(void *Context, |
| HTC_PACKET *pPacket) |
| { |
| /* |
| * Call netif_stop_queue frequently will impact the mboxping tx t-put. |
| * Return HTC_SEND_FULL_KEEP directly in epping_tx_queue_full to avoid. |
| */ |
| return HTC_SEND_FULL_KEEP; |
| } |
| #endif /* HIF_SDIO */ |
| void epping_tx_complete_multiple(void *ctx, |
| HTC_PACKET_QUEUE *pPacketQueue) |
| { |
| epping_context_t *pEpping_ctx = (epping_context_t *)ctx; |
| epping_adapter_t *pAdapter = pEpping_ctx->epping_adapter; |
| struct net_device* dev = pAdapter->dev; |
| A_STATUS status; |
| HTC_ENDPOINT_ID eid; |
| adf_nbuf_t pktSkb; |
| struct epping_cookie *cookie; |
| A_BOOL flushing = FALSE; |
| adf_nbuf_queue_t skb_queue; |
| HTC_PACKET *htc_pkt; |
| |
| adf_nbuf_queue_init(&skb_queue); |
| |
| adf_os_spin_lock_bh(&pAdapter->data_lock); |
| |
| while (!HTC_QUEUE_EMPTY(pPacketQueue)) { |
| htc_pkt = HTC_PACKET_DEQUEUE(pPacketQueue); |
| if (htc_pkt == NULL) |
| break; |
| status=htc_pkt->Status; |
| eid=htc_pkt->Endpoint; |
| pktSkb=GET_HTC_PACKET_NET_BUF_CONTEXT(htc_pkt); |
| cookie = htc_pkt->pPktContext; |
| |
| if (!pktSkb) { |
| EPPING_LOG(VOS_TRACE_LEVEL_ERROR, |
| "%s: pktSkb is NULL", __func__); |
| ASSERT(0); |
| } else { |
| if (htc_pkt->pBuffer != adf_nbuf_data(pktSkb)) { |
| EPPING_LOG(VOS_TRACE_LEVEL_ERROR, |
| "%s: htc_pkt buffer not equal to skb->data", __func__); |
| ASSERT(0); |
| } |
| |
| /* add this to the list, use faster non-lock API */ |
| adf_nbuf_queue_add(&skb_queue,pktSkb); |
| |
| if (A_SUCCESS(status)) |
| if (htc_pkt->ActualLength != adf_nbuf_len(pktSkb)) { |
| EPPING_LOG(VOS_TRACE_LEVEL_ERROR, |
| "%s: htc_pkt length not equal to skb->len", __func__); |
| ASSERT(0); |
| } |
| } |
| |
| EPPING_LOG(VOS_TRACE_LEVEL_INFO, |
| "%s skb=%pK data=%pK len=0x%x eid=%d ", |
| __func__, pktSkb, htc_pkt->pBuffer, |
| htc_pkt->ActualLength, eid); |
| |
| if (A_FAILED(status)) { |
| if (status == A_ECANCELED) { |
| /* a packet was flushed */ |
| flushing = TRUE; |
| } |
| if (status != A_NO_RESOURCE) { |
| printk("%s() -TX ERROR, status: 0x%x\n", __func__, |
| status); |
| } |
| } else { |
| EPPING_LOG(VOS_TRACE_LEVEL_INFO, "%s: OK\n", __func__); |
| flushing = FALSE; |
| } |
| |
| epping_free_cookie(pAdapter->pEpping_ctx, cookie); |
| } |
| |
| adf_os_spin_unlock_bh(&pAdapter->data_lock); |
| |
| /* free all skbs in our local list */ |
| while (adf_nbuf_queue_len(&skb_queue)) { |
| /* use non-lock version */ |
| pktSkb = adf_nbuf_queue_remove(&skb_queue); |
| if (pktSkb == NULL) |
| break; |
| adf_nbuf_tx_free(pktSkb, ADF_NBUF_PKT_ERROR); |
| pEpping_ctx->total_tx_acks++; |
| } |
| |
| if (!flushing) { |
| netif_wake_queue(dev); |
| } |
| } |