/*
 * 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 <linux/delay.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/delay.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"

int epping_cookie_init(epping_context_t*pEpping_ctx)
{
   A_UINT32 i, j;

   pEpping_ctx->cookie_list = NULL;
   pEpping_ctx->cookie_count = 0;
   for (i = 0; i < MAX_COOKIE_SLOTS_NUM; i++) {
      pEpping_ctx->s_cookie_mem[i] =
         vos_mem_malloc(sizeof(struct epping_cookie)*MAX_COOKIE_SLOT_SIZE);
      if (pEpping_ctx->s_cookie_mem[i] == NULL) {
         EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
            "%s: no mem for cookie (idx = %d)", __func__, i);
         goto error;
      }
      vos_mem_zero(pEpping_ctx->s_cookie_mem[i],
         sizeof(struct epping_cookie)*MAX_COOKIE_SLOT_SIZE);
   }
   adf_os_spinlock_init(&pEpping_ctx->cookie_lock);

   for (i = 0; i < MAX_COOKIE_SLOTS_NUM; i++) {
      struct epping_cookie *cookie_mem = pEpping_ctx->s_cookie_mem[i];
      for (j = 0; j < MAX_COOKIE_SLOT_SIZE; j++) {
         epping_free_cookie(pEpping_ctx, &cookie_mem[j]);
      }
   }
   return 0;
error:
   for (i = 0; i < MAX_COOKIE_SLOTS_NUM; i++) {
      if (pEpping_ctx->s_cookie_mem[i]) {
         vos_mem_free(pEpping_ctx->s_cookie_mem[i]);
         pEpping_ctx->s_cookie_mem[i] = NULL;
      }
   }
   return -ENOMEM;
}

/* cleanup cookie queue */
void epping_cookie_cleanup(epping_context_t*pEpping_ctx)
{
   int i;
   adf_os_spin_lock_bh(&pEpping_ctx->cookie_lock);
   pEpping_ctx->cookie_list = NULL;
   pEpping_ctx->cookie_count = 0;
   adf_os_spin_unlock_bh(&pEpping_ctx->cookie_lock);
   for (i = 0; i < MAX_COOKIE_SLOTS_NUM; i++) {
      if (pEpping_ctx->s_cookie_mem[i]) {
         vos_mem_free(pEpping_ctx->s_cookie_mem[i]);
         pEpping_ctx->s_cookie_mem[i] = NULL;
      }
   }
}

void epping_free_cookie(epping_context_t*pEpping_ctx,
                        struct epping_cookie *cookie)
{
   adf_os_spin_lock_bh(&pEpping_ctx->cookie_lock);
   cookie->next = pEpping_ctx->cookie_list;
   pEpping_ctx->cookie_list = cookie;
   pEpping_ctx->cookie_count++;
   adf_os_spin_unlock_bh(&pEpping_ctx->cookie_lock);
}

struct epping_cookie *epping_alloc_cookie(epping_context_t*pEpping_ctx)
{
   struct epping_cookie   *cookie;

   adf_os_spin_lock_bh(&pEpping_ctx->cookie_lock);
   cookie = pEpping_ctx->cookie_list;
   if(cookie != NULL)
   {
      pEpping_ctx->cookie_list = cookie->next;
      pEpping_ctx->cookie_count--;
   }
   adf_os_spin_unlock_bh(&pEpping_ctx->cookie_lock);
   return cookie;
}

void epping_get_dummy_mac_addr(tSirMacAddr macAddr)
{
   macAddr[0] = 69; /* E */
   macAddr[1] = 80; /* P */
   macAddr[2] = 80; /* P */
   macAddr[3] = 73; /* I */
   macAddr[4] = 78; /* N */
   macAddr[5] = 71; /* G */
}

void epping_hex_dump(void *data, int buf_len, const char *str)
{
   char *buf = (char *)data;
   int i;

   printk("%s: E, %s\n", __func__, str);
   for (i=0; (i+7)< buf_len; i+=8)
   {
      printk("%02x %02x %02x %02x %02x %02x %02x %02x\n",
         buf[i],
         buf[i+1],
         buf[i+2],
         buf[i+3],
         buf[i+4],
         buf[i+5],
         buf[i+6],
         buf[i+7]);
   }

   // Dump the bytes in the last line
   for (; i < buf_len; i++)
   {
      printk("%02x ", buf[i]);
   }
   printk("\n%s: X %s\n", __func__, str);
}


void *epping_get_adf_ctx(void)
{
   VosContextType *pVosContext = NULL;
   adf_os_device_t *pAdfCtx;
   pVosContext = vos_get_global_context(VOS_MODULE_ID_SYS, NULL);
   pAdfCtx = vos_get_context(VOS_MODULE_ID_ADF, pVosContext);
   return pAdfCtx;
}

void epping_log_packet(epping_adapter_t *pAdapter,
                       EPPING_HEADER *eppingHdr, int ret, const char *str)
{
   if (eppingHdr->Cmd_h & EPPING_LOG_MASK) {
      EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
         "%s: cmd = %d, seqNo = %u, flag = 0x%x, ret = %d, "
         "txCount = %lu, txDrop =  %lu, txBytes = %lu,"
         "rxCount = %lu, rxDrop = %lu, rxBytes = %lu\n",
         str, eppingHdr->Cmd_h, eppingHdr->SeqNo,
         eppingHdr->CmdFlags_h, ret,
         pAdapter->stats.tx_packets,
         pAdapter->stats.tx_dropped,
         pAdapter->stats.tx_bytes,
         pAdapter->stats.rx_packets,
         pAdapter->stats.rx_dropped,
         pAdapter->stats.rx_bytes);
   }
}

void epping_log_stats(epping_adapter_t *pAdapter, const char *str)
{
   EPPING_LOG(VOS_TRACE_LEVEL_FATAL,
      "%s: txCount = %lu, txDrop = %lu, tx_bytes = %lu, "
      "rxCount = %lu, rxDrop = %lu, rx_bytes = %lu, tx_acks = %u\n",
      str,
      pAdapter->stats.tx_packets,
      pAdapter->stats.tx_dropped,
      pAdapter->stats.tx_bytes,
      pAdapter->stats.rx_packets,
      pAdapter->stats.rx_dropped,
      pAdapter->stats.rx_bytes,
      pAdapter->pEpping_ctx->total_tx_acks);
}

void epping_set_kperf_flag(epping_adapter_t *pAdapter,
                           HTC_ENDPOINT_ID eid,
                           A_UINT8 kperf_flag)
{
   pAdapter->pEpping_ctx->kperf[eid] = kperf_flag;
   pAdapter->pEpping_ctx->kperf_num_rx_recv[eid] = 0;
   pAdapter->pEpping_ctx->kperf_num_tx_acks[eid] = 0;
}

#ifdef HIF_PCI

static int epping_tx_thread_fn(void *data)
{
    int i;
   epping_poll_t *epping_poll = data;

   EPPING_LOG(VOS_TRACE_LEVEL_FATAL, "%s: arg = %pK", __func__, data);
   while (!epping_poll->done) {
      down(&epping_poll->sem);
      adf_os_atomic_dec(&epping_poll->atm);
      if (epping_poll->skb && !epping_poll->done) {
         for (i = 0; i < MAX_TX_PKT_DUP_NUM; i++) {
            epping_tx_dup_pkt((epping_adapter_t *)epping_poll->arg,
               epping_poll->eid, epping_poll->skb);
            udelay(WLAN_EPPING_DELAY_TIMEOUT_US);
         }
      }
   }
   return 0;
}

#define EPPING_TX_THREAD "EPPINGTX"
void epping_register_tx_copier(HTC_ENDPOINT_ID eid, epping_context_t *pEpping_ctx)
{
   epping_poll_t *epping_poll = &pEpping_ctx->epping_poll[eid];
   epping_poll->eid = eid;
   epping_poll->arg = pEpping_ctx->epping_adapter;
   epping_poll->done = false;
   EPPING_LOG(VOS_TRACE_LEVEL_FATAL, "%s: eid = %d, arg = %pK",
              __func__, eid, pEpping_ctx->epping_adapter);
   sema_init(&epping_poll->sem, 0);
   adf_os_atomic_init(&epping_poll->atm);
   epping_poll->inited = true;
   epping_poll->pid = kthread_create(epping_tx_thread_fn,
                                     epping_poll, EPPING_TX_THREAD);
   wake_up_process(epping_poll->pid);
}
void epping_unregister_tx_copier(HTC_ENDPOINT_ID eid, epping_context_t *pEpping_ctx)
{
   epping_poll_t *epping_poll;

   if (eid < 0 || eid >= EPPING_MAX_NUM_EPIDS ) {
      EPPING_LOG(VOS_TRACE_LEVEL_FATAL, "%s: invalid eid = %d",
         __func__, eid);
      return;
   }

   epping_poll = &pEpping_ctx->epping_poll[eid];

   epping_poll->done = true;
   if (epping_poll->inited) {
      epping_tx_copier_schedule(pEpping_ctx, eid, NULL);
      msleep(EPPING_KTID_KILL_WAIT_TIME_MS);
   }
   if (epping_poll->skb)
      adf_nbuf_free(epping_poll->skb);
   OS_MEMZERO(epping_poll, sizeof(epping_poll_t));
   EPPING_LOG(VOS_TRACE_LEVEL_FATAL, "%s: eid = %d",
               __func__, eid);
}
void epping_tx_copier_schedule(epping_context_t *pEpping_ctx, HTC_ENDPOINT_ID eid, adf_nbuf_t skb)
{
   epping_poll_t *epping_poll = &pEpping_ctx->epping_poll[eid];

   if (!epping_poll->skb && skb) {
      epping_poll->skb = adf_nbuf_copy(skb);
   }
   if (adf_os_atomic_read(&epping_poll->atm) < EPPING_MAX_WATER_MARK) {
      adf_os_atomic_inc(&epping_poll->atm);
      up(&epping_poll->sem);
   }
}
#endif /* HIF_PCI */
