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/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 */