qtnfmac: implement cfg80211 dump_survey handler
authorSergey Matyukevich <sergey.matyukevich.os@quantenna.com>
Thu, 27 Jul 2017 23:06:46 +0000 (02:06 +0300)
committerKalle Valo <kvalo@codeaurora.org>
Thu, 3 Aug 2017 09:58:11 +0000 (12:58 +0300)
This patch implements cfg80211 dump_survey handler enabling
per-channel survey data reports.

Signed-off-by: Igor Mitsyanko <igor.mitsyanko.os@quantenna.com>
Signed-off-by: Sergey Matyukevich <sergey.matyukevich.os@quantenna.com>
Signed-off-by: Avinash Patil <avinashp@quantenna.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
drivers/net/wireless/quantenna/qtnfmac/commands.c
drivers/net/wireless/quantenna/qtnfmac/commands.h
drivers/net/wireless/quantenna/qtnfmac/core.h
drivers/net/wireless/quantenna/qtnfmac/qlink.h

index e288b1d4432aaa0d9352204551d0216d47e9cadc..23f180b7d43cd3631f79cb03673d2552598f15c6 100644 (file)
@@ -677,6 +677,72 @@ qtnf_disconnect(struct wiphy *wiphy, struct net_device *dev,
        return 0;
 }
 
+static int
+qtnf_dump_survey(struct wiphy *wiphy, struct net_device *dev,
+                int idx, struct survey_info *survey)
+{
+       struct qtnf_wmac *mac = wiphy_priv(wiphy);
+       struct ieee80211_supported_band *sband;
+       struct ieee80211_channel *chan;
+       struct qtnf_chan_stats stats;
+       int ret;
+
+       sband = wiphy->bands[NL80211_BAND_2GHZ];
+       if (sband && idx >= sband->n_channels) {
+               idx -= sband->n_channels;
+               sband = NULL;
+       }
+
+       if (!sband)
+               sband = wiphy->bands[NL80211_BAND_5GHZ];
+
+       if (!sband || idx >= sband->n_channels)
+               return -ENOENT;
+
+       chan = &sband->channels[idx];
+       memset(&stats, 0, sizeof(stats));
+
+       survey->channel = chan;
+       survey->filled = 0x0;
+
+       ret = qtnf_cmd_get_chan_stats(mac, chan->hw_value, &stats);
+       switch (ret) {
+       case 0:
+               if (unlikely(stats.chan_num != chan->hw_value)) {
+                       pr_err("received stats for channel %d instead of %d\n",
+                              stats.chan_num, chan->hw_value);
+                       ret = -EINVAL;
+                       break;
+               }
+
+               survey->filled = SURVEY_INFO_TIME |
+                                SURVEY_INFO_TIME_SCAN |
+                                SURVEY_INFO_TIME_BUSY |
+                                SURVEY_INFO_TIME_RX |
+                                SURVEY_INFO_TIME_TX |
+                                SURVEY_INFO_NOISE_DBM;
+
+               survey->time_scan = stats.cca_try;
+               survey->time = stats.cca_try;
+               survey->time_tx = stats.cca_tx;
+               survey->time_rx = stats.cca_rx;
+               survey->time_busy = stats.cca_busy;
+               survey->noise = stats.chan_noise;
+               break;
+       case -ENOENT:
+               pr_debug("no stats for channel %u\n", chan->hw_value);
+               ret = 0;
+               break;
+       default:
+               pr_debug("failed to get chan(%d) stats from card\n",
+                        chan->hw_value);
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
 static struct cfg80211_ops qtn_cfg80211_ops = {
        .add_virtual_intf       = qtnf_add_virtual_intf,
        .change_virtual_intf    = qtnf_change_virtual_intf,
@@ -697,7 +763,8 @@ static struct cfg80211_ops qtn_cfg80211_ops = {
        .set_default_mgmt_key   = qtnf_set_default_mgmt_key,
        .scan                   = qtnf_scan,
        .connect                = qtnf_connect,
-       .disconnect             = qtnf_disconnect
+       .disconnect             = qtnf_disconnect,
+       .dump_survey            = qtnf_dump_survey
 };
 
 static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy_in,
index f5a294f3c2a4c080322ad6fe73259771958f9581..a1ce12082e1014c10da730f2ff981a245335701a 100644 (file)
@@ -1333,6 +1333,62 @@ static int qtnf_cmd_resp_proc_phy_params(struct qtnf_wmac *mac,
        return 0;
 }
 
+static int
+qtnf_cmd_resp_proc_chan_stat_info(struct qtnf_chan_stats *stats,
+                                 const u8 *payload, size_t payload_len)
+{
+       struct qlink_chan_stats *qlink_stats;
+       const struct qlink_tlv_hdr *tlv;
+       size_t tlv_full_len;
+       u16 tlv_value_len;
+       u16 tlv_type;
+
+       tlv = (struct qlink_tlv_hdr *)payload;
+       while (payload_len >= sizeof(struct qlink_tlv_hdr)) {
+               tlv_type = le16_to_cpu(tlv->type);
+               tlv_value_len = le16_to_cpu(tlv->len);
+               tlv_full_len = tlv_value_len + sizeof(struct qlink_tlv_hdr);
+               if (tlv_full_len > payload_len) {
+                       pr_warn("malformed TLV 0x%.2X; LEN: %u\n",
+                               tlv_type, tlv_value_len);
+                       return -EINVAL;
+               }
+               switch (tlv_type) {
+               case QTN_TLV_ID_CHANNEL_STATS:
+                       if (unlikely(tlv_value_len != sizeof(*qlink_stats))) {
+                               pr_err("invalid CHANNEL_STATS entry size\n");
+                               return -EINVAL;
+                       }
+
+                       qlink_stats = (void *)tlv->val;
+
+                       stats->chan_num = le32_to_cpu(qlink_stats->chan_num);
+                       stats->cca_tx = le32_to_cpu(qlink_stats->cca_tx);
+                       stats->cca_rx = le32_to_cpu(qlink_stats->cca_rx);
+                       stats->cca_busy = le32_to_cpu(qlink_stats->cca_busy);
+                       stats->cca_try = le32_to_cpu(qlink_stats->cca_try);
+                       stats->chan_noise = qlink_stats->chan_noise;
+
+                       pr_debug("chan(%u) try(%u) busy(%u) noise(%d)\n",
+                                stats->chan_num, stats->cca_try,
+                                stats->cca_busy, stats->chan_noise);
+                       break;
+               default:
+                       pr_warn("Unknown TLV type: %#x\n",
+                               le16_to_cpu(tlv->type));
+               }
+               payload_len -= tlv_full_len;
+               tlv = (struct qlink_tlv_hdr *)(tlv->val + tlv_value_len);
+       }
+
+       if (payload_len) {
+               pr_warn("malformed TLV buf; bytes left: %zu\n", payload_len);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 int qtnf_cmd_get_mac_info(struct qtnf_wmac *mac)
 {
        struct sk_buff *cmd_skb, *resp_skb = NULL;
@@ -2176,3 +2232,54 @@ out:
 
        return ret;
 }
+
+int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u16 channel,
+                           struct qtnf_chan_stats *stats)
+{
+       struct sk_buff *cmd_skb, *resp_skb = NULL;
+       struct qlink_cmd_get_chan_stats *cmd;
+       struct qlink_resp_get_chan_stats *resp;
+       size_t var_data_len;
+       u16 res_code = QLINK_CMD_RESULT_OK;
+       int ret = 0;
+
+       cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, QLINK_VIFID_RSVD,
+                                           QLINK_CMD_CHAN_STATS,
+                                           sizeof(*cmd));
+       if (!cmd_skb)
+               return -ENOMEM;
+
+       qtnf_bus_lock(mac->bus);
+
+       cmd = (struct qlink_cmd_get_chan_stats *)cmd_skb->data;
+       cmd->channel = cpu_to_le16(channel);
+
+       ret = qtnf_cmd_send_with_reply(mac->bus, cmd_skb, &resp_skb, &res_code,
+                                      sizeof(*resp), &var_data_len);
+       if (unlikely(ret)) {
+               qtnf_bus_unlock(mac->bus);
+               return ret;
+       }
+
+       if (unlikely(res_code != QLINK_CMD_RESULT_OK)) {
+               switch (res_code) {
+               case QLINK_CMD_RESULT_ENOTFOUND:
+                       ret = -ENOENT;
+                       break;
+               default:
+                       pr_err("cmd exec failed: 0x%.4X\n", res_code);
+                       ret = -EFAULT;
+                       break;
+               }
+               goto out;
+       }
+
+       resp = (struct qlink_resp_get_chan_stats *)resp_skb->data;
+       ret = qtnf_cmd_resp_proc_chan_stat_info(stats, resp->info,
+                                               var_data_len);
+
+out:
+       qtnf_bus_unlock(mac->bus);
+       consume_skb(resp_skb);
+       return ret;
+}
index 155b265d42bf515c402c074e81842d8db8de19a1..41e2d50988b70719caadc308def4e6a79c642640 100644 (file)
@@ -71,5 +71,7 @@ int qtnf_cmd_send_disconnect(struct qtnf_vif *vif,
 int qtnf_cmd_send_updown_intf(struct qtnf_vif *vif,
                              bool up);
 int qtnf_cmd_reg_notify(struct qtnf_bus *bus, struct regulatory_request *req);
+int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u16 channel,
+                           struct qtnf_chan_stats *stats);
 
 #endif /* QLINK_COMMANDS_H_ */
index 31b7ec2bfd3e0abe0c6459f17dee1b4c288561b1..b2d050c4f1e2c61f7f078f8271ddfb0467c8b9b7 100644 (file)
@@ -124,6 +124,15 @@ struct qtnf_mac_info {
        size_t n_limits;
 };
 
+struct qtnf_chan_stats {
+       u32 chan_num;
+       u32 cca_tx;
+       u32 cca_rx;
+       u32 cca_busy;
+       u32 cca_try;
+       s8 chan_noise;
+};
+
 struct qtnf_wmac {
        u8 macid;
        u8 wiphy_registered;
index e27833b78940743bf7150a0ad0c0c4b5104e3e74..dd01c0b4f6328d5b433939d7bab2494089fa0faf 100644 (file)
@@ -164,6 +164,7 @@ enum qlink_cmd_type {
        QLINK_CMD_CHANGE_STA            = 0x0051,
        QLINK_CMD_DEL_STA               = 0x0052,
        QLINK_CMD_SCAN                  = 0x0053,
+       QLINK_CMD_CHAN_STATS            = 0x0054,
        QLINK_CMD_CONNECT               = 0x0060,
        QLINK_CMD_DISCONNECT            = 0x0061,
 };
@@ -433,6 +434,16 @@ struct qlink_cmd_chans_info_get {
        u8 band;
 } __packed;
 
+/**
+ * struct qlink_cmd_get_chan_stats - data for QLINK_CMD_CHAN_STATS command
+ *
+ * @channel: channel number according to 802.11 17.3.8.3.2 and Annex J
+ */
+struct qlink_cmd_get_chan_stats {
+       struct qlink_cmd chdr;
+       __le16 channel;
+} __packed;
+
 /**
  * enum qlink_reg_initiator - Indicates the initiator of a reg domain request
  *
@@ -635,6 +646,16 @@ struct qlink_resp_phy_params {
        u8 info[0];
 } __packed;
 
+/**
+ * struct qlink_resp_get_chan_stats - response for QLINK_CMD_CHAN_STATS cmd
+ *
+ * @info: variable-length channel info.
+ */
+struct qlink_resp_get_chan_stats {
+       struct qlink_cmd rhdr;
+       u8 info[0];
+} __packed;
+
 /* QLINK Events messages related definitions
  */
 
@@ -807,6 +828,7 @@ enum qlink_tlv_id {
        QTN_TLV_ID_COVERAGE_CLASS       = 0x0213,
        QTN_TLV_ID_IFACE_LIMIT          = 0x0214,
        QTN_TLV_ID_NUM_IFACE_COMB       = 0x0215,
+       QTN_TLV_ID_CHANNEL_STATS        = 0x0216,
        QTN_TLV_ID_STA_BASIC_COUNTERS   = 0x0300,
        QTN_TLV_ID_STA_GENERIC_INFO     = 0x0301,
        QTN_TLV_ID_KEY                  = 0x0302,
@@ -1008,4 +1030,13 @@ struct qlink_auth_encr {
        u8 control_port_no_encrypt;
 } __packed;
 
+struct qlink_chan_stats {
+       __le32 chan_num;
+       __le32 cca_tx;
+       __le32 cca_rx;
+       __le32 cca_busy;
+       __le32 cca_try;
+       s8 chan_noise;
+} __packed;
+
 #endif /* _QTN_QLINK_H_ */