prima: Add new vendor command to get link properties

Add support in driver for a new vendor command to get
the link properties nss, rate flags and operating
frequency.

Change-Id: Ie3b8d5b2c3886055d303441c5d8b2f2a0a2719bd
CRs-Fixed: 899952
diff --git a/CORE/HDD/inc/wlan_hdd_assoc.h b/CORE/HDD/inc/wlan_hdd_assoc.h
index ea3642b..d96e66d 100644
--- a/CORE/HDD/inc/wlan_hdd_assoc.h
+++ b/CORE/HDD/inc/wlan_hdd_assoc.h
@@ -105,6 +105,8 @@
 
    /** Dot11Mode */
    tANI_U32 dot11Mode;
+
+   uint32_t  rate_flags;
    
 }connection_info_t;
 /*Forward declaration of Adapter*/
diff --git a/CORE/HDD/inc/wlan_hdd_cfg80211.h b/CORE/HDD/inc/wlan_hdd_cfg80211.h
index f6554af..6953a6c 100644
--- a/CORE/HDD/inc/wlan_hdd_cfg80211.h
+++ b/CORE/HDD/inc/wlan_hdd_cfg80211.h
@@ -181,6 +181,9 @@
 
     QCA_NL80211_VENDOR_SUBCMD_OFFLOADED_PACKETS = 79,
 
+    /* subcommand to get link properties */
+    QCA_NL80211_VENDOR_SUBCMD_LINK_PROPERTIES = 101,
+
     QCA_NL80211_VENDOR_SUBCMD_SETBAND = 105,
 };
 
@@ -1056,6 +1059,25 @@
        QCA_WLAN_VENDOR_ATTR_WIFI_LOGGER_START_AFTER_LAST - 1,
 };
 
+/* NL attributes for data used by
+ * QCA_NL80211_VENDOR_SUBCMD_LINK_PROPERTIES.
+ */
+enum qca_wlan_vendor_attr_link_properties {
+    QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_INVALID    = 0,
+    /* Unsigned 8bit value for specifying nof spatial streams */
+    QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_NSS        = 1,
+    /* Unsigned 8bit value for the rate flags */
+    QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_RATE_FLAGS = 2,
+    /* Unsigned 32bit value for operating frequency */
+    QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_FREQ       = 3,
+
+    /* KEEP LAST */
+    QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_AFTER_LAST,
+    QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_MAX =
+    QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_AFTER_LAST - 1,
+};
+
+
 /* Feature defines */
 #define WIFI_FEATURE_INFRA              0x0001   /* Basic infrastructure mode */
 #define WIFI_FEATURE_INFRA_5G           0x0002   /* Support for 5 GHz Band */
diff --git a/CORE/HDD/src/wlan_hdd_assoc.c b/CORE/HDD/src/wlan_hdd_assoc.c
index af362c1..bb44804 100644
--- a/CORE/HDD/src/wlan_hdd_assoc.c
+++ b/CORE/HDD/src/wlan_hdd_assoc.c
@@ -326,6 +326,8 @@
 
           // Save  dot11mode in which STA associated to AP
           pHddStaCtx->conn_info.dot11Mode = pRoamInfo->u.pConnectedProfile->dot11Mode;
+
+          pHddStaCtx->conn_info.rate_flags = pRoamInfo->maxRateFlags;
       }
    }
 
diff --git a/CORE/HDD/src/wlan_hdd_cfg80211.c b/CORE/HDD/src/wlan_hdd_cfg80211.c
index ab96977..412c940 100644
--- a/CORE/HDD/src/wlan_hdd_cfg80211.c
+++ b/CORE/HDD/src/wlan_hdd_cfg80211.c
@@ -6997,6 +6997,175 @@
 }
 #endif
 
+static const struct
+nla_policy
+qca_wlan_vendor_attr_policy[QCA_WLAN_VENDOR_ATTR_MAX+1] = {
+    [QCA_WLAN_VENDOR_ATTR_MAC_ADDR] = { .type = NLA_UNSPEC },
+};
+
+/**
+ * wlan_hdd_cfg80211_get_link_properties() - This function is used to
+ * get link properties like nss, rate flags and operating frequency for
+ * the connection with the given peer.
+ * @wiphy:    WIPHY structure pointer
+ * @wdev:     Wireless device structure pointer
+ * @data:     Pointer to the data received
+ * @data_len: Length of the data received
+ *
+ * This function return the above link properties on success.
+ *
+ * Return: 0 on success and errno on failure
+ */
+static int wlan_hdd_cfg80211_get_link_properties(struct wiphy *wiphy,
+        struct wireless_dev *wdev,
+        const void *data,
+        int data_len)
+{
+    hdd_context_t       *hdd_ctx = wiphy_priv(wiphy);
+    struct net_device   *dev = wdev->netdev;
+    hdd_adapter_t       *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
+    hdd_station_ctx_t   *hdd_sta_ctx;
+    struct nlattr       *tb[QCA_WLAN_VENDOR_ATTR_MAX+1];
+    uint8_t             peer_mac[VOS_MAC_ADDR_SIZE];
+    uint32_t            sta_id;
+    struct sk_buff      *reply_skb;
+    uint32_t            rate_flags = 0;
+    uint8_t             nss;
+    uint8_t             final_rate_flags = 0;
+    uint32_t            freq;
+    v_CONTEXT_t pVosContext = NULL;
+    ptSapContext pSapCtx = NULL;
+
+    if (0 != wlan_hdd_validate_context(hdd_ctx)) {
+        hddLog(VOS_TRACE_LEVEL_ERROR, FL("HDD context is not valid"));
+        return -EINVAL;
+    }
+
+    if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_MAX, data, data_len,
+                qca_wlan_vendor_attr_policy)) {
+        hddLog(VOS_TRACE_LEVEL_ERROR, FL("Invalid attribute"));
+        return -EINVAL;
+    }
+
+    if (!tb[QCA_WLAN_VENDOR_ATTR_MAC_ADDR]) {
+        hddLog(VOS_TRACE_LEVEL_ERROR,
+                FL("Attribute peerMac not provided for mode=%d"),
+                adapter->device_mode);
+        return -EINVAL;
+    }
+
+    memcpy(peer_mac, nla_data(tb[QCA_WLAN_VENDOR_ATTR_MAC_ADDR]),
+            sizeof(peer_mac));
+    hddLog(VOS_TRACE_LEVEL_INFO,
+            FL("peerMac="MAC_ADDRESS_STR" for device_mode:%d"),
+            MAC_ADDR_ARRAY(peer_mac), adapter->device_mode);
+
+    if (adapter->device_mode == WLAN_HDD_INFRA_STATION ||
+            adapter->device_mode == WLAN_HDD_P2P_CLIENT) {
+        hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter);
+        if ((hdd_sta_ctx->conn_info.connState !=
+                    eConnectionState_Associated) ||
+                !vos_mem_compare(hdd_sta_ctx->conn_info.bssId, peer_mac,
+                    VOS_MAC_ADDRESS_LEN)) {
+            hddLog(VOS_TRACE_LEVEL_ERROR,
+                    FL("Not Associated to mac "MAC_ADDRESS_STR),
+                    MAC_ADDR_ARRAY(peer_mac));
+            return -EINVAL;
+        }
+
+        nss  = 1; //pronto supports only one spatial stream
+        freq = vos_chan_to_freq(
+                hdd_sta_ctx->conn_info.operationChannel);
+        rate_flags = hdd_sta_ctx->conn_info.rate_flags;
+
+    } else if (adapter->device_mode == WLAN_HDD_P2P_GO ||
+            adapter->device_mode == WLAN_HDD_SOFTAP) {
+
+          pVosContext = ( WLAN_HDD_GET_CTX(adapter))->pvosContext;
+          pSapCtx = VOS_GET_SAP_CB(pVosContext);
+          if(pSapCtx == NULL){
+                 VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
+                   FL("psapCtx is NULL"));
+                     return -ENOENT;
+          }
+
+
+        for (sta_id = 0; sta_id < WLAN_MAX_STA_COUNT; sta_id++) {
+            if (pSapCtx->aStaInfo[sta_id].isUsed &&
+                    !vos_is_macaddr_broadcast(
+                        &pSapCtx->aStaInfo[sta_id].macAddrSTA) &&
+                    vos_mem_compare(
+                        &pSapCtx->aStaInfo[sta_id].macAddrSTA,
+                        peer_mac, VOS_MAC_ADDRESS_LEN))
+                break;
+        }
+
+        if (WLAN_MAX_STA_COUNT == sta_id) {
+            hddLog(VOS_TRACE_LEVEL_ERROR,
+                    FL("No active peer with mac="MAC_ADDRESS_STR),
+                    MAC_ADDR_ARRAY(peer_mac));
+            return -EINVAL;
+        }
+
+        nss  = 1; //pronto supports only one spatial stream
+        freq = vos_chan_to_freq(
+                (WLAN_HDD_GET_AP_CTX_PTR(adapter))->operatingChannel);
+        rate_flags = pSapCtx->aStaInfo[sta_id].rate_flags;
+    } else {
+        hddLog(VOS_TRACE_LEVEL_ERROR,
+                FL("Not Associated! with mac"MAC_ADDRESS_STR),
+                MAC_ADDR_ARRAY(peer_mac));
+        return -EINVAL;
+    }
+
+    if (!(rate_flags & eHAL_TX_RATE_LEGACY)) {
+        if (rate_flags & eHAL_TX_RATE_VHT80) {
+            final_rate_flags |= RATE_INFO_FLAGS_VHT_MCS;
+            final_rate_flags |= RATE_INFO_FLAGS_80_MHZ_WIDTH;
+        } else if (rate_flags & eHAL_TX_RATE_VHT40) {
+            final_rate_flags |= RATE_INFO_FLAGS_VHT_MCS;
+            final_rate_flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH;
+        } else if (rate_flags & eHAL_TX_RATE_VHT20) {
+            final_rate_flags |= RATE_INFO_FLAGS_VHT_MCS;
+        } else if (rate_flags & (eHAL_TX_RATE_HT20 | eHAL_TX_RATE_HT40)) {
+            final_rate_flags |= RATE_INFO_FLAGS_MCS;
+            if (rate_flags & eHAL_TX_RATE_HT40)
+                final_rate_flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH;
+        }
+
+        if (rate_flags & eHAL_TX_RATE_SGI) {
+            if (!(final_rate_flags & RATE_INFO_FLAGS_VHT_MCS))
+                final_rate_flags |= RATE_INFO_FLAGS_MCS;
+            final_rate_flags |= RATE_INFO_FLAGS_SHORT_GI;
+        }
+    }
+
+    reply_skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy,
+            sizeof(u8) + sizeof(u8) + sizeof(u32) + NLMSG_HDRLEN);
+
+    if (NULL == reply_skb) {
+        hddLog(VOS_TRACE_LEVEL_ERROR,
+                FL("getLinkProperties: skb alloc failed"));
+        return -EINVAL;
+    }
+
+    if (nla_put_u8(reply_skb,
+                QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_NSS,
+                nss) ||
+            nla_put_u8(reply_skb,
+                QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_RATE_FLAGS,
+                final_rate_flags) ||
+            nla_put_u32(reply_skb,
+                QCA_WLAN_VENDOR_ATTR_LINK_PROPERTIES_FREQ,
+                freq)) {
+        hddLog(VOS_TRACE_LEVEL_ERROR, FL("nla_put failed"));
+        kfree_skb(reply_skb);
+        return -EINVAL;
+    }
+
+    return cfg80211_vendor_cmd_reply(reply_skb);
+}
+
 const struct wiphy_vendor_command hdd_wiphy_vendor_commands[] =
 {
     {
@@ -7225,8 +7394,16 @@
             WIPHY_VENDOR_CMD_NEED_NETDEV |
             WIPHY_VENDOR_CMD_NEED_RUNNING,
         .doit = wlan_hdd_cfg80211_offloaded_packets
-    }
+    },
 #endif
+    {
+        .info.vendor_id = QCA_NL80211_VENDOR_ID,
+        .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_LINK_PROPERTIES,
+        .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+            WIPHY_VENDOR_CMD_NEED_NETDEV |
+            WIPHY_VENDOR_CMD_NEED_RUNNING,
+        .doit = wlan_hdd_cfg80211_get_link_properties
+    }
 };
 
 /* vendor specific events */
diff --git a/CORE/HDD/src/wlan_hdd_hostapd.c b/CORE/HDD/src/wlan_hdd_hostapd.c
index 77e6f5f..9cf9f5d 100644
--- a/CORE/HDD/src/wlan_hdd_hostapd.c
+++ b/CORE/HDD/src/wlan_hdd_hostapd.c
@@ -1020,6 +1020,15 @@
                                      vos_status, MAC_ADDR_ARRAY(wrqu.addr.sa_data));
             }
 
+            staId =
+                pSapEvent->sapevt.sapStationAssocReassocCompleteEvent.staId;
+            if (VOS_IS_STATUS_SUCCESS(vos_status))
+            {
+
+                pSapCtx->aStaInfo[staId].rate_flags =
+                pSapEvent->sapevt.sapStationAssocReassocCompleteEvent.rate_flags;
+            }
+
             // Stop AP inactivity timer
             if (pHddApCtx->hdd_ap_inactivity_timer.state == VOS_TIMER_STATE_RUNNING)
             {