Hello Kalle and all,
Here is the next set of patches for qtnfmac driver. This set
enables the following features:
- radar detection and CAC support
- MAC-based ACL support
- make GET_STA_STATS format more flexible and future-proof
- inform wireless core about supported extended capabilities
Besides, several issues has been fixed. The most notable
among them are:
- fix RSSI data passed from qtnfmac to wireless core
- pass complete channel information between firmware and driver,
including both primary channel and center frequency
The last fix enables proper operation channel report by iw tool
and fixes ACS mode in hostapd.
Changes v1 -> v2
- remove conditional enablement of WIPHY_FLAG_HAVE_AP_SME when
non-zero max_acl_mac_addrs is reported by firmware
- rebase on top of the latest wireless-drivers-next/master
Regards,
Sergey
Igor Mitsyanko (6):
qtnfmac: check that MAC exists in regulatory notifier
qtnfmac: add support for radar detection and CAC
qtnfmac: change default interface mode from AP to STA
qtnfmac: check for passed channel being NULL in MGMT_TX
qtnfmac: modify GET_STA_STATS cmd format for
qtnfmac: keeping track of "generation" for STA info
Sergey Matyukevich (2):
qtnfmac: pass complete channel data between driver and
qtnfmac: fix rssi data passed to wireless core
Vasily Ulyanov (2):
qtnfmac: fill wiphy's extended capabilities
qtnfmac: support MAC address based access control
cfg80211.c | 60 ++++++++-
commands.c | 375 ++++++++++++++++++++++++++++++++++++++++++-----------------
commands.h | 5
core.c | 13 --
core.h | 8 -
event.c | 76 ++++++++++-
qlink.h | 340 ++++++++++++++++++++++++++++++++++++++++-------------
qlink_util.c | 78 +++++++-----
qlink_util.h | 4
util.c | 8 -
util.h | 4
11 files changed, 725 insertions(+), 246 deletions(-)
From: Igor Mitsyanko <[email protected]>
Implement two parts of radar handling logic:
- cfg80211 .start_radar_detect callback to allow nl80211 to initiate CAC
- radar event to allow wlan device to advertize CAC and radar events
Signed-off-by: Igor Mitsyanko <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 18 ++++++-
drivers/net/wireless/quantenna/qtnfmac/commands.c | 38 ++++++++++++++
drivers/net/wireless/quantenna/qtnfmac/commands.h | 3 ++
drivers/net/wireless/quantenna/qtnfmac/event.c | 61 +++++++++++++++++++++++
drivers/net/wireless/quantenna/qtnfmac/qlink.h | 36 +++++++++++++
5 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
index b8e5f32cebdf..d4a98d62ecbe 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -751,6 +751,21 @@ static int qtnf_channel_switch(struct wiphy *wiphy, struct net_device *dev,
return ret;
}
+static int qtnf_start_radar_detection(struct wiphy *wiphy,
+ struct net_device *ndev,
+ struct cfg80211_chan_def *chandef,
+ u32 cac_time_ms)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ int ret;
+
+ ret = qtnf_cmd_start_cac(vif, chandef, cac_time_ms);
+ if (ret)
+ pr_err("%s: failed to start CAC ret=%d\n", ndev->name, ret);
+
+ return ret;
+}
+
static struct cfg80211_ops qtn_cfg80211_ops = {
.add_virtual_intf = qtnf_add_virtual_intf,
.change_virtual_intf = qtnf_change_virtual_intf,
@@ -774,7 +789,8 @@ static struct cfg80211_ops qtn_cfg80211_ops = {
.disconnect = qtnf_disconnect,
.dump_survey = qtnf_dump_survey,
.get_channel = qtnf_get_channel,
- .channel_switch = qtnf_channel_switch
+ .channel_switch = qtnf_channel_switch,
+ .start_radar_detection = qtnf_start_radar_detection,
};
static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy_in,
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
index bed81f0cb1cd..7089f3eb7a87 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -2512,3 +2512,41 @@ int qtnf_cmd_get_channel(struct qtnf_vif *vif, struct cfg80211_chan_def *chdef)
consume_skb(resp_skb);
return ret;
}
+
+int qtnf_cmd_start_cac(const struct qtnf_vif *vif,
+ const struct cfg80211_chan_def *chdef,
+ u32 cac_time_ms)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_start_cac *cmd;
+ int ret;
+ u16 res_code;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_START_CAC,
+ sizeof(*cmd));
+ if (unlikely(!cmd_skb))
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_start_cac *)cmd_skb->data;
+ cmd->cac_time_ms = cpu_to_le32(cac_time_ms);
+ qlink_chandef_cfg2q(chdef, &cmd->chan);
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send(bus, cmd_skb, &res_code);
+ qtnf_bus_unlock(bus);
+
+ if (ret)
+ return ret;
+
+ switch (res_code) {
+ case QLINK_CMD_RESULT_OK:
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.h b/drivers/net/wireless/quantenna/qtnfmac/commands.h
index d981a76e5835..07a957af9a58 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.h
@@ -76,5 +76,8 @@ int qtnf_cmd_get_chan_stats(struct qtnf_wmac *mac, u16 channel,
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);
+int qtnf_cmd_start_cac(const struct qtnf_vif *vif,
+ const struct cfg80211_chan_def *chdef,
+ u32 cac_time_ms);
#endif /* QLINK_COMMANDS_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/event.c b/drivers/net/wireless/quantenna/qtnfmac/event.c
index a3a18d8469ae..9843ca36b74b 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/event.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/event.c
@@ -395,6 +395,63 @@ qtnf_event_handle_freq_change(struct qtnf_wmac *mac,
return 0;
}
+static int qtnf_event_handle_radar(struct qtnf_vif *vif,
+ const struct qlink_event_radar *ev,
+ u16 len)
+{
+ struct wiphy *wiphy = priv_to_wiphy(vif->mac);
+ struct cfg80211_chan_def chandef;
+
+ if (len < sizeof(*ev)) {
+ pr_err("MAC%u: payload is too short\n", vif->mac->macid);
+ return -EINVAL;
+ }
+
+ if (!wiphy->registered || !vif->netdev)
+ return 0;
+
+ qlink_chandef_q2cfg(wiphy, &ev->chan, &chandef);
+
+ if (!cfg80211_chandef_valid(&chandef)) {
+ pr_err("MAC%u: bad channel f1=%u f2=%u bw=%u\n",
+ vif->mac->macid,
+ chandef.center_freq1, chandef.center_freq2,
+ chandef.width);
+ return -EINVAL;
+ }
+
+ pr_info("%s: radar event=%u f1=%u f2=%u bw=%u\n",
+ vif->netdev->name, ev->event,
+ chandef.center_freq1, chandef.center_freq2,
+ chandef.width);
+
+ switch (ev->event) {
+ case QLINK_RADAR_DETECTED:
+ cfg80211_radar_event(wiphy, &chandef, GFP_KERNEL);
+ break;
+ case QLINK_RADAR_CAC_FINISHED:
+ if (!vif->wdev.cac_started)
+ break;
+
+ cfg80211_cac_event(vif->netdev, &chandef,
+ NL80211_RADAR_CAC_FINISHED, GFP_KERNEL);
+ break;
+ case QLINK_RADAR_CAC_ABORTED:
+ if (!vif->wdev.cac_started)
+ break;
+
+ cfg80211_cac_event(vif->netdev, &chandef,
+ NL80211_RADAR_CAC_ABORTED, GFP_KERNEL);
+ break;
+ default:
+ pr_warn("%s: unhandled radar event %u\n",
+ vif->netdev->name, ev->event);
+ break;
+ }
+
+ return 0;
+}
+
static int qtnf_event_parse(struct qtnf_wmac *mac,
const struct sk_buff *event_skb)
{
@@ -449,6 +506,10 @@ static int qtnf_event_parse(struct qtnf_wmac *mac,
ret = qtnf_event_handle_freq_change(mac, (const void *)event,
event_len);
break;
+ case QLINK_EVENT_RADAR:
+ ret = qtnf_event_handle_radar(vif, (const void *)event,
+ event_len);
+ break;
default:
pr_warn("unknown event type: %x\n", event_id);
break;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
index 534d11e4175a..3e3de4629a53 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -205,6 +205,7 @@ struct qlink_auth_encr {
* @QLINK_CMD_REG_NOTIFY: notify device about regulatory domain change. This
* command is supported only if device reports QLINK_HW_SUPPORTS_REG_UPDATE
* capability.
+ * @QLINK_CMD_START_CAC: start radar detection procedure on a specified channel.
*/
enum qlink_cmd_type {
QLINK_CMD_FW_INIT = 0x0001,
@@ -224,6 +225,7 @@ enum qlink_cmd_type {
QLINK_CMD_BAND_INFO_GET = 0x001A,
QLINK_CMD_CHAN_SWITCH = 0x001B,
QLINK_CMD_CHAN_GET = 0x001C,
+ QLINK_CMD_START_CAC = 0x001D,
QLINK_CMD_START_AP = 0x0021,
QLINK_CMD_STOP_AP = 0x0022,
QLINK_CMD_GET_STA_INFO = 0x0030,
@@ -617,6 +619,18 @@ struct qlink_cmd_start_ap {
u8 info[0];
} __packed;
+/**
+ * struct qlink_cmd_start_cac - data for QLINK_CMD_START_CAC command
+ *
+ * @chan: a channel to start a radar detection procedure on.
+ * @cac_time_ms: CAC time.
+ */
+struct qlink_cmd_start_cac {
+ struct qlink_cmd chdr;
+ struct qlink_chandef chan;
+ __le32 cac_time_ms;
+} __packed;
+
/* QLINK Command Responses messages related definitions
*/
@@ -814,6 +828,7 @@ enum qlink_event_type {
QLINK_EVENT_BSS_JOIN = 0x0026,
QLINK_EVENT_BSS_LEAVE = 0x0027,
QLINK_EVENT_FREQ_CHANGE = 0x0028,
+ QLINK_EVENT_RADAR = 0x0029,
};
/**
@@ -963,6 +978,27 @@ struct qlink_event_scan_complete {
__le32 flags;
} __packed;
+enum qlink_radar_event {
+ QLINK_RADAR_DETECTED,
+ QLINK_RADAR_CAC_FINISHED,
+ QLINK_RADAR_CAC_ABORTED,
+ QLINK_RADAR_NOP_FINISHED,
+ QLINK_RADAR_PRE_CAC_EXPIRED,
+};
+
+/**
+ * struct qlink_event_radar - data for QLINK_EVENT_RADAR event
+ *
+ * @chan: channel on which radar event happened.
+ * @event: radar event type, one of &enum qlink_radar_event.
+ */
+struct qlink_event_radar {
+ struct qlink_event ehdr;
+ struct qlink_chandef chan;
+ u8 event;
+ u8 rsvd[3];
+} __packed;
+
/* QLINK TLVs (Type-Length Values) definitions
*/
--
2.11.0
From: Igor Mitsyanko <[email protected]>
To mimic mac80211 behaviour, change default interface type from AP to STA.
Signed-off-by: Igor Mitsyanko <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 2 +-
drivers/net/wireless/quantenna/qtnfmac/core.c | 11 ++++-------
drivers/net/wireless/quantenna/qtnfmac/core.h | 3 +--
3 files changed, 6 insertions(+), 10 deletions(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
index d4a98d62ecbe..c026882bf9c7 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -190,7 +190,7 @@ static struct wireless_dev *qtnf_add_virtual_intf(struct wiphy *wiphy,
goto err_mac;
}
- if (qtnf_core_net_attach(mac, vif, name, name_assign_t, type)) {
+ if (qtnf_core_net_attach(mac, vif, name, name_assign_t)) {
pr_err("VIF%u.%u: failed to attach netdev\n", mac->macid,
vif->vifid);
goto err_net;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.c b/drivers/net/wireless/quantenna/qtnfmac/core.c
index 3423dc51198b..518a2fe927f7 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.c
@@ -258,7 +258,7 @@ static void qtnf_mac_init_primary_intf(struct qtnf_wmac *mac)
{
struct qtnf_vif *vif = &mac->iflist[QTNF_PRIMARY_VIF_IDX];
- vif->wdev.iftype = NL80211_IFTYPE_AP;
+ vif->wdev.iftype = NL80211_IFTYPE_STATION;
vif->bss_priority = QTNF_DEF_BSS_PRIORITY;
vif->wdev.wiphy = priv_to_wiphy(mac);
INIT_WORK(&vif->reset_work, qtnf_vif_reset_handler);
@@ -298,8 +298,7 @@ static struct qtnf_wmac *qtnf_core_mac_alloc(struct qtnf_bus *bus,
}
int qtnf_core_net_attach(struct qtnf_wmac *mac, struct qtnf_vif *vif,
- const char *name, unsigned char name_assign_type,
- enum nl80211_iftype iftype)
+ const char *name, unsigned char name_assign_type)
{
struct wiphy *wiphy = priv_to_wiphy(mac);
struct net_device *dev;
@@ -320,7 +319,6 @@ int qtnf_core_net_attach(struct qtnf_wmac *mac, struct qtnf_vif *vif,
dev->needs_free_netdev = true;
dev_net_set(dev, wiphy_net(wiphy));
dev->ieee80211_ptr = &vif->wdev;
- dev->ieee80211_ptr->iftype = iftype;
ether_addr_copy(dev->dev_addr, vif->mac_addr);
SET_NETDEV_DEV(dev, wiphy_dev(wiphy));
dev->flags |= IFF_BROADCAST | IFF_MULTICAST;
@@ -418,7 +416,7 @@ static int qtnf_core_mac_attach(struct qtnf_bus *bus, unsigned int macid)
goto error;
}
- ret = qtnf_cmd_send_add_intf(vif, NL80211_IFTYPE_AP, vif->mac_addr);
+ ret = qtnf_cmd_send_add_intf(vif, vif->wdev.iftype, vif->mac_addr);
if (ret) {
pr_err("MAC%u: failed to add VIF\n", macid);
goto error;
@@ -446,8 +444,7 @@ static int qtnf_core_mac_attach(struct qtnf_bus *bus, unsigned int macid)
rtnl_lock();
- ret = qtnf_core_net_attach(mac, vif, "wlan%d", NET_NAME_ENUM,
- NL80211_IFTYPE_AP);
+ ret = qtnf_core_net_attach(mac, vif, "wlan%d", NET_NAME_ENUM);
rtnl_unlock();
if (ret) {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.h b/drivers/net/wireless/quantenna/qtnfmac/core.h
index 1b7bc0318f3e..8e42d1bdc65f 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.h
@@ -144,8 +144,7 @@ struct qtnf_vif *qtnf_mac_get_free_vif(struct qtnf_wmac *mac);
struct qtnf_vif *qtnf_mac_get_base_vif(struct qtnf_wmac *mac);
struct wiphy *qtnf_wiphy_allocate(struct qtnf_bus *bus);
int qtnf_core_net_attach(struct qtnf_wmac *mac, struct qtnf_vif *priv,
- const char *name, unsigned char name_assign_type,
- enum nl80211_iftype iftype);
+ const char *name, unsigned char name_assign_type);
void qtnf_main_work_queue(struct work_struct *work);
int qtnf_cmd_send_update_phy_params(struct qtnf_wmac *mac, u32 changed);
int qtnf_cmd_send_get_phy_params(struct qtnf_wmac *mac);
--
2.11.0
From: Igor Mitsyanko <[email protected]>
Keep generation in per-VIF data structure and increment it whenever STA
list is changed. Use generation value to fill struct station_info when
required.
Signed-off-by: Igor Mitsyanko <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 5 ++++-
drivers/net/wireless/quantenna/qtnfmac/core.h | 1 +
drivers/net/wireless/quantenna/qtnfmac/event.c | 5 +++--
drivers/net/wireless/quantenna/qtnfmac/util.c | 8 ++++++--
drivers/net/wireless/quantenna/qtnfmac/util.h | 4 ++--
5 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
index be15d0ed362c..cedb18e04bc2 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -418,6 +418,7 @@ qtnf_get_station(struct wiphy *wiphy, struct net_device *dev,
{
struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ sinfo->generation = vif->generation;
return qtnf_cmd_get_sta_info(vif, mac, sinfo);
}
@@ -439,11 +440,13 @@ qtnf_dump_station(struct wiphy *wiphy, struct net_device *dev,
ret = qtnf_cmd_get_sta_info(vif, sta_node->mac_addr, sinfo);
if (unlikely(ret == -ENOENT)) {
- qtnf_sta_list_del(&vif->sta_list, mac);
+ qtnf_sta_list_del(vif, mac);
cfg80211_del_sta(vif->netdev, mac, GFP_KERNEL);
sinfo->filled = 0;
}
+ sinfo->generation = vif->generation;
+
return ret;
}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.h b/drivers/net/wireless/quantenna/qtnfmac/core.h
index d7e295efb07d..e7bd21ed371b 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.h
@@ -88,6 +88,7 @@ struct qtnf_vif {
struct work_struct reset_work;
struct qtnf_sta_list sta_list;
unsigned long cons_tx_timeout_cnt;
+ int generation;
};
struct qtnf_mac_info {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/event.c b/drivers/net/wireless/quantenna/qtnfmac/event.c
index b3489b5b5d9e..8a3d2b1194e4 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/event.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/event.c
@@ -59,10 +59,11 @@ qtnf_event_handle_sta_assoc(struct qtnf_wmac *mac, struct qtnf_vif *vif,
pr_debug("VIF%u.%u: MAC:%pM FC:%x\n", mac->macid, vif->vifid, sta_addr,
frame_control);
- qtnf_sta_list_add(&vif->sta_list, sta_addr);
+ qtnf_sta_list_add(vif, sta_addr);
sinfo.assoc_req_ies = NULL;
sinfo.assoc_req_ies_len = 0;
+ sinfo.generation = vif->generation;
payload_len = len - sizeof(*sta_assoc);
tlv = (const struct qlink_tlv_hdr *)sta_assoc->ies;
@@ -132,7 +133,7 @@ qtnf_event_handle_sta_deauth(struct qtnf_wmac *mac, struct qtnf_vif *vif,
pr_debug("VIF%u.%u: MAC:%pM reason:%x\n", mac->macid, vif->vifid,
sta_addr, reason);
- if (qtnf_sta_list_del(&vif->sta_list, sta_addr))
+ if (qtnf_sta_list_del(vif, sta_addr))
cfg80211_del_sta(vif->netdev, sta_deauth->sta_addr,
GFP_KERNEL);
diff --git a/drivers/net/wireless/quantenna/qtnfmac/util.c b/drivers/net/wireless/quantenna/qtnfmac/util.c
index ed38e87471bf..e745733ba417 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/util.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/util.c
@@ -57,9 +57,10 @@ struct qtnf_sta_node *qtnf_sta_list_lookup_index(struct qtnf_sta_list *list,
return NULL;
}
-struct qtnf_sta_node *qtnf_sta_list_add(struct qtnf_sta_list *list,
+struct qtnf_sta_node *qtnf_sta_list_add(struct qtnf_vif *vif,
const u8 *mac)
{
+ struct qtnf_sta_list *list = &vif->sta_list;
struct qtnf_sta_node *node;
if (unlikely(!mac))
@@ -77,13 +78,15 @@ struct qtnf_sta_node *qtnf_sta_list_add(struct qtnf_sta_list *list,
ether_addr_copy(node->mac_addr, mac);
list_add_tail(&node->list, &list->head);
atomic_inc(&list->size);
+ ++vif->generation;
done:
return node;
}
-bool qtnf_sta_list_del(struct qtnf_sta_list *list, const u8 *mac)
+bool qtnf_sta_list_del(struct qtnf_vif *vif, const u8 *mac)
{
+ struct qtnf_sta_list *list = &vif->sta_list;
struct qtnf_sta_node *node;
bool ret = false;
@@ -93,6 +96,7 @@ bool qtnf_sta_list_del(struct qtnf_sta_list *list, const u8 *mac)
list_del(&node->list);
atomic_dec(&list->size);
kfree(node);
+ ++vif->generation;
ret = true;
}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/util.h b/drivers/net/wireless/quantenna/qtnfmac/util.h
index 0359eae8c24b..0d4d92b11540 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/util.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/util.h
@@ -26,9 +26,9 @@ struct qtnf_sta_node *qtnf_sta_list_lookup(struct qtnf_sta_list *list,
const u8 *mac);
struct qtnf_sta_node *qtnf_sta_list_lookup_index(struct qtnf_sta_list *list,
size_t index);
-struct qtnf_sta_node *qtnf_sta_list_add(struct qtnf_sta_list *list,
+struct qtnf_sta_node *qtnf_sta_list_add(struct qtnf_vif *vif,
const u8 *mac);
-bool qtnf_sta_list_del(struct qtnf_sta_list *list, const u8 *mac);
+bool qtnf_sta_list_del(struct qtnf_vif *vif, const u8 *mac);
void qtnf_sta_list_free(struct qtnf_sta_list *list);
--
2.11.0
From: Vasily Ulyanov <[email protected]>
This allows a running AP to blacklist STAs by their MAC addresses
respecting the configured policy (either accept or deny unless listed).
It can be setup on .start_ap or with .set_mac_acl commands.
Signed-off-by: Vasily Ulyanov <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 16 ++++++
drivers/net/wireless/quantenna/qtnfmac/commands.c | 62 ++++++++++++++++++++++
drivers/net/wireless/quantenna/qtnfmac/commands.h | 2 +
drivers/net/wireless/quantenna/qtnfmac/core.h | 1 +
drivers/net/wireless/quantenna/qtnfmac/qlink.h | 37 ++++++++++++-
.../net/wireless/quantenna/qtnfmac/qlink_util.c | 18 +++++++
.../net/wireless/quantenna/qtnfmac/qlink_util.h | 2 +
7 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
index cedb18e04bc2..abf10996c26a 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -778,6 +778,20 @@ static int qtnf_start_radar_detection(struct wiphy *wiphy,
return ret;
}
+static int qtnf_set_mac_acl(struct wiphy *wiphy,
+ struct net_device *dev,
+ const struct cfg80211_acl_data *params)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ ret = qtnf_cmd_set_mac_acl(vif, params);
+ if (ret)
+ pr_err("%s: failed to set mac ACL ret=%d\n", dev->name, ret);
+
+ return ret;
+}
+
static struct cfg80211_ops qtn_cfg80211_ops = {
.add_virtual_intf = qtnf_add_virtual_intf,
.change_virtual_intf = qtnf_change_virtual_intf,
@@ -803,6 +817,7 @@ static struct cfg80211_ops qtn_cfg80211_ops = {
.get_channel = qtnf_get_channel,
.channel_switch = qtnf_channel_switch,
.start_radar_detection = qtnf_start_radar_detection,
+ .set_mac_acl = qtnf_set_mac_acl,
};
static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy_in,
@@ -918,6 +933,7 @@ int qtnf_wiphy_register(struct qtnf_hw_info *hw_info, struct qtnf_wmac *mac)
wiphy->max_scan_ie_len = QTNF_MAX_VSIE_LEN;
wiphy->mgmt_stypes = qtnf_mgmt_stypes;
wiphy->max_remain_on_channel_duration = 5000;
+ wiphy->max_acl_mac_addrs = mac->macinfo.max_acl_mac_addrs;
wiphy->iface_combinations = iface_comb;
wiphy->n_iface_combinations = 1;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
index 38b9c1078058..6ffe4837bbdb 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -162,6 +162,14 @@ static void qtnf_cmd_tlv_ie_set_add(struct sk_buff *cmd_skb, u8 frame_type,
memcpy(tlv->ie_data, buf, len);
}
+static inline size_t qtnf_cmd_acl_data_size(const struct cfg80211_acl_data *acl)
+{
+ size_t size = sizeof(struct qlink_acl_data) +
+ acl->n_acl_entries * sizeof(struct qlink_mac_address);
+
+ return size;
+}
+
static bool qtnf_cmd_start_ap_can_fit(const struct qtnf_vif *vif,
const struct cfg80211_ap_settings *s)
{
@@ -178,6 +186,9 @@ static bool qtnf_cmd_start_ap_can_fit(const struct qtnf_vif *vif,
if (cfg80211_chandef_valid(&s->chandef))
len += sizeof(struct qlink_tlv_chandef);
+ if (s->acl)
+ len += qtnf_cmd_acl_data_size(s->acl);
+
if (len > (sizeof(struct qlink_cmd) + QTNF_MAX_CMD_BUF_SIZE)) {
pr_err("VIF%u.%u: can not fit AP settings: %u\n",
vif->mac->macid, vif->vifid, len);
@@ -283,6 +294,16 @@ int qtnf_cmd_send_start_ap(struct qtnf_vif *vif,
memcpy(tlv->val, s->vht_cap, sizeof(*s->vht_cap));
}
+ if (s->acl) {
+ size_t acl_size = qtnf_cmd_acl_data_size(s->acl);
+ struct qlink_tlv_hdr *tlv =
+ skb_put(cmd_skb, sizeof(*tlv) + acl_size);
+
+ tlv->type = cpu_to_le16(QTN_TLV_ID_ACL_DATA);
+ tlv->len = cpu_to_le16(acl_size);
+ qlink_acl_data_cfg2q(s->acl, (struct qlink_acl_data *)tlv->val);
+ }
+
qtnf_bus_lock(vif->mac->bus);
ret = qtnf_cmd_send(vif->mac->bus, cmd_skb, &res_code);
@@ -1206,6 +1227,7 @@ qtnf_cmd_resp_proc_mac_info(struct qtnf_wmac *mac,
mac_info->radar_detect_widths =
qlink_chan_width_mask_to_nl(le16_to_cpu(
resp_info->radar_detect_widths));
+ mac_info->max_acl_mac_addrs = le32_to_cpu(resp_info->max_acl_mac_addrs);
memcpy(&mac_info->ht_cap_mod_mask, &resp_info->ht_cap_mod_mask,
sizeof(mac_info->ht_cap_mod_mask));
@@ -2609,3 +2631,43 @@ int qtnf_cmd_start_cac(const struct qtnf_vif *vif,
return ret;
}
+
+int qtnf_cmd_set_mac_acl(const struct qtnf_vif *vif,
+ const struct cfg80211_acl_data *params)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_set_mac_acl *cmd;
+ u16 res_code;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_SET_MAC_ACL,
+ sizeof(*cmd) +
+ qtnf_cmd_acl_data_size(params));
+ if (unlikely(!cmd_skb))
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_set_mac_acl *)cmd_skb->data;
+ qlink_acl_data_cfg2q(params, &cmd->acl);
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send(bus, cmd_skb, &res_code);
+ qtnf_bus_unlock(bus);
+
+ if (unlikely(ret))
+ return ret;
+
+ switch (res_code) {
+ case QLINK_CMD_RESULT_OK:
+ break;
+ case QLINK_CMD_RESULT_INVALID:
+ ret = -EINVAL;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.h b/drivers/net/wireless/quantenna/qtnfmac/commands.h
index 07a957af9a58..69a7d56f7e58 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.h
@@ -79,5 +79,7 @@ int qtnf_cmd_get_channel(struct qtnf_vif *vif, struct cfg80211_chan_def *chdef);
int qtnf_cmd_start_cac(const struct qtnf_vif *vif,
const struct cfg80211_chan_def *chdef,
u32 cac_time_ms);
+int qtnf_cmd_set_mac_acl(const struct qtnf_vif *vif,
+ const struct cfg80211_acl_data *params);
#endif /* QLINK_COMMANDS_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.h b/drivers/net/wireless/quantenna/qtnfmac/core.h
index e7bd21ed371b..c10900162297 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.h
@@ -103,6 +103,7 @@ struct qtnf_mac_info {
u8 sretry_limit;
u8 coverage_class;
u8 radar_detect_widths;
+ u32 max_acl_mac_addrs;
struct ieee80211_ht_cap ht_cap_mod_mask;
struct ieee80211_vht_cap vht_cap_mod_mask;
struct ieee80211_iface_limit *limits;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
index 5d98000b0f5b..6a1f960228a1 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -19,7 +19,7 @@
#include <linux/ieee80211.h>
-#define QLINK_PROTO_VER 9
+#define QLINK_PROTO_VER 10
#define QLINK_MACID_RSVD 0xFF
#define QLINK_VIFID_RSVD 0xFF
@@ -239,6 +239,7 @@ enum qlink_cmd_type {
QLINK_CMD_START_CAC = 0x001D,
QLINK_CMD_START_AP = 0x0021,
QLINK_CMD_STOP_AP = 0x0022,
+ QLINK_CMD_SET_MAC_ACL = 0x0023,
QLINK_CMD_GET_STA_INFO = 0x0030,
QLINK_CMD_ADD_KEY = 0x0040,
QLINK_CMD_DEL_KEY = 0x0041,
@@ -640,6 +641,38 @@ struct qlink_cmd_start_cac {
__le32 cac_time_ms;
} __packed;
+enum qlink_acl_policy {
+ QLINK_ACL_POLICY_ACCEPT_UNLESS_LISTED,
+ QLINK_ACL_POLICY_DENY_UNLESS_LISTED,
+};
+
+struct qlink_mac_address {
+ u8 addr[ETH_ALEN];
+} __packed;
+
+/**
+ * struct qlink_acl_data - ACL data
+ *
+ * @policy: filter policy, one of &enum qlink_acl_policy.
+ * @num_entries: number of MAC addresses in array.
+ * @mac_address: MAC addresses array.
+ */
+struct qlink_acl_data {
+ __le32 policy;
+ __le32 num_entries;
+ struct qlink_mac_address mac_addrs[0];
+} __packed;
+
+/**
+ * struct qlink_cmd_set_mac_acl - data for QLINK_CMD_SET_MAC_ACL command
+ *
+ * @acl: ACL data.
+ */
+struct qlink_cmd_set_mac_acl {
+ struct qlink_cmd chdr;
+ struct qlink_acl_data acl;
+} __packed;
+
/* QLINK Command Responses messages related definitions
*/
@@ -701,6 +734,7 @@ struct qlink_resp_get_mac_info {
struct ieee80211_ht_cap ht_cap_mod_mask;
__le16 max_ap_assoc_sta;
__le16 radar_detect_widths;
+ __le32 max_acl_mac_addrs;
u8 bands_cap;
u8 rsvd[1];
u8 var_info[0];
@@ -1049,6 +1083,7 @@ enum qlink_tlv_id {
QTN_TLV_ID_SEQ = 0x0303,
QTN_TLV_ID_IE_SET = 0x0305,
QTN_TLV_ID_EXT_CAPABILITY_MASK = 0x0306,
+ QTN_TLV_ID_ACL_DATA = 0x0307,
};
struct qlink_tlv_hdr {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
index 19981d6440b6..aeeda81b09ea 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
@@ -176,3 +176,21 @@ bool qtnf_utils_is_bit_set(const u8 *arr, unsigned int bit,
return arr[idx] & mask;
}
+
+void qlink_acl_data_cfg2q(const struct cfg80211_acl_data *acl,
+ struct qlink_acl_data *qacl)
+{
+ switch (acl->acl_policy) {
+ case NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED:
+ qacl->policy =
+ cpu_to_le32(QLINK_ACL_POLICY_ACCEPT_UNLESS_LISTED);
+ break;
+ case NL80211_ACL_POLICY_DENY_UNLESS_LISTED:
+ qacl->policy = cpu_to_le32(QLINK_ACL_POLICY_DENY_UNLESS_LISTED);
+ break;
+ }
+
+ qacl->num_entries = cpu_to_le32(acl->n_acl_entries);
+ memcpy(qacl->mac_addrs, acl->mac_addrs,
+ acl->n_acl_entries * sizeof(*qacl->mac_addrs));
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
index 6c24561eb41f..54caeb38917c 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
@@ -71,5 +71,7 @@ void qlink_chandef_cfg2q(const struct cfg80211_chan_def *chdef,
enum qlink_hidden_ssid qlink_hidden_ssid_nl2q(enum nl80211_hidden_ssid nl_val);
bool qtnf_utils_is_bit_set(const u8 *arr, unsigned int bit,
unsigned int arr_max_len);
+void qlink_acl_data_cfg2q(const struct cfg80211_acl_data *acl,
+ struct qlink_acl_data *qacl);
#endif /* _QTN_FMAC_QLINK_UTIL_H_ */
--
2.11.0
From: Vasily Ulyanov <[email protected]>
These are needed to inform userspace about features the hardware
supports (e.g. BSS Transition Management 802.11v)
Signed-off-by: Vasily Ulyanov <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/commands.c | 44 +++++++++++++++++++++++
drivers/net/wireless/quantenna/qtnfmac/core.c | 2 ++
drivers/net/wireless/quantenna/qtnfmac/core.h | 3 ++
drivers/net/wireless/quantenna/qtnfmac/qlink.h | 1 +
4 files changed, 50 insertions(+)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
index 7089f3eb7a87..839e86b99837 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -1029,6 +1029,10 @@ static int qtnf_parse_variable_mac_info(struct qtnf_wmac *mac,
struct qlink_iface_comb_num *comb;
size_t tlv_full_len;
const struct qlink_tlv_hdr *tlv;
+ u8 *ext_capa = NULL;
+ u8 *ext_capa_mask = NULL;
+ u8 ext_capa_len = 0;
+ u8 ext_capa_mask_len = 0;
mac->macinfo.n_limits = 0;
@@ -1092,6 +1096,18 @@ static int qtnf_parse_variable_mac_info(struct qtnf_wmac *mac,
if (limits[rec].types)
rec++;
break;
+ case WLAN_EID_EXT_CAPABILITY:
+ if (unlikely(tlv_value_len > U8_MAX))
+ return -EINVAL;
+ ext_capa = (u8 *)tlv->val;
+ ext_capa_len = tlv_value_len;
+ break;
+ case QTN_TLV_ID_EXT_CAPABILITY_MASK:
+ if (unlikely(tlv_value_len > U8_MAX))
+ return -EINVAL;
+ ext_capa_mask = (u8 *)tlv->val;
+ ext_capa_mask_len = tlv_value_len;
+ break;
default:
break;
}
@@ -1112,6 +1128,34 @@ static int qtnf_parse_variable_mac_info(struct qtnf_wmac *mac,
return -EINVAL;
}
+ if (ext_capa_len != ext_capa_mask_len) {
+ pr_err("MAC%u: ext_capa/_mask lengths mismatch: %u != %u\n",
+ mac->macid, ext_capa_len, ext_capa_mask_len);
+ return -EINVAL;
+ }
+
+ if (ext_capa_len > 0) {
+ ext_capa = kmemdup(ext_capa, ext_capa_len, GFP_KERNEL);
+ if (!ext_capa)
+ return -ENOMEM;
+
+ ext_capa_mask =
+ kmemdup(ext_capa_mask, ext_capa_mask_len, GFP_KERNEL);
+ if (!ext_capa_mask) {
+ kfree(ext_capa);
+ return -ENOMEM;
+ }
+ } else {
+ ext_capa = NULL;
+ ext_capa_mask = NULL;
+ }
+
+ kfree(mac->macinfo.extended_capabilities);
+ kfree(mac->macinfo.extended_capabilities_mask);
+ mac->macinfo.extended_capabilities = ext_capa;
+ mac->macinfo.extended_capabilities_mask = ext_capa_mask;
+ mac->macinfo.extended_capabilities_len = ext_capa_len;
+
return 0;
}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.c b/drivers/net/wireless/quantenna/qtnfmac/core.c
index 518a2fe927f7..ccd982b1c957 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.c
@@ -381,6 +381,8 @@ static void qtnf_core_mac_detach(struct qtnf_bus *bus, unsigned int macid)
}
kfree(mac->macinfo.limits);
+ kfree(mac->macinfo.extended_capabilities);
+ kfree(mac->macinfo.extended_capabilities_mask);
kfree(wiphy->iface_combinations);
wiphy_free(wiphy);
bus->mac[macid] = NULL;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.h b/drivers/net/wireless/quantenna/qtnfmac/core.h
index 8e42d1bdc65f..d7e295efb07d 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.h
@@ -106,6 +106,9 @@ struct qtnf_mac_info {
struct ieee80211_vht_cap vht_cap_mod_mask;
struct ieee80211_iface_limit *limits;
size_t n_limits;
+ u8 *extended_capabilities;
+ u8 *extended_capabilities_mask;
+ u8 extended_capabilities_len;
};
struct qtnf_chan_stats {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
index 1f150be98820..f4d7d1603e3c 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -1020,6 +1020,7 @@ enum qlink_tlv_id {
QTN_TLV_ID_KEY = 0x0302,
QTN_TLV_ID_SEQ = 0x0303,
QTN_TLV_ID_IE_SET = 0x0305,
+ QTN_TLV_ID_EXT_CAPABILITY_MASK = 0x0306,
};
struct qlink_tlv_hdr {
--
2.11.0
From: Igor Mitsyanko <[email protected]>
A set of per-STA statistics can potentially change quite often.
To ensure backwards and forward compatibility,
modify GET_STA_STATS command format:
- introduce two TLV types
- first TLV is a variable-sized bitmap of statistics values
that are filled by firmware
- second TLV is a structure with statistics itself
Only values specified in first TLV are valid.
Signed-off-by: Igor Mitsyanko <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/commands.c | 217 +++++++++++----------
drivers/net/wireless/quantenna/qtnfmac/qlink.h | 204 ++++++++++++-------
.../net/wireless/quantenna/qtnfmac/qlink_util.c | 12 ++
.../net/wireless/quantenna/qtnfmac/qlink_util.h | 2 +
4 files changed, 267 insertions(+), 168 deletions(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
index 839e86b99837..38b9c1078058 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -461,30 +461,8 @@ int qtnf_cmd_send_mgmt_set_appie(struct qtnf_vif *vif, u8 frame_type,
}
static void
-qtnf_sta_info_parse_basic_counters(struct station_info *sinfo,
- const struct qlink_sta_stat_basic_counters *counters)
-{
- sinfo->filled |= BIT(NL80211_STA_INFO_RX_BYTES) |
- BIT(NL80211_STA_INFO_TX_BYTES);
- sinfo->rx_bytes = get_unaligned_le64(&counters->rx_bytes);
- sinfo->tx_bytes = get_unaligned_le64(&counters->tx_bytes);
-
- sinfo->filled |= BIT(NL80211_STA_INFO_RX_PACKETS) |
- BIT(NL80211_STA_INFO_TX_PACKETS) |
- BIT(NL80211_STA_INFO_BEACON_RX);
- sinfo->rx_packets = get_unaligned_le32(&counters->rx_packets);
- sinfo->tx_packets = get_unaligned_le32(&counters->tx_packets);
- sinfo->rx_beacon = get_unaligned_le64(&counters->rx_beacons);
-
- sinfo->filled |= BIT(NL80211_STA_INFO_RX_DROP_MISC) |
- BIT(NL80211_STA_INFO_TX_FAILED);
- sinfo->rx_dropped_misc = get_unaligned_le32(&counters->rx_dropped);
- sinfo->tx_failed = get_unaligned_le32(&counters->tx_failed);
-}
-
-static void
qtnf_sta_info_parse_rate(struct rate_info *rate_dst,
- const struct qlink_sta_info_rate *rate_src)
+ const struct qlink_sta_info_rate *rate_src)
{
rate_dst->legacy = get_unaligned_le16(&rate_src->rate) * 10;
@@ -493,22 +471,23 @@ qtnf_sta_info_parse_rate(struct rate_info *rate_dst,
rate_dst->flags = 0;
switch (rate_src->bw) {
- case QLINK_STA_INFO_RATE_BW_5:
+ case QLINK_CHAN_WIDTH_5:
rate_dst->bw = RATE_INFO_BW_5;
break;
- case QLINK_STA_INFO_RATE_BW_10:
+ case QLINK_CHAN_WIDTH_10:
rate_dst->bw = RATE_INFO_BW_10;
break;
- case QLINK_STA_INFO_RATE_BW_20:
+ case QLINK_CHAN_WIDTH_20:
+ case QLINK_CHAN_WIDTH_20_NOHT:
rate_dst->bw = RATE_INFO_BW_20;
break;
- case QLINK_STA_INFO_RATE_BW_40:
+ case QLINK_CHAN_WIDTH_40:
rate_dst->bw = RATE_INFO_BW_40;
break;
- case QLINK_STA_INFO_RATE_BW_80:
+ case QLINK_CHAN_WIDTH_80:
rate_dst->bw = RATE_INFO_BW_80;
break;
- case QLINK_STA_INFO_RATE_BW_160:
+ case QLINK_CHAN_WIDTH_160:
rate_dst->bw = RATE_INFO_BW_160;
break;
default:
@@ -578,87 +557,125 @@ qtnf_sta_info_parse_flags(struct nl80211_sta_flag_update *dst,
}
static void
-qtnf_sta_info_parse_generic_info(struct station_info *sinfo,
- const struct qlink_sta_info_generic *info)
+qtnf_cmd_sta_info_parse(struct station_info *sinfo,
+ const struct qlink_tlv_hdr *tlv,
+ size_t resp_size)
{
- sinfo->filled |= BIT(NL80211_STA_INFO_CONNECTED_TIME) |
- BIT(NL80211_STA_INFO_INACTIVE_TIME);
- sinfo->connected_time = get_unaligned_le32(&info->connected_time);
- sinfo->inactive_time = get_unaligned_le32(&info->inactive_time);
+ const struct qlink_sta_stats *stats = NULL;
+ const u8 *map = NULL;
+ unsigned int map_len = 0;
+ unsigned int stats_len = 0;
+ u16 tlv_len;
+
+#define qtnf_sta_stat_avail(stat_name, bitn) \
+ (qtnf_utils_is_bit_set(map, bitn, map_len) && \
+ (offsetofend(struct qlink_sta_stats, stat_name) <= stats_len))
+
+ while (resp_size >= sizeof(*tlv)) {
+ tlv_len = le16_to_cpu(tlv->len);
+
+ switch (le16_to_cpu(tlv->type)) {
+ case QTN_TLV_ID_STA_STATS_MAP:
+ map_len = tlv_len;
+ map = tlv->val;
+ break;
+ case QTN_TLV_ID_STA_STATS:
+ stats_len = tlv_len;
+ stats = (const struct qlink_sta_stats *)tlv->val;
+ break;
+ default:
+ break;
+ }
+
+ resp_size -= tlv_len + sizeof(*tlv);
+ tlv = (const struct qlink_tlv_hdr *)(tlv->val + tlv_len);
+ }
- sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL) |
- BIT(NL80211_STA_INFO_SIGNAL_AVG);
- sinfo->signal = info->rssi - 120;
- sinfo->signal_avg = info->rssi_avg - QLINK_RSSI_OFFSET;
+ if (!map || !stats)
+ return;
+
+ if (qtnf_sta_stat_avail(inactive_time, QLINK_STA_INFO_INACTIVE_TIME)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_INACTIVE_TIME);
+ sinfo->inactive_time = le32_to_cpu(stats->inactive_time);
+ }
- if (info->rx_rate.rate) {
+ if (qtnf_sta_stat_avail(connected_time,
+ QLINK_STA_INFO_CONNECTED_TIME)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_CONNECTED_TIME);
+ sinfo->connected_time = le32_to_cpu(stats->connected_time);
+ }
+
+ if (qtnf_sta_stat_avail(signal, QLINK_STA_INFO_SIGNAL)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL);
+ sinfo->signal = stats->signal - QLINK_RSSI_OFFSET;
+ }
+
+ if (qtnf_sta_stat_avail(signal_avg, QLINK_STA_INFO_SIGNAL_AVG)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL_AVG);
+ sinfo->signal_avg = stats->signal_avg - QLINK_RSSI_OFFSET;
+ }
+
+ if (qtnf_sta_stat_avail(rxrate, QLINK_STA_INFO_RX_BITRATE)) {
sinfo->filled |= BIT(NL80211_STA_INFO_RX_BITRATE);
- qtnf_sta_info_parse_rate(&sinfo->rxrate, &info->rx_rate);
+ qtnf_sta_info_parse_rate(&sinfo->rxrate, &stats->rxrate);
}
- if (info->tx_rate.rate) {
+ if (qtnf_sta_stat_avail(txrate, QLINK_STA_INFO_TX_BITRATE)) {
sinfo->filled |= BIT(NL80211_STA_INFO_TX_BITRATE);
- qtnf_sta_info_parse_rate(&sinfo->txrate, &info->tx_rate);
+ qtnf_sta_info_parse_rate(&sinfo->txrate, &stats->txrate);
}
- sinfo->filled |= BIT(NL80211_STA_INFO_STA_FLAGS);
- qtnf_sta_info_parse_flags(&sinfo->sta_flags, &info->state);
-}
+ if (qtnf_sta_stat_avail(sta_flags, QLINK_STA_INFO_STA_FLAGS)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_STA_FLAGS);
+ qtnf_sta_info_parse_flags(&sinfo->sta_flags, &stats->sta_flags);
+ }
-static int qtnf_cmd_sta_info_parse(struct station_info *sinfo,
- const u8 *payload, size_t payload_size)
-{
- const struct qlink_sta_stat_basic_counters *counters;
- const struct qlink_sta_info_generic *sta_info;
- u16 tlv_type;
- u16 tlv_value_len;
- size_t tlv_full_len;
- const struct qlink_tlv_hdr *tlv;
+ if (qtnf_sta_stat_avail(rx_bytes, QLINK_STA_INFO_RX_BYTES)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_RX_BYTES);
+ sinfo->rx_bytes = le64_to_cpu(stats->rx_bytes);
+ }
- sinfo->filled = 0;
+ if (qtnf_sta_stat_avail(tx_bytes, QLINK_STA_INFO_TX_BYTES)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_TX_BYTES);
+ sinfo->tx_bytes = le64_to_cpu(stats->tx_bytes);
+ }
- tlv = (const struct qlink_tlv_hdr *)payload;
- while (payload_size >= 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_size) {
- pr_warn("malformed TLV 0x%.2X; LEN: %u\n",
- tlv_type, tlv_value_len);
- return -EINVAL;
- }
- switch (tlv_type) {
- case QTN_TLV_ID_STA_BASIC_COUNTERS:
- if (unlikely(tlv_value_len < sizeof(*counters))) {
- pr_err("invalid TLV size %.4X: %u\n",
- tlv_type, tlv_value_len);
- break;
- }
+ if (qtnf_sta_stat_avail(rx_bytes, QLINK_STA_INFO_RX_BYTES64)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_RX_BYTES64);
+ sinfo->rx_bytes = le64_to_cpu(stats->rx_bytes);
+ }
- counters = (void *)tlv->val;
- qtnf_sta_info_parse_basic_counters(sinfo, counters);
- break;
- case QTN_TLV_ID_STA_GENERIC_INFO:
- if (unlikely(tlv_value_len < sizeof(*sta_info)))
- break;
+ if (qtnf_sta_stat_avail(tx_bytes, QLINK_STA_INFO_TX_BYTES64)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_TX_BYTES64);
+ sinfo->tx_bytes = le64_to_cpu(stats->tx_bytes);
+ }
- sta_info = (void *)tlv->val;
- qtnf_sta_info_parse_generic_info(sinfo, sta_info);
- break;
- default:
- pr_warn("unexpected TLV type: %.4X\n", tlv_type);
- break;
- }
- payload_size -= tlv_full_len;
- tlv = (struct qlink_tlv_hdr *)(tlv->val + tlv_value_len);
+ if (qtnf_sta_stat_avail(rx_packets, QLINK_STA_INFO_RX_PACKETS)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_RX_PACKETS);
+ sinfo->rx_packets = le32_to_cpu(stats->rx_packets);
}
- if (payload_size) {
- pr_warn("malformed TLV buf; bytes left: %zu\n", payload_size);
- return -EINVAL;
+ if (qtnf_sta_stat_avail(tx_packets, QLINK_STA_INFO_TX_PACKETS)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_TX_PACKETS);
+ sinfo->tx_packets = le32_to_cpu(stats->tx_packets);
}
- return 0;
+ if (qtnf_sta_stat_avail(rx_beacon, QLINK_STA_INFO_BEACON_RX)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_BEACON_RX);
+ sinfo->rx_beacon = le64_to_cpu(stats->rx_beacon);
+ }
+
+ if (qtnf_sta_stat_avail(rx_dropped_misc, QLINK_STA_INFO_RX_DROP_MISC)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_RX_DROP_MISC);
+ sinfo->rx_dropped_misc = le32_to_cpu(stats->rx_dropped_misc);
+ }
+
+ if (qtnf_sta_stat_avail(tx_failed, QLINK_STA_INFO_TX_FAILED)) {
+ sinfo->filled |= BIT(NL80211_STA_INFO_TX_FAILED);
+ sinfo->tx_failed = le32_to_cpu(stats->tx_failed);
+ }
+
+#undef qtnf_sta_stat_avail
}
int qtnf_cmd_get_sta_info(struct qtnf_vif *vif, const u8 *sta_mac,
@@ -715,7 +732,9 @@ int qtnf_cmd_get_sta_info(struct qtnf_vif *vif, const u8 *sta_mac,
goto out;
}
- ret = qtnf_cmd_sta_info_parse(sinfo, resp->info, var_resp_len);
+ qtnf_cmd_sta_info_parse(sinfo,
+ (const struct qlink_tlv_hdr *)resp->info,
+ var_resp_len);
out:
qtnf_bus_unlock(vif->mac->bus);
@@ -1992,21 +2011,17 @@ int qtnf_cmd_send_change_sta(struct qtnf_vif *vif, const u8 *mac,
cmd = (struct qlink_cmd_change_sta *)cmd_skb->data;
ether_addr_copy(cmd->sta_addr, mac);
+ cmd->flag_update.mask =
+ cpu_to_le32(qtnf_encode_sta_flags(params->sta_flags_mask));
+ cmd->flag_update.value =
+ cpu_to_le32(qtnf_encode_sta_flags(params->sta_flags_set));
switch (vif->wdev.iftype) {
case NL80211_IFTYPE_AP:
cmd->if_type = cpu_to_le16(QLINK_IFTYPE_AP);
- cmd->sta_flags_mask = cpu_to_le32(qtnf_encode_sta_flags(
- params->sta_flags_mask));
- cmd->sta_flags_set = cpu_to_le32(qtnf_encode_sta_flags(
- params->sta_flags_set));
break;
case NL80211_IFTYPE_STATION:
cmd->if_type = cpu_to_le16(QLINK_IFTYPE_STATION);
- cmd->sta_flags_mask = cpu_to_le32(qtnf_encode_sta_flags(
- params->sta_flags_mask));
- cmd->sta_flags_set = cpu_to_le32(qtnf_encode_sta_flags(
- params->sta_flags_set));
break;
default:
pr_err("unsupported iftype %d\n", vif->wdev.iftype);
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
index f4d7d1603e3c..5d98000b0f5b 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -19,7 +19,7 @@
#include <linux/ieee80211.h>
-#define QLINK_PROTO_VER 8
+#define QLINK_PROTO_VER 9
#define QLINK_MACID_RSVD 0xFF
#define QLINK_VIFID_RSVD 0xFF
@@ -185,6 +185,17 @@ struct qlink_auth_encr {
u8 rsvd[2];
} __packed;
+/**
+ * struct qlink_sta_info_state - station flags mask/value
+ *
+ * @mask: STA flags mask, bitmap of &enum qlink_sta_flags
+ * @value: STA flags values, bitmap of &enum qlink_sta_flags
+ */
+struct qlink_sta_info_state {
+ __le32 mask;
+ __le32 value;
+} __packed;
+
/* QLINK Command messages related definitions
*/
@@ -402,16 +413,14 @@ struct qlink_cmd_set_def_mgmt_key {
/**
* struct qlink_cmd_change_sta - data for QLINK_CMD_CHANGE_STA command
*
- * @sta_flags_mask: STA flags mask, bitmap of &enum qlink_sta_flags
- * @sta_flags_set: STA flags values, bitmap of &enum qlink_sta_flags
+ * @flag_update: STA flags to update
* @if_type: Mode of interface operation, one of &enum qlink_iface_type
* @vlanid: VLAN ID to assign to specific STA
* @sta_addr: address of the STA for which parameters are set.
*/
struct qlink_cmd_change_sta {
struct qlink_cmd chdr;
- __le32 sta_flags_mask;
- __le32 sta_flags_set;
+ struct qlink_sta_info_state flag_update;
__le16 if_type;
__le16 vlanid;
u8 sta_addr[ETH_ALEN];
@@ -755,17 +764,27 @@ struct qlink_resp_manage_intf {
struct qlink_intf_info intf_info;
} __packed;
+enum qlink_sta_info_rate_flags {
+ QLINK_STA_INFO_RATE_FLAG_HT_MCS = BIT(0),
+ QLINK_STA_INFO_RATE_FLAG_VHT_MCS = BIT(1),
+ QLINK_STA_INFO_RATE_FLAG_SHORT_GI = BIT(2),
+ QLINK_STA_INFO_RATE_FLAG_60G = BIT(3),
+};
+
/**
* struct qlink_resp_get_sta_info - response for QLINK_CMD_GET_STA_INFO command
*
* 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: statistics for specified STA.
+ * @info: variable statistics for specified STA.
*/
struct qlink_resp_get_sta_info {
struct qlink_resp rhdr;
u8 sta_addr[ETH_ALEN];
+ u8 rsvd[2];
u8 info[0];
} __packed;
@@ -1003,6 +1022,15 @@ struct qlink_event_radar {
/* QLINK TLVs (Type-Length Values) definitions
*/
+/**
+ * enum qlink_tlv_id - list of TLVs that Qlink messages can carry
+ *
+ * @QTN_TLV_ID_STA_STATS_MAP: a bitmap of &enum qlink_sta_info, used to
+ * indicate which statistic carried in QTN_TLV_ID_STA_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_STA_STATS_MAP.
+ */
enum qlink_tlv_id {
QTN_TLV_ID_FRAG_THRESH = 0x0201,
QTN_TLV_ID_RTS_THRESH = 0x0202,
@@ -1011,12 +1039,12 @@ enum qlink_tlv_id {
QTN_TLV_ID_REG_RULE = 0x0207,
QTN_TLV_ID_CHANNEL = 0x020F,
QTN_TLV_ID_CHANDEF = 0x0210,
+ QTN_TLV_ID_STA_STATS_MAP = 0x0211,
+ QTN_TLV_ID_STA_STATS = 0x0212,
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,
QTN_TLV_ID_SEQ = 0x0303,
QTN_TLV_ID_IE_SET = 0x0305,
@@ -1038,67 +1066,8 @@ struct qlink_iface_comb_num {
__le16 iface_comb_num;
} __packed;
-struct qlink_sta_stat_basic_counters {
- __le64 rx_bytes;
- __le64 tx_bytes;
- __le64 rx_beacons;
- __le32 rx_packets;
- __le32 tx_packets;
- __le32 rx_dropped;
- __le32 tx_failed;
-} __packed;
-
-enum qlink_sta_info_rate_flags {
- QLINK_STA_INFO_RATE_FLAG_INVALID = 0,
- QLINK_STA_INFO_RATE_FLAG_HT_MCS = BIT(0),
- QLINK_STA_INFO_RATE_FLAG_VHT_MCS = BIT(1),
- QLINK_STA_INFO_RATE_FLAG_SHORT_GI = BIT(2),
- QLINK_STA_INFO_RATE_FLAG_60G = BIT(3),
-};
-
-enum qlink_sta_info_rate_bw {
- QLINK_STA_INFO_RATE_BW_5 = 0,
- QLINK_STA_INFO_RATE_BW_10 = 1,
- QLINK_STA_INFO_RATE_BW_20 = 2,
- QLINK_STA_INFO_RATE_BW_40 = 3,
- QLINK_STA_INFO_RATE_BW_80 = 4,
- QLINK_STA_INFO_RATE_BW_160 = 5,
-};
-
-/**
- * struct qlink_sta_info_rate - STA rate statistics
- *
- * @rate: data rate in Mbps.
- * @flags: bitmap of &enum qlink_sta_flags.
- * @mcs: 802.11-defined MCS index.
- * nss: Number of Spatial Streams.
- * @bw: bandwidth, one of &enum qlink_sta_info_rate_bw.
- */
-struct qlink_sta_info_rate {
- __le16 rate;
- u8 flags;
- u8 mcs;
- u8 nss;
- u8 bw;
-} __packed;
-
-struct qlink_sta_info_state {
- __le32 mask;
- __le32 value;
-} __packed;
-
#define QLINK_RSSI_OFFSET 120
-struct qlink_sta_info_generic {
- struct qlink_sta_info_state state;
- __le32 connected_time;
- __le32 inactive_time;
- struct qlink_sta_info_rate rx_rate;
- struct qlink_sta_info_rate tx_rate;
- u8 rssi;
- u8 rssi_avg;
-} __packed;
-
struct qlink_tlv_frag_rts_thr {
struct qlink_tlv_hdr hdr;
__le16 thr;
@@ -1243,4 +1212,105 @@ struct qlink_chan_stats {
s8 chan_noise;
} __packed;
+/**
+ * enum qlink_sta_info - station information bitmap
+ *
+ * Used to indicate which statistics values in &struct qlink_sta_stats
+ * are valid. Individual values are used to fill a bitmap carried in a
+ * payload of QTN_TLV_ID_STA_STATS_MAP.
+ *
+ * @QLINK_STA_INFO_CONNECTED_TIME: connected_time value is valid.
+ * @QLINK_STA_INFO_INACTIVE_TIME: inactive_time value is valid.
+ * @QLINK_STA_INFO_RX_BYTES: lower 32 bits of rx_bytes value are valid.
+ * @QLINK_STA_INFO_TX_BYTES: lower 32 bits of tx_bytes value are valid.
+ * @QLINK_STA_INFO_RX_BYTES64: rx_bytes value is valid.
+ * @QLINK_STA_INFO_TX_BYTES64: tx_bytes value is valid.
+ * @QLINK_STA_INFO_RX_DROP_MISC: rx_dropped_misc value is valid.
+ * @QLINK_STA_INFO_BEACON_RX: rx_beacon value is valid.
+ * @QLINK_STA_INFO_SIGNAL: signal value is valid.
+ * @QLINK_STA_INFO_SIGNAL_AVG: signal_avg value is valid.
+ * @QLINK_STA_INFO_RX_BITRATE: rxrate value is valid.
+ * @QLINK_STA_INFO_TX_BITRATE: txrate value is valid.
+ * @QLINK_STA_INFO_RX_PACKETS: rx_packets value is valid.
+ * @QLINK_STA_INFO_TX_PACKETS: tx_packets value is valid.
+ * @QLINK_STA_INFO_TX_RETRIES: tx_retries value is valid.
+ * @QLINK_STA_INFO_TX_FAILED: tx_failed value is valid.
+ * @QLINK_STA_INFO_STA_FLAGS: sta_flags value is valid.
+ */
+enum qlink_sta_info {
+ QLINK_STA_INFO_CONNECTED_TIME,
+ QLINK_STA_INFO_INACTIVE_TIME,
+ QLINK_STA_INFO_RX_BYTES,
+ QLINK_STA_INFO_TX_BYTES,
+ QLINK_STA_INFO_RX_BYTES64,
+ QLINK_STA_INFO_TX_BYTES64,
+ QLINK_STA_INFO_RX_DROP_MISC,
+ QLINK_STA_INFO_BEACON_RX,
+ QLINK_STA_INFO_SIGNAL,
+ QLINK_STA_INFO_SIGNAL_AVG,
+ QLINK_STA_INFO_RX_BITRATE,
+ QLINK_STA_INFO_TX_BITRATE,
+ QLINK_STA_INFO_RX_PACKETS,
+ QLINK_STA_INFO_TX_PACKETS,
+ QLINK_STA_INFO_TX_RETRIES,
+ QLINK_STA_INFO_TX_FAILED,
+ QLINK_STA_INFO_STA_FLAGS,
+ QLINK_STA_INFO_NUM,
+};
+
+/**
+ * struct qlink_sta_info_rate - STA rate statistics
+ *
+ * @rate: data rate in Mbps.
+ * @flags: bitmap of &enum qlink_sta_info_rate_flags.
+ * @mcs: 802.11-defined MCS index.
+ * nss: Number of Spatial Streams.
+ * @bw: bandwidth, one of &enum qlink_channel_width.
+ */
+struct qlink_sta_info_rate {
+ __le16 rate;
+ u8 flags;
+ u8 mcs;
+ u8 nss;
+ u8 bw;
+} __packed;
+
+/**
+ * struct qlink_sta_stats - data for QTN_TLV_ID_STA_STATS
+ *
+ * Carries statistics of a STA. Not all fields may be filled with
+ * valid values. Valid fields should be indicated as such using a bitmap of
+ * &enum qlink_sta_info. Bitmap is carried separately in a payload of
+ * QTN_TLV_ID_STA_STATS_MAP.
+ */
+struct qlink_sta_stats {
+ __le64 rx_bytes;
+ __le64 tx_bytes;
+ __le64 rx_beacon;
+ __le64 rx_duration;
+ __le64 t_offset;
+ __le32 connected_time;
+ __le32 inactive_time;
+ __le32 rx_packets;
+ __le32 tx_packets;
+ __le32 tx_retries;
+ __le32 tx_failed;
+ __le32 rx_dropped_misc;
+ __le32 beacon_loss_count;
+ __le32 expected_throughput;
+ struct qlink_sta_info_state sta_flags;
+ struct qlink_sta_info_rate txrate;
+ struct qlink_sta_info_rate rxrate;
+ __le16 llid;
+ __le16 plid;
+ u8 local_pm;
+ u8 peer_pm;
+ u8 nonpeer_pm;
+ u8 rx_beacon_signal_avg;
+ u8 plink_state;
+ u8 signal;
+ u8 signal_avg;
+ u8 rsvd[1];
+};
+
#endif /* _QTN_QLINK_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
index 37b78f00e8e5..19981d6440b6 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
@@ -164,3 +164,15 @@ enum qlink_hidden_ssid qlink_hidden_ssid_nl2q(enum nl80211_hidden_ssid nl_val)
return QLINK_HIDDEN_SSID_NOT_IN_USE;
}
}
+
+bool qtnf_utils_is_bit_set(const u8 *arr, unsigned int bit,
+ unsigned int arr_max_len)
+{
+ unsigned int idx = bit / BITS_PER_BYTE;
+ u8 mask = 1 << (bit - (idx * BITS_PER_BYTE));
+
+ if (idx >= arr_max_len)
+ return false;
+
+ return arr[idx] & mask;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
index 260383d6d109..6c24561eb41f 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
@@ -69,5 +69,7 @@ void qlink_chandef_q2cfg(struct wiphy *wiphy,
void qlink_chandef_cfg2q(const struct cfg80211_chan_def *chdef,
struct qlink_chandef *qch);
enum qlink_hidden_ssid qlink_hidden_ssid_nl2q(enum nl80211_hidden_ssid nl_val);
+bool qtnf_utils_is_bit_set(const u8 *arr, unsigned int bit,
+ unsigned int arr_max_len);
#endif /* _QTN_FMAC_QLINK_UTIL_H_ */
--
2.11.0
Center frequency is not enough to describe the channel in HT and VHT
modes. For 40MHz and 80MHz channels both primary channel and center
frequency should be specified in order to qualify channel completely.
This change adds primary channel info into qlink_chandef structure.
Signed-off-by: Sergey Matyukevich <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 3 +-
drivers/net/wireless/quantenna/qtnfmac/commands.c | 14 +++---
drivers/net/wireless/quantenna/qtnfmac/event.c | 3 +-
drivers/net/wireless/quantenna/qtnfmac/qlink.h | 57 ++++++++++++++++------
.../net/wireless/quantenna/qtnfmac/qlink_util.c | 48 ++++++++----------
5 files changed, 74 insertions(+), 51 deletions(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
index 63d274c3f2f7..b8e5f32cebdf 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -717,7 +717,8 @@ qtnf_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
}
if (!cfg80211_chandef_valid(chandef)) {
- pr_err("%s: bad chan freq1=%u freq2=%u bw=%u\n", ndev->name,
+ pr_err("%s: bad channel freq=%u cf1=%u cf2=%u bw=%u\n",
+ ndev->name, chandef->chan->center_freq,
chandef->center_freq1, chandef->center_freq2,
chandef->width);
ret = -ENODATA;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
index 8bc8dd637315..bed81f0cb1cd 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -247,7 +247,7 @@ int qtnf_cmd_send_start_ap(struct qtnf_vif *vif,
chtlv->hdr.type = cpu_to_le16(QTN_TLV_ID_CHANDEF);
chtlv->hdr.len = cpu_to_le16(sizeof(*chtlv) -
sizeof(chtlv->hdr));
- qlink_chandef_cfg2q(&s->chandef, &chtlv->chan);
+ qlink_chandef_cfg2q(&s->chandef, &chtlv->chdef);
}
qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_BEACON_HEAD,
@@ -1186,7 +1186,7 @@ qtnf_cmd_resp_fill_band_info(struct ieee80211_supported_band *band,
size_t tlv_len;
size_t tlv_dlen;
const struct qlink_tlv_hdr *tlv;
- const struct qlink_tlv_channel *qchan;
+ const struct qlink_channel *qchan;
struct ieee80211_channel *chan;
unsigned int chidx = 0;
u32 qflags;
@@ -1232,7 +1232,7 @@ qtnf_cmd_resp_fill_band_info(struct ieee80211_supported_band *band,
switch (tlv_type) {
case QTN_TLV_ID_CHANNEL:
- if (unlikely(tlv_len != sizeof(*qchan))) {
+ if (unlikely(tlv_dlen != sizeof(*qchan))) {
pr_err("invalid channel TLV len %zu\n",
tlv_len);
goto error_ret;
@@ -1243,7 +1243,7 @@ qtnf_cmd_resp_fill_band_info(struct ieee80211_supported_band *band,
goto error_ret;
}
- qchan = (const struct qlink_tlv_channel *)tlv;
+ qchan = (const struct qlink_channel *)tlv->val;
chan = &band->channels[chidx++];
qflags = le32_to_cpu(qchan->flags);
@@ -2037,8 +2037,8 @@ static void qtnf_cmd_channel_tlv_add(struct sk_buff *cmd_skb,
qchan = skb_put_zero(cmd_skb, sizeof(*qchan));
qchan->hdr.type = cpu_to_le16(QTN_TLV_ID_CHANNEL);
qchan->hdr.len = cpu_to_le16(sizeof(*qchan) - sizeof(qchan->hdr));
- qchan->center_freq = cpu_to_le16(sc->center_freq);
- qchan->hw_value = cpu_to_le16(sc->hw_value);
+ qchan->chan.center_freq = cpu_to_le16(sc->center_freq);
+ qchan->chan.hw_value = cpu_to_le16(sc->hw_value);
if (sc->flags & IEEE80211_CHAN_NO_IR)
flags |= QLINK_CHAN_NO_IR;
@@ -2046,7 +2046,7 @@ static void qtnf_cmd_channel_tlv_add(struct sk_buff *cmd_skb,
if (sc->flags & IEEE80211_CHAN_RADAR)
flags |= QLINK_CHAN_RADAR;
- qchan->flags = cpu_to_le32(flags);
+ qchan->chan.flags = cpu_to_le32(flags);
}
int qtnf_cmd_send_scan(struct qtnf_wmac *mac)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/event.c b/drivers/net/wireless/quantenna/qtnfmac/event.c
index 4abc6d9ed560..a3a18d8469ae 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/event.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/event.c
@@ -369,7 +369,8 @@ qtnf_event_handle_freq_change(struct qtnf_wmac *mac,
qlink_chandef_q2cfg(wiphy, &data->chan, &chandef);
if (!cfg80211_chandef_valid(&chandef)) {
- pr_err("MAC%u: bad channel f1=%u f2=%u bw=%u\n", mac->macid,
+ pr_err("MAC%u: bad channel freq=%u cf1=%u cf2=%u bw=%u\n",
+ mac->macid, chandef.chan->center_freq,
chandef.center_freq1, chandef.center_freq2,
chandef.width);
return -EINVAL;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
index a432fb001c41..534d11e4175a 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -19,7 +19,7 @@
#include <linux/ieee80211.h>
-#define QLINK_PROTO_VER 6
+#define QLINK_PROTO_VER 7
#define QLINK_MACID_RSVD 0xFF
#define QLINK_VIFID_RSVD 0xFF
@@ -122,17 +122,49 @@ enum qlink_channel_width {
};
/**
+ * struct qlink_channel - qlink control channel definition
+ *
+ * @hw_value: hardware-specific value for the channel
+ * @center_freq: center frequency in MHz
+ * @flags: channel flags from &enum qlink_channel_flags
+ * @band: band this channel belongs to
+ * @max_antenna_gain: maximum antenna gain in dBi
+ * @max_power: maximum transmission power (in dBm)
+ * @max_reg_power: maximum regulatory transmission power (in dBm)
+ * @dfs_state: current state of this channel.
+ * Only relevant if radar is required on this channel.
+ * @beacon_found: helper to regulatory code to indicate when a beacon
+ * has been found on this channel. Use regulatory_hint_found_beacon()
+ * to enable this, this is useful only on 5 GHz band.
+ */
+struct qlink_channel {
+ __le16 hw_value;
+ __le16 center_freq;
+ __le32 flags;
+ u8 band;
+ u8 max_antenna_gain;
+ u8 max_power;
+ u8 max_reg_power;
+ __le32 dfs_cac_ms;
+ u8 dfs_state;
+ u8 beacon_found;
+ u8 rsvd[2];
+} __packed;
+
+/**
* struct qlink_chandef - qlink channel definition
*
+ * @chan: primary channel definition
* @center_freq1: center frequency of first segment
* @center_freq2: center frequency of second segment (80+80 only)
* @width: channel width, one of @enum qlink_channel_width
*/
struct qlink_chandef {
+ struct qlink_channel chan;
__le16 center_freq1;
__le16 center_freq2;
u8 width;
- u8 rsvd[3];
+ u8 rsvd;
} __packed;
#define QLINK_MAX_NR_CIPHER_SUITES 5
@@ -1113,19 +1145,16 @@ enum qlink_dfs_state {
QLINK_DFS_AVAILABLE,
};
+/**
+ * struct qlink_tlv_channel - data for QTN_TLV_ID_CHANNEL TLV
+ *
+ * Channel settings.
+ *
+ * @channel: ieee80211 channel settings.
+ */
struct qlink_tlv_channel {
struct qlink_tlv_hdr hdr;
- __le16 hw_value;
- __le16 center_freq;
- __le32 flags;
- u8 band;
- u8 max_antenna_gain;
- u8 max_power;
- u8 max_reg_power;
- __le32 dfs_cac_ms;
- u8 dfs_state;
- u8 beacon_found;
- u8 rsvd[2];
+ struct qlink_channel chan;
} __packed;
/**
@@ -1137,7 +1166,7 @@ struct qlink_tlv_channel {
*/
struct qlink_tlv_chandef {
struct qlink_tlv_hdr hdr;
- struct qlink_chandef chan;
+ struct qlink_chandef chdef;
} __packed;
enum qlink_ie_set_type {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
index 61d999affb09..37b78f00e8e5 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
@@ -100,34 +100,6 @@ static enum nl80211_chan_width qlink_chanwidth_to_nl(u8 qlw)
}
}
-void qlink_chandef_q2cfg(struct wiphy *wiphy,
- const struct qlink_chandef *qch,
- struct cfg80211_chan_def *chdef)
-{
- chdef->center_freq1 = le16_to_cpu(qch->center_freq1);
- chdef->center_freq2 = le16_to_cpu(qch->center_freq2);
- chdef->width = qlink_chanwidth_to_nl(qch->width);
-
- switch (chdef->width) {
- case NL80211_CHAN_WIDTH_20_NOHT:
- case NL80211_CHAN_WIDTH_20:
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
- chdef->chan = ieee80211_get_channel(wiphy, chdef->center_freq1);
- break;
- case NL80211_CHAN_WIDTH_40:
- case NL80211_CHAN_WIDTH_80:
- case NL80211_CHAN_WIDTH_80P80:
- case NL80211_CHAN_WIDTH_160:
- chdef->chan = ieee80211_get_channel(wiphy,
- chdef->center_freq1 - 10);
- break;
- default:
- chdef->chan = NULL;
- break;
- }
-}
-
static u8 qlink_chanwidth_nl_to_qlink(enum nl80211_chan_width nlwidth)
{
switch (nlwidth) {
@@ -152,9 +124,29 @@ static u8 qlink_chanwidth_nl_to_qlink(enum nl80211_chan_width nlwidth)
}
}
+void qlink_chandef_q2cfg(struct wiphy *wiphy,
+ const struct qlink_chandef *qch,
+ struct cfg80211_chan_def *chdef)
+{
+ struct ieee80211_channel *chan;
+
+ chan = ieee80211_get_channel(wiphy, le16_to_cpu(qch->chan.center_freq));
+
+ chdef->chan = chan;
+ chdef->center_freq1 = le16_to_cpu(qch->center_freq1);
+ chdef->center_freq2 = le16_to_cpu(qch->center_freq2);
+ chdef->width = qlink_chanwidth_to_nl(qch->width);
+}
+
void qlink_chandef_cfg2q(const struct cfg80211_chan_def *chdef,
struct qlink_chandef *qch)
{
+ struct ieee80211_channel *chan = chdef->chan;
+
+ qch->chan.hw_value = cpu_to_le16(chan->hw_value);
+ qch->chan.center_freq = cpu_to_le16(chan->center_freq);
+ qch->chan.flags = cpu_to_le32(chan->flags);
+
qch->center_freq1 = cpu_to_le16(chdef->center_freq1);
qch->center_freq2 = cpu_to_le16(chdef->center_freq2);
qch->width = qlink_chanwidth_nl_to_qlink(chdef->width);
--
2.11.0
From: Igor Mitsyanko <[email protected]>
Parameters passed into .mgmt_tx callback may have a NULL channel in case
userspace wants to send a frame on current channel. Make sure this case
is handled, pass "freq==0" in case channel is not specififed to tell
wlan device to use current channel.
Signed-off-by: Igor Mitsyanko <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
index c026882bf9c7..be15d0ed362c 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -381,6 +381,7 @@ qtnf_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
const struct ieee80211_mgmt *mgmt_frame = (void *)params->buf;
u32 short_cookie = prandom_u32();
u16 flags = 0;
+ u16 freq;
*cookie = short_cookie;
@@ -393,13 +394,21 @@ qtnf_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
if (params->dont_wait_for_ack)
flags |= QLINK_MGMT_FRAME_TX_FLAG_ACK_NOWAIT;
+ /* If channel is not specified, pass "freq = 0" to tell device
+ * firmware to use current channel.
+ */
+ if (params->chan)
+ freq = params->chan->center_freq;
+ else
+ freq = 0;
+
pr_debug("%s freq:%u; FC:%.4X; DA:%pM; len:%zu; C:%.8X; FL:%.4X\n",
- wdev->netdev->name, params->chan->center_freq,
+ wdev->netdev->name, freq,
le16_to_cpu(mgmt_frame->frame_control), mgmt_frame->da,
params->len, short_cookie, flags);
return qtnf_cmd_send_mgmt_frame(vif, short_cookie, flags,
- params->chan->center_freq,
+ freq,
params->buf, params->len);
}
--
2.11.0
Fix RSSI values passed to wireless core by qtnfmac driver:
- fix RSSI values in scan results:
driver registers wiphy with CFG80211_SIGNAL_TYPE_MBM signal type,
so mBm should be passed using DBM_TO_MBM macro
- accompany firmware changes fixing RSSI values in received mgmt frames
update qlink message format and pass correct signed values to core
Signed-off-by: Sergey Matyukevich <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/event.c | 7 +++----
drivers/net/wireless/quantenna/qtnfmac/qlink.h | 11 ++++++-----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/event.c b/drivers/net/wireless/quantenna/qtnfmac/event.c
index 9843ca36b74b..b3489b5b5d9e 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/event.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/event.c
@@ -237,9 +237,8 @@ qtnf_event_handle_mgmt_received(struct qtnf_vif *vif,
pr_debug("%s LEN:%u FC:%.4X SA:%pM\n", vif->netdev->name, frame_len,
le16_to_cpu(frame->frame_control), frame->addr2);
- cfg80211_rx_mgmt(&vif->wdev, le32_to_cpu(rxmgmt->freq),
- le32_to_cpu(rxmgmt->sig_dbm), rxmgmt->frame_data,
- frame_len, flags);
+ cfg80211_rx_mgmt(&vif->wdev, le32_to_cpu(rxmgmt->freq), rxmgmt->sig_dbm,
+ rxmgmt->frame_data, frame_len, flags);
return 0;
}
@@ -324,7 +323,7 @@ qtnf_event_handle_scan_results(struct qtnf_vif *vif,
sr->bssid, get_unaligned_le64(&sr->tsf),
le16_to_cpu(sr->capab),
le16_to_cpu(sr->bintval), ies, ies_len,
- sr->signal, GFP_KERNEL);
+ DBM_TO_MBM(sr->sig_dbm), GFP_KERNEL);
if (!bss)
return -ENOMEM;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
index 3e3de4629a53..1f150be98820 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -19,7 +19,7 @@
#include <linux/ieee80211.h>
-#define QLINK_PROTO_VER 7
+#define QLINK_PROTO_VER 8
#define QLINK_MACID_RSVD 0xFF
#define QLINK_VIFID_RSVD 0xFF
@@ -916,15 +916,16 @@ enum qlink_rxmgmt_flags {
* struct qlink_event_rxmgmt - data for QLINK_EVENT_MGMT_RECEIVED event
*
* @freq: Frequency on which the frame was received in MHz.
- * @sig_dbm: signal strength in dBm.
* @flags: bitmap of &enum qlink_rxmgmt_flags.
+ * @sig_dbm: signal strength in dBm.
* @frame_data: data of Rx'd frame itself.
*/
struct qlink_event_rxmgmt {
struct qlink_event ehdr;
__le32 freq;
- __le32 sig_dbm;
__le32 flags;
+ s8 sig_dbm;
+ u8 rsvd[3];
u8 frame_data[0];
} __packed;
@@ -936,7 +937,7 @@ struct qlink_event_rxmgmt {
* event was generated was discovered.
* @capab: capabilities field.
* @bintval: beacon interval announced by discovered BSS.
- * @signal: signal strength.
+ * @sig_dbm: signal strength in dBm.
* @bssid: BSSID announced by discovered BSS.
* @ssid_len: length of SSID announced by BSS.
* @ssid: SSID announced by discovered BSS.
@@ -948,7 +949,7 @@ struct qlink_event_scan_result {
__le16 freq;
__le16 capab;
__le16 bintval;
- s8 signal;
+ s8 sig_dbm;
u8 ssid_len;
u8 ssid[IEEE80211_MAX_SSID_LEN];
u8 bssid[ETH_ALEN];
--
2.11.0
From: Igor Mitsyanko <[email protected]>
It is possible that regulatory notifier is called before MAC data
was allocated. We need to verify that MAC data exists before trying
to send a regulatory change event.
Signed-off-by: Igor Mitsyanko <[email protected]>
---
drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
index 6711e7fb6926..63d274c3f2f7 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -802,6 +802,9 @@ static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy_in,
continue;
mac = bus->mac[mac_idx];
+ if (!mac)
+ continue;
+
wiphy = priv_to_wiphy(mac);
for (band = 0; band < NUM_NL80211_BANDS; ++band) {
--
2.11.0
Sergey Matyukevich <[email protected]> writes:
> Thanks Kalle,
>
>> > It is possible that regulatory notifier is called before MAC data
>> > was allocated. We need to verify that MAC data exists before trying
>> > to send a regulatory change event.
>> >
>> > Signed-off-by: Igor Mitsyanko <[email protected]>
>>
>> 10 patches applied to wireless-drivers-next.git, thanks.
>>
>> 4fd045cdb239 qtnfmac: check that MAC exists in regulatory notifier
>> 5bf374ab91ad qtnfmac: pass complete channel data between driver and firmware
>> b05ee456fd21 qtnfmac: add support for radar detection and CAC
>> e6ef8cd05145 qtnfmac: change default interface mode from AP to STA
>> e6e594aff26a qtnfmac: check for passed channel being NULL in MGMT_TX command
>> fbad963a28e4 qtnfmac: fix rssi data passed to wireless core
>> 9cbd599966d8 qtnfmac: fill wiphy's extended capabilities
>> 4d2a7a1cfa68 qtnfmac: modify GET_STA_STATS cmd format for back/forward compatibility
>> 7a4d3a3bc84e qtnfmac: keeping track of "generation" for STA info
>> f1398fd2dd8c qtnfmac: support MAC address based access control
>
> Just to understand the timelines... Could you please clarify if these patches
> are still going to make it into 4.16 merge window ?
They should, but of course I can't promise anything.
My deadline is that when merge window opens the patches need to be in
net-next. You can follow the predictions for the schedule here:
http://phb-crystal-ball.org/
--
Kalle Valo
Thanks Kalle,
> > It is possible that regulatory notifier is called before MAC data
> > was allocated. We need to verify that MAC data exists before trying
> > to send a regulatory change event.
> >
> > Signed-off-by: Igor Mitsyanko <[email protected]>
>
> 10 patches applied to wireless-drivers-next.git, thanks.
>
> 4fd045cdb239 qtnfmac: check that MAC exists in regulatory notifier
> 5bf374ab91ad qtnfmac: pass complete channel data between driver and firmware
> b05ee456fd21 qtnfmac: add support for radar detection and CAC
> e6ef8cd05145 qtnfmac: change default interface mode from AP to STA
> e6e594aff26a qtnfmac: check for passed channel being NULL in MGMT_TX command
> fbad963a28e4 qtnfmac: fix rssi data passed to wireless core
> 9cbd599966d8 qtnfmac: fill wiphy's extended capabilities
> 4d2a7a1cfa68 qtnfmac: modify GET_STA_STATS cmd format for back/forward compatibility
> 7a4d3a3bc84e qtnfmac: keeping track of "generation" for STA info
> f1398fd2dd8c qtnfmac: support MAC address based access control
Just to understand the timelines... Could you please clarify if these patches
are still going to make it into 4.16 merge window ?
Regards,
Sergey
Sergey Matyukevich <[email protected]> wrote:
> From: Igor Mitsyanko <[email protected]>
>
> It is possible that regulatory notifier is called before MAC data
> was allocated. We need to verify that MAC data exists before trying
> to send a regulatory change event.
>
> Signed-off-by: Igor Mitsyanko <[email protected]>
10 patches applied to wireless-drivers-next.git, thanks.
4fd045cdb239 qtnfmac: check that MAC exists in regulatory notifier
5bf374ab91ad qtnfmac: pass complete channel data between driver and firmware
b05ee456fd21 qtnfmac: add support for radar detection and CAC
e6ef8cd05145 qtnfmac: change default interface mode from AP to STA
e6e594aff26a qtnfmac: check for passed channel being NULL in MGMT_TX command
fbad963a28e4 qtnfmac: fix rssi data passed to wireless core
9cbd599966d8 qtnfmac: fill wiphy's extended capabilities
4d2a7a1cfa68 qtnfmac: modify GET_STA_STATS cmd format for back/forward compatibility
7a4d3a3bc84e qtnfmac: keeping track of "generation" for STA info
f1398fd2dd8c qtnfmac: support MAC address based access control
--
https://patchwork.kernel.org/patch/10123079/
https://wireless.wiki.kernel.org/en/developers/documentation/submittingpatches