wlan: [Part 2] Implement Soft AP flow control

Currently TL fetches packets in a round robin fashion from station's
access queues with same weight or priority. When one station's link
deteriorate it impacts the throughput of the other stations with good
links. To address this implement flow control based on the firmware Tx
indication stats.

Change-Id: I6094cb9395efdcc57ac5aff3588d59c32fac6627
CRs-Fixed: 1097660
diff --git a/CORE/HDD/inc/wlan_hdd_main.h b/CORE/HDD/inc/wlan_hdd_main.h
index 5b9f38d..f7de1fb 100644
--- a/CORE/HDD/inc/wlan_hdd_main.h
+++ b/CORE/HDD/inc/wlan_hdd_main.h
@@ -243,6 +243,8 @@
 
 #define WLAN_WAIT_TIME_EXTSCAN  1000
 
+#define HDD_MAX_STA_COUNT (HAL_NUM_STA)
+
 /*
  * Generic asynchronous request/response support
  *
@@ -1460,6 +1462,8 @@
    
    v_BOOL_t hdd_wlan_suspended;
    bool rx_wow_dump;
+
+   uint8_t bad_sta[HDD_MAX_STA_COUNT];
    
    spinlock_t filter_lock;
    
diff --git a/CORE/HDD/src/wlan_hdd_main.c b/CORE/HDD/src/wlan_hdd_main.c
index 237daca..faae74f 100644
--- a/CORE/HDD/src/wlan_hdd_main.c
+++ b/CORE/HDD/src/wlan_hdd_main.c
@@ -12366,6 +12366,8 @@
    }
 #endif
 
+   vos_mem_set((uint8_t *)&pHddCtx->bad_sta, HDD_MAX_STA_COUNT, 0);
+
    // Register IPv4 notifier to notify if any change in IP
    // So that we can reconfigure the offload parameters
    pHddCtx->ipv4_notifier.notifier_call = wlan_hdd_ipv4_changed;
diff --git a/CORE/HDD/src/wlan_hdd_softap_tx_rx.c b/CORE/HDD/src/wlan_hdd_softap_tx_rx.c
index b6cf636..e9d70d7 100644
--- a/CORE/HDD/src/wlan_hdd_softap_tx_rx.c
+++ b/CORE/HDD/src/wlan_hdd_softap_tx_rx.c
@@ -449,6 +449,23 @@
    // checked. Over-limit packets will be dropped.
     spin_lock_bh(&pSapCtx->aStaInfo[STAId].wmm_tx_queue[ac].lock);
     hdd_list_size(&pSapCtx->aStaInfo[STAId].wmm_tx_queue[ac], &pktListSize);
+
+    if (pHddCtx->bad_sta[STAId]) {
+       hdd_list_node_t *anchor = NULL;
+       skb_list_node_t *pktNode = NULL;
+       struct sk_buff *fskb = NULL;
+       if(pktListSize >= (pAdapter->aTxQueueLimit[ac])/2) {
+          hdd_list_remove_front(&pSapCtx->aStaInfo[STAId].wmm_tx_queue[ac],
+                                &anchor);
+          pktNode = list_entry(anchor, skb_list_node_t, anchor);
+          fskb = pktNode->skb;
+          kfree_skb(fskb);
+          pktListSize--;
+          ++pAdapter->stats.tx_dropped;
+          ++pAdapter->hdd_stats.hddTxRxStats.txXmitDropped;
+       }
+    }
+
     if(pktListSize >= pAdapter->aTxQueueLimit[ac])
     {
        VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_WARN,
@@ -736,7 +753,8 @@
    hdd_context_t *pHddCtx;
 
    VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
-      "%s: Transmission timeout occurred", __func__);
+      "%s: Transmission timeout occurred jiffies %lu dev->trans_start %lu",
+        __func__, jiffies, dev->trans_start);
 
    if ( NULL == pAdapter )
    {
diff --git a/CORE/TL/src/wlan_qct_tl.c b/CORE/TL/src/wlan_qct_tl.c
index f03689e..21a6232 100644
--- a/CORE/TL/src/wlan_qct_tl.c
+++ b/CORE/TL/src/wlan_qct_tl.c
@@ -243,6 +243,9 @@
 #define ENABLE_ARP_TOGGLE  1
 #define SEND_ARP_ON_WQ5    2
 
+#define WLANTL_RATE_RATIO_THRESHOLD 2
+#define WLANTL_GOOD_STA_WEIGHT 1
+
 /*----------------------------------------------------------------------------
  * Type Declarations
  * -------------------------------------------------------------------------*/
@@ -5969,6 +5972,104 @@
   return VOS_STATUS_SUCCESS;
 }
 
+/**
+ * WLANTL_FlowControl() - TX Flow control
+ * @pTLCb: TL context pointer
+ * @pvosDataBuff: Firmware indication data buffer
+ *
+ * This function implenets the algorithm to flow control TX traffic in  case
+ * of SAP and SAP + STA concurrency.
+ *
+ * Algorithm goal is to fetch more packets from good peer than bad peer by
+ * introducing weights for each station connected. Weight of each station is
+ * calcutated by taking ratio of max RA rate of the peers to its rate. If the
+ * ratio is less than two based on number of queued frames in the station BTQM
+ * weight is modified. If the queued frames reaches the defined threshold weight
+ * is assigned as four. Here weight is inversely proportional to the number of
+ * packets fetch.
+ *
+ * Return true if flow controlled or false otherwise.
+ */
+static bool WLANTL_FlowControl(WLANTL_CbType* pTLCb, vos_pkt_t* pvosDataBuff)
+{
+   WLANTL_FlowControlInfo *fc_data = NULL;
+   WLANTL_PerStaFlowControlParam *sta_fc_params = NULL;
+   uint8_t num_stas;
+   struct sk_buff *skb = NULL;
+   uint16_t data_len;
+   uint8_t *staid;
+   uint16_t max_rate = 0;
+   uint8_t i;
+
+   vos_pkt_get_packet_length(pvosDataBuff, &data_len);
+   if (!data_len) {
+      TLLOGE(VOS_TRACE( VOS_MODULE_ID_TL, VOS_TRACE_LEVEL_ERROR,
+              "%s: Null fw indication data", __func__));
+      return false;
+   }
+
+   vos_pkt_get_os_packet(pvosDataBuff, (v_VOID_t **)&skb, 0);
+   fc_data = (WLANTL_FlowControlInfo *)skb->data;
+   num_stas = fc_data->num_stas;
+   if (!num_stas) {
+      TLLOG1(VOS_TRACE(VOS_MODULE_ID_TL, VOS_TRACE_LEVEL_INFO,
+          "%s: No connected stations", __func__));
+      return true;
+   }
+
+   /* Skip flow control for one connected station */
+   if (1 == num_stas)
+      return true;
+
+   staid = (uint8_t *)vos_mem_malloc(WLAN_MAX_STA_COUNT);
+   if (!staid) {
+      TLLOGE(VOS_TRACE( VOS_MODULE_ID_TL, VOS_TRACE_LEVEL_ERROR,
+              "%s: mem allocation failure", __func__));
+      return false;
+   }
+   vos_mem_set((uint8_t *)staid, WLAN_MAX_STA_COUNT, 0);
+
+   sta_fc_params = (WLANTL_PerStaFlowControlParam *)(&fc_data->num_stas + 1);
+
+   for(i = 0; i < num_stas; i ++, sta_fc_params ++) {
+      staid[i] = sta_fc_params->sta_id;
+
+      if (!pTLCb->atlSTAClients[staid[i]])
+         continue;
+
+      pTLCb->atlSTAClients[staid[i]]->per = sta_fc_params->avg_per;
+      pTLCb->atlSTAClients[staid[i]]->queue = sta_fc_params->queue_len;
+      pTLCb->atlSTAClients[staid[i]]->trate = sta_fc_params->rate;
+      max_rate = max_rate > pTLCb->atlSTAClients[staid[i]]->trate ?
+                            max_rate : pTLCb->atlSTAClients[staid[i]]->trate;
+
+      TLLOG2(VOS_TRACE( VOS_MODULE_ID_TL, VOS_TRACE_LEVEL_INFO_HIGH,
+          "%s: sta_id:%d in:%d queue:%d avg_per:%d rate:%d", __func__,
+          staid[i], pTLCb->atlSTAClients[staid[i]]->tx_samples_sum,
+          pTLCb->atlSTAClients[staid[i]]->queue,
+          pTLCb->atlSTAClients[staid[i]]->per,
+          pTLCb->atlSTAClients[staid[i]]->trate));
+   }
+
+   for (i = 0; i < num_stas; i++) {
+      pTLCb->atlSTAClients[staid[i]]->weight =
+                             max_rate/pTLCb->atlSTAClients[staid[i]]->trate;
+      if (pTLCb->atlSTAClients[staid[i]]->weight >= WLANTL_RATE_RATIO_THRESHOLD
+          && !pTLCb->atlSTAClients[staid[i]]->set_flag) {
+            vos_set_hdd_bad_sta(staid[i]);
+            pTLCb->atlSTAClients[staid[i]]->set_flag = true;
+      }
+      if (pTLCb->atlSTAClients[staid[i]]->weight <
+          WLANTL_RATE_RATIO_THRESHOLD) {
+         if (pTLCb->atlSTAClients[staid[i]]->set_flag) {
+            vos_reset_hdd_bad_sta(staid[i]);
+            pTLCb->atlSTAClients[staid[i]]->set_flag = false;
+         }
+      }
+   }
+   vos_mem_free(staid);
+   return true;
+}
 
 /*==========================================================================
 
@@ -6113,7 +6214,8 @@
       TLLOG2(VOS_TRACE( VOS_MODULE_ID_TL, VOS_TRACE_LEVEL_INFO_HIGH,
                  "WLAN TL:receive one FC frame"));
 
-      WLANTL_ProcessFCFrame(pvosGCtx, vosTempBuff, pvBDHeader);
+      WLANTL_FlowControl(pTLCb, vosTempBuff);
+
       /* Drop packet */
       vos_pkt_return_packet(vosTempBuff);
       vosTempBuff = vosDataBuff;
@@ -11435,12 +11537,13 @@
 {
   WLANTL_CbType*  pTLCb;
   v_U8_t          ucACFilter = 1;
-  v_U8_t          ucNextSTA ; 
+  v_U8_t          ucNextSTA, ucTempSTA;
   v_BOOL_t        isServed = TRUE;  //current round has find a packet or not
   v_U8_t          ucACLoopNum = WLANTL_AC_HIGH_PRIO + 1; //number of loop to go
   v_U8_t          uFlowMask; // TX FlowMask from WDA
   uint8           ucACMask; 
-  uint8           i = 0; 
+  uint8           i = 0;
+  uint8           j;
   /*------------------------------------------------------------------------
     Extract TL control block
   ------------------------------------------------------------------------*/
@@ -11505,6 +11608,8 @@
     } // (0 == pTLCb->ucCurLeftWeight)
   } //( WLAN_MAX_STA_COUNT == ucNextSTA )
 
+  ucTempSTA = ucNextSTA;
+
   //decide how many loops to go. if current loop is partial, do one extra to make sure
   //we cover every station
   if ((1 == pTLCb->ucCurLeftWeight) && (ucNextSTA != 0))
@@ -11561,6 +11666,22 @@
           continue;
         }
 
+        /*
+         * Initial weight is 0 is for all the stations. As part of FW TX stats
+         * indication to host, good peer weight is updated to one and the
+         * remaining peers weight is updated based on their RA rates and BTQM
+         * queued frames length. TL skips fetching the packet until the station
+         * has got chances equal to its weight.
+         */
+        if (pTLCb->atlSTAClients[ucNextSTA]->weight > WLANTL_GOOD_STA_WEIGHT) {
+           if (pTLCb->atlSTAClients[ucNextSTA]->weight_count <=
+               pTLCb->atlSTAClients[ucNextSTA]->weight) {
+              pTLCb->atlSTAClients[ucNextSTA]->weight_count++;
+              continue;
+           }
+           else
+              pTLCb->atlSTAClients[ucNextSTA]->weight_count = 0;
+        }
 
         // Find a station. Weight is updated already.
         *pucSTAId = ucNextSTA;
@@ -11574,6 +11695,50 @@
         return VOS_STATUS_SUCCESS;
       } //STA loop
 
+      for (j = 0; j < ucTempSTA; j++) {
+         if (NULL == pTLCb->atlSTAClients[j])
+            continue;
+
+         WLAN_TL_AC_ARRAY_2_MASK (pTLCb->atlSTAClients[j], ucACMask, i);
+
+         if ((0 == pTLCb->atlSTAClients[j]->ucExists) ||
+             ((0 == pTLCb->atlSTAClients[j]->ucPktPending) && !(ucACMask)) ||
+             (0 == (ucACMask & ucACFilter)))
+            continue;
+
+         if ((WLANTL_STA_AUTHENTICATED != pTLCb->atlSTAClients[j]->tlState) ||
+             (pTLCb->atlSTAClients[j]->disassoc_progress == VOS_TRUE)) {
+            TLLOG2(VOS_TRACE( VOS_MODULE_ID_TL, VOS_TRACE_LEVEL_INFO_HIGH,
+                  "%s Sta %d not in auth state so skipping it.",
+                  __func__, ucNextSTA));
+            continue;
+         }
+
+         if ((TRUE == pTLCb->atlSTAClients[j]->ucLwmModeEnabled) &&
+            ((FALSE == pTLCb->atlSTAClients[j]->ucLwmEventReported) ||
+             (0 < pTLCb->atlSTAClients[j]->uBuffThresholdMax)))
+            continue;
+
+         if (pTLCb->atlSTAClients[j]->weight > WLANTL_GOOD_STA_WEIGHT) {
+            if (pTLCb->atlSTAClients[j]->weight_count <=
+                pTLCb->atlSTAClients[j]->weight) {
+               pTLCb->atlSTAClients[j]->weight_count++;
+               continue;
+            }
+            else
+               pTLCb->atlSTAClients[j]->weight_count = 0;
+         }
+
+         *pucSTAId = j;
+         pTLCb->ucCurrentSTA = j;
+         pTLCb->atlSTAClients[*pucSTAId]->ucCurrentAC = pTLCb->uCurServedAC;
+
+         TLLOG4(VOS_TRACE( VOS_MODULE_ID_TL, VOS_TRACE_LEVEL_INFO_LOW,
+                    " TL serve one station AC: %d  W: %d StaId: %d",
+                   pTLCb->uCurServedAC, pTLCb->ucCurLeftWeight, pTLCb->ucCurrentSTA ));
+         return VOS_STATUS_SUCCESS;
+      }
+
       ucNextSTA = 0;
       if ( FALSE == isServed )
       {
@@ -11729,6 +11894,18 @@
     {
         continue;
     }
+
+    if ((pTLCb->atlSTAClients[ucNextSTA]->weight > WLANTL_GOOD_STA_WEIGHT) &&
+        (pTLCb->atlSTAClients[ucNextSTA]->ucPktPending)) {
+       if (pTLCb->atlSTAClients[ucNextSTA]->weight_count <=
+           pTLCb->atlSTAClients[ucNextSTA]->weight) {
+          pTLCb->atlSTAClients[ucNextSTA]->weight_count++;
+          continue;
+       }
+       else
+          pTLCb->atlSTAClients[ucNextSTA]->weight_count = 0;
+    }
+
     if (( pTLCb->atlSTAClients[ucNextSTA]->ucExists ) &&
         ( pTLCb->atlSTAClients[ucNextSTA]->ucPktPending ))
     {
diff --git a/CORE/TL/src/wlan_qct_tli.h b/CORE/TL/src/wlan_qct_tli.h
index 480673c..254122f 100644
--- a/CORE/TL/src/wlan_qct_tli.h
+++ b/CORE/TL/src/wlan_qct_tli.h
@@ -736,6 +736,14 @@
   uint32_t tx_sample[WLANTL_SAMPLE_COUNT];
   uint64_t tx_samples_sum;
 
+  /* flow control */
+  uint8_t weight;
+  uint8_t weight_count;
+  uint8_t per;
+  uint8_t set_flag;
+  uint16_t queue;
+  uint16_t trate;
+
 }WLANTL_STAClientType;
 
 /*---------------------------------------------------------------------------
@@ -820,6 +828,19 @@
    vos_lock_t                           hosLock;
 } WLANTL_HO_SUPPORT_TYPE;
 
+typedef struct {
+  uint8 sta_id;
+  uint8 avg_per;
+  uint16 queue_len;
+  uint16_t rate;
+  uint16_t reserved;
+} WLANTL_PerStaFlowControlParam;
+
+typedef struct {
+  uint8 num_stas;
+  WLANTL_PerStaFlowControlParam *sta_fc_info;
+} WLANTL_FlowControlInfo;
+
 #ifdef WLAN_FEATURE_ROAM_SCAN_OFFLOAD
 #define ROAM_MAX_INDEX_NUM              50
 #define ROAM_PER_INDEX_TIME             500 /* (msec) */
diff --git a/CORE/VOSS/inc/vos_api.h b/CORE/VOSS/inc/vos_api.h
index 99039cd..f012a7f 100644
--- a/CORE/VOSS/inc/vos_api.h
+++ b/CORE/VOSS/inc/vos_api.h
@@ -506,6 +506,9 @@
 bool vos_get_rx_wow_dump(void);
 void vos_set_rx_wow_dump(bool value);
 
+void vos_set_hdd_bad_sta(uint8_t sta_id);
+void vos_reset_hdd_bad_sta(uint8_t sta_id);
+
 void vos_probe_threads(void);
 void vos_per_pkt_stats_to_user(void *perPktStat);
 void vos_updatePktStatsInfo(void * pktStat);
diff --git a/CORE/VOSS/src/vos_api.c b/CORE/VOSS/src/vos_api.c
index 7d347fb..be479f9 100644
--- a/CORE/VOSS/src/vos_api.c
+++ b/CORE/VOSS/src/vos_api.c
@@ -3266,6 +3266,59 @@
 }
 
 /**
+ * vos_set_hdd_bad_sta() - Set bad link peer sta id
+ * @sta_id: sta id of the bad peer
+ *
+ * Return none.
+ */
+void vos_set_hdd_bad_sta(uint8_t sta_id)
+{
+    hdd_context_t *pHddCtx = NULL;
+    v_CONTEXT_t pVosContext = vos_get_global_context(VOS_MODULE_ID_SYS, NULL);
+
+    if(!pVosContext)
+    {
+       hddLog(VOS_TRACE_LEVEL_FATAL,"%s: Global VOS context is Null", __func__);
+       return;
+    }
+
+    pHddCtx = (hdd_context_t *)vos_get_context(VOS_MODULE_ID_HDD, pVosContext );
+    if(!pHddCtx) {
+       VOS_TRACE(VOS_MODULE_ID_VOSS, VOS_TRACE_LEVEL_FATAL,
+                "%s: HDD context is Null", __func__);
+       return;
+    }
+
+    pHddCtx->bad_sta[sta_id] = 1;
+}
+
+/**
+ * vos_reset_hdd_bad_sta() - Reset the bad peer sta_id
+ * @sta_id: sta id of the peer
+ *
+ * Return none.
+ */
+void vos_reset_hdd_bad_sta(uint8_t sta_id)
+{
+    hdd_context_t *pHddCtx = NULL;
+    v_CONTEXT_t pVosContext = vos_get_global_context(VOS_MODULE_ID_SYS, NULL);
+
+    if(!pVosContext) {
+       hddLog(VOS_TRACE_LEVEL_FATAL,"%s: Global VOS context is Null", __func__);
+       return;
+    }
+
+    pHddCtx = (hdd_context_t *)vos_get_context(VOS_MODULE_ID_HDD, pVosContext );
+    if(!pHddCtx) {
+       VOS_TRACE(VOS_MODULE_ID_VOSS, VOS_TRACE_LEVEL_FATAL,
+                "%s: HDD context is Null", __func__);
+       return;
+    }
+
+    pHddCtx->bad_sta[sta_id] = 0;
+}
+
+/**
  * vos_probe_threads() - VOS API to post messages
  * to all the threads to detect if they are active or not
  *