Commit 601ce21f authored by Igor Mitsyanko's avatar Igor Mitsyanko Committed by Kalle Valo

qtnfmac: implement extendable channel survey dump

Switch to extendable implementation of channel survey dump to make sure
that any new channel statistics can be added in the future without any
backwards compatibility issues. For this purpose use a separate variable
length bitmap to pass the list of valid statistics in firmware response.
Besides, switch to using channel frequency instead of IEEE channel
number to prepare for adding support of 6GHz band.
Signed-off-by: default avatarIgor Mitsyanko <igor.mitsyanko.os@quantenna.com>
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
parent bc5db734
......@@ -739,7 +739,6 @@ qtnf_dump_survey(struct wiphy *wiphy, struct net_device *dev,
struct ieee80211_supported_band *sband;
const struct cfg80211_chan_def *chandef = &wdev->chandef;
struct ieee80211_channel *chan;
struct qtnf_chan_stats stats;
int ret;
sband = wiphy->bands[NL80211_BAND_2GHZ];
......@@ -755,49 +754,16 @@ qtnf_dump_survey(struct wiphy *wiphy, struct net_device *dev,
return -ENOENT;
chan = &sband->channels[idx];
memset(&stats, 0, sizeof(stats));
survey->channel = chan;
survey->filled = 0x0;
if (chandef->chan) {
if (chan->hw_value == chandef->chan->hw_value)
survey->filled = SURVEY_INFO_IN_USE;
}
if (chan == chandef->chan)
survey->filled = SURVEY_INFO_IN_USE;
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:
ret = qtnf_cmd_get_chan_stats(mac, chan->center_freq, survey);
if (ret)
pr_debug("failed to get chan(%d) stats from card\n",
chan->hw_value);
break;
}
return ret;
}
......
......@@ -1566,62 +1566,6 @@ qtnf_cmd_resp_fill_band_info(struct ieee80211_supported_band *band,
return ret;
}
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;
......@@ -2468,8 +2412,104 @@ int qtnf_cmd_reg_notify(struct qtnf_wmac *mac, struct regulatory_request *req,
return ret;
}
int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u16 channel,
struct qtnf_chan_stats *stats)
static int
qtnf_cmd_resp_proc_chan_stat_info(struct survey_info *survey,
const u8 *payload, size_t payload_len)
{
const struct qlink_chan_stats *stats = NULL;
const struct qlink_tlv_hdr *tlv;
size_t tlv_full_len;
u16 tlv_value_len;
u16 tlv_type;
const u8 *map = NULL;
unsigned int map_len = 0;
unsigned int stats_len = 0;
tlv = (struct qlink_tlv_hdr *)payload;
while (payload_len >= sizeof(*tlv)) {
tlv_type = le16_to_cpu(tlv->type);
tlv_value_len = le16_to_cpu(tlv->len);
tlv_full_len = tlv_value_len + sizeof(*tlv);
if (tlv_full_len > payload_len) {
pr_warn("malformed TLV 0x%.2X; LEN: %u\n",
tlv_type, tlv_value_len);
return -ENOSPC;
}
switch (tlv_type) {
case QTN_TLV_ID_BITMAP:
map = tlv->val;
map_len = tlv_value_len;
break;
case QTN_TLV_ID_CHANNEL_STATS:
stats = (struct qlink_chan_stats *)tlv->val;
stats_len = tlv_value_len;
break;
default:
pr_info("Unknown TLV type: %#x\n", tlv_type);
break;
}
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;
}
if (!map || !stats)
return 0;
#define qtnf_chan_stat_avail(stat_name, bitn) \
(qtnf_utils_is_bit_set(map, bitn, map_len) && \
(offsetofend(struct qlink_chan_stats, stat_name) <= stats_len))
if (qtnf_chan_stat_avail(time_on, QLINK_CHAN_STAT_TIME_ON)) {
survey->filled |= SURVEY_INFO_TIME;
survey->time = le64_to_cpu(stats->time_on);
}
if (qtnf_chan_stat_avail(time_tx, QLINK_CHAN_STAT_TIME_TX)) {
survey->filled |= SURVEY_INFO_TIME_TX;
survey->time_tx = le64_to_cpu(stats->time_tx);
}
if (qtnf_chan_stat_avail(time_rx, QLINK_CHAN_STAT_TIME_RX)) {
survey->filled |= SURVEY_INFO_TIME_RX;
survey->time_rx = le64_to_cpu(stats->time_rx);
}
if (qtnf_chan_stat_avail(cca_busy, QLINK_CHAN_STAT_CCA_BUSY)) {
survey->filled |= SURVEY_INFO_TIME_BUSY;
survey->time_busy = le64_to_cpu(stats->cca_busy);
}
if (qtnf_chan_stat_avail(cca_busy_ext, QLINK_CHAN_STAT_CCA_BUSY_EXT)) {
survey->filled |= SURVEY_INFO_TIME_EXT_BUSY;
survey->time_ext_busy = le64_to_cpu(stats->cca_busy_ext);
}
if (qtnf_chan_stat_avail(time_scan, QLINK_CHAN_STAT_TIME_SCAN)) {
survey->filled |= SURVEY_INFO_TIME_SCAN;
survey->time_scan = le64_to_cpu(stats->time_scan);
}
if (qtnf_chan_stat_avail(chan_noise, QLINK_CHAN_STAT_CHAN_NOISE)) {
survey->filled |= SURVEY_INFO_NOISE_DBM;
survey->noise = stats->chan_noise;
}
#undef qtnf_chan_stat_avail
return 0;
}
int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u32 chan_freq,
struct survey_info *survey)
{
struct sk_buff *cmd_skb, *resp_skb = NULL;
struct qlink_cmd_get_chan_stats *cmd;
......@@ -2483,22 +2523,30 @@ int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u16 channel,
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);
cmd->channel_freq = cpu_to_le32(chan_freq);
qtnf_bus_lock(mac->bus);
ret = qtnf_cmd_send_with_reply(mac->bus, cmd_skb, &resp_skb,
sizeof(*resp), &var_data_len);
qtnf_bus_unlock(mac->bus);
if (ret)
goto out;
resp = (struct qlink_resp_get_chan_stats *)resp_skb->data;
ret = qtnf_cmd_resp_proc_chan_stat_info(stats, resp->info,
if (le32_to_cpu(resp->chan_freq) != chan_freq) {
pr_err("[MAC%u] channel stats freq %u != requested %u\n",
mac->macid, le32_to_cpu(resp->chan_freq), chan_freq);
ret = -EINVAL;
goto out;
}
ret = qtnf_cmd_resp_proc_chan_stat_info(survey, resp->info,
var_data_len);
out:
qtnf_bus_unlock(mac->bus);
consume_skb(resp_skb);
return ret;
......
......@@ -59,8 +59,8 @@ int qtnf_cmd_send_updown_intf(struct qtnf_vif *vif,
bool up);
int qtnf_cmd_reg_notify(struct qtnf_wmac *mac, struct regulatory_request *req,
bool slave_radar, bool dfs_offload);
int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u16 channel,
struct qtnf_chan_stats *stats);
int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u32 chan_freq,
struct survey_info *survey);
int qtnf_cmd_send_chan_switch(struct qtnf_vif *vif,
struct cfg80211_csa_settings *params);
int qtnf_cmd_get_channel(struct qtnf_vif *vif, struct cfg80211_chan_def *chdef);
......
......@@ -95,15 +95,6 @@ struct qtnf_mac_info {
struct wiphy_wowlan_support *wowlan;
};
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;
......
......@@ -632,11 +632,11 @@ struct qlink_cmd_band_info_get {
/**
* 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
* @channel_freq: channel center frequency
*/
struct qlink_cmd_get_chan_stats {
struct qlink_cmd chdr;
__le16 channel;
__le32 channel_freq;
} __packed;
/**
......@@ -1077,8 +1077,6 @@ enum qlink_sta_info_rate_flags {
*
* Response data containing statistics for specified STA.
*
* @filled: a bitmask of &enum qlink_sta_info, specifies which info in response
* is valid.
* @sta_addr: MAC address of STA the response carries statistic for.
* @info: variable statistics for specified STA.
*/
......@@ -1109,10 +1107,12 @@ struct qlink_resp_band_info_get {
/**
* struct qlink_resp_get_chan_stats - response for QLINK_CMD_CHAN_STATS cmd
*
* @chan_freq: center frequency for a channel the report is sent for.
* @info: variable-length channel info.
*/
struct qlink_resp_get_chan_stats {
struct qlink_cmd rhdr;
struct qlink_resp rhdr;
__le32 chan_freq;
u8 info[0];
} __packed;
......@@ -1373,6 +1373,8 @@ struct qlink_event_mic_failure {
* QTN_TLV_ID_STA_STATS is valid.
* &enum qlink_hw_capab listing wireless card capabilities.
* &enum qlink_driver_capab listing driver/host system capabilities.
* &enum qlink_chan_stat used to indicate which statistic carried in
* QTN_TLV_ID_CHANNEL_STATS is valid.
* @QTN_TLV_ID_STA_STATS: per-STA statistics as defined by
* &struct qlink_sta_stats. Valid values are marked as such in a bitmap
* carried by QTN_TLV_ID_BITMAP.
......@@ -1596,13 +1598,57 @@ struct qlink_tlv_iftype_data {
struct qlink_sband_iftype_data iftype_data[0];
} __packed;
/**
* enum qlink_chan_stat - channel statistics bitmap
*
* Used to indicate which statistics values in &struct qlink_chan_stats
* are valid. Individual values are used to fill a bitmap carried in a
* payload of QTN_TLV_ID_BITMAP.
*
* @QLINK_CHAN_STAT_TIME_ON: time_on value is valid.
* @QLINK_CHAN_STAT_TIME_TX: time_tx value is valid.
* @QLINK_CHAN_STAT_TIME_RX: time_rx value is valid.
* @QLINK_CHAN_STAT_CCA_BUSY: cca_busy value is valid.
* @QLINK_CHAN_STAT_CCA_BUSY_EXT: cca_busy_ext value is valid.
* @QLINK_CHAN_STAT_TIME_SCAN: time_scan value is valid.
* @QLINK_CHAN_STAT_CHAN_NOISE: chan_noise value is valid.
*/
enum qlink_chan_stat {
QLINK_CHAN_STAT_TIME_ON,
QLINK_CHAN_STAT_TIME_TX,
QLINK_CHAN_STAT_TIME_RX,
QLINK_CHAN_STAT_CCA_BUSY,
QLINK_CHAN_STAT_CCA_BUSY_EXT,
QLINK_CHAN_STAT_TIME_SCAN,
QLINK_CHAN_STAT_CHAN_NOISE,
QLINK_CHAN_STAT_NUM,
};
/**
* struct qlink_chan_stats - data for QTN_TLV_ID_CHANNEL_STATS
*
* Carries a per-channel statistics. Not all fields may be filled with
* valid values. Valid fields should be indicated as such using a bitmap of
* &enum qlink_chan_stat. Bitmap is carried separately in a payload of
* QTN_TLV_ID_BITMAP.
*
* @time_on: amount of time radio operated on that channel.
* @time_tx: amount of time radio spent transmitting on the channel.
* @time_rx: amount of time radio spent receiving on the channel.
* @cca_busy: amount of time the the primary channel was busy.
* @cca_busy_ext: amount of time the the secondary channel was busy.
* @time_scan: amount of radio spent scanning on the channel.
* @chan_noise: channel noise.
*/
struct qlink_chan_stats {
__le32 chan_num;
__le32 cca_tx;
__le32 cca_rx;
__le32 cca_busy;
__le32 cca_try;
__le64 time_on;
__le64 time_tx;
__le64 time_rx;
__le64 cca_busy;
__le64 cca_busy_ext;
__le64 time_scan;
s8 chan_noise;
u8 rsvd[3];
} __packed;
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment