summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/quantenna
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/net/wireless/quantenna
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/net/wireless/quantenna')
-rw-r--r--drivers/net/wireless/quantenna/Kconfig17
-rw-r--r--drivers/net/wireless/quantenna/Makefile7
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/Kconfig21
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/Makefile30
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/bus.h158
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/cfg80211.c1289
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/cfg80211.h18
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/commands.c2765
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/commands.h82
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/core.c933
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/core.h159
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/debug.c23
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/debug.h37
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/event.c797
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/event.h14
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c493
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h90
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c1199
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_ipc.h82
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_regs.h121
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c1234
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h94
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h45
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/qlink.h1891
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/qlink_util.c303
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/qlink_util.h73
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h35
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/shm_ipc.c162
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/shm_ipc.h65
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/shm_ipc_defs.h33
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/switchdev.h24
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/trans.c213
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/trans.h44
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/util.c123
-rw-r--r--drivers/net/wireless/quantenna/qtnfmac/util.h34
35 files changed, 12708 insertions, 0 deletions
diff --git a/drivers/net/wireless/quantenna/Kconfig b/drivers/net/wireless/quantenna/Kconfig
new file mode 100644
index 000000000..91d78c685
--- /dev/null
+++ b/drivers/net/wireless/quantenna/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config WLAN_VENDOR_QUANTENNA
+ bool "Quantenna wireless cards support"
+ default y
+ help
+ If you have a wireless card belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all the
+ questions about these cards. If you say Y, you will be asked for
+ your specific card in the following questions.
+
+if WLAN_VENDOR_QUANTENNA
+
+source "drivers/net/wireless/quantenna/qtnfmac/Kconfig"
+
+endif # WLAN_VENDOR_QUANTENNA
diff --git a/drivers/net/wireless/quantenna/Makefile b/drivers/net/wireless/quantenna/Makefile
new file mode 100644
index 000000000..cea83d178
--- /dev/null
+++ b/drivers/net/wireless/quantenna/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2015-2016 Quantenna Communications, Inc.
+# All rights reserved.
+#
+
+obj-$(CONFIG_QTNFMAC) += qtnfmac/
diff --git a/drivers/net/wireless/quantenna/qtnfmac/Kconfig b/drivers/net/wireless/quantenna/qtnfmac/Kconfig
new file mode 100644
index 000000000..b4a6df06d
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config QTNFMAC
+ tristate
+ depends on QTNFMAC_PCIE
+ default m if QTNFMAC_PCIE=m
+ default y if QTNFMAC_PCIE=y
+
+config QTNFMAC_PCIE
+ tristate "Quantenna QSR1000/QSR2000/QSR10g PCIe support"
+ default n
+ depends on PCI && CFG80211
+ select QTNFMAC
+ select FW_LOADER
+ select CRC32
+ help
+ This option adds support for wireless adapters based on Quantenna
+ 802.11ac QSR10g (aka Pearl) and QSR1000/QSR2000 (aka Topaz)
+ FullMAC chipsets running over PCIe.
+
+ If you choose to build it as a module, two modules will be built:
+ qtnfmac.ko and qtnfmac_pcie.ko.
diff --git a/drivers/net/wireless/quantenna/qtnfmac/Makefile b/drivers/net/wireless/quantenna/qtnfmac/Makefile
new file mode 100644
index 000000000..40dffbd2e
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/Makefile
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2015-2016 Quantenna Communications, Inc.
+# All rights reserved.
+#
+
+ccflags-y += \
+ -Idrivers/net/wireless/quantenna/qtnfmac
+
+obj-$(CONFIG_QTNFMAC) += qtnfmac.o
+qtnfmac-objs += \
+ core.o \
+ commands.o \
+ trans.o \
+ cfg80211.o \
+ event.o \
+ util.o \
+ qlink_util.o
+
+#
+
+obj-$(CONFIG_QTNFMAC_PCIE) += qtnfmac_pcie.o
+
+qtnfmac_pcie-objs += \
+ shm_ipc.o \
+ pcie/pcie.o \
+ pcie/pearl_pcie.o \
+ pcie/topaz_pcie.o
+
+qtnfmac_pcie-$(CONFIG_DEBUG_FS) += debug.o
diff --git a/drivers/net/wireless/quantenna/qtnfmac/bus.h b/drivers/net/wireless/quantenna/qtnfmac/bus.h
new file mode 100644
index 000000000..7f8646e77
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/bus.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015 Quantenna Communications. All rights reserved. */
+
+#ifndef QTNFMAC_BUS_H
+#define QTNFMAC_BUS_H
+
+#include <linux/netdevice.h>
+#include <linux/workqueue.h>
+
+#include "trans.h"
+#include "core.h"
+
+#define QTNF_MAX_MAC 3
+
+#define HBM_FRAME_META_MAGIC_PATTERN_S 0xAB
+#define HBM_FRAME_META_MAGIC_PATTERN_E 0xBA
+
+struct qtnf_frame_meta_info {
+ u8 magic_s;
+ u8 ifidx;
+ u8 macid;
+ u8 magic_e;
+} __packed;
+
+enum qtnf_fw_state {
+ QTNF_FW_STATE_DETACHED,
+ QTNF_FW_STATE_BOOT_DONE,
+ QTNF_FW_STATE_ACTIVE,
+ QTNF_FW_STATE_RUNNING,
+ QTNF_FW_STATE_DEAD,
+};
+
+struct qtnf_bus;
+
+struct qtnf_bus_ops {
+ /* mgmt methods */
+ int (*preinit)(struct qtnf_bus *);
+ void (*stop)(struct qtnf_bus *);
+
+ /* control path methods */
+ int (*control_tx)(struct qtnf_bus *, struct sk_buff *);
+
+ /* data xfer methods */
+ int (*data_tx)(struct qtnf_bus *bus, struct sk_buff *skb,
+ unsigned int macid, unsigned int vifid);
+ void (*data_tx_timeout)(struct qtnf_bus *, struct net_device *);
+ void (*data_tx_use_meta_set)(struct qtnf_bus *bus, bool use_meta);
+ void (*data_rx_start)(struct qtnf_bus *);
+ void (*data_rx_stop)(struct qtnf_bus *);
+};
+
+struct qtnf_bus {
+ struct device *dev;
+ enum qtnf_fw_state fw_state;
+ u32 chip;
+ u32 chiprev;
+ struct qtnf_bus_ops *bus_ops;
+ struct qtnf_wmac *mac[QTNF_MAX_MAC];
+ struct qtnf_qlink_transport trans;
+ struct qtnf_hw_info hw_info;
+ struct napi_struct mux_napi;
+ struct net_device *mux_dev;
+ struct workqueue_struct *workqueue;
+ struct workqueue_struct *hprio_workqueue;
+ struct work_struct fw_work;
+ struct work_struct event_work;
+ struct mutex bus_lock; /* lock during command/event processing */
+ struct dentry *dbg_dir;
+ struct notifier_block netdev_nb;
+ u8 hw_id[ETH_ALEN];
+ /* bus private data */
+ char bus_priv[] __aligned(sizeof(void *));
+};
+
+static inline bool qtnf_fw_is_up(struct qtnf_bus *bus)
+{
+ enum qtnf_fw_state state = bus->fw_state;
+
+ return ((state == QTNF_FW_STATE_ACTIVE) ||
+ (state == QTNF_FW_STATE_RUNNING));
+}
+
+static inline bool qtnf_fw_is_attached(struct qtnf_bus *bus)
+{
+ enum qtnf_fw_state state = bus->fw_state;
+
+ return ((state == QTNF_FW_STATE_ACTIVE) ||
+ (state == QTNF_FW_STATE_RUNNING) ||
+ (state == QTNF_FW_STATE_DEAD));
+}
+
+static inline void *get_bus_priv(struct qtnf_bus *bus)
+{
+ if (WARN(!bus, "qtnfmac: invalid bus pointer"))
+ return NULL;
+
+ return &bus->bus_priv;
+}
+
+/* callback wrappers */
+
+static inline int qtnf_bus_preinit(struct qtnf_bus *bus)
+{
+ if (!bus->bus_ops->preinit)
+ return 0;
+ return bus->bus_ops->preinit(bus);
+}
+
+static inline void qtnf_bus_stop(struct qtnf_bus *bus)
+{
+ if (!bus->bus_ops->stop)
+ return;
+ bus->bus_ops->stop(bus);
+}
+
+static inline int qtnf_bus_data_tx(struct qtnf_bus *bus, struct sk_buff *skb,
+ unsigned int macid, unsigned int vifid)
+{
+ return bus->bus_ops->data_tx(bus, skb, macid, vifid);
+}
+
+static inline void
+qtnf_bus_data_tx_timeout(struct qtnf_bus *bus, struct net_device *ndev)
+{
+ return bus->bus_ops->data_tx_timeout(bus, ndev);
+}
+
+static inline int qtnf_bus_control_tx(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+ return bus->bus_ops->control_tx(bus, skb);
+}
+
+static inline void qtnf_bus_data_rx_start(struct qtnf_bus *bus)
+{
+ return bus->bus_ops->data_rx_start(bus);
+}
+
+static inline void qtnf_bus_data_rx_stop(struct qtnf_bus *bus)
+{
+ return bus->bus_ops->data_rx_stop(bus);
+}
+
+static __always_inline void qtnf_bus_lock(struct qtnf_bus *bus)
+{
+ mutex_lock(&bus->bus_lock);
+}
+
+static __always_inline void qtnf_bus_unlock(struct qtnf_bus *bus)
+{
+ mutex_unlock(&bus->bus_lock);
+}
+
+/* interface functions from common layer */
+
+int qtnf_core_attach(struct qtnf_bus *bus);
+void qtnf_core_detach(struct qtnf_bus *bus);
+
+#endif /* QTNFMAC_BUS_H */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
new file mode 100644
index 000000000..45e2b7ae9
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
@@ -0,0 +1,1289 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/vmalloc.h>
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include <net/netlink.h>
+
+#include "cfg80211.h"
+#include "commands.h"
+#include "core.h"
+#include "util.h"
+#include "bus.h"
+
+/* Supported rates to be advertised to the cfg80211 */
+static struct ieee80211_rate qtnf_rates_2g[] = {
+ {.bitrate = 10, .hw_value = 2, },
+ {.bitrate = 20, .hw_value = 4, },
+ {.bitrate = 55, .hw_value = 11, },
+ {.bitrate = 110, .hw_value = 22, },
+ {.bitrate = 60, .hw_value = 12, },
+ {.bitrate = 90, .hw_value = 18, },
+ {.bitrate = 120, .hw_value = 24, },
+ {.bitrate = 180, .hw_value = 36, },
+ {.bitrate = 240, .hw_value = 48, },
+ {.bitrate = 360, .hw_value = 72, },
+ {.bitrate = 480, .hw_value = 96, },
+ {.bitrate = 540, .hw_value = 108, },
+};
+
+/* Supported rates to be advertised to the cfg80211 */
+static struct ieee80211_rate qtnf_rates_5g[] = {
+ {.bitrate = 60, .hw_value = 12, },
+ {.bitrate = 90, .hw_value = 18, },
+ {.bitrate = 120, .hw_value = 24, },
+ {.bitrate = 180, .hw_value = 36, },
+ {.bitrate = 240, .hw_value = 48, },
+ {.bitrate = 360, .hw_value = 72, },
+ {.bitrate = 480, .hw_value = 96, },
+ {.bitrate = 540, .hw_value = 108, },
+};
+
+/* Supported crypto cipher suits to be advertised to cfg80211 */
+static const u32 qtnf_cipher_suites[] = {
+ WLAN_CIPHER_SUITE_TKIP,
+ WLAN_CIPHER_SUITE_CCMP,
+ WLAN_CIPHER_SUITE_AES_CMAC,
+};
+
+/* Supported mgmt frame types to be advertised to cfg80211 */
+static const struct ieee80211_txrx_stypes
+qtnf_mgmt_stypes[NUM_NL80211_IFTYPES] = {
+ [NL80211_IFTYPE_STATION] = {
+ .tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4),
+ .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4),
+ },
+ [NL80211_IFTYPE_AP] = {
+ .tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4),
+ .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4),
+ },
+};
+
+static int
+qtnf_validate_iface_combinations(struct wiphy *wiphy,
+ struct qtnf_vif *change_vif,
+ enum nl80211_iftype new_type)
+{
+ struct qtnf_wmac *mac;
+ struct qtnf_vif *vif;
+ int i;
+ int ret = 0;
+ struct iface_combination_params params = {
+ .num_different_channels = 1,
+ };
+
+ mac = wiphy_priv(wiphy);
+ if (!mac)
+ return -EFAULT;
+
+ for (i = 0; i < QTNF_MAX_INTF; i++) {
+ vif = &mac->iflist[i];
+ if (vif->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED)
+ params.iftype_num[vif->wdev.iftype]++;
+ }
+
+ if (change_vif) {
+ params.iftype_num[new_type]++;
+ params.iftype_num[change_vif->wdev.iftype]--;
+ } else {
+ params.iftype_num[new_type]++;
+ }
+
+ ret = cfg80211_check_combinations(wiphy, &params);
+
+ if (ret)
+ return ret;
+
+ /* Check repeater interface combination: primary VIF should be STA only.
+ * STA (primary) + AP (secondary) is OK.
+ * AP (primary) + STA (secondary) is not supported.
+ */
+ vif = qtnf_mac_get_base_vif(mac);
+ if (vif && vif->wdev.iftype == NL80211_IFTYPE_AP &&
+ vif != change_vif && new_type == NL80211_IFTYPE_STATION) {
+ ret = -EINVAL;
+ pr_err("MAC%u invalid combination: AP as primary repeater interface is not supported\n",
+ mac->macid);
+ }
+
+ return ret;
+}
+
+static int
+qtnf_change_virtual_intf(struct wiphy *wiphy,
+ struct net_device *dev,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ u8 *mac_addr = NULL;
+ int use4addr = 0;
+ int ret;
+
+ ret = qtnf_validate_iface_combinations(wiphy, vif, type);
+ if (ret) {
+ pr_err("VIF%u.%u combination check: failed to set type %d\n",
+ vif->mac->macid, vif->vifid, type);
+ return ret;
+ }
+
+ if (params) {
+ mac_addr = params->macaddr;
+ use4addr = params->use_4addr;
+ }
+
+ qtnf_scan_done(vif->mac, true);
+
+ ret = qtnf_cmd_send_change_intf_type(vif, type, use4addr, mac_addr);
+ if (ret) {
+ pr_err("VIF%u.%u: failed to change type to %d\n",
+ vif->mac->macid, vif->vifid, type);
+ return ret;
+ }
+
+ vif->wdev.iftype = type;
+ return 0;
+}
+
+int qtnf_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+ struct net_device *netdev = wdev->netdev;
+ struct qtnf_vif *vif;
+ struct sk_buff *skb;
+
+ if (WARN_ON(!netdev))
+ return -EFAULT;
+
+ vif = qtnf_netdev_get_priv(wdev->netdev);
+
+ qtnf_scan_done(vif->mac, true);
+
+ /* Stop data */
+ netif_tx_stop_all_queues(netdev);
+ if (netif_carrier_ok(netdev))
+ netif_carrier_off(netdev);
+
+ while ((skb = skb_dequeue(&vif->high_pri_tx_queue)))
+ dev_kfree_skb_any(skb);
+
+ cancel_work_sync(&vif->high_pri_tx_work);
+
+ if (netdev->reg_state == NETREG_REGISTERED)
+ cfg80211_unregister_netdevice(netdev);
+
+ if (qtnf_cmd_send_del_intf(vif))
+ pr_err("VIF%u.%u: failed to delete VIF\n", vif->mac->macid,
+ vif->vifid);
+
+ vif->netdev->ieee80211_ptr = NULL;
+ vif->netdev = NULL;
+ vif->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED;
+
+ return 0;
+}
+
+static struct wireless_dev *qtnf_add_virtual_intf(struct wiphy *wiphy,
+ const char *name,
+ unsigned char name_assign_t,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ struct qtnf_wmac *mac;
+ struct qtnf_vif *vif;
+ u8 *mac_addr = NULL;
+ int use4addr = 0;
+ int ret;
+
+ mac = wiphy_priv(wiphy);
+
+ if (!mac)
+ return ERR_PTR(-EFAULT);
+
+ ret = qtnf_validate_iface_combinations(wiphy, NULL, type);
+ if (ret) {
+ pr_err("MAC%u invalid combination: failed to add type %d\n",
+ mac->macid, type);
+ return ERR_PTR(ret);
+ }
+
+ switch (type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_AP:
+ vif = qtnf_mac_get_free_vif(mac);
+ if (!vif) {
+ pr_err("MAC%u: no free VIF available\n", mac->macid);
+ return ERR_PTR(-EFAULT);
+ }
+
+ eth_zero_addr(vif->mac_addr);
+ eth_zero_addr(vif->bssid);
+ vif->bss_priority = QTNF_DEF_BSS_PRIORITY;
+ memset(&vif->wdev, 0, sizeof(vif->wdev));
+ vif->wdev.wiphy = wiphy;
+ vif->wdev.iftype = type;
+ break;
+ default:
+ pr_err("MAC%u: unsupported IF type %d\n", mac->macid, type);
+ return ERR_PTR(-ENOTSUPP);
+ }
+
+ if (params) {
+ mac_addr = params->macaddr;
+ use4addr = params->use_4addr;
+ }
+
+ ret = qtnf_cmd_send_add_intf(vif, type, use4addr, mac_addr);
+ if (ret) {
+ pr_err("VIF%u.%u: failed to add VIF %pM\n",
+ mac->macid, vif->vifid, mac_addr);
+ goto err_cmd;
+ }
+
+ if (!is_valid_ether_addr(vif->mac_addr)) {
+ pr_err("VIF%u.%u: FW reported bad MAC: %pM\n",
+ mac->macid, vif->vifid, vif->mac_addr);
+ ret = -EINVAL;
+ goto error_del_vif;
+ }
+
+ ret = qtnf_core_net_attach(mac, vif, name, name_assign_t);
+ if (ret) {
+ pr_err("VIF%u.%u: failed to attach netdev\n", mac->macid,
+ vif->vifid);
+ goto error_del_vif;
+ }
+
+ if (qtnf_hwcap_is_set(&mac->bus->hw_info, QLINK_HW_CAPAB_HW_BRIDGE)) {
+ ret = qtnf_cmd_netdev_changeupper(vif, vif->netdev->ifindex);
+ if (ret) {
+ cfg80211_unregister_netdevice(vif->netdev);
+ vif->netdev = NULL;
+ goto error_del_vif;
+ }
+ }
+
+ vif->wdev.netdev = vif->netdev;
+ return &vif->wdev;
+
+error_del_vif:
+ qtnf_cmd_send_del_intf(vif);
+err_cmd:
+ vif->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED;
+
+ return ERR_PTR(ret);
+}
+
+static int qtnf_mgmt_set_appie(struct qtnf_vif *vif,
+ const struct cfg80211_beacon_data *info)
+{
+ int ret = 0;
+
+ if (!info->beacon_ies || !info->beacon_ies_len) {
+ ret = qtnf_cmd_send_mgmt_set_appie(vif, QLINK_IE_SET_BEACON_IES,
+ NULL, 0);
+ } else {
+ ret = qtnf_cmd_send_mgmt_set_appie(vif, QLINK_IE_SET_BEACON_IES,
+ info->beacon_ies,
+ info->beacon_ies_len);
+ }
+
+ if (ret)
+ goto out;
+
+ if (!info->proberesp_ies || !info->proberesp_ies_len) {
+ ret = qtnf_cmd_send_mgmt_set_appie(vif,
+ QLINK_IE_SET_PROBE_RESP_IES,
+ NULL, 0);
+ } else {
+ ret = qtnf_cmd_send_mgmt_set_appie(vif,
+ QLINK_IE_SET_PROBE_RESP_IES,
+ info->proberesp_ies,
+ info->proberesp_ies_len);
+ }
+
+ if (ret)
+ goto out;
+
+ if (!info->assocresp_ies || !info->assocresp_ies_len) {
+ ret = qtnf_cmd_send_mgmt_set_appie(vif,
+ QLINK_IE_SET_ASSOC_RESP,
+ NULL, 0);
+ } else {
+ ret = qtnf_cmd_send_mgmt_set_appie(vif,
+ QLINK_IE_SET_ASSOC_RESP,
+ info->assocresp_ies,
+ info->assocresp_ies_len);
+ }
+
+out:
+ return ret;
+}
+
+static int qtnf_change_beacon(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_ap_update *info)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+
+ return qtnf_mgmt_set_appie(vif, &info->beacon);
+}
+
+static int qtnf_start_ap(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_ap_settings *settings)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ ret = qtnf_cmd_send_start_ap(vif, settings);
+ if (ret)
+ pr_err("VIF%u.%u: failed to start AP\n", vif->mac->macid,
+ vif->vifid);
+
+ return ret;
+}
+
+static int qtnf_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+ unsigned int link_id)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ qtnf_scan_done(vif->mac, true);
+
+ ret = qtnf_cmd_send_stop_ap(vif);
+ if (ret)
+ pr_err("VIF%u.%u: failed to stop AP operation in FW\n",
+ vif->mac->macid, vif->vifid);
+
+ netif_carrier_off(vif->netdev);
+
+ return ret;
+}
+
+static int qtnf_set_wiphy_params(struct wiphy *wiphy, int radio_idx,
+ u32 changed)
+{
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+ struct qtnf_vif *vif;
+ int ret;
+
+ vif = qtnf_mac_get_base_vif(mac);
+ if (!vif) {
+ pr_err("MAC%u: primary VIF is not configured\n", mac->macid);
+ return -EFAULT;
+ }
+
+ ret = qtnf_cmd_send_update_phy_params(mac, changed);
+ if (ret)
+ pr_err("MAC%u: failed to update PHY params\n", mac->macid);
+
+ return ret;
+}
+
+static void
+qtnf_update_mgmt_frame_registrations(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct mgmt_frame_regs *upd)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ u16 new_mask = upd->interface_stypes;
+ u16 old_mask = vif->mgmt_frames_bitmask;
+ static const struct {
+ u16 mask, qlink_type;
+ } updates[] = {
+ {
+ .mask = BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_ASSOC_REQ >> 4),
+ .qlink_type = QLINK_MGMT_FRAME_ASSOC_REQ,
+ },
+ {
+ .mask = BIT(IEEE80211_STYPE_AUTH >> 4),
+ .qlink_type = QLINK_MGMT_FRAME_AUTH,
+ },
+ {
+ .mask = BIT(IEEE80211_STYPE_PROBE_REQ >> 4),
+ .qlink_type = QLINK_MGMT_FRAME_PROBE_REQ,
+ },
+ {
+ .mask = BIT(IEEE80211_STYPE_ACTION >> 4),
+ .qlink_type = QLINK_MGMT_FRAME_ACTION,
+ },
+ };
+ unsigned int i;
+
+ if (new_mask == old_mask)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(updates); i++) {
+ u16 mask = updates[i].mask;
+ u16 qlink_frame_type = updates[i].qlink_type;
+ bool reg;
+
+ /* the ! are here due to the assoc/reassoc merge */
+ if (!(new_mask & mask) == !(old_mask & mask))
+ continue;
+
+ reg = new_mask & mask;
+
+ if (qtnf_cmd_send_register_mgmt(vif, qlink_frame_type, reg))
+ pr_warn("VIF%u.%u: failed to %sregister qlink frame type 0x%x\n",
+ vif->mac->macid, vif->vifid, reg ? "" : "un",
+ qlink_frame_type);
+ }
+
+ vif->mgmt_frames_bitmask = new_mask;
+}
+
+static int
+qtnf_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_mgmt_tx_params *params, u64 cookie)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ const struct ieee80211_mgmt *mgmt_frame = (void *)params->buf;
+ u32 short_cookie = get_random_u32();
+ u16 flags = 0;
+ u16 freq;
+
+ if (params->offchan)
+ flags |= QLINK_FRAME_TX_FLAG_OFFCHAN;
+
+ if (params->no_cck)
+ flags |= QLINK_FRAME_TX_FLAG_NO_CCK;
+
+ if (params->dont_wait_for_ack)
+ flags |= QLINK_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, freq,
+ le16_to_cpu(mgmt_frame->frame_control), mgmt_frame->da,
+ params->len, short_cookie, flags);
+
+ return qtnf_cmd_send_frame(vif, short_cookie, flags,
+ freq, params->buf, params->len);
+}
+
+static int
+qtnf_get_station(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const u8 *mac, struct station_info *sinfo)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+
+ sinfo->generation = vif->generation;
+ return qtnf_cmd_get_sta_info(vif, mac, sinfo);
+}
+
+static int
+qtnf_dump_station(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int idx, u8 *mac, struct station_info *sinfo)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ const struct qtnf_sta_node *sta_node;
+ int ret;
+
+ switch (wdev->iftype) {
+ case NL80211_IFTYPE_STATION:
+ if (idx != 0 || !wdev->connected)
+ return -ENOENT;
+
+ ether_addr_copy(mac, vif->bssid);
+ break;
+ case NL80211_IFTYPE_AP:
+ sta_node = qtnf_sta_list_lookup_index(&vif->sta_list, idx);
+ if (unlikely(!sta_node))
+ return -ENOENT;
+
+ ether_addr_copy(mac, sta_node->mac_addr);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ ret = qtnf_cmd_get_sta_info(vif, mac, sinfo);
+
+ if (wdev->iftype == NL80211_IFTYPE_AP) {
+ if (ret == -ENOENT) {
+ cfg80211_del_sta(&vif->wdev, mac, GFP_KERNEL);
+ sinfo->filled = 0;
+ }
+ }
+
+ sinfo->generation = vif->generation;
+
+ return ret;
+}
+
+static int qtnf_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int link_id, u8 key_index, bool pairwise,
+ const u8 *mac_addr, struct key_params *params)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ int ret;
+
+ ret = qtnf_cmd_send_add_key(vif, key_index, pairwise, mac_addr, params);
+ if (ret)
+ pr_err("VIF%u.%u: failed to add key: cipher=%x idx=%u pw=%u\n",
+ vif->mac->macid, vif->vifid, params->cipher, key_index,
+ pairwise);
+
+ return ret;
+}
+
+static int qtnf_del_key(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int link_id, u8 key_index, bool pairwise,
+ const u8 *mac_addr)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ int ret;
+
+ ret = qtnf_cmd_send_del_key(vif, key_index, pairwise, mac_addr);
+ if (ret) {
+ if (ret == -ENOENT) {
+ pr_debug("VIF%u.%u: key index %d out of bounds\n",
+ vif->mac->macid, vif->vifid, key_index);
+ } else {
+ pr_err("VIF%u.%u: failed to delete key: idx=%u pw=%u\n",
+ vif->mac->macid, vif->vifid,
+ key_index, pairwise);
+ }
+ }
+
+ return ret;
+}
+
+static int qtnf_set_default_key(struct wiphy *wiphy, struct net_device *dev,
+ int link_id, u8 key_index, bool unicast,
+ bool multicast)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ ret = qtnf_cmd_send_set_default_key(vif, key_index, unicast, multicast);
+ if (ret)
+ pr_err("VIF%u.%u: failed to set dflt key: idx=%u uc=%u mc=%u\n",
+ vif->mac->macid, vif->vifid, key_index, unicast,
+ multicast);
+
+ return ret;
+}
+
+static int
+qtnf_set_default_mgmt_key(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int link_id, u8 key_index)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ int ret;
+
+ ret = qtnf_cmd_send_set_default_mgmt_key(vif, key_index);
+ if (ret)
+ pr_err("VIF%u.%u: failed to set default MGMT key: idx=%u\n",
+ vif->mac->macid, vif->vifid, key_index);
+
+ return ret;
+}
+
+static int
+qtnf_change_station(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const u8 *mac, struct station_parameters *params)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ int ret;
+
+ ret = qtnf_cmd_send_change_sta(vif, mac, params);
+ if (ret)
+ pr_err("VIF%u.%u: failed to change STA %pM\n",
+ vif->mac->macid, vif->vifid, mac);
+
+ return ret;
+}
+
+static int
+qtnf_del_station(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct station_del_parameters *params)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ int ret;
+
+ if (params->mac &&
+ (wdev->iftype == NL80211_IFTYPE_AP) &&
+ !is_broadcast_ether_addr(params->mac) &&
+ !qtnf_sta_list_lookup(&vif->sta_list, params->mac))
+ return 0;
+
+ ret = qtnf_cmd_send_del_sta(vif, params);
+ if (ret)
+ pr_err("VIF%u.%u: failed to delete STA %pM\n",
+ vif->mac->macid, vif->vifid, params->mac);
+
+ return ret;
+}
+
+static int
+qtnf_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request)
+{
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+ int ret;
+
+ cancel_delayed_work_sync(&mac->scan_timeout);
+
+ mac->scan_req = request;
+
+ ret = qtnf_cmd_send_scan(mac);
+ if (ret) {
+ pr_err("MAC%u: failed to start scan\n", mac->macid);
+ mac->scan_req = NULL;
+ goto out;
+ }
+
+ pr_debug("MAC%u: scan started\n", mac->macid);
+ queue_delayed_work(mac->bus->workqueue, &mac->scan_timeout,
+ QTNF_SCAN_TIMEOUT_SEC * HZ);
+
+out:
+ return ret;
+}
+
+static int
+qtnf_connect(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_connect_params *sme)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_STATION)
+ return -EOPNOTSUPP;
+
+ if (sme->auth_type == NL80211_AUTHTYPE_SAE &&
+ !(sme->flags & CONNECT_REQ_EXTERNAL_AUTH_SUPPORT)) {
+ pr_err("can not offload authentication to userspace\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (sme->bssid)
+ ether_addr_copy(vif->bssid, sme->bssid);
+ else
+ eth_zero_addr(vif->bssid);
+
+ ret = qtnf_cmd_send_connect(vif, sme);
+ if (ret)
+ pr_err("VIF%u.%u: failed to connect\n",
+ vif->mac->macid, vif->vifid);
+
+ return ret;
+}
+
+static int
+qtnf_external_auth(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_external_auth_params *auth)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION &&
+ !ether_addr_equal(vif->bssid, auth->bssid))
+ pr_warn("unexpected bssid: %pM", auth->bssid);
+
+ ret = qtnf_cmd_send_external_auth(vif, auth);
+ if (ret)
+ pr_err("VIF%u.%u: failed to report external auth\n",
+ vif->mac->macid, vif->vifid);
+
+ return ret;
+}
+
+static int
+qtnf_disconnect(struct wiphy *wiphy, struct net_device *dev,
+ u16 reason_code)
+{
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+ struct qtnf_vif *vif;
+ int ret = 0;
+
+ vif = qtnf_mac_get_base_vif(mac);
+ if (!vif) {
+ pr_err("MAC%u: primary VIF is not configured\n", mac->macid);
+ return -EFAULT;
+ }
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_STATION)
+ return -EOPNOTSUPP;
+
+ ret = qtnf_cmd_send_disconnect(vif, reason_code);
+ if (ret)
+ pr_err("VIF%u.%u: failed to disconnect\n",
+ mac->macid, vif->vifid);
+
+ if (vif->wdev.connected) {
+ netif_carrier_off(vif->netdev);
+ cfg80211_disconnected(vif->netdev, reason_code,
+ NULL, 0, true, GFP_KERNEL);
+ }
+
+ return ret;
+}
+
+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 wireless_dev *wdev = dev->ieee80211_ptr;
+ struct ieee80211_supported_band *sband;
+ const struct cfg80211_chan_def *chandef = wdev_chandef(wdev, 0);
+ struct ieee80211_channel *chan;
+ 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];
+ survey->channel = chan;
+ survey->filled = 0x0;
+
+ if (chandef && chan == chandef->chan)
+ survey->filled = SURVEY_INFO_IN_USE;
+
+ 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);
+
+ return ret;
+}
+
+static int
+qtnf_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
+ unsigned int link_id, struct cfg80211_chan_def *chandef)
+{
+ struct net_device *ndev = wdev->netdev;
+ struct qtnf_vif *vif;
+ int ret;
+
+ if (!ndev)
+ return -ENODEV;
+
+ vif = qtnf_netdev_get_priv(wdev->netdev);
+
+ ret = qtnf_cmd_get_channel(vif, chandef);
+ if (ret) {
+ pr_err("%s: failed to get channel: %d\n", ndev->name, ret);
+ ret = -ENODATA;
+ goto out;
+ }
+
+ if (!cfg80211_chandef_valid(chandef)) {
+ 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;
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static int qtnf_channel_switch(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_csa_settings *params)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ pr_debug("%s: chan(%u) count(%u) radar(%u) block_tx(%u)\n", dev->name,
+ params->chandef.chan->hw_value, params->count,
+ params->radar_required, params->block_tx);
+
+ if (!cfg80211_chandef_valid(&params->chandef)) {
+ pr_err("%s: invalid channel\n", dev->name);
+ return -EINVAL;
+ }
+
+ ret = qtnf_cmd_send_chan_switch(vif, params);
+ if (ret)
+ pr_warn("%s: failed to switch to channel (%u)\n",
+ dev->name, params->chandef.chan->hw_value);
+
+ return ret;
+}
+
+static int qtnf_start_radar_detection(struct wiphy *wiphy,
+ struct net_device *ndev,
+ struct cfg80211_chan_def *chandef,
+ u32 cac_time_ms, int link_id)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ int ret;
+
+ if (wiphy_ext_feature_isset(wiphy, NL80211_EXT_FEATURE_DFS_OFFLOAD))
+ return -ENOTSUPP;
+
+ 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 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 int qtnf_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
+ bool enabled, int timeout)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ ret = qtnf_cmd_send_pm_set(vif, enabled ? QLINK_PM_AUTO_STANDBY :
+ QLINK_PM_OFF, timeout);
+ if (ret)
+ pr_err("%s: failed to set PM mode ret=%d\n", dev->name, ret);
+
+ return ret;
+}
+
+static int qtnf_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int radio_idx, unsigned int link_id, int *dbm)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev);
+ int ret;
+
+ ret = qtnf_cmd_get_tx_power(vif, dbm);
+ if (ret)
+ pr_err("MAC%u: failed to get Tx power\n", vif->mac->macid);
+
+ return ret;
+}
+
+static int qtnf_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int radio_idx, enum nl80211_tx_power_setting type,
+ int mbm)
+{
+ struct qtnf_vif *vif;
+ int ret;
+
+ if (wdev) {
+ vif = qtnf_netdev_get_priv(wdev->netdev);
+ } else {
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+
+ vif = qtnf_mac_get_base_vif(mac);
+ if (!vif) {
+ pr_err("MAC%u: primary VIF is not configured\n",
+ mac->macid);
+ return -EFAULT;
+ }
+ }
+
+ ret = qtnf_cmd_set_tx_power(vif, type, mbm);
+ if (ret)
+ pr_err("MAC%u: failed to set Tx power\n", vif->mac->macid);
+
+ return ret;
+}
+
+static int qtnf_update_owe_info(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_update_owe_info *owe_info)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
+ int ret;
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_AP)
+ return -EOPNOTSUPP;
+
+ ret = qtnf_cmd_send_update_owe(vif, owe_info);
+ if (ret)
+ pr_err("VIF%u.%u: failed to update owe info\n",
+ vif->mac->macid, vif->vifid);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int qtnf_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wowlan)
+{
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+ struct qtnf_vif *vif;
+ int ret = 0;
+
+ vif = qtnf_mac_get_base_vif(mac);
+ if (!vif) {
+ pr_err("MAC%u: primary VIF is not configured\n", mac->macid);
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ if (!wowlan) {
+ pr_debug("WoWLAN triggers are not enabled\n");
+ qtnf_virtual_intf_cleanup(vif->netdev);
+ goto exit;
+ }
+
+ qtnf_scan_done(vif->mac, true);
+
+ ret = qtnf_cmd_send_wowlan_set(vif, wowlan);
+ if (ret) {
+ pr_err("MAC%u: failed to set WoWLAN triggers\n",
+ mac->macid);
+ goto exit;
+ }
+
+exit:
+ return ret;
+}
+
+static int qtnf_resume(struct wiphy *wiphy)
+{
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+ struct qtnf_vif *vif;
+ int ret = 0;
+
+ vif = qtnf_mac_get_base_vif(mac);
+ if (!vif) {
+ pr_err("MAC%u: primary VIF is not configured\n", mac->macid);
+ return -EFAULT;
+ }
+
+ ret = qtnf_cmd_send_wowlan_set(vif, NULL);
+ if (ret)
+ pr_err("MAC%u: failed to reset WoWLAN triggers\n",
+ mac->macid);
+
+ return ret;
+}
+
+static void qtnf_set_wakeup(struct wiphy *wiphy, bool enabled)
+{
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+ struct qtnf_bus *bus = mac->bus;
+
+ device_set_wakeup_enable(bus->dev, enabled);
+}
+#endif
+
+static struct cfg80211_ops qtn_cfg80211_ops = {
+ .add_virtual_intf = qtnf_add_virtual_intf,
+ .change_virtual_intf = qtnf_change_virtual_intf,
+ .del_virtual_intf = qtnf_del_virtual_intf,
+ .start_ap = qtnf_start_ap,
+ .change_beacon = qtnf_change_beacon,
+ .stop_ap = qtnf_stop_ap,
+ .set_wiphy_params = qtnf_set_wiphy_params,
+ .update_mgmt_frame_registrations =
+ qtnf_update_mgmt_frame_registrations,
+ .mgmt_tx = qtnf_mgmt_tx,
+ .change_station = qtnf_change_station,
+ .del_station = qtnf_del_station,
+ .get_station = qtnf_get_station,
+ .dump_station = qtnf_dump_station,
+ .add_key = qtnf_add_key,
+ .del_key = qtnf_del_key,
+ .set_default_key = qtnf_set_default_key,
+ .set_default_mgmt_key = qtnf_set_default_mgmt_key,
+ .scan = qtnf_scan,
+ .connect = qtnf_connect,
+ .external_auth = qtnf_external_auth,
+ .disconnect = qtnf_disconnect,
+ .dump_survey = qtnf_dump_survey,
+ .get_channel = qtnf_get_channel,
+ .channel_switch = qtnf_channel_switch,
+ .start_radar_detection = qtnf_start_radar_detection,
+ .set_mac_acl = qtnf_set_mac_acl,
+ .set_power_mgmt = qtnf_set_power_mgmt,
+ .get_tx_power = qtnf_get_tx_power,
+ .set_tx_power = qtnf_set_tx_power,
+ .update_owe_info = qtnf_update_owe_info,
+#ifdef CONFIG_PM
+ .suspend = qtnf_suspend,
+ .resume = qtnf_resume,
+ .set_wakeup = qtnf_set_wakeup,
+#endif
+};
+
+static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy,
+ struct regulatory_request *req)
+{
+ struct qtnf_wmac *mac = wiphy_priv(wiphy);
+ enum nl80211_band band;
+ int ret;
+
+ pr_debug("MAC%u: initiator=%d alpha=%c%c\n", mac->macid, req->initiator,
+ req->alpha2[0], req->alpha2[1]);
+
+ ret = qtnf_cmd_reg_notify(mac, req, qtnf_slave_radar_get(),
+ qtnf_dfs_offload_get());
+ if (ret) {
+ pr_err("MAC%u: failed to update region to %c%c: %d\n",
+ mac->macid, req->alpha2[0], req->alpha2[1], ret);
+ return;
+ }
+
+ for (band = 0; band < NUM_NL80211_BANDS; ++band) {
+ if (!wiphy->bands[band])
+ continue;
+
+ ret = qtnf_cmd_band_info_get(mac, wiphy->bands[band]);
+ if (ret)
+ pr_err("MAC%u: failed to update band %u\n",
+ mac->macid, band);
+ }
+}
+
+struct wiphy *qtnf_wiphy_allocate(struct qtnf_bus *bus,
+ struct platform_device *pdev)
+{
+ struct wiphy *wiphy;
+
+ if (qtnf_dfs_offload_get() &&
+ qtnf_hwcap_is_set(&bus->hw_info, QLINK_HW_CAPAB_DFS_OFFLOAD))
+ qtn_cfg80211_ops.start_radar_detection = NULL;
+
+ if (!qtnf_hwcap_is_set(&bus->hw_info, QLINK_HW_CAPAB_PWR_MGMT))
+ qtn_cfg80211_ops.set_power_mgmt = NULL;
+
+ wiphy = wiphy_new(&qtn_cfg80211_ops, sizeof(struct qtnf_wmac));
+ if (!wiphy)
+ return NULL;
+
+ if (pdev)
+ set_wiphy_dev(wiphy, &pdev->dev);
+ else
+ set_wiphy_dev(wiphy, bus->dev);
+
+ return wiphy;
+}
+
+static int
+qtnf_wiphy_setup_if_comb(struct wiphy *wiphy, struct qtnf_mac_info *mac_info)
+{
+ struct ieee80211_iface_combination *if_comb;
+ size_t n_if_comb;
+ u16 interface_modes = 0;
+ size_t i, j;
+
+ if_comb = mac_info->if_comb;
+ n_if_comb = mac_info->n_if_comb;
+
+ if (!if_comb || !n_if_comb)
+ return -ENOENT;
+
+ for (i = 0; i < n_if_comb; i++) {
+ if_comb[i].radar_detect_widths = mac_info->radar_detect_widths;
+
+ for (j = 0; j < if_comb[i].n_limits; j++)
+ interface_modes |= if_comb[i].limits[j].types;
+ }
+
+ wiphy->iface_combinations = if_comb;
+ wiphy->n_iface_combinations = n_if_comb;
+ wiphy->interface_modes = interface_modes;
+
+ return 0;
+}
+
+int qtnf_wiphy_register(struct qtnf_hw_info *hw_info, struct qtnf_wmac *mac)
+{
+ struct wiphy *wiphy = priv_to_wiphy(mac);
+ struct qtnf_mac_info *macinfo = &mac->macinfo;
+ int ret;
+ bool regdomain_is_known;
+
+ if (!wiphy) {
+ pr_err("invalid wiphy pointer\n");
+ return -EFAULT;
+ }
+
+ wiphy->frag_threshold = macinfo->frag_thr;
+ wiphy->rts_threshold = macinfo->rts_thr;
+ wiphy->retry_short = macinfo->sretry_limit;
+ wiphy->retry_long = macinfo->lretry_limit;
+ wiphy->coverage_class = macinfo->coverage_class;
+
+ wiphy->max_scan_ssids =
+ (macinfo->max_scan_ssids) ? macinfo->max_scan_ssids : 1;
+ 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 = macinfo->max_acl_mac_addrs;
+ wiphy->max_num_csa_counters = 2;
+
+ ret = qtnf_wiphy_setup_if_comb(wiphy, macinfo);
+ if (ret)
+ goto out;
+
+ /* Initialize cipher suits */
+ wiphy->cipher_suites = qtnf_cipher_suites;
+ wiphy->n_cipher_suites = ARRAY_SIZE(qtnf_cipher_suites);
+ wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+ wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME |
+ WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD |
+ WIPHY_FLAG_AP_UAPSD |
+ WIPHY_FLAG_HAS_CHANNEL_SWITCH |
+ WIPHY_FLAG_4ADDR_STATION |
+ WIPHY_FLAG_NETNS_OK;
+ wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
+
+ if (qtnf_dfs_offload_get() &&
+ qtnf_hwcap_is_set(hw_info, QLINK_HW_CAPAB_DFS_OFFLOAD))
+ wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_DFS_OFFLOAD);
+
+ if (qtnf_hwcap_is_set(hw_info, QLINK_HW_CAPAB_SCAN_DWELL))
+ wiphy_ext_feature_set(wiphy,
+ NL80211_EXT_FEATURE_SET_SCAN_DWELL);
+
+ wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
+ NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2;
+
+ wiphy->available_antennas_tx = macinfo->num_tx_chain;
+ wiphy->available_antennas_rx = macinfo->num_rx_chain;
+
+ wiphy->max_ap_assoc_sta = macinfo->max_ap_assoc_sta;
+ wiphy->ht_capa_mod_mask = &macinfo->ht_cap_mod_mask;
+ wiphy->vht_capa_mod_mask = &macinfo->vht_cap_mod_mask;
+
+ ether_addr_copy(wiphy->perm_addr, mac->macaddr);
+
+ if (qtnf_hwcap_is_set(hw_info, QLINK_HW_CAPAB_STA_INACT_TIMEOUT))
+ wiphy->features |= NL80211_FEATURE_INACTIVITY_TIMER;
+
+ if (qtnf_hwcap_is_set(hw_info, QLINK_HW_CAPAB_SCAN_RANDOM_MAC_ADDR))
+ wiphy->features |= NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR;
+
+ if (!qtnf_hwcap_is_set(hw_info, QLINK_HW_CAPAB_OBSS_SCAN))
+ wiphy->features |= NL80211_FEATURE_NEED_OBSS_SCAN;
+
+ if (qtnf_hwcap_is_set(hw_info, QLINK_HW_CAPAB_SAE))
+ wiphy->features |= NL80211_FEATURE_SAE;
+
+#ifdef CONFIG_PM
+ if (macinfo->wowlan)
+ wiphy->wowlan = macinfo->wowlan;
+#endif
+
+ regdomain_is_known = isalpha(mac->rd->alpha2[0]) &&
+ isalpha(mac->rd->alpha2[1]);
+
+ if (qtnf_hwcap_is_set(hw_info, QLINK_HW_CAPAB_REG_UPDATE)) {
+ wiphy->reg_notifier = qtnf_cfg80211_reg_notifier;
+
+ if (mac->rd->alpha2[0] == '9' && mac->rd->alpha2[1] == '9') {
+ wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG |
+ REGULATORY_STRICT_REG;
+ wiphy_apply_custom_regulatory(wiphy, mac->rd);
+ } else if (regdomain_is_known) {
+ wiphy->regulatory_flags |= REGULATORY_STRICT_REG;
+ }
+ } else {
+ wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
+ }
+
+ if (mac->macinfo.extended_capabilities_len) {
+ wiphy->extended_capabilities =
+ mac->macinfo.extended_capabilities;
+ wiphy->extended_capabilities_mask =
+ mac->macinfo.extended_capabilities_mask;
+ wiphy->extended_capabilities_len =
+ mac->macinfo.extended_capabilities_len;
+ }
+
+ strscpy(wiphy->fw_version, hw_info->fw_version,
+ sizeof(wiphy->fw_version));
+ wiphy->hw_version = hw_info->hw_version;
+
+ ret = wiphy_register(wiphy);
+ if (ret < 0)
+ goto out;
+
+ if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
+ ret = regulatory_set_wiphy_regd(wiphy, mac->rd);
+ else if (regdomain_is_known)
+ ret = regulatory_hint(wiphy, mac->rd->alpha2);
+
+out:
+ return ret;
+}
+
+void qtnf_netdev_updown(struct net_device *ndev, bool up)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+
+ if (qtnf_cmd_send_updown_intf(vif, up))
+ pr_err("failed to send %s command to VIF%u.%u\n",
+ up ? "UP" : "DOWN", vif->mac->macid, vif->vifid);
+}
+
+void qtnf_virtual_intf_cleanup(struct net_device *ndev)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ struct qtnf_wmac *mac = wiphy_priv(vif->wdev.wiphy);
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION)
+ qtnf_disconnect(vif->wdev.wiphy, ndev,
+ WLAN_REASON_DEAUTH_LEAVING);
+
+ qtnf_scan_done(mac, true);
+}
+
+void qtnf_cfg80211_vif_reset(struct qtnf_vif *vif)
+{
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION)
+ cfg80211_disconnected(vif->netdev, WLAN_REASON_DEAUTH_LEAVING,
+ NULL, 0, 1, GFP_KERNEL);
+
+ cfg80211_shutdown_all_interfaces(vif->wdev.wiphy);
+}
+
+void qtnf_band_init_rates(struct ieee80211_supported_band *band)
+{
+ switch (band->band) {
+ case NL80211_BAND_2GHZ:
+ band->bitrates = qtnf_rates_2g;
+ band->n_bitrates = ARRAY_SIZE(qtnf_rates_2g);
+ break;
+ case NL80211_BAND_5GHZ:
+ band->bitrates = qtnf_rates_5g;
+ band->n_bitrates = ARRAY_SIZE(qtnf_rates_5g);
+ break;
+ default:
+ band->bitrates = NULL;
+ band->n_bitrates = 0;
+ break;
+ }
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.h b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.h
new file mode 100644
index 000000000..c37485728
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_CFG80211_H_
+#define _QTN_FMAC_CFG80211_H_
+
+#include <net/cfg80211.h>
+
+#include "core.h"
+
+int qtnf_wiphy_register(struct qtnf_hw_info *hw_info, struct qtnf_wmac *mac);
+int qtnf_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev);
+void qtnf_cfg80211_vif_reset(struct qtnf_vif *vif);
+void qtnf_band_init_rates(struct ieee80211_supported_band *band);
+void qtnf_band_setup_htvht_caps(struct qtnf_mac_info *macinfo,
+ struct ieee80211_supported_band *band);
+
+#endif /* _QTN_FMAC_CFG80211_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
new file mode 100644
index 000000000..77929ed9e
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -0,0 +1,2765 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+
+#include "cfg80211.h"
+#include "core.h"
+#include "qlink.h"
+#include "qlink_util.h"
+#include "bus.h"
+#include "commands.h"
+
+/* Let device itself to select best values for current conditions */
+#define QTNF_SCAN_TIME_AUTO 0
+
+#define QTNF_SCAN_DWELL_ACTIVE_DEFAULT 90
+#define QTNF_SCAN_DWELL_PASSIVE_DEFAULT 100
+#define QTNF_SCAN_SAMPLE_DURATION_DEFAULT QTNF_SCAN_TIME_AUTO
+
+static int qtnf_cmd_check_reply_header(const struct qlink_resp *resp,
+ u16 cmd_id, u8 mac_id, u8 vif_id,
+ size_t resp_size)
+{
+ if (unlikely(le16_to_cpu(resp->cmd_id) != cmd_id)) {
+ pr_warn("VIF%u.%u CMD%x: bad cmd_id in response: 0x%.4X\n",
+ mac_id, vif_id, cmd_id, le16_to_cpu(resp->cmd_id));
+ return -EINVAL;
+ }
+
+ if (unlikely(resp->macid != mac_id)) {
+ pr_warn("VIF%u.%u CMD%x: bad MAC in response: %u\n",
+ mac_id, vif_id, cmd_id, resp->macid);
+ return -EINVAL;
+ }
+
+ if (unlikely(resp->vifid != vif_id)) {
+ pr_warn("VIF%u.%u CMD%x: bad VIF in response: %u\n",
+ mac_id, vif_id, cmd_id, resp->vifid);
+ return -EINVAL;
+ }
+
+ if (unlikely(le16_to_cpu(resp->mhdr.len) < resp_size)) {
+ pr_warn("VIF%u.%u CMD%x: bad response size %u < %zu\n",
+ mac_id, vif_id, cmd_id,
+ le16_to_cpu(resp->mhdr.len), resp_size);
+ return -ENOSPC;
+ }
+
+ return 0;
+}
+
+static int qtnf_cmd_resp_result_decode(enum qlink_cmd_result qcode)
+{
+ switch (qcode) {
+ case QLINK_CMD_RESULT_OK:
+ return 0;
+ case QLINK_CMD_RESULT_INVALID:
+ return -EINVAL;
+ case QLINK_CMD_RESULT_ENOTSUPP:
+ return -ENOTSUPP;
+ case QLINK_CMD_RESULT_ENOTFOUND:
+ return -ENOENT;
+ case QLINK_CMD_RESULT_EALREADY:
+ return -EALREADY;
+ case QLINK_CMD_RESULT_EADDRINUSE:
+ return -EADDRINUSE;
+ case QLINK_CMD_RESULT_EADDRNOTAVAIL:
+ return -EADDRNOTAVAIL;
+ case QLINK_CMD_RESULT_EBUSY:
+ return -EBUSY;
+ default:
+ return -EFAULT;
+ }
+}
+
+static int qtnf_cmd_send_with_reply(struct qtnf_bus *bus,
+ struct sk_buff *cmd_skb,
+ struct sk_buff **response_skb,
+ size_t const_resp_size,
+ size_t *var_resp_size)
+{
+ struct qlink_cmd *cmd;
+ struct qlink_resp *resp = NULL;
+ struct sk_buff *resp_skb = NULL;
+ int resp_res = 0;
+ u16 cmd_id;
+ u8 mac_id;
+ u8 vif_id;
+ int ret;
+
+ cmd = (struct qlink_cmd *)cmd_skb->data;
+ cmd_id = le16_to_cpu(cmd->cmd_id);
+ mac_id = cmd->macid;
+ vif_id = cmd->vifid;
+ cmd->mhdr.len = cpu_to_le16(cmd_skb->len);
+
+ pr_debug("VIF%u.%u cmd=0x%.4X\n", mac_id, vif_id, cmd_id);
+
+ if (!qtnf_fw_is_up(bus) && cmd_id != QLINK_CMD_FW_INIT) {
+ pr_warn("VIF%u.%u: drop cmd 0x%.4X in fw state %d\n",
+ mac_id, vif_id, cmd_id, bus->fw_state);
+ dev_kfree_skb(cmd_skb);
+ return -ENODEV;
+ }
+
+ ret = qtnf_trans_send_cmd_with_resp(bus, cmd_skb, &resp_skb);
+ if (ret)
+ goto out;
+
+ if (WARN_ON(!resp_skb || !resp_skb->data)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ resp = (struct qlink_resp *)resp_skb->data;
+ resp_res = le16_to_cpu(resp->result);
+ ret = qtnf_cmd_check_reply_header(resp, cmd_id, mac_id, vif_id,
+ const_resp_size);
+ if (ret)
+ goto out;
+
+ /* Return length of variable part of response */
+ if (response_skb && var_resp_size)
+ *var_resp_size = le16_to_cpu(resp->mhdr.len) - const_resp_size;
+
+out:
+ if (response_skb)
+ *response_skb = resp_skb;
+ else
+ consume_skb(resp_skb);
+
+ if (!ret)
+ return qtnf_cmd_resp_result_decode(resp_res);
+
+ pr_warn("VIF%u.%u: cmd 0x%.4X failed: %d\n",
+ mac_id, vif_id, cmd_id, ret);
+
+ return ret;
+}
+
+static inline int qtnf_cmd_send(struct qtnf_bus *bus, struct sk_buff *cmd_skb)
+{
+ return qtnf_cmd_send_with_reply(bus, cmd_skb, NULL,
+ sizeof(struct qlink_resp), NULL);
+}
+
+static struct sk_buff *qtnf_cmd_alloc_new_cmdskb(u8 macid, u8 vifid, u16 cmd_no,
+ size_t cmd_size)
+{
+ struct qlink_cmd *cmd;
+ struct sk_buff *cmd_skb;
+
+ cmd_skb = __dev_alloc_skb(sizeof(*cmd) +
+ QTNF_MAX_CMD_BUF_SIZE, GFP_KERNEL);
+ if (unlikely(!cmd_skb)) {
+ pr_err("VIF%u.%u CMD %u: alloc failed\n", macid, vifid, cmd_no);
+ return NULL;
+ }
+
+ skb_put_zero(cmd_skb, cmd_size);
+
+ cmd = (struct qlink_cmd *)cmd_skb->data;
+ cmd->mhdr.len = cpu_to_le16(cmd_skb->len);
+ cmd->mhdr.type = cpu_to_le16(QLINK_MSG_TYPE_CMD);
+ cmd->cmd_id = cpu_to_le16(cmd_no);
+ cmd->macid = macid;
+ cmd->vifid = vifid;
+
+ return cmd_skb;
+}
+
+static void qtnf_cmd_tlv_ie_set_add(struct sk_buff *cmd_skb, u8 frame_type,
+ const u8 *buf, size_t len)
+{
+ struct qlink_tlv_ie_set *tlv;
+
+ tlv = (struct qlink_tlv_ie_set *)skb_put(cmd_skb, sizeof(*tlv) +
+ round_up(len, QLINK_ALIGN));
+ tlv->hdr.type = cpu_to_le16(QTN_TLV_ID_IE_SET);
+ tlv->hdr.len = cpu_to_le16(len + sizeof(*tlv) - sizeof(tlv->hdr));
+ tlv->type = frame_type;
+ tlv->flags = 0;
+
+ if (len && buf)
+ memcpy(tlv->ie_data, buf, len);
+}
+
+static bool qtnf_cmd_start_ap_can_fit(const struct qtnf_vif *vif,
+ const struct cfg80211_ap_settings *s)
+{
+ unsigned int len = sizeof(struct qlink_cmd_start_ap);
+
+ len += round_up(s->ssid_len, QLINK_ALIGN);
+ len += round_up(s->beacon.head_len, QLINK_ALIGN);
+ len += round_up(s->beacon.tail_len, QLINK_ALIGN);
+ len += round_up(s->beacon.beacon_ies_len, QLINK_ALIGN);
+ len += round_up(s->beacon.proberesp_ies_len, QLINK_ALIGN);
+ len += round_up(s->beacon.assocresp_ies_len, QLINK_ALIGN);
+ len += round_up(s->beacon.probe_resp_len, QLINK_ALIGN);
+
+ if (cfg80211_chandef_valid(&s->chandef))
+ len += sizeof(struct qlink_tlv_chandef);
+
+ if (s->acl) {
+ unsigned int acl_len = struct_size(s->acl, mac_addrs,
+ s->acl->n_acl_entries);
+
+ len += sizeof(struct qlink_tlv_hdr) +
+ round_up(acl_len, QLINK_ALIGN);
+ }
+
+ 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);
+ return false;
+ }
+
+ return true;
+}
+
+static void qtnf_cmd_tlv_ie_ext_add(struct sk_buff *cmd_skb, u8 eid_ext,
+ const void *buf, size_t len)
+{
+ struct qlink_tlv_ext_ie *tlv;
+
+ tlv = (struct qlink_tlv_ext_ie *)skb_put(cmd_skb, sizeof(*tlv) + len);
+ tlv->hdr.type = cpu_to_le16(WLAN_EID_EXTENSION);
+ tlv->hdr.len = cpu_to_le16(sizeof(*tlv) + len - sizeof(tlv->hdr));
+ tlv->eid_ext = eid_ext;
+
+ if (len && buf)
+ memcpy(tlv->ie_data, buf, len);
+}
+
+int qtnf_cmd_send_start_ap(struct qtnf_vif *vif,
+ const struct cfg80211_ap_settings *s)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_start_ap *cmd;
+ struct qlink_auth_encr *aen;
+ int ret;
+ int i;
+ int n;
+
+ if (!qtnf_cmd_start_ap_can_fit(vif, s))
+ return -E2BIG;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_START_AP,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_start_ap *)cmd_skb->data;
+ cmd->dtim_period = s->dtim_period;
+ cmd->beacon_interval = cpu_to_le16(s->beacon_interval);
+ cmd->hidden_ssid = qlink_hidden_ssid_nl2q(s->hidden_ssid);
+ cmd->inactivity_timeout = cpu_to_le16(s->inactivity_timeout);
+ cmd->smps_mode = NL80211_SMPS_OFF;
+ cmd->p2p_ctwindow = s->p2p_ctwindow;
+ cmd->p2p_opp_ps = s->p2p_opp_ps;
+ cmd->pbss = s->pbss;
+ cmd->ht_required = s->beacon.ht_required;
+ cmd->vht_required = s->beacon.vht_required;
+ cmd->twt_responder = s->twt_responder;
+ if (s->he_obss_pd.enable) {
+ cmd->sr_params.sr_control |= QLINK_SR_SRG_INFORMATION_PRESENT;
+ cmd->sr_params.srg_obss_pd_min_offset =
+ s->he_obss_pd.min_offset;
+ cmd->sr_params.srg_obss_pd_max_offset =
+ s->he_obss_pd.max_offset;
+ }
+
+ aen = &cmd->aen;
+ aen->auth_type = s->auth_type;
+ aen->privacy = !!s->privacy;
+ aen->wpa_versions = cpu_to_le32(s->crypto.wpa_versions);
+ aen->cipher_group = cpu_to_le32(s->crypto.cipher_group);
+ aen->n_ciphers_pairwise = cpu_to_le32(s->crypto.n_ciphers_pairwise);
+ for (i = 0; i < QLINK_MAX_NR_CIPHER_SUITES; i++)
+ aen->ciphers_pairwise[i] =
+ cpu_to_le32(s->crypto.ciphers_pairwise[i]);
+ n = min(QLINK_MAX_NR_AKM_SUITES, s->crypto.n_akm_suites);
+ aen->n_akm_suites = cpu_to_le32(n);
+ for (i = 0; i < n; i++)
+ aen->akm_suites[i] = cpu_to_le32(s->crypto.akm_suites[i]);
+ aen->control_port = s->crypto.control_port;
+ aen->control_port_no_encrypt = s->crypto.control_port_no_encrypt;
+ aen->control_port_ethertype =
+ cpu_to_le16(be16_to_cpu(s->crypto.control_port_ethertype));
+
+ if (s->ssid && s->ssid_len > 0 && s->ssid_len <= IEEE80211_MAX_SSID_LEN)
+ qtnf_cmd_skb_put_tlv_arr(cmd_skb, WLAN_EID_SSID, s->ssid,
+ s->ssid_len);
+
+ if (cfg80211_chandef_valid(&s->chandef)) {
+ struct qlink_tlv_chandef *chtlv =
+ (struct qlink_tlv_chandef *)skb_put(cmd_skb,
+ sizeof(*chtlv));
+
+ 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->chdef);
+ }
+
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_BEACON_HEAD,
+ s->beacon.head, s->beacon.head_len);
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_BEACON_TAIL,
+ s->beacon.tail, s->beacon.tail_len);
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_BEACON_IES,
+ s->beacon.beacon_ies, s->beacon.beacon_ies_len);
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_PROBE_RESP,
+ s->beacon.probe_resp, s->beacon.probe_resp_len);
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_PROBE_RESP_IES,
+ s->beacon.proberesp_ies,
+ s->beacon.proberesp_ies_len);
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_ASSOC_RESP,
+ s->beacon.assocresp_ies,
+ s->beacon.assocresp_ies_len);
+
+ if (s->ht_cap) {
+ struct qlink_tlv_hdr *tlv = (struct qlink_tlv_hdr *)
+ skb_put(cmd_skb, sizeof(*tlv) +
+ round_up(sizeof(*s->ht_cap), QLINK_ALIGN));
+
+ tlv->type = cpu_to_le16(WLAN_EID_HT_CAPABILITY);
+ tlv->len = cpu_to_le16(sizeof(*s->ht_cap));
+ memcpy(tlv->val, s->ht_cap, sizeof(*s->ht_cap));
+ }
+
+ if (s->vht_cap) {
+ struct qlink_tlv_hdr *tlv = (struct qlink_tlv_hdr *)
+ skb_put(cmd_skb, sizeof(*tlv) + sizeof(*s->vht_cap));
+
+ tlv->type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY);
+ tlv->len = cpu_to_le16(sizeof(*s->vht_cap));
+ memcpy(tlv->val, s->vht_cap, sizeof(*s->vht_cap));
+ }
+
+ if (s->he_cap)
+ qtnf_cmd_tlv_ie_ext_add(cmd_skb, WLAN_EID_EXT_HE_CAPABILITY,
+ s->he_cap, sizeof(*s->he_cap));
+
+ if (s->acl) {
+ size_t acl_size = struct_size(s->acl, mac_addrs,
+ s->acl->n_acl_entries);
+ struct qlink_tlv_hdr *tlv =
+ skb_put(cmd_skb,
+ sizeof(*tlv) + round_up(acl_size, QLINK_ALIGN));
+
+ 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);
+ if (ret)
+ goto out;
+
+ netif_carrier_on(vif->netdev);
+
+out:
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_stop_ap(struct qtnf_vif *vif)
+{
+ struct sk_buff *cmd_skb;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_STOP_AP,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_register_mgmt(struct qtnf_vif *vif, u16 frame_type, bool reg)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_mgmt_frame_register *cmd;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_REGISTER_MGMT,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_mgmt_frame_register *)cmd_skb->data;
+ cmd->frame_type = cpu_to_le16(frame_type);
+ cmd->do_register = reg;
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_frame(struct qtnf_vif *vif, u32 cookie, u16 flags,
+ u16 freq, const u8 *buf, size_t len)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_frame_tx *cmd;
+ int ret;
+
+ if (sizeof(*cmd) + len > QTNF_MAX_CMD_BUF_SIZE) {
+ pr_warn("VIF%u.%u: frame is too big: %zu\n", vif->mac->macid,
+ vif->vifid, len);
+ return -E2BIG;
+ }
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_SEND_FRAME,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_frame_tx *)cmd_skb->data;
+ cmd->cookie = cpu_to_le32(cookie);
+ cmd->freq = cpu_to_le16(freq);
+ cmd->flags = cpu_to_le16(flags);
+
+ if (len && buf)
+ qtnf_cmd_skb_put_buffer(cmd_skb, buf, len);
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_mgmt_set_appie(struct qtnf_vif *vif, u8 frame_type,
+ const u8 *buf, size_t len)
+{
+ struct sk_buff *cmd_skb;
+ int ret;
+
+ if (len > QTNF_MAX_CMD_BUF_SIZE) {
+ pr_warn("VIF%u.%u: %u frame is too big: %zu\n", vif->mac->macid,
+ vif->vifid, frame_type, len);
+ return -E2BIG;
+ }
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_MGMT_SET_APPIE,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, frame_type, buf, len);
+
+ qtnf_bus_lock(vif->mac->bus);
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+static void
+qtnf_sta_info_parse_rate(struct rate_info *rate_dst,
+ const struct qlink_sta_info_rate *rate_src)
+{
+ rate_dst->legacy = get_unaligned_le16(&rate_src->rate) * 10;
+
+ rate_dst->mcs = rate_src->mcs;
+ rate_dst->nss = rate_src->nss;
+ rate_dst->flags = 0;
+
+ switch (rate_src->bw) {
+ case QLINK_CHAN_WIDTH_5:
+ rate_dst->bw = RATE_INFO_BW_5;
+ break;
+ case QLINK_CHAN_WIDTH_10:
+ rate_dst->bw = RATE_INFO_BW_10;
+ break;
+ case QLINK_CHAN_WIDTH_20:
+ case QLINK_CHAN_WIDTH_20_NOHT:
+ rate_dst->bw = RATE_INFO_BW_20;
+ break;
+ case QLINK_CHAN_WIDTH_40:
+ rate_dst->bw = RATE_INFO_BW_40;
+ break;
+ case QLINK_CHAN_WIDTH_80:
+ rate_dst->bw = RATE_INFO_BW_80;
+ break;
+ case QLINK_CHAN_WIDTH_160:
+ rate_dst->bw = RATE_INFO_BW_160;
+ break;
+ default:
+ rate_dst->bw = 0;
+ break;
+ }
+
+ if (rate_src->flags & QLINK_STA_INFO_RATE_FLAG_HT_MCS)
+ rate_dst->flags |= RATE_INFO_FLAGS_MCS;
+ else if (rate_src->flags & QLINK_STA_INFO_RATE_FLAG_VHT_MCS)
+ rate_dst->flags |= RATE_INFO_FLAGS_VHT_MCS;
+ else if (rate_src->flags & QLINK_STA_INFO_RATE_FLAG_HE_MCS)
+ rate_dst->flags |= RATE_INFO_FLAGS_HE_MCS;
+
+ if (rate_src->flags & QLINK_STA_INFO_RATE_FLAG_SHORT_GI)
+ rate_dst->flags |= RATE_INFO_FLAGS_SHORT_GI;
+}
+
+static void
+qtnf_sta_info_parse_flags(struct nl80211_sta_flag_update *dst,
+ const struct qlink_sta_info_state *src)
+{
+ u32 mask, value;
+
+ dst->mask = 0;
+ dst->set = 0;
+
+ mask = le32_to_cpu(src->mask);
+ value = le32_to_cpu(src->value);
+
+ if (mask & QLINK_STA_FLAG_AUTHORIZED) {
+ dst->mask |= BIT(NL80211_STA_FLAG_AUTHORIZED);
+ if (value & QLINK_STA_FLAG_AUTHORIZED)
+ dst->set |= BIT(NL80211_STA_FLAG_AUTHORIZED);
+ }
+
+ if (mask & QLINK_STA_FLAG_SHORT_PREAMBLE) {
+ dst->mask |= BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
+ if (value & QLINK_STA_FLAG_SHORT_PREAMBLE)
+ dst->set |= BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
+ }
+
+ if (mask & QLINK_STA_FLAG_WME) {
+ dst->mask |= BIT(NL80211_STA_FLAG_WME);
+ if (value & QLINK_STA_FLAG_WME)
+ dst->set |= BIT(NL80211_STA_FLAG_WME);
+ }
+
+ if (mask & QLINK_STA_FLAG_MFP) {
+ dst->mask |= BIT(NL80211_STA_FLAG_MFP);
+ if (value & QLINK_STA_FLAG_MFP)
+ dst->set |= BIT(NL80211_STA_FLAG_MFP);
+ }
+
+ if (mask & QLINK_STA_FLAG_AUTHENTICATED) {
+ dst->mask |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+ if (value & QLINK_STA_FLAG_AUTHENTICATED)
+ dst->set |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+ }
+
+ if (mask & QLINK_STA_FLAG_TDLS_PEER) {
+ dst->mask |= BIT(NL80211_STA_FLAG_TDLS_PEER);
+ if (value & QLINK_STA_FLAG_TDLS_PEER)
+ dst->set |= BIT(NL80211_STA_FLAG_TDLS_PEER);
+ }
+
+ if (mask & QLINK_STA_FLAG_ASSOCIATED) {
+ dst->mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+ if (value & QLINK_STA_FLAG_ASSOCIATED)
+ dst->set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+ }
+}
+
+static void
+qtnf_cmd_sta_info_parse(struct station_info *sinfo, const u8 *data,
+ size_t resp_size)
+{
+ const struct qlink_tlv_hdr *tlv;
+ 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))
+
+ qlink_for_each_tlv(tlv, data, resp_size) {
+ tlv_len = le16_to_cpu(tlv->len);
+
+ switch (le16_to_cpu(tlv->type)) {
+ case QTN_TLV_ID_BITMAP:
+ 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;
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, data, resp_size)) {
+ pr_err("Malformed TLV buffer\n");
+ return;
+ }
+
+ if (!map || !stats)
+ return;
+
+ if (qtnf_sta_stat_avail(inactive_time, QLINK_STA_INFO_INACTIVE_TIME)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME);
+ sinfo->inactive_time = le32_to_cpu(stats->inactive_time);
+ }
+
+ if (qtnf_sta_stat_avail(connected_time,
+ QLINK_STA_INFO_CONNECTED_TIME)) {
+ sinfo->filled |= BIT_ULL(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_ULL(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_ULL(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_ULL(NL80211_STA_INFO_RX_BITRATE);
+ qtnf_sta_info_parse_rate(&sinfo->rxrate, &stats->rxrate);
+ }
+
+ if (qtnf_sta_stat_avail(txrate, QLINK_STA_INFO_TX_BITRATE)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+ qtnf_sta_info_parse_rate(&sinfo->txrate, &stats->txrate);
+ }
+
+ if (qtnf_sta_stat_avail(sta_flags, QLINK_STA_INFO_STA_FLAGS)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_STA_FLAGS);
+ qtnf_sta_info_parse_flags(&sinfo->sta_flags, &stats->sta_flags);
+ }
+
+ if (qtnf_sta_stat_avail(rx_bytes, QLINK_STA_INFO_RX_BYTES)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES);
+ sinfo->rx_bytes = le64_to_cpu(stats->rx_bytes);
+ }
+
+ if (qtnf_sta_stat_avail(tx_bytes, QLINK_STA_INFO_TX_BYTES)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES);
+ sinfo->tx_bytes = le64_to_cpu(stats->tx_bytes);
+ }
+
+ if (qtnf_sta_stat_avail(rx_bytes, QLINK_STA_INFO_RX_BYTES64)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES64);
+ sinfo->rx_bytes = le64_to_cpu(stats->rx_bytes);
+ }
+
+ if (qtnf_sta_stat_avail(tx_bytes, QLINK_STA_INFO_TX_BYTES64)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES64);
+ sinfo->tx_bytes = le64_to_cpu(stats->tx_bytes);
+ }
+
+ if (qtnf_sta_stat_avail(rx_packets, QLINK_STA_INFO_RX_PACKETS)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
+ sinfo->rx_packets = le32_to_cpu(stats->rx_packets);
+ }
+
+ if (qtnf_sta_stat_avail(tx_packets, QLINK_STA_INFO_TX_PACKETS)) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS);
+ sinfo->tx_packets = le32_to_cpu(stats->tx_packets);
+ }
+
+ if (qtnf_sta_stat_avail(rx_beacon, QLINK_STA_INFO_BEACON_RX)) {
+ sinfo->filled |= BIT_ULL(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_ULL(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_ULL(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,
+ struct station_info *sinfo)
+{
+ struct sk_buff *cmd_skb, *resp_skb = NULL;
+ struct qlink_cmd_get_sta_info *cmd;
+ const struct qlink_resp_get_sta_info *resp;
+ size_t var_resp_len = 0;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_GET_STA_INFO,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_get_sta_info *)cmd_skb->data;
+ ether_addr_copy(cmd->sta_addr, sta_mac);
+
+ ret = qtnf_cmd_send_with_reply(vif->mac->bus, cmd_skb, &resp_skb,
+ sizeof(*resp), &var_resp_len);
+ if (ret)
+ goto out;
+
+ resp = (const struct qlink_resp_get_sta_info *)resp_skb->data;
+
+ if (!ether_addr_equal(sta_mac, resp->sta_addr)) {
+ pr_err("VIF%u.%u: wrong mac in reply: %pM != %pM\n",
+ vif->mac->macid, vif->vifid, resp->sta_addr, sta_mac);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ qtnf_cmd_sta_info_parse(sinfo, resp->info, var_resp_len);
+
+out:
+ qtnf_bus_unlock(vif->mac->bus);
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+static int qtnf_cmd_send_add_change_intf(struct qtnf_vif *vif,
+ enum nl80211_iftype iftype,
+ int use4addr,
+ u8 *mac_addr,
+ enum qlink_cmd_type cmd_type)
+{
+ struct sk_buff *cmd_skb, *resp_skb = NULL;
+ struct qlink_cmd_manage_intf *cmd;
+ const struct qlink_resp_manage_intf *resp;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ cmd_type,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_manage_intf *)cmd_skb->data;
+ cmd->intf_info.use4addr = use4addr;
+
+ switch (iftype) {
+ case NL80211_IFTYPE_AP:
+ cmd->intf_info.if_type = cpu_to_le16(QLINK_IFTYPE_AP);
+ break;
+ case NL80211_IFTYPE_STATION:
+ cmd->intf_info.if_type = cpu_to_le16(QLINK_IFTYPE_STATION);
+ break;
+ default:
+ pr_err("VIF%u.%u: unsupported type %d\n", vif->mac->macid,
+ vif->vifid, iftype);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (mac_addr)
+ ether_addr_copy(cmd->intf_info.mac_addr, mac_addr);
+ else
+ eth_zero_addr(cmd->intf_info.mac_addr);
+
+ ret = qtnf_cmd_send_with_reply(vif->mac->bus, cmd_skb, &resp_skb,
+ sizeof(*resp), NULL);
+ if (ret)
+ goto out;
+
+ resp = (const struct qlink_resp_manage_intf *)resp_skb->data;
+ ether_addr_copy(vif->mac_addr, resp->intf_info.mac_addr);
+
+out:
+ qtnf_bus_unlock(vif->mac->bus);
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+int qtnf_cmd_send_add_intf(struct qtnf_vif *vif, enum nl80211_iftype iftype,
+ int use4addr, u8 *mac_addr)
+{
+ return qtnf_cmd_send_add_change_intf(vif, iftype, use4addr, mac_addr,
+ QLINK_CMD_ADD_INTF);
+}
+
+int qtnf_cmd_send_change_intf_type(struct qtnf_vif *vif,
+ enum nl80211_iftype iftype,
+ int use4addr,
+ u8 *mac_addr)
+{
+ int ret;
+
+ ret = qtnf_cmd_send_add_change_intf(vif, iftype, use4addr, mac_addr,
+ QLINK_CMD_CHANGE_INTF);
+
+ /* Regulatory settings may be different for different interface types */
+ if (ret == 0 && vif->wdev.iftype != iftype) {
+ enum nl80211_band band;
+ struct wiphy *wiphy = priv_to_wiphy(vif->mac);
+
+ for (band = 0; band < NUM_NL80211_BANDS; ++band) {
+ if (!wiphy->bands[band])
+ continue;
+
+ qtnf_cmd_band_info_get(vif->mac, wiphy->bands[band]);
+ }
+ }
+
+ return ret;
+}
+
+int qtnf_cmd_send_del_intf(struct qtnf_vif *vif)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_manage_intf *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_DEL_INTF,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_manage_intf *)cmd_skb->data;
+
+ switch (vif->wdev.iftype) {
+ case NL80211_IFTYPE_AP:
+ cmd->intf_info.if_type = cpu_to_le16(QLINK_IFTYPE_AP);
+ break;
+ case NL80211_IFTYPE_STATION:
+ cmd->intf_info.if_type = cpu_to_le16(QLINK_IFTYPE_STATION);
+ break;
+ default:
+ pr_warn("VIF%u.%u: unsupported iftype %d\n", vif->mac->macid,
+ vif->vifid, vif->wdev.iftype);
+ dev_kfree_skb(cmd_skb);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ eth_zero_addr(cmd->intf_info.mac_addr);
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+ if (ret)
+ goto out;
+
+out:
+ qtnf_bus_unlock(vif->mac->bus);
+ return ret;
+}
+
+static int
+qtnf_cmd_resp_proc_hw_info(struct qtnf_bus *bus,
+ const struct qlink_resp_get_hw_info *resp,
+ size_t info_len)
+{
+ struct qtnf_hw_info *hwinfo = &bus->hw_info;
+ const struct qlink_tlv_hdr *tlv;
+ const char *bld_name = NULL;
+ const char *bld_rev = NULL;
+ const char *bld_type = NULL;
+ const char *bld_label = NULL;
+ u32 bld_tmstamp = 0;
+ u32 plat_id = 0;
+ const char *hw_id = NULL;
+ const char *calibration_ver = NULL;
+ const char *uboot_ver = NULL;
+ u32 hw_ver = 0;
+ u16 tlv_type;
+ u16 tlv_len;
+
+ hwinfo->num_mac = resp->num_mac;
+ hwinfo->mac_bitmap = resp->mac_bitmap;
+ hwinfo->fw_ver = le32_to_cpu(resp->fw_ver);
+ hwinfo->total_tx_chain = resp->total_tx_chain;
+ hwinfo->total_rx_chain = resp->total_rx_chain;
+
+ bld_tmstamp = le32_to_cpu(resp->bld_tmstamp);
+ plat_id = le32_to_cpu(resp->plat_id);
+ hw_ver = le32_to_cpu(resp->hw_ver);
+
+ qlink_for_each_tlv(tlv, resp->info, info_len) {
+ tlv_type = le16_to_cpu(tlv->type);
+ tlv_len = le16_to_cpu(tlv->len);
+
+ switch (tlv_type) {
+ case QTN_TLV_ID_BUILD_NAME:
+ bld_name = (const void *)tlv->val;
+ break;
+ case QTN_TLV_ID_BUILD_REV:
+ bld_rev = (const void *)tlv->val;
+ break;
+ case QTN_TLV_ID_BUILD_TYPE:
+ bld_type = (const void *)tlv->val;
+ break;
+ case QTN_TLV_ID_BUILD_LABEL:
+ bld_label = (const void *)tlv->val;
+ break;
+ case QTN_TLV_ID_HW_ID:
+ hw_id = (const void *)tlv->val;
+ break;
+ case QTN_TLV_ID_CALIBRATION_VER:
+ calibration_ver = (const void *)tlv->val;
+ break;
+ case QTN_TLV_ID_UBOOT_VER:
+ uboot_ver = (const void *)tlv->val;
+ break;
+ case QTN_TLV_ID_BITMAP:
+ memcpy(hwinfo->hw_capab, tlv->val,
+ min(sizeof(hwinfo->hw_capab), (size_t)tlv_len));
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, resp->info, info_len)) {
+ pr_err("Malformed TLV buffer\n");
+ return -EINVAL;
+ }
+
+ pr_info("\nBuild name: %s\n"
+ "Build revision: %s\n"
+ "Build type: %s\n"
+ "Build label: %s\n"
+ "Build timestamp: %lu\n"
+ "Platform ID: %lu\n"
+ "Hardware ID: %s\n"
+ "Calibration version: %s\n"
+ "U-Boot version: %s\n"
+ "Hardware version: 0x%08x\n"
+ "Qlink ver: %u.%u\n"
+ "MACs map: %#x\n"
+ "Chains Rx-Tx: %ux%u\n"
+ "FW version: 0x%x\n",
+ bld_name, bld_rev, bld_type, bld_label,
+ (unsigned long)bld_tmstamp,
+ (unsigned long)plat_id,
+ hw_id, calibration_ver, uboot_ver, hw_ver,
+ QLINK_VER_MAJOR(bus->hw_info.ql_proto_ver),
+ QLINK_VER_MINOR(bus->hw_info.ql_proto_ver),
+ hwinfo->mac_bitmap,
+ hwinfo->total_rx_chain, hwinfo->total_tx_chain,
+ hwinfo->fw_ver);
+
+ strscpy(hwinfo->fw_version, bld_label, sizeof(hwinfo->fw_version));
+ hwinfo->hw_version = hw_ver;
+
+ return 0;
+}
+
+static void
+qtnf_parse_wowlan_info(struct qtnf_wmac *mac,
+ const struct qlink_wowlan_capab_data *wowlan)
+{
+ struct qtnf_mac_info *mac_info = &mac->macinfo;
+ const struct qlink_wowlan_support *data1;
+ struct wiphy_wowlan_support *supp;
+
+ supp = kzalloc_obj(*supp);
+ if (!supp)
+ return;
+
+ switch (le16_to_cpu(wowlan->version)) {
+ case 0x1:
+ data1 = (struct qlink_wowlan_support *)wowlan->data;
+
+ supp->flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT;
+ supp->n_patterns = le32_to_cpu(data1->n_patterns);
+ supp->pattern_max_len = le32_to_cpu(data1->pattern_max_len);
+ supp->pattern_min_len = le32_to_cpu(data1->pattern_min_len);
+
+ mac_info->wowlan = supp;
+ break;
+ default:
+ pr_warn("MAC%u: unsupported WoWLAN version 0x%x\n",
+ mac->macid, le16_to_cpu(wowlan->version));
+ kfree(supp);
+ break;
+ }
+}
+
+static int
+qtnf_parse_variable_mac_info(struct qtnf_wmac *mac,
+ const struct qlink_resp_get_mac_info *resp,
+ size_t tlv_buf_size)
+{
+ struct ieee80211_iface_combination *comb = mac->macinfo.if_comb;
+ size_t n_comb = 0;
+ struct ieee80211_iface_limit *limits;
+ const struct qlink_iface_limit_record *rec;
+ const struct qlink_iface_limit *lim;
+ const struct qlink_wowlan_capab_data *wowlan;
+ u16 rec_len;
+ u16 tlv_type;
+ u16 tlv_value_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;
+ int i = 0;
+ struct ieee80211_reg_rule *rule;
+ unsigned int rule_idx = 0;
+ const struct qlink_tlv_reg_rule *tlv_rule;
+
+ if (WARN_ON(resp->n_reg_rules > NL80211_MAX_SUPP_REG_RULES))
+ return -E2BIG;
+
+ mac->rd = kzalloc_flex(*mac->rd, reg_rules, resp->n_reg_rules);
+ if (!mac->rd)
+ return -ENOMEM;
+
+ mac->rd->n_reg_rules = resp->n_reg_rules;
+ mac->rd->alpha2[0] = resp->alpha2[0];
+ mac->rd->alpha2[1] = resp->alpha2[1];
+
+ switch (resp->dfs_region) {
+ case QLINK_DFS_FCC:
+ mac->rd->dfs_region = NL80211_DFS_FCC;
+ break;
+ case QLINK_DFS_ETSI:
+ mac->rd->dfs_region = NL80211_DFS_ETSI;
+ break;
+ case QLINK_DFS_JP:
+ mac->rd->dfs_region = NL80211_DFS_JP;
+ break;
+ case QLINK_DFS_UNSET:
+ default:
+ mac->rd->dfs_region = NL80211_DFS_UNSET;
+ break;
+ }
+
+ qlink_for_each_tlv(tlv, resp->var_info, tlv_buf_size) {
+ tlv_type = le16_to_cpu(tlv->type);
+ tlv_value_len = le16_to_cpu(tlv->len);
+
+ switch (tlv_type) {
+ case QTN_TLV_ID_IFACE_LIMIT:
+ if (unlikely(!comb)) {
+ pr_warn("MAC%u: no combinations advertised\n",
+ mac->macid);
+ return -EINVAL;
+ }
+
+ if (n_comb >= mac->macinfo.n_if_comb) {
+ pr_warn("MAC%u: combinations count exceeded\n",
+ mac->macid);
+ n_comb++;
+ break;
+ }
+
+ rec = (void *)tlv->val;
+ rec_len = sizeof(*rec) + rec->n_limits * sizeof(*lim);
+
+ if (unlikely(tlv_value_len != rec_len)) {
+ pr_warn("MAC%u: record %zu size mismatch\n",
+ mac->macid, n_comb);
+ return -EINVAL;
+ }
+
+ limits = kzalloc_objs(*limits, rec->n_limits);
+ if (!limits)
+ return -ENOMEM;
+
+ comb[n_comb].num_different_channels =
+ rec->num_different_channels;
+ comb[n_comb].max_interfaces =
+ le16_to_cpu(rec->max_interfaces);
+ comb[n_comb].n_limits = rec->n_limits;
+ comb[n_comb].limits = limits;
+
+ for (i = 0; i < rec->n_limits; i++) {
+ lim = &rec->limits[i];
+ limits[i].max = le16_to_cpu(lim->max_num);
+ limits[i].types =
+ qlink_iface_type_to_nl_mask(le16_to_cpu(lim->type));
+ pr_debug("MAC%u: comb[%zu]: MAX:%u TYPES:%.4X\n",
+ mac->macid, n_comb,
+ limits[i].max, limits[i].types);
+ }
+
+ n_comb++;
+ 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;
+ case QTN_TLV_ID_WOWLAN_CAPAB:
+ if (tlv_value_len < sizeof(*wowlan))
+ return -EINVAL;
+
+ wowlan = (void *)tlv->val;
+ if (!le16_to_cpu(wowlan->len)) {
+ pr_warn("MAC%u: skip empty WoWLAN data\n",
+ mac->macid);
+ break;
+ }
+
+ rec_len = sizeof(*wowlan) + le16_to_cpu(wowlan->len);
+ if (unlikely(tlv_value_len != rec_len)) {
+ pr_warn("MAC%u: WoWLAN data size mismatch\n",
+ mac->macid);
+ return -EINVAL;
+ }
+
+ kfree(mac->macinfo.wowlan);
+ mac->macinfo.wowlan = NULL;
+ qtnf_parse_wowlan_info(mac, wowlan);
+ break;
+ case QTN_TLV_ID_REG_RULE:
+ if (rule_idx >= resp->n_reg_rules) {
+ pr_warn("unexpected number of rules: %u\n",
+ resp->n_reg_rules);
+ return -EINVAL;
+ }
+
+ if (tlv_value_len != sizeof(*tlv_rule) - sizeof(*tlv)) {
+ pr_warn("malformed TLV 0x%.2X; LEN: %u\n",
+ tlv_type, tlv_value_len);
+ return -EINVAL;
+ }
+
+ tlv_rule = (const struct qlink_tlv_reg_rule *)tlv;
+ rule = &mac->rd->reg_rules[rule_idx++];
+ qlink_utils_regrule_q2nl(rule, tlv_rule);
+ break;
+ default:
+ pr_warn("MAC%u: unknown TLV type %u\n",
+ mac->macid, tlv_type);
+ break;
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, resp->var_info, tlv_buf_size)) {
+ pr_err("Malformed TLV buffer\n");
+ return -EINVAL;
+ }
+
+ if (mac->macinfo.n_if_comb != n_comb) {
+ pr_err("MAC%u: combination mismatch: reported=%zu parsed=%zu\n",
+ mac->macid, mac->macinfo.n_if_comb, n_comb);
+ 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 (rule_idx != resp->n_reg_rules) {
+ pr_warn("unexpected number of rules: expected %u got %u\n",
+ resp->n_reg_rules, rule_idx);
+ 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;
+ }
+
+ qtnf_mac_ext_caps_free(mac);
+ mac->macinfo.extended_capabilities = ext_capa;
+ mac->macinfo.extended_capabilities_mask = ext_capa_mask;
+ mac->macinfo.extended_capabilities_len = ext_capa_len;
+
+ return 0;
+}
+
+static int
+qtnf_cmd_resp_proc_mac_info(struct qtnf_wmac *mac,
+ const struct qlink_resp_get_mac_info *resp_info)
+{
+ struct qtnf_mac_info *mac_info;
+ struct qtnf_vif *vif;
+
+ qtnf_mac_iface_comb_free(mac);
+
+ mac_info = &mac->macinfo;
+
+ mac_info->bands_cap = resp_info->bands_cap;
+ ether_addr_copy(mac->macaddr, resp_info->dev_mac);
+
+ vif = qtnf_mac_get_base_vif(mac);
+ if (vif)
+ ether_addr_copy(vif->mac_addr, mac->macaddr);
+ else
+ pr_err("could not get valid base vif\n");
+
+ mac_info->num_tx_chain = resp_info->num_tx_chain;
+ mac_info->num_rx_chain = resp_info->num_rx_chain;
+
+ mac_info->max_ap_assoc_sta = le16_to_cpu(resp_info->max_ap_assoc_sta);
+ 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 = le16_to_cpu(resp_info->max_acl_mac_addrs);
+ mac_info->frag_thr = le32_to_cpu(resp_info->frag_threshold);
+ mac_info->rts_thr = le32_to_cpu(resp_info->rts_threshold);
+ mac_info->sretry_limit = resp_info->retry_short;
+ mac_info->lretry_limit = resp_info->retry_long;
+ mac_info->coverage_class = resp_info->coverage_class;
+ mac_info->max_scan_ssids = resp_info->max_scan_ssids;
+
+ memcpy(&mac_info->ht_cap_mod_mask, &resp_info->ht_cap_mod_mask,
+ sizeof(mac_info->ht_cap_mod_mask));
+ memcpy(&mac_info->vht_cap_mod_mask, &resp_info->vht_cap_mod_mask,
+ sizeof(mac_info->vht_cap_mod_mask));
+
+ mac_info->n_if_comb = resp_info->n_iface_combinations;
+ mac_info->if_comb = kzalloc_objs(*mac->macinfo.if_comb,
+ mac->macinfo.n_if_comb);
+
+ if (!mac->macinfo.if_comb)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void qtnf_cmd_resp_band_fill_htcap(const u8 *info,
+ struct ieee80211_sta_ht_cap *bcap)
+{
+ const struct ieee80211_ht_cap *ht_cap =
+ (const struct ieee80211_ht_cap *)info;
+
+ bcap->ht_supported = true;
+ bcap->cap = le16_to_cpu(ht_cap->cap_info);
+ bcap->ampdu_factor =
+ ht_cap->ampdu_params_info & IEEE80211_HT_AMPDU_PARM_FACTOR;
+ bcap->ampdu_density =
+ (ht_cap->ampdu_params_info & IEEE80211_HT_AMPDU_PARM_DENSITY) >>
+ IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT;
+ memcpy(&bcap->mcs, &ht_cap->mcs, sizeof(bcap->mcs));
+}
+
+static void qtnf_cmd_resp_band_fill_vhtcap(const u8 *info,
+ struct ieee80211_sta_vht_cap *bcap)
+{
+ const struct ieee80211_vht_cap *vht_cap =
+ (const struct ieee80211_vht_cap *)info;
+
+ bcap->vht_supported = true;
+ bcap->cap = le32_to_cpu(vht_cap->vht_cap_info);
+ memcpy(&bcap->vht_mcs, &vht_cap->supp_mcs, sizeof(bcap->vht_mcs));
+}
+
+static void qtnf_cmd_conv_iftype(struct ieee80211_sband_iftype_data
+ *iftype_data,
+ const struct qlink_sband_iftype_data
+ *qlink_data)
+{
+ iftype_data->types_mask = le16_to_cpu(qlink_data->types_mask);
+
+ iftype_data->he_cap.has_he = true;
+ memcpy(&iftype_data->he_cap.he_cap_elem, &qlink_data->he_cap_elem,
+ sizeof(qlink_data->he_cap_elem));
+ memcpy(iftype_data->he_cap.ppe_thres, qlink_data->ppe_thres,
+ ARRAY_SIZE(qlink_data->ppe_thres));
+
+ iftype_data->he_cap.he_mcs_nss_supp.rx_mcs_80 =
+ qlink_data->he_mcs_nss_supp.rx_mcs_80;
+ iftype_data->he_cap.he_mcs_nss_supp.tx_mcs_80 =
+ qlink_data->he_mcs_nss_supp.tx_mcs_80;
+ iftype_data->he_cap.he_mcs_nss_supp.rx_mcs_160 =
+ qlink_data->he_mcs_nss_supp.rx_mcs_160;
+ iftype_data->he_cap.he_mcs_nss_supp.tx_mcs_160 =
+ qlink_data->he_mcs_nss_supp.tx_mcs_160;
+ iftype_data->he_cap.he_mcs_nss_supp.rx_mcs_80p80 =
+ qlink_data->he_mcs_nss_supp.rx_mcs_80p80;
+ iftype_data->he_cap.he_mcs_nss_supp.tx_mcs_80p80 =
+ qlink_data->he_mcs_nss_supp.tx_mcs_80p80;
+}
+
+static int qtnf_cmd_band_fill_iftype(const u8 *data,
+ struct ieee80211_supported_band *band)
+{
+ unsigned int i;
+ struct ieee80211_sband_iftype_data *iftype_data;
+ const struct qlink_tlv_iftype_data *tlv =
+ (const struct qlink_tlv_iftype_data *)data;
+ size_t payload_len;
+
+ payload_len = struct_size(tlv, iftype_data, tlv->n_iftype_data);
+ payload_len = size_sub(payload_len, sizeof(struct qlink_tlv_hdr));
+
+ if (tlv->hdr.len != cpu_to_le16(payload_len)) {
+ pr_err("bad IFTYPE_DATA TLV len %u\n", tlv->hdr.len);
+ return -EINVAL;
+ }
+
+ kfree((__force void *)band->iftype_data);
+ band->iftype_data = NULL;
+ band->n_iftype_data = tlv->n_iftype_data;
+ if (band->n_iftype_data == 0)
+ return 0;
+
+ iftype_data = kzalloc_objs(*iftype_data, band->n_iftype_data);
+ if (!iftype_data) {
+ band->n_iftype_data = 0;
+ return -ENOMEM;
+ }
+
+ _ieee80211_set_sband_iftype_data(band, iftype_data, tlv->n_iftype_data);
+
+ for (i = 0; i < band->n_iftype_data; i++)
+ qtnf_cmd_conv_iftype(iftype_data++, &tlv->iftype_data[i]);
+
+ return 0;
+}
+
+static int
+qtnf_cmd_resp_fill_band_info(struct ieee80211_supported_band *band,
+ struct qlink_resp_band_info_get *resp,
+ size_t payload_len)
+{
+ u16 tlv_type;
+ size_t tlv_dlen;
+ const struct qlink_tlv_hdr *tlv;
+ const struct qlink_channel *qchan;
+ struct ieee80211_channel *chan;
+ unsigned int chidx = 0;
+ u32 qflags;
+ int ret = -EINVAL;
+
+ memset(&band->ht_cap, 0, sizeof(band->ht_cap));
+ memset(&band->vht_cap, 0, sizeof(band->vht_cap));
+
+ if (band->channels) {
+ if (band->n_channels == resp->num_chans) {
+ memset(band->channels, 0,
+ sizeof(*band->channels) * band->n_channels);
+ } else {
+ kfree(band->channels);
+ band->n_channels = 0;
+ band->channels = NULL;
+ }
+ }
+
+ band->n_channels = resp->num_chans;
+ if (band->n_channels == 0)
+ return 0;
+
+ if (!band->channels)
+ band->channels = kzalloc_objs(*chan, band->n_channels);
+ if (!band->channels) {
+ band->n_channels = 0;
+ return -ENOMEM;
+ }
+
+ qlink_for_each_tlv(tlv, resp->info, payload_len) {
+ tlv_type = le16_to_cpu(tlv->type);
+ tlv_dlen = le16_to_cpu(tlv->len);
+
+ switch (tlv_type) {
+ case QTN_TLV_ID_CHANNEL:
+ if (unlikely(tlv_dlen != sizeof(*qchan))) {
+ pr_err("invalid channel TLV len %zu\n",
+ tlv_dlen);
+ goto error_ret;
+ }
+
+ if (chidx == band->n_channels) {
+ pr_err("too many channel TLVs\n");
+ goto error_ret;
+ }
+
+ qchan = (const struct qlink_channel *)tlv->val;
+ chan = &band->channels[chidx++];
+ qflags = le32_to_cpu(qchan->flags);
+
+ chan->hw_value = le16_to_cpu(qchan->hw_value);
+ chan->band = band->band;
+ chan->center_freq = le16_to_cpu(qchan->center_freq);
+ chan->max_antenna_gain = (int)qchan->max_antenna_gain;
+ chan->max_power = (int)qchan->max_power;
+ chan->max_reg_power = (int)qchan->max_reg_power;
+ chan->beacon_found = qchan->beacon_found;
+ chan->dfs_cac_ms = le32_to_cpu(qchan->dfs_cac_ms);
+ chan->flags = 0;
+
+ if (qflags & QLINK_CHAN_DISABLED)
+ chan->flags |= IEEE80211_CHAN_DISABLED;
+
+ if (qflags & QLINK_CHAN_NO_IR)
+ chan->flags |= IEEE80211_CHAN_NO_IR;
+
+ if (qflags & QLINK_CHAN_NO_HT40PLUS)
+ chan->flags |= IEEE80211_CHAN_NO_HT40PLUS;
+
+ if (qflags & QLINK_CHAN_NO_HT40MINUS)
+ chan->flags |= IEEE80211_CHAN_NO_HT40MINUS;
+
+ if (qflags & QLINK_CHAN_NO_OFDM)
+ chan->flags |= IEEE80211_CHAN_NO_OFDM;
+
+ if (qflags & QLINK_CHAN_NO_80MHZ)
+ chan->flags |= IEEE80211_CHAN_NO_80MHZ;
+
+ if (qflags & QLINK_CHAN_NO_160MHZ)
+ chan->flags |= IEEE80211_CHAN_NO_160MHZ;
+
+ if (qflags & QLINK_CHAN_INDOOR_ONLY)
+ chan->flags |= IEEE80211_CHAN_INDOOR_ONLY;
+
+ if (qflags & QLINK_CHAN_IR_CONCURRENT)
+ chan->flags |= IEEE80211_CHAN_IR_CONCURRENT;
+
+ if (qflags & QLINK_CHAN_NO_20MHZ)
+ chan->flags |= IEEE80211_CHAN_NO_20MHZ;
+
+ if (qflags & QLINK_CHAN_NO_10MHZ)
+ chan->flags |= IEEE80211_CHAN_NO_10MHZ;
+
+ if (qflags & QLINK_CHAN_RADAR) {
+ chan->flags |= IEEE80211_CHAN_RADAR;
+ chan->dfs_state_entered = jiffies;
+
+ if (qchan->dfs_state == QLINK_DFS_USABLE)
+ chan->dfs_state = NL80211_DFS_USABLE;
+ else if (qchan->dfs_state ==
+ QLINK_DFS_AVAILABLE)
+ chan->dfs_state = NL80211_DFS_AVAILABLE;
+ else
+ chan->dfs_state =
+ NL80211_DFS_UNAVAILABLE;
+ }
+
+ pr_debug("chan=%d flags=%#x max_pow=%d max_reg_pow=%d\n",
+ chan->hw_value, chan->flags, chan->max_power,
+ chan->max_reg_power);
+ break;
+ case WLAN_EID_HT_CAPABILITY:
+ if (unlikely(tlv_dlen !=
+ sizeof(struct ieee80211_ht_cap))) {
+ pr_err("bad HTCAP TLV len %zu\n", tlv_dlen);
+ goto error_ret;
+ }
+
+ qtnf_cmd_resp_band_fill_htcap(tlv->val, &band->ht_cap);
+ break;
+ case WLAN_EID_VHT_CAPABILITY:
+ if (unlikely(tlv_dlen !=
+ sizeof(struct ieee80211_vht_cap))) {
+ pr_err("bad VHTCAP TLV len %zu\n", tlv_dlen);
+ goto error_ret;
+ }
+
+ qtnf_cmd_resp_band_fill_vhtcap(tlv->val,
+ &band->vht_cap);
+ break;
+ case QTN_TLV_ID_IFTYPE_DATA:
+ ret = qtnf_cmd_band_fill_iftype((const uint8_t *)tlv,
+ band);
+ if (ret)
+ goto error_ret;
+ break;
+ default:
+ pr_warn("unknown TLV type: %#x\n", tlv_type);
+ break;
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, resp->info, payload_len)) {
+ pr_err("Malformed TLV buffer\n");
+ goto error_ret;
+ }
+
+ if (band->n_channels != chidx) {
+ pr_err("channel count mismatch: reported=%d, parsed=%d\n",
+ band->n_channels, chidx);
+ goto error_ret;
+ }
+
+ return 0;
+
+error_ret:
+ kfree(band->channels);
+ band->channels = NULL;
+ band->n_channels = 0;
+
+ return ret;
+}
+
+int qtnf_cmd_get_mac_info(struct qtnf_wmac *mac)
+{
+ struct sk_buff *cmd_skb, *resp_skb = NULL;
+ const struct qlink_resp_get_mac_info *resp;
+ size_t var_data_len = 0;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, QLINK_VIFID_RSVD,
+ QLINK_CMD_MAC_INFO,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(mac->bus);
+ ret = qtnf_cmd_send_with_reply(mac->bus, cmd_skb, &resp_skb,
+ sizeof(*resp), &var_data_len);
+ if (ret)
+ goto out;
+
+ resp = (const struct qlink_resp_get_mac_info *)resp_skb->data;
+ ret = qtnf_cmd_resp_proc_mac_info(mac, resp);
+ if (ret)
+ goto out;
+
+ ret = qtnf_parse_variable_mac_info(mac, resp, var_data_len);
+
+out:
+ qtnf_bus_unlock(mac->bus);
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+int qtnf_cmd_get_hw_info(struct qtnf_bus *bus)
+{
+ struct sk_buff *cmd_skb, *resp_skb = NULL;
+ const struct qlink_resp_get_hw_info *resp;
+ size_t info_len = 0;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(QLINK_MACID_RSVD, QLINK_VIFID_RSVD,
+ QLINK_CMD_GET_HW_INFO,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send_with_reply(bus, cmd_skb, &resp_skb,
+ sizeof(*resp), &info_len);
+ if (ret)
+ goto out;
+
+ resp = (const struct qlink_resp_get_hw_info *)resp_skb->data;
+ ret = qtnf_cmd_resp_proc_hw_info(bus, resp, info_len);
+
+out:
+ qtnf_bus_unlock(bus);
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+int qtnf_cmd_band_info_get(struct qtnf_wmac *mac,
+ struct ieee80211_supported_band *band)
+{
+ struct sk_buff *cmd_skb, *resp_skb = NULL;
+ struct qlink_cmd_band_info_get *cmd;
+ struct qlink_resp_band_info_get *resp;
+ size_t info_len = 0;
+ int ret = 0;
+ u8 qband = qlink_utils_band_cfg2q(band->band);
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, 0,
+ QLINK_CMD_BAND_INFO_GET,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_band_info_get *)cmd_skb->data;
+ cmd->band = qband;
+
+ qtnf_bus_lock(mac->bus);
+ ret = qtnf_cmd_send_with_reply(mac->bus, cmd_skb, &resp_skb,
+ sizeof(*resp), &info_len);
+ if (ret)
+ goto out;
+
+ resp = (struct qlink_resp_band_info_get *)resp_skb->data;
+ if (resp->band != qband) {
+ pr_err("MAC%u: reply band %u != cmd band %u\n", mac->macid,
+ resp->band, qband);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = qtnf_cmd_resp_fill_band_info(band, resp, info_len);
+
+out:
+ qtnf_bus_unlock(mac->bus);
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+int qtnf_cmd_send_update_phy_params(struct qtnf_wmac *mac, u32 changed)
+{
+ struct wiphy *wiphy = priv_to_wiphy(mac);
+ struct sk_buff *cmd_skb;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, 0,
+ QLINK_CMD_PHY_PARAMS_SET,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(mac->bus);
+
+ if (changed & WIPHY_PARAM_FRAG_THRESHOLD)
+ qtnf_cmd_skb_put_tlv_u32(cmd_skb, QTN_TLV_ID_FRAG_THRESH,
+ wiphy->frag_threshold);
+ if (changed & WIPHY_PARAM_RTS_THRESHOLD)
+ qtnf_cmd_skb_put_tlv_u32(cmd_skb, QTN_TLV_ID_RTS_THRESH,
+ wiphy->rts_threshold);
+ if (changed & WIPHY_PARAM_COVERAGE_CLASS)
+ qtnf_cmd_skb_put_tlv_u32(cmd_skb, QTN_TLV_ID_COVERAGE_CLASS,
+ wiphy->coverage_class);
+
+ if (changed & WIPHY_PARAM_RETRY_LONG)
+ qtnf_cmd_skb_put_tlv_u32(cmd_skb, QTN_TLV_ID_LRETRY_LIMIT,
+ wiphy->retry_long);
+
+ if (changed & WIPHY_PARAM_RETRY_SHORT)
+ qtnf_cmd_skb_put_tlv_u32(cmd_skb, QTN_TLV_ID_SRETRY_LIMIT,
+ wiphy->retry_short);
+
+ ret = qtnf_cmd_send(mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_init_fw(struct qtnf_bus *bus)
+{
+ struct sk_buff *resp_skb = NULL;
+ struct qlink_resp_init_fw *resp;
+ struct qlink_cmd_init_fw *cmd;
+ struct sk_buff *cmd_skb;
+ size_t info_len = 0;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(QLINK_MACID_RSVD, QLINK_VIFID_RSVD,
+ QLINK_CMD_FW_INIT,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_init_fw *)cmd_skb->data;
+ cmd->qlink_proto_ver = cpu_to_le32(QLINK_PROTO_VER);
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send_with_reply(bus, cmd_skb, &resp_skb,
+ sizeof(*resp), &info_len);
+ qtnf_bus_unlock(bus);
+
+ if (ret)
+ goto out;
+
+ resp = (struct qlink_resp_init_fw *)resp_skb->data;
+ bus->hw_info.ql_proto_ver = le32_to_cpu(resp->qlink_proto_ver);
+
+out:
+ consume_skb(resp_skb);
+ return ret;
+}
+
+void qtnf_cmd_send_deinit_fw(struct qtnf_bus *bus)
+{
+ struct sk_buff *cmd_skb;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(QLINK_MACID_RSVD, QLINK_VIFID_RSVD,
+ QLINK_CMD_FW_DEINIT,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return;
+
+ qtnf_bus_lock(bus);
+ qtnf_cmd_send(bus, cmd_skb);
+ qtnf_bus_unlock(bus);
+}
+
+int qtnf_cmd_send_add_key(struct qtnf_vif *vif, u8 key_index, bool pairwise,
+ const u8 *mac_addr, struct key_params *params)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_add_key *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_ADD_KEY,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_add_key *)cmd_skb->data;
+
+ if (mac_addr)
+ ether_addr_copy(cmd->addr, mac_addr);
+ else
+ eth_broadcast_addr(cmd->addr);
+
+ cmd->cipher = cpu_to_le32(params->cipher);
+ cmd->key_index = key_index;
+ cmd->pairwise = pairwise;
+
+ if (params->key && params->key_len > 0)
+ qtnf_cmd_skb_put_tlv_arr(cmd_skb, QTN_TLV_ID_KEY,
+ params->key,
+ params->key_len);
+
+ if (params->seq && params->seq_len > 0)
+ qtnf_cmd_skb_put_tlv_arr(cmd_skb, QTN_TLV_ID_SEQ,
+ params->seq,
+ params->seq_len);
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_del_key(struct qtnf_vif *vif, u8 key_index, bool pairwise,
+ const u8 *mac_addr)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_del_key *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_DEL_KEY,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_del_key *)cmd_skb->data;
+
+ if (mac_addr)
+ ether_addr_copy(cmd->addr, mac_addr);
+ else
+ eth_broadcast_addr(cmd->addr);
+
+ cmd->key_index = key_index;
+ cmd->pairwise = pairwise;
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_set_default_key(struct qtnf_vif *vif, u8 key_index,
+ bool unicast, bool multicast)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_set_def_key *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_SET_DEFAULT_KEY,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_set_def_key *)cmd_skb->data;
+ cmd->key_index = key_index;
+ cmd->unicast = unicast;
+ cmd->multicast = multicast;
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_set_default_mgmt_key(struct qtnf_vif *vif, u8 key_index)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_set_def_mgmt_key *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_SET_DEFAULT_MGMT_KEY,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_set_def_mgmt_key *)cmd_skb->data;
+ cmd->key_index = key_index;
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+static u32 qtnf_encode_sta_flags(u32 flags)
+{
+ u32 code = 0;
+
+ if (flags & BIT(NL80211_STA_FLAG_AUTHORIZED))
+ code |= QLINK_STA_FLAG_AUTHORIZED;
+ if (flags & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
+ code |= QLINK_STA_FLAG_SHORT_PREAMBLE;
+ if (flags & BIT(NL80211_STA_FLAG_WME))
+ code |= QLINK_STA_FLAG_WME;
+ if (flags & BIT(NL80211_STA_FLAG_MFP))
+ code |= QLINK_STA_FLAG_MFP;
+ if (flags & BIT(NL80211_STA_FLAG_AUTHENTICATED))
+ code |= QLINK_STA_FLAG_AUTHENTICATED;
+ if (flags & BIT(NL80211_STA_FLAG_TDLS_PEER))
+ code |= QLINK_STA_FLAG_TDLS_PEER;
+ if (flags & BIT(NL80211_STA_FLAG_ASSOCIATED))
+ code |= QLINK_STA_FLAG_ASSOCIATED;
+ return code;
+}
+
+int qtnf_cmd_send_change_sta(struct qtnf_vif *vif, const u8 *mac,
+ struct station_parameters *params)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_change_sta *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_CHANGE_STA,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ 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);
+ break;
+ case NL80211_IFTYPE_STATION:
+ cmd->if_type = cpu_to_le16(QLINK_IFTYPE_STATION);
+ break;
+ default:
+ pr_err("unsupported iftype %d\n", vif->wdev.iftype);
+ dev_kfree_skb(cmd_skb);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+out:
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_del_sta(struct qtnf_vif *vif,
+ struct station_del_parameters *params)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_del_sta *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_DEL_STA,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_del_sta *)cmd_skb->data;
+
+ if (params->mac)
+ ether_addr_copy(cmd->sta_addr, params->mac);
+ else
+ eth_broadcast_addr(cmd->sta_addr); /* flush all stations */
+
+ cmd->subtype = params->subtype;
+ cmd->reason_code = cpu_to_le16(params->reason_code);
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+static void qtnf_cmd_channel_tlv_add(struct sk_buff *cmd_skb,
+ const struct ieee80211_channel *sc)
+{
+ struct qlink_tlv_channel *tlv;
+ struct qlink_channel *qch;
+
+ tlv = skb_put_zero(cmd_skb, sizeof(*tlv));
+ qch = &tlv->chan;
+ tlv->hdr.type = cpu_to_le16(QTN_TLV_ID_CHANNEL);
+ tlv->hdr.len = cpu_to_le16(sizeof(*qch));
+
+ qch->center_freq = cpu_to_le16(sc->center_freq);
+ qch->hw_value = cpu_to_le16(sc->hw_value);
+ qch->band = qlink_utils_band_cfg2q(sc->band);
+ qch->max_power = sc->max_power;
+ qch->max_reg_power = sc->max_reg_power;
+ qch->max_antenna_gain = sc->max_antenna_gain;
+ qch->beacon_found = sc->beacon_found;
+ qch->dfs_state = qlink_utils_dfs_state_cfg2q(sc->dfs_state);
+ qch->flags = cpu_to_le32(qlink_utils_chflags_cfg2q(sc->flags));
+}
+
+static void qtnf_cmd_randmac_tlv_add(struct sk_buff *cmd_skb,
+ const u8 *mac_addr,
+ const u8 *mac_addr_mask)
+{
+ struct qlink_random_mac_addr *randmac;
+ struct qlink_tlv_hdr *hdr =
+ skb_put(cmd_skb, sizeof(*hdr) + sizeof(*randmac));
+
+ hdr->type = cpu_to_le16(QTN_TLV_ID_RANDOM_MAC_ADDR);
+ hdr->len = cpu_to_le16(sizeof(*randmac));
+ randmac = (struct qlink_random_mac_addr *)hdr->val;
+
+ memcpy(randmac->mac_addr, mac_addr, ETH_ALEN);
+ memcpy(randmac->mac_addr_mask, mac_addr_mask, ETH_ALEN);
+}
+
+int qtnf_cmd_send_scan(struct qtnf_wmac *mac)
+{
+ struct cfg80211_scan_request *scan_req = mac->scan_req;
+ u16 dwell_passive = QTNF_SCAN_DWELL_PASSIVE_DEFAULT;
+ u16 dwell_active = QTNF_SCAN_DWELL_ACTIVE_DEFAULT;
+ struct wireless_dev *wdev = scan_req->wdev;
+ struct ieee80211_channel *sc;
+ struct qlink_cmd_scan *cmd;
+ struct sk_buff *cmd_skb;
+ int n_channels = 0;
+ u64 flags = 0;
+ int count;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, QLINK_VIFID_RSVD,
+ QLINK_CMD_SCAN,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_scan *)cmd_skb->data;
+
+ if (scan_req->duration) {
+ dwell_active = scan_req->duration;
+ dwell_passive = scan_req->duration;
+ } else if (wdev->iftype == NL80211_IFTYPE_STATION &&
+ wdev->connected) {
+ /* let device select dwell based on traffic conditions */
+ dwell_active = QTNF_SCAN_TIME_AUTO;
+ dwell_passive = QTNF_SCAN_TIME_AUTO;
+ }
+
+ cmd->n_ssids = cpu_to_le16(scan_req->n_ssids);
+ for (count = 0; count < scan_req->n_ssids; ++count) {
+ qtnf_cmd_skb_put_tlv_arr(cmd_skb, WLAN_EID_SSID,
+ scan_req->ssids[count].ssid,
+ scan_req->ssids[count].ssid_len);
+ }
+
+ if (scan_req->ie_len != 0)
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_PROBE_REQ,
+ scan_req->ie, scan_req->ie_len);
+
+ for (count = 0; count < scan_req->n_channels; ++count) {
+ sc = scan_req->channels[count];
+ if (sc->flags & IEEE80211_CHAN_DISABLED)
+ continue;
+
+ pr_debug("[MAC%u] scan chan=%d, freq=%d, flags=%#x\n",
+ mac->macid, sc->hw_value, sc->center_freq,
+ sc->flags);
+
+ qtnf_cmd_channel_tlv_add(cmd_skb, sc);
+ ++n_channels;
+ }
+
+ if (scan_req->flags & NL80211_SCAN_FLAG_FLUSH)
+ flags |= QLINK_SCAN_FLAG_FLUSH;
+
+ if (scan_req->duration_mandatory)
+ flags |= QLINK_SCAN_FLAG_DURATION_MANDATORY;
+
+ cmd->n_channels = cpu_to_le16(n_channels);
+ cmd->active_dwell = cpu_to_le16(dwell_active);
+ cmd->passive_dwell = cpu_to_le16(dwell_passive);
+ cmd->sample_duration = cpu_to_le16(QTNF_SCAN_SAMPLE_DURATION_DEFAULT);
+ cmd->flags = cpu_to_le64(flags);
+
+ pr_debug("[MAC%u] %s scan dwell active=%u passive=%u duration=%u\n",
+ mac->macid,
+ scan_req->duration_mandatory ? "mandatory" : "max",
+ dwell_active, dwell_passive,
+ QTNF_SCAN_SAMPLE_DURATION_DEFAULT);
+
+ if (scan_req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
+ pr_debug("[MAC%u] scan with random addr=%pM, mask=%pM\n",
+ mac->macid,
+ scan_req->mac_addr, scan_req->mac_addr_mask);
+ qtnf_cmd_randmac_tlv_add(cmd_skb, scan_req->mac_addr,
+ scan_req->mac_addr_mask);
+ }
+
+ qtnf_bus_lock(mac->bus);
+ ret = qtnf_cmd_send(mac->bus, cmd_skb);
+ qtnf_bus_unlock(mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_connect(struct qtnf_vif *vif,
+ struct cfg80211_connect_params *sme)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_connect *cmd;
+ struct qlink_auth_encr *aen;
+ int ret;
+ int i;
+ int n;
+ u32 connect_flags = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_CONNECT,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_connect *)cmd_skb->data;
+
+ ether_addr_copy(cmd->bssid, vif->bssid);
+
+ if (sme->bssid_hint)
+ ether_addr_copy(cmd->bssid_hint, sme->bssid_hint);
+ else
+ eth_zero_addr(cmd->bssid_hint);
+
+ if (sme->prev_bssid)
+ ether_addr_copy(cmd->prev_bssid, sme->prev_bssid);
+ else
+ eth_zero_addr(cmd->prev_bssid);
+
+ if ((sme->bg_scan_period >= 0) &&
+ (sme->bg_scan_period <= SHRT_MAX))
+ cmd->bg_scan_period = cpu_to_le16(sme->bg_scan_period);
+ else
+ cmd->bg_scan_period = cpu_to_le16(-1); /* use default value */
+
+ if (sme->flags & ASSOC_REQ_DISABLE_HT)
+ connect_flags |= QLINK_STA_CONNECT_DISABLE_HT;
+ if (sme->flags & ASSOC_REQ_DISABLE_VHT)
+ connect_flags |= QLINK_STA_CONNECT_DISABLE_VHT;
+ if (sme->flags & ASSOC_REQ_USE_RRM)
+ connect_flags |= QLINK_STA_CONNECT_USE_RRM;
+
+ cmd->flags = cpu_to_le32(connect_flags);
+ memcpy(&cmd->ht_capa, &sme->ht_capa, sizeof(cmd->ht_capa));
+ memcpy(&cmd->ht_capa_mask, &sme->ht_capa_mask,
+ sizeof(cmd->ht_capa_mask));
+ memcpy(&cmd->vht_capa, &sme->vht_capa, sizeof(cmd->vht_capa));
+ memcpy(&cmd->vht_capa_mask, &sme->vht_capa_mask,
+ sizeof(cmd->vht_capa_mask));
+ cmd->pbss = sme->pbss;
+
+ aen = &cmd->aen;
+ aen->auth_type = sme->auth_type;
+ aen->privacy = !!sme->privacy;
+ cmd->mfp = sme->mfp;
+ aen->wpa_versions = cpu_to_le32(sme->crypto.wpa_versions);
+ aen->cipher_group = cpu_to_le32(sme->crypto.cipher_group);
+ aen->n_ciphers_pairwise = cpu_to_le32(sme->crypto.n_ciphers_pairwise);
+
+ for (i = 0; i < QLINK_MAX_NR_CIPHER_SUITES; i++)
+ aen->ciphers_pairwise[i] =
+ cpu_to_le32(sme->crypto.ciphers_pairwise[i]);
+
+ n = min(QLINK_MAX_NR_AKM_SUITES, sme->crypto.n_akm_suites);
+ aen->n_akm_suites = cpu_to_le32(n);
+
+ for (i = 0; i < n; i++)
+ aen->akm_suites[i] = cpu_to_le32(sme->crypto.akm_suites[i]);
+
+ aen->control_port = sme->crypto.control_port;
+ aen->control_port_no_encrypt =
+ sme->crypto.control_port_no_encrypt;
+ aen->control_port_ethertype =
+ cpu_to_le16(be16_to_cpu(sme->crypto.control_port_ethertype));
+
+ qtnf_cmd_skb_put_tlv_arr(cmd_skb, WLAN_EID_SSID, sme->ssid,
+ sme->ssid_len);
+
+ if (sme->ie_len != 0)
+ qtnf_cmd_tlv_ie_set_add(cmd_skb, QLINK_IE_SET_ASSOC_REQ,
+ sme->ie, sme->ie_len);
+
+ if (sme->channel)
+ qtnf_cmd_channel_tlv_add(cmd_skb, sme->channel);
+
+ qtnf_bus_lock(vif->mac->bus);
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_external_auth(struct qtnf_vif *vif,
+ struct cfg80211_external_auth_params *auth)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_external_auth *cmd;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_EXTERNAL_AUTH,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_external_auth *)cmd_skb->data;
+
+ ether_addr_copy(cmd->peer, auth->bssid);
+ cmd->status = cpu_to_le16(auth->status);
+
+ qtnf_bus_lock(vif->mac->bus);
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_disconnect(struct qtnf_vif *vif, u16 reason_code)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_disconnect *cmd;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_DISCONNECT,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(vif->mac->bus);
+
+ cmd = (struct qlink_cmd_disconnect *)cmd_skb->data;
+ cmd->reason = cpu_to_le16(reason_code);
+
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_updown_intf(struct qtnf_vif *vif, bool up)
+{
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_updown *cmd;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_UPDOWN_INTF,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_updown *)cmd_skb->data;
+ cmd->if_up = !!up;
+
+ qtnf_bus_lock(vif->mac->bus);
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_reg_notify(struct qtnf_wmac *mac, struct regulatory_request *req,
+ bool slave_radar, bool dfs_offload)
+{
+ struct wiphy *wiphy = priv_to_wiphy(mac);
+ struct qtnf_bus *bus = mac->bus;
+ struct sk_buff *cmd_skb;
+ int ret;
+ struct qlink_cmd_reg_notify *cmd;
+ enum nl80211_band band;
+ const struct ieee80211_supported_band *cfg_band;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, QLINK_VIFID_RSVD,
+ QLINK_CMD_REG_NOTIFY,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_reg_notify *)cmd_skb->data;
+ cmd->alpha2[0] = req->alpha2[0];
+ cmd->alpha2[1] = req->alpha2[1];
+
+ switch (req->initiator) {
+ case NL80211_REGDOM_SET_BY_CORE:
+ cmd->initiator = QLINK_REGDOM_SET_BY_CORE;
+ break;
+ case NL80211_REGDOM_SET_BY_USER:
+ cmd->initiator = QLINK_REGDOM_SET_BY_USER;
+ break;
+ case NL80211_REGDOM_SET_BY_DRIVER:
+ cmd->initiator = QLINK_REGDOM_SET_BY_DRIVER;
+ break;
+ case NL80211_REGDOM_SET_BY_COUNTRY_IE:
+ cmd->initiator = QLINK_REGDOM_SET_BY_COUNTRY_IE;
+ break;
+ }
+
+ switch (req->user_reg_hint_type) {
+ case NL80211_USER_REG_HINT_USER:
+ cmd->user_reg_hint_type = QLINK_USER_REG_HINT_USER;
+ break;
+ case NL80211_USER_REG_HINT_CELL_BASE:
+ cmd->user_reg_hint_type = QLINK_USER_REG_HINT_CELL_BASE;
+ break;
+ case NL80211_USER_REG_HINT_INDOOR:
+ cmd->user_reg_hint_type = QLINK_USER_REG_HINT_INDOOR;
+ break;
+ }
+
+ switch (req->dfs_region) {
+ case NL80211_DFS_FCC:
+ cmd->dfs_region = QLINK_DFS_FCC;
+ break;
+ case NL80211_DFS_ETSI:
+ cmd->dfs_region = QLINK_DFS_ETSI;
+ break;
+ case NL80211_DFS_JP:
+ cmd->dfs_region = QLINK_DFS_JP;
+ break;
+ default:
+ cmd->dfs_region = QLINK_DFS_UNSET;
+ break;
+ }
+
+ cmd->slave_radar = slave_radar;
+ cmd->dfs_offload = dfs_offload;
+ cmd->num_channels = 0;
+
+ for (band = 0; band < NUM_NL80211_BANDS; band++) {
+ unsigned int i;
+
+ cfg_band = wiphy->bands[band];
+ if (!cfg_band)
+ continue;
+
+ cmd->num_channels += cfg_band->n_channels;
+
+ for (i = 0; i < cfg_band->n_channels; ++i) {
+ qtnf_cmd_channel_tlv_add(cmd_skb,
+ &cfg_band->channels[i]);
+ }
+ }
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send(bus, cmd_skb);
+ qtnf_bus_unlock(bus);
+
+ return ret;
+}
+
+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;
+ u16 tlv_value_len;
+ u16 tlv_type;
+ const u8 *map = NULL;
+ unsigned int map_len = 0;
+ unsigned int stats_len = 0;
+
+ qlink_for_each_tlv(tlv, payload, payload_len) {
+ tlv_type = le16_to_cpu(tlv->type);
+ tlv_value_len = le16_to_cpu(tlv->len);
+
+ 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;
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, payload, payload_len)) {
+ pr_err("Malformed TLV buffer\n");
+ 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;
+ struct qlink_resp_get_chan_stats *resp;
+ size_t var_data_len = 0;
+ 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;
+
+ cmd = (struct qlink_cmd_get_chan_stats *)cmd_skb->data;
+ 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;
+
+ 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:
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+int qtnf_cmd_send_chan_switch(struct qtnf_vif *vif,
+ struct cfg80211_csa_settings *params)
+{
+ struct qtnf_wmac *mac = vif->mac;
+ struct qlink_cmd_chan_switch *cmd;
+ struct sk_buff *cmd_skb;
+ int ret;
+ u64 flags = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, vif->vifid,
+ QLINK_CMD_CHAN_SWITCH,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ if (params->radar_required)
+ flags |= QLINK_CHAN_SW_RADAR_REQUIRED;
+
+ if (params->block_tx)
+ flags |= QLINK_CHAN_SW_BLOCK_TX;
+
+ cmd = (struct qlink_cmd_chan_switch *)cmd_skb->data;
+ qlink_chandef_cfg2q(&params->chandef, &cmd->channel);
+ cmd->flags = cpu_to_le64(flags);
+ cmd->beacon_count = params->count;
+
+ qtnf_bus_lock(mac->bus);
+ ret = qtnf_cmd_send(mac->bus, cmd_skb);
+ qtnf_bus_unlock(mac->bus);
+
+ return ret;
+}
+
+int qtnf_cmd_get_channel(struct qtnf_vif *vif, struct cfg80211_chan_def *chdef)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ const struct qlink_resp_channel_get *resp;
+ struct sk_buff *cmd_skb;
+ struct sk_buff *resp_skb = NULL;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_CHAN_GET,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send_with_reply(bus, cmd_skb, &resp_skb,
+ sizeof(*resp), NULL);
+ if (ret)
+ goto out;
+
+ resp = (const struct qlink_resp_channel_get *)resp_skb->data;
+ qlink_chandef_q2cfg(priv_to_wiphy(vif->mac), &resp->chan, chdef);
+
+out:
+ qtnf_bus_unlock(bus);
+ 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;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_START_CAC,
+ sizeof(*cmd));
+ if (!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);
+ qtnf_bus_unlock(bus);
+
+ 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_tlv_hdr *tlv;
+ size_t acl_size = struct_size(params, mac_addrs, params->n_acl_entries);
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_SET_MAC_ACL,
+ sizeof(struct qlink_cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ tlv = skb_put(cmd_skb, sizeof(*tlv) + round_up(acl_size, QLINK_ALIGN));
+ tlv->type = cpu_to_le16(QTN_TLV_ID_ACL_DATA);
+ tlv->len = cpu_to_le16(acl_size);
+ qlink_acl_data_cfg2q(params, (struct qlink_acl_data *)tlv->val);
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send(bus, cmd_skb);
+ qtnf_bus_unlock(bus);
+
+ return ret;
+}
+
+int qtnf_cmd_send_pm_set(const struct qtnf_vif *vif, u8 pm_mode, int timeout)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_pm_set *cmd;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_PM_SET, sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_pm_set *)cmd_skb->data;
+ cmd->pm_mode = pm_mode;
+ cmd->pm_standby_timer = cpu_to_le32(timeout);
+
+ qtnf_bus_lock(bus);
+
+ ret = qtnf_cmd_send(bus, cmd_skb);
+
+ qtnf_bus_unlock(bus);
+
+ return ret;
+}
+
+int qtnf_cmd_get_tx_power(const struct qtnf_vif *vif, int *dbm)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ const struct qlink_resp_txpwr *resp;
+ struct sk_buff *resp_skb = NULL;
+ struct qlink_cmd_txpwr *cmd;
+ struct sk_buff *cmd_skb;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_TXPWR, sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_txpwr *)cmd_skb->data;
+ cmd->op_type = QLINK_TXPWR_GET;
+
+ qtnf_bus_lock(bus);
+
+ ret = qtnf_cmd_send_with_reply(bus, cmd_skb, &resp_skb,
+ sizeof(*resp), NULL);
+ if (ret)
+ goto out;
+
+ resp = (const struct qlink_resp_txpwr *)resp_skb->data;
+ *dbm = MBM_TO_DBM(le32_to_cpu(resp->txpwr));
+
+out:
+ qtnf_bus_unlock(bus);
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+int qtnf_cmd_set_tx_power(const struct qtnf_vif *vif,
+ enum nl80211_tx_power_setting type, int mbm)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ const struct qlink_resp_txpwr *resp;
+ struct sk_buff *resp_skb = NULL;
+ struct qlink_cmd_txpwr *cmd;
+ struct sk_buff *cmd_skb;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_TXPWR, sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_txpwr *)cmd_skb->data;
+ cmd->op_type = QLINK_TXPWR_SET;
+ cmd->txpwr_setting = type;
+ cmd->txpwr = cpu_to_le32(mbm);
+
+ qtnf_bus_lock(bus);
+
+ ret = qtnf_cmd_send_with_reply(bus, cmd_skb, &resp_skb,
+ sizeof(*resp), NULL);
+
+ qtnf_bus_unlock(bus);
+ consume_skb(resp_skb);
+
+ return ret;
+}
+
+int qtnf_cmd_send_wowlan_set(const struct qtnf_vif *vif,
+ const struct cfg80211_wowlan *wowl)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_wowlan_set *cmd;
+ u32 triggers = 0;
+ int count = 0;
+ int ret = 0;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_WOWLAN_SET, sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ qtnf_bus_lock(bus);
+
+ cmd = (struct qlink_cmd_wowlan_set *)cmd_skb->data;
+
+ if (wowl) {
+ if (wowl->disconnect)
+ triggers |= QLINK_WOWLAN_TRIG_DISCONNECT;
+
+ if (wowl->magic_pkt)
+ triggers |= QLINK_WOWLAN_TRIG_MAGIC_PKT;
+
+ if (wowl->n_patterns && wowl->patterns) {
+ triggers |= QLINK_WOWLAN_TRIG_PATTERN_PKT;
+ while (count < wowl->n_patterns) {
+ qtnf_cmd_skb_put_tlv_arr(cmd_skb,
+ QTN_TLV_ID_WOWLAN_PATTERN,
+ wowl->patterns[count].pattern,
+ wowl->patterns[count].pattern_len);
+ count++;
+ }
+ }
+ }
+
+ cmd->triggers = cpu_to_le32(triggers);
+
+ ret = qtnf_cmd_send(bus, cmd_skb);
+
+ qtnf_bus_unlock(bus);
+ return ret;
+}
+
+int qtnf_cmd_netdev_changeupper(const struct qtnf_vif *vif, int br_domain)
+{
+ struct qtnf_bus *bus = vif->mac->bus;
+ struct sk_buff *cmd_skb;
+ struct qlink_cmd_ndev_changeupper *cmd;
+ int ret;
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_NDEV_EVENT,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ pr_debug("[VIF%u.%u] set broadcast domain to %d\n",
+ vif->mac->macid, vif->vifid, br_domain);
+
+ cmd = (struct qlink_cmd_ndev_changeupper *)cmd_skb->data;
+ cmd->nehdr.event = cpu_to_le16(QLINK_NDEV_EVENT_CHANGEUPPER);
+ cmd->upper_type = QLINK_NDEV_UPPER_TYPE_BRIDGE;
+ cmd->br_domain = cpu_to_le32(br_domain);
+
+ qtnf_bus_lock(bus);
+ ret = qtnf_cmd_send(bus, cmd_skb);
+ qtnf_bus_unlock(bus);
+
+ if (ret)
+ pr_err("[VIF%u.%u] failed to set broadcast domain\n",
+ vif->mac->macid, vif->vifid);
+
+ return ret;
+}
+
+int qtnf_cmd_send_update_owe(struct qtnf_vif *vif,
+ struct cfg80211_update_owe_info *owe)
+{
+ struct qlink_cmd_update_owe *cmd;
+ struct sk_buff *cmd_skb;
+ int ret;
+
+ if (sizeof(*cmd) + owe->ie_len > QTNF_MAX_CMD_BUF_SIZE) {
+ pr_warn("VIF%u.%u: OWE update IEs too big: %zu\n",
+ vif->mac->macid, vif->vifid, owe->ie_len);
+ return -E2BIG;
+ }
+
+ cmd_skb = qtnf_cmd_alloc_new_cmdskb(vif->mac->macid, vif->vifid,
+ QLINK_CMD_UPDATE_OWE,
+ sizeof(*cmd));
+ if (!cmd_skb)
+ return -ENOMEM;
+
+ cmd = (struct qlink_cmd_update_owe *)cmd_skb->data;
+ ether_addr_copy(cmd->peer, owe->peer);
+ cmd->status = cpu_to_le16(owe->status);
+ if (owe->ie_len && owe->ie)
+ qtnf_cmd_skb_put_buffer(cmd_skb, owe->ie, owe->ie_len);
+
+ qtnf_bus_lock(vif->mac->bus);
+ ret = qtnf_cmd_send(vif->mac->bus, cmd_skb);
+ qtnf_bus_unlock(vif->mac->bus);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.h b/drivers/net/wireless/quantenna/qtnfmac/commands.h
new file mode 100644
index 000000000..72ad6ae5c
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2016 Quantenna Communications. All rights reserved. */
+
+#ifndef QLINK_COMMANDS_H_
+#define QLINK_COMMANDS_H_
+
+#include <linux/nl80211.h>
+
+#include "core.h"
+#include "bus.h"
+
+int qtnf_cmd_send_init_fw(struct qtnf_bus *bus);
+void qtnf_cmd_send_deinit_fw(struct qtnf_bus *bus);
+int qtnf_cmd_get_hw_info(struct qtnf_bus *bus);
+int qtnf_cmd_get_mac_info(struct qtnf_wmac *mac);
+int qtnf_cmd_send_add_intf(struct qtnf_vif *vif, enum nl80211_iftype iftype,
+ int use4addr, u8 *mac_addr);
+int qtnf_cmd_send_change_intf_type(struct qtnf_vif *vif,
+ enum nl80211_iftype iftype,
+ int use4addr,
+ u8 *mac_addr);
+int qtnf_cmd_send_del_intf(struct qtnf_vif *vif);
+int qtnf_cmd_band_info_get(struct qtnf_wmac *mac,
+ struct ieee80211_supported_band *band);
+int qtnf_cmd_send_regulatory_config(struct qtnf_wmac *mac, const char *alpha2);
+int qtnf_cmd_send_start_ap(struct qtnf_vif *vif,
+ const struct cfg80211_ap_settings *s);
+int qtnf_cmd_send_stop_ap(struct qtnf_vif *vif);
+int qtnf_cmd_send_register_mgmt(struct qtnf_vif *vif, u16 frame_type, bool reg);
+int qtnf_cmd_send_frame(struct qtnf_vif *vif, u32 cookie, u16 flags,
+ u16 freq, const u8 *buf, size_t len);
+int qtnf_cmd_send_mgmt_set_appie(struct qtnf_vif *vif, u8 frame_type,
+ const u8 *buf, size_t len);
+int qtnf_cmd_get_sta_info(struct qtnf_vif *vif, const u8 *sta_mac,
+ struct station_info *sinfo);
+int qtnf_cmd_send_phy_params(struct qtnf_wmac *mac, u16 cmd_action,
+ void *data_buf);
+int qtnf_cmd_send_add_key(struct qtnf_vif *vif, u8 key_index, bool pairwise,
+ const u8 *mac_addr, struct key_params *params);
+int qtnf_cmd_send_del_key(struct qtnf_vif *vif, u8 key_index, bool pairwise,
+ const u8 *mac_addr);
+int qtnf_cmd_send_set_default_key(struct qtnf_vif *vif, u8 key_index,
+ bool unicast, bool multicast);
+int qtnf_cmd_send_set_default_mgmt_key(struct qtnf_vif *vif, u8 key_index);
+int qtnf_cmd_send_add_sta(struct qtnf_vif *vif, const u8 *mac,
+ struct station_parameters *params);
+int qtnf_cmd_send_change_sta(struct qtnf_vif *vif, const u8 *mac,
+ struct station_parameters *params);
+int qtnf_cmd_send_del_sta(struct qtnf_vif *vif,
+ struct station_del_parameters *params);
+int qtnf_cmd_send_scan(struct qtnf_wmac *mac);
+int qtnf_cmd_send_connect(struct qtnf_vif *vif,
+ struct cfg80211_connect_params *sme);
+int qtnf_cmd_send_external_auth(struct qtnf_vif *vif,
+ struct cfg80211_external_auth_params *auth);
+int qtnf_cmd_send_disconnect(struct qtnf_vif *vif,
+ u16 reason_code);
+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, 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);
+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);
+int qtnf_cmd_send_pm_set(const struct qtnf_vif *vif, u8 pm_mode, int timeout);
+int qtnf_cmd_get_tx_power(const struct qtnf_vif *vif, int *dbm);
+int qtnf_cmd_set_tx_power(const struct qtnf_vif *vif,
+ enum nl80211_tx_power_setting type, int mbm);
+int qtnf_cmd_send_wowlan_set(const struct qtnf_vif *vif,
+ const struct cfg80211_wowlan *wowl);
+int qtnf_cmd_netdev_changeupper(const struct qtnf_vif *vif, int br_domain);
+int qtnf_cmd_send_update_owe(struct qtnf_vif *vif,
+ struct cfg80211_update_owe_info *owe);
+
+#endif /* QLINK_COMMANDS_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.c b/drivers/net/wireless/quantenna/qtnfmac/core.c
new file mode 100644
index 000000000..0b5b60815
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.c
@@ -0,0 +1,933 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/if_ether.h>
+#include <linux/nospec.h>
+
+#include "core.h"
+#include "bus.h"
+#include "trans.h"
+#include "commands.h"
+#include "cfg80211.h"
+#include "event.h"
+#include "util.h"
+#include "switchdev.h"
+
+#define QTNF_PRIMARY_VIF_IDX 0
+
+static bool slave_radar = true;
+module_param(slave_radar, bool, 0644);
+MODULE_PARM_DESC(slave_radar, "set 0 to disable radar detection in slave mode");
+
+static bool dfs_offload;
+module_param(dfs_offload, bool, 0644);
+MODULE_PARM_DESC(dfs_offload, "set 1 to enable DFS offload to firmware");
+
+static struct dentry *qtnf_debugfs_dir;
+
+bool qtnf_slave_radar_get(void)
+{
+ return slave_radar;
+}
+
+bool qtnf_dfs_offload_get(void)
+{
+ return dfs_offload;
+}
+
+struct qtnf_wmac *qtnf_core_get_mac(const struct qtnf_bus *bus, u8 macid)
+{
+ struct qtnf_wmac *mac = NULL;
+
+ if (macid >= QTNF_MAX_MAC) {
+ pr_err("invalid MAC index %u\n", macid);
+ return NULL;
+ }
+
+ macid = array_index_nospec(macid, QTNF_MAX_MAC);
+ mac = bus->mac[macid];
+
+ if (unlikely(!mac)) {
+ pr_err("MAC%u: not initialized\n", macid);
+ return NULL;
+ }
+
+ return mac;
+}
+
+/* Netdev handler for open.
+ */
+static int qtnf_netdev_open(struct net_device *ndev)
+{
+ netif_carrier_off(ndev);
+ qtnf_netdev_updown(ndev, 1);
+ return 0;
+}
+
+/* Netdev handler for close.
+ */
+static int qtnf_netdev_close(struct net_device *ndev)
+{
+ netif_carrier_off(ndev);
+ qtnf_virtual_intf_cleanup(ndev);
+ qtnf_netdev_updown(ndev, 0);
+ return 0;
+}
+
+static void qtnf_packet_send_hi_pri(struct sk_buff *skb)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(skb->dev);
+
+ skb_queue_tail(&vif->high_pri_tx_queue, skb);
+ queue_work(vif->mac->bus->hprio_workqueue, &vif->high_pri_tx_work);
+}
+
+/* Netdev handler for data transmission.
+ */
+static netdev_tx_t
+qtnf_netdev_hard_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct qtnf_vif *vif;
+ struct qtnf_wmac *mac;
+
+ vif = qtnf_netdev_get_priv(ndev);
+
+ if (unlikely(skb->dev != ndev)) {
+ pr_err_ratelimited("invalid skb->dev");
+ dev_kfree_skb_any(skb);
+ return 0;
+ }
+
+ if (unlikely(vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED)) {
+ pr_err_ratelimited("%s: VIF not initialized\n", ndev->name);
+ dev_kfree_skb_any(skb);
+ return 0;
+ }
+
+ mac = vif->mac;
+ if (unlikely(!mac)) {
+ pr_err_ratelimited("%s: NULL mac pointer", ndev->name);
+ dev_kfree_skb_any(skb);
+ return 0;
+ }
+
+ if (!skb->len || (skb->len > ETH_FRAME_LEN)) {
+ pr_err_ratelimited("%s: invalid skb len %d\n", ndev->name,
+ skb->len);
+ dev_kfree_skb_any(skb);
+ ndev->stats.tx_dropped++;
+ return 0;
+ }
+
+ /* tx path is enabled: reset vif timeout */
+ vif->cons_tx_timeout_cnt = 0;
+
+ if (unlikely(skb->protocol == htons(ETH_P_PAE))) {
+ qtnf_packet_send_hi_pri(skb);
+ dev_sw_netstats_tx_add(ndev, 1, skb->len);
+ return NETDEV_TX_OK;
+ }
+
+ return qtnf_bus_data_tx(mac->bus, skb, mac->macid, vif->vifid);
+}
+
+/* Netdev handler for transmission timeout.
+ */
+static void qtnf_netdev_tx_timeout(struct net_device *ndev, unsigned int txqueue)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ struct qtnf_wmac *mac;
+ struct qtnf_bus *bus;
+
+ if (unlikely(!vif || !vif->mac || !vif->mac->bus))
+ return;
+
+ mac = vif->mac;
+ bus = mac->bus;
+
+ pr_warn("VIF%u.%u: Tx timeout- %lu\n", mac->macid, vif->vifid, jiffies);
+
+ qtnf_bus_data_tx_timeout(bus, ndev);
+ ndev->stats.tx_errors++;
+
+ if (++vif->cons_tx_timeout_cnt > QTNF_TX_TIMEOUT_TRSHLD) {
+ pr_err("Tx timeout threshold exceeded !\n");
+ pr_err("schedule interface %s reset !\n", netdev_name(ndev));
+ queue_work(bus->workqueue, &vif->reset_work);
+ }
+}
+
+static int qtnf_netdev_set_mac_address(struct net_device *ndev, void *addr)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ struct sockaddr *sa = addr;
+ int ret;
+ unsigned char old_addr[ETH_ALEN];
+
+ memcpy(old_addr, sa->sa_data, sizeof(old_addr));
+
+ ret = eth_mac_addr(ndev, sa);
+ if (ret)
+ return ret;
+
+ qtnf_scan_done(vif->mac, true);
+
+ ret = qtnf_cmd_send_change_intf_type(vif, vif->wdev.iftype,
+ vif->wdev.use_4addr,
+ sa->sa_data);
+
+ if (ret)
+ eth_hw_addr_set(ndev, old_addr);
+
+ return ret;
+}
+
+static int qtnf_netdev_port_parent_id(struct net_device *ndev,
+ struct netdev_phys_item_id *ppid)
+{
+ const struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ const struct qtnf_bus *bus = vif->mac->bus;
+
+ ppid->id_len = sizeof(bus->hw_id);
+ memcpy(&ppid->id, bus->hw_id, ppid->id_len);
+
+ return 0;
+}
+
+/* Network device ops handlers */
+const struct net_device_ops qtnf_netdev_ops = {
+ .ndo_open = qtnf_netdev_open,
+ .ndo_stop = qtnf_netdev_close,
+ .ndo_start_xmit = qtnf_netdev_hard_start_xmit,
+ .ndo_tx_timeout = qtnf_netdev_tx_timeout,
+ .ndo_set_mac_address = qtnf_netdev_set_mac_address,
+ .ndo_get_port_parent_id = qtnf_netdev_port_parent_id,
+};
+
+static int qtnf_mac_init_single_band(struct wiphy *wiphy,
+ struct qtnf_wmac *mac,
+ enum nl80211_band band)
+{
+ int ret;
+
+ wiphy->bands[band] = kzalloc_obj(*wiphy->bands[band]);
+ if (!wiphy->bands[band])
+ return -ENOMEM;
+
+ wiphy->bands[band]->band = band;
+
+ ret = qtnf_cmd_band_info_get(mac, wiphy->bands[band]);
+ if (ret) {
+ pr_err("MAC%u: band %u: failed to get chans info: %d\n",
+ mac->macid, band, ret);
+ return ret;
+ }
+
+ qtnf_band_init_rates(wiphy->bands[band]);
+
+ return 0;
+}
+
+static int qtnf_mac_init_bands(struct qtnf_wmac *mac)
+{
+ struct wiphy *wiphy = priv_to_wiphy(mac);
+ int ret = 0;
+
+ if (mac->macinfo.bands_cap & QLINK_BAND_2GHZ) {
+ ret = qtnf_mac_init_single_band(wiphy, mac, NL80211_BAND_2GHZ);
+ if (ret)
+ goto out;
+ }
+
+ if (mac->macinfo.bands_cap & QLINK_BAND_5GHZ) {
+ ret = qtnf_mac_init_single_band(wiphy, mac, NL80211_BAND_5GHZ);
+ if (ret)
+ goto out;
+ }
+
+ if (mac->macinfo.bands_cap & QLINK_BAND_60GHZ)
+ ret = qtnf_mac_init_single_band(wiphy, mac, NL80211_BAND_60GHZ);
+
+out:
+ return ret;
+}
+
+struct qtnf_vif *qtnf_mac_get_free_vif(struct qtnf_wmac *mac)
+{
+ struct qtnf_vif *vif;
+ int i;
+
+ for (i = 0; i < QTNF_MAX_INTF; i++) {
+ vif = &mac->iflist[i];
+ if (vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED)
+ return vif;
+ }
+
+ return NULL;
+}
+
+struct qtnf_vif *qtnf_mac_get_base_vif(struct qtnf_wmac *mac)
+{
+ struct qtnf_vif *vif;
+
+ vif = &mac->iflist[QTNF_PRIMARY_VIF_IDX];
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED)
+ return NULL;
+
+ return vif;
+}
+
+void qtnf_mac_iface_comb_free(struct qtnf_wmac *mac)
+{
+ struct ieee80211_iface_combination *comb;
+ int i;
+
+ if (mac->macinfo.if_comb) {
+ for (i = 0; i < mac->macinfo.n_if_comb; i++) {
+ comb = &mac->macinfo.if_comb[i];
+ kfree(comb->limits);
+ comb->limits = NULL;
+ }
+
+ kfree(mac->macinfo.if_comb);
+ mac->macinfo.if_comb = NULL;
+ }
+}
+
+void qtnf_mac_ext_caps_free(struct qtnf_wmac *mac)
+{
+ if (mac->macinfo.extended_capabilities_len) {
+ kfree(mac->macinfo.extended_capabilities);
+ mac->macinfo.extended_capabilities = NULL;
+
+ kfree(mac->macinfo.extended_capabilities_mask);
+ mac->macinfo.extended_capabilities_mask = NULL;
+
+ mac->macinfo.extended_capabilities_len = 0;
+ }
+}
+
+static void qtnf_vif_reset_handler(struct work_struct *work)
+{
+ struct qtnf_vif *vif = container_of(work, struct qtnf_vif, reset_work);
+
+ rtnl_lock();
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED) {
+ rtnl_unlock();
+ return;
+ }
+
+ /* stop tx completely */
+ netif_tx_stop_all_queues(vif->netdev);
+ if (netif_carrier_ok(vif->netdev))
+ netif_carrier_off(vif->netdev);
+
+ qtnf_cfg80211_vif_reset(vif);
+
+ rtnl_unlock();
+}
+
+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_STATION;
+ vif->bss_priority = QTNF_DEF_BSS_PRIORITY;
+ vif->wdev.wiphy = priv_to_wiphy(mac);
+ INIT_WORK(&vif->reset_work, qtnf_vif_reset_handler);
+ vif->cons_tx_timeout_cnt = 0;
+}
+
+static void qtnf_mac_scan_finish(struct qtnf_wmac *mac, bool aborted)
+{
+ struct cfg80211_scan_info info = {
+ .aborted = aborted,
+ };
+
+ mutex_lock(&mac->mac_lock);
+
+ if (mac->scan_req) {
+ cfg80211_scan_done(mac->scan_req, &info);
+ mac->scan_req = NULL;
+ }
+
+ mutex_unlock(&mac->mac_lock);
+}
+
+void qtnf_scan_done(struct qtnf_wmac *mac, bool aborted)
+{
+ cancel_delayed_work_sync(&mac->scan_timeout);
+ qtnf_mac_scan_finish(mac, aborted);
+}
+
+static void qtnf_mac_scan_timeout(struct work_struct *work)
+{
+ struct qtnf_wmac *mac =
+ container_of(work, struct qtnf_wmac, scan_timeout.work);
+
+ pr_warn("MAC%d: scan timed out\n", mac->macid);
+ qtnf_mac_scan_finish(mac, true);
+}
+
+static void qtnf_vif_send_data_high_pri(struct work_struct *work)
+{
+ struct qtnf_vif *vif =
+ container_of(work, struct qtnf_vif, high_pri_tx_work);
+ struct sk_buff *skb;
+
+ if (!vif->netdev ||
+ vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED)
+ return;
+
+ while ((skb = skb_dequeue(&vif->high_pri_tx_queue))) {
+ qtnf_cmd_send_frame(vif, 0, QLINK_FRAME_TX_FLAG_8023,
+ 0, skb->data, skb->len);
+ dev_kfree_skb_any(skb);
+ }
+}
+
+static struct qtnf_wmac *qtnf_core_mac_alloc(struct qtnf_bus *bus,
+ unsigned int macid)
+{
+ struct platform_device *pdev = NULL;
+ struct qtnf_wmac *mac;
+ struct qtnf_vif *vif;
+ struct wiphy *wiphy;
+ unsigned int i;
+
+ if (bus->hw_info.num_mac > 1) {
+ pdev = platform_device_register_data(bus->dev,
+ dev_name(bus->dev),
+ macid, NULL, 0);
+ if (IS_ERR(pdev))
+ return ERR_PTR(-EINVAL);
+ }
+
+ wiphy = qtnf_wiphy_allocate(bus, pdev);
+ if (!wiphy) {
+ if (pdev)
+ platform_device_unregister(pdev);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ mac = wiphy_priv(wiphy);
+
+ mac->macid = macid;
+ mac->pdev = pdev;
+ mac->bus = bus;
+ mutex_init(&mac->mac_lock);
+ INIT_DELAYED_WORK(&mac->scan_timeout, qtnf_mac_scan_timeout);
+
+ for (i = 0; i < QTNF_MAX_INTF; i++) {
+ vif = &mac->iflist[i];
+
+ memset(vif, 0, sizeof(*vif));
+ vif->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED;
+ vif->mac = mac;
+ vif->vifid = i;
+ qtnf_sta_list_init(&vif->sta_list);
+ INIT_WORK(&vif->high_pri_tx_work, qtnf_vif_send_data_high_pri);
+ skb_queue_head_init(&vif->high_pri_tx_queue);
+ }
+
+ qtnf_mac_init_primary_intf(mac);
+ bus->mac[macid] = mac;
+
+ return mac;
+}
+
+static const struct ethtool_ops qtnf_ethtool_ops = {
+ .get_drvinfo = cfg80211_get_drvinfo,
+};
+
+int qtnf_core_net_attach(struct qtnf_wmac *mac, struct qtnf_vif *vif,
+ const char *name, unsigned char name_assign_type)
+{
+ struct wiphy *wiphy = priv_to_wiphy(mac);
+ struct net_device *dev;
+ void *qdev_vif;
+ int ret;
+
+ dev = alloc_netdev(sizeof(struct qtnf_vif *), name,
+ name_assign_type, ether_setup);
+ if (!dev)
+ return -ENOMEM;
+
+ vif->netdev = dev;
+
+ dev->netdev_ops = &qtnf_netdev_ops;
+ dev->needs_free_netdev = true;
+ dev_net_set(dev, wiphy_net(wiphy));
+ dev->ieee80211_ptr = &vif->wdev;
+ eth_hw_addr_set(dev, vif->mac_addr);
+ dev->flags |= IFF_BROADCAST | IFF_MULTICAST;
+ dev->watchdog_timeo = QTNF_DEF_WDOG_TIMEOUT;
+ dev->tx_queue_len = 100;
+ dev->ethtool_ops = &qtnf_ethtool_ops;
+ dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
+
+ if (qtnf_hwcap_is_set(&mac->bus->hw_info, QLINK_HW_CAPAB_HW_BRIDGE))
+ dev->needed_tailroom = sizeof(struct qtnf_frame_meta_info);
+
+ qdev_vif = netdev_priv(dev);
+ *((void **)qdev_vif) = vif;
+
+ SET_NETDEV_DEV(dev, wiphy_dev(wiphy));
+
+ ret = cfg80211_register_netdevice(dev);
+ if (ret) {
+ free_netdev(dev);
+ vif->netdev = NULL;
+ }
+
+ return ret;
+}
+
+static void qtnf_core_mac_detach(struct qtnf_bus *bus, unsigned int macid)
+{
+ struct qtnf_wmac *mac;
+ struct wiphy *wiphy;
+ struct qtnf_vif *vif;
+ unsigned int i;
+ enum nl80211_band band;
+
+ mac = bus->mac[macid];
+
+ if (!mac)
+ return;
+
+ wiphy = priv_to_wiphy(mac);
+
+ for (i = 0; i < QTNF_MAX_INTF; i++) {
+ vif = &mac->iflist[i];
+ rtnl_lock();
+ if (vif->netdev &&
+ vif->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) {
+ qtnf_virtual_intf_cleanup(vif->netdev);
+ qtnf_del_virtual_intf(wiphy, &vif->wdev);
+ }
+ rtnl_unlock();
+ qtnf_sta_list_free(&vif->sta_list);
+ }
+
+ if (mac->wiphy_registered)
+ wiphy_unregister(wiphy);
+
+ for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; ++band) {
+ if (!wiphy->bands[band])
+ continue;
+
+ kfree((__force void *)wiphy->bands[band]->iftype_data);
+ wiphy->bands[band]->n_iftype_data = 0;
+
+ kfree(wiphy->bands[band]->channels);
+ wiphy->bands[band]->n_channels = 0;
+
+ kfree(wiphy->bands[band]);
+ wiphy->bands[band] = NULL;
+ }
+
+ platform_device_unregister(mac->pdev);
+ qtnf_mac_iface_comb_free(mac);
+ qtnf_mac_ext_caps_free(mac);
+ kfree(mac->macinfo.wowlan);
+ kfree(mac->rd);
+ mac->rd = NULL;
+ wiphy_free(wiphy);
+ bus->mac[macid] = NULL;
+}
+
+static int qtnf_core_mac_attach(struct qtnf_bus *bus, unsigned int macid)
+{
+ struct qtnf_wmac *mac;
+ struct qtnf_vif *vif;
+ int ret;
+
+ if (!(bus->hw_info.mac_bitmap & BIT(macid))) {
+ pr_info("MAC%u is not active in FW\n", macid);
+ return 0;
+ }
+
+ mac = qtnf_core_mac_alloc(bus, macid);
+ if (IS_ERR(mac)) {
+ pr_err("MAC%u allocation failed\n", macid);
+ return PTR_ERR(mac);
+ }
+
+ vif = qtnf_mac_get_base_vif(mac);
+ if (!vif) {
+ pr_err("MAC%u: primary VIF is not ready\n", macid);
+ ret = -EFAULT;
+ goto error;
+ }
+
+ ret = qtnf_cmd_send_add_intf(vif, vif->wdev.iftype,
+ vif->wdev.use_4addr, vif->mac_addr);
+ if (ret) {
+ pr_err("MAC%u: failed to add VIF\n", macid);
+ goto error;
+ }
+
+ ret = qtnf_cmd_get_mac_info(mac);
+ if (ret) {
+ pr_err("MAC%u: failed to get MAC info\n", macid);
+ goto error_del_vif;
+ }
+
+ /* Use MAC address of the first active radio as a unique device ID */
+ if (is_zero_ether_addr(mac->bus->hw_id))
+ ether_addr_copy(mac->bus->hw_id, mac->macaddr);
+
+ ret = qtnf_mac_init_bands(mac);
+ if (ret) {
+ pr_err("MAC%u: failed to init bands\n", macid);
+ goto error_del_vif;
+ }
+
+ ret = qtnf_wiphy_register(&bus->hw_info, mac);
+ if (ret) {
+ pr_err("MAC%u: wiphy registration failed\n", macid);
+ goto error_del_vif;
+ }
+
+ mac->wiphy_registered = 1;
+
+ rtnl_lock();
+ wiphy_lock(priv_to_wiphy(mac));
+ ret = qtnf_core_net_attach(mac, vif, "wlan%d", NET_NAME_ENUM);
+ wiphy_unlock(priv_to_wiphy(mac));
+ rtnl_unlock();
+
+ if (ret) {
+ pr_err("MAC%u: failed to attach netdev\n", macid);
+ goto error_del_vif;
+ }
+
+ if (qtnf_hwcap_is_set(&bus->hw_info, QLINK_HW_CAPAB_HW_BRIDGE)) {
+ ret = qtnf_cmd_netdev_changeupper(vif, vif->netdev->ifindex);
+ if (ret)
+ goto error;
+ }
+
+ pr_debug("MAC%u initialized\n", macid);
+
+ return 0;
+
+error_del_vif:
+ qtnf_cmd_send_del_intf(vif);
+ vif->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED;
+error:
+ qtnf_core_mac_detach(bus, macid);
+ return ret;
+}
+
+bool qtnf_netdev_is_qtn(const struct net_device *ndev)
+{
+ return ndev->netdev_ops == &qtnf_netdev_ops;
+}
+
+static int qtnf_check_br_ports(struct net_device *dev,
+ struct netdev_nested_priv *priv)
+{
+ struct net_device *ndev = (struct net_device *)priv->data;
+
+ if (dev != ndev && netdev_port_same_parent_id(dev, ndev))
+ return -ENOTSUPP;
+
+ return 0;
+}
+
+static int qtnf_core_netdevice_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
+ const struct netdev_notifier_changeupper_info *info;
+ struct netdev_nested_priv priv = {
+ .data = (void *)ndev,
+ };
+ struct net_device *brdev;
+ struct qtnf_vif *vif;
+ struct qtnf_bus *bus;
+ int br_domain;
+ int ret = 0;
+
+ if (!qtnf_netdev_is_qtn(ndev))
+ return NOTIFY_DONE;
+
+ if (!net_eq(dev_net(ndev), &init_net))
+ return NOTIFY_OK;
+
+ vif = qtnf_netdev_get_priv(ndev);
+ bus = vif->mac->bus;
+
+ switch (event) {
+ case NETDEV_CHANGEUPPER:
+ info = ptr;
+ brdev = info->upper_dev;
+
+ if (!netif_is_bridge_master(brdev))
+ break;
+
+ pr_debug("[VIF%u.%u] change bridge: %s %s\n",
+ vif->mac->macid, vif->vifid, netdev_name(brdev),
+ info->linking ? "add" : "del");
+
+ if (IS_ENABLED(CONFIG_NET_SWITCHDEV) &&
+ qtnf_hwcap_is_set(&bus->hw_info,
+ QLINK_HW_CAPAB_HW_BRIDGE)) {
+ if (info->linking)
+ br_domain = brdev->ifindex;
+ else
+ br_domain = ndev->ifindex;
+
+ ret = qtnf_cmd_netdev_changeupper(vif, br_domain);
+ } else {
+ ret = netdev_walk_all_lower_dev(brdev,
+ qtnf_check_br_ports,
+ &priv);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ return notifier_from_errno(ret);
+}
+
+int qtnf_core_attach(struct qtnf_bus *bus)
+{
+ unsigned int i;
+ int ret;
+
+ qtnf_trans_init(bus);
+ qtnf_bus_data_rx_start(bus);
+
+ bus->workqueue = alloc_ordered_workqueue("QTNF_BUS", 0);
+ if (!bus->workqueue) {
+ pr_err("failed to alloc main workqueue\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ bus->hprio_workqueue = alloc_workqueue("QTNF_HPRI",
+ WQ_HIGHPRI | WQ_PERCPU, 0);
+ if (!bus->hprio_workqueue) {
+ pr_err("failed to alloc high prio workqueue\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ INIT_WORK(&bus->event_work, qtnf_event_work_handler);
+
+ ret = qtnf_cmd_send_init_fw(bus);
+ if (ret) {
+ pr_err("failed to init FW: %d\n", ret);
+ goto error;
+ }
+
+ if (QLINK_VER_MAJOR(bus->hw_info.ql_proto_ver) !=
+ QLINK_PROTO_VER_MAJOR) {
+ pr_err("qlink driver vs FW version mismatch: %u vs %u\n",
+ QLINK_PROTO_VER_MAJOR,
+ QLINK_VER_MAJOR(bus->hw_info.ql_proto_ver));
+ ret = -EPROTONOSUPPORT;
+ goto error;
+ }
+
+ bus->fw_state = QTNF_FW_STATE_ACTIVE;
+ ret = qtnf_cmd_get_hw_info(bus);
+ if (ret) {
+ pr_err("failed to get HW info: %d\n", ret);
+ goto error;
+ }
+
+ if (qtnf_hwcap_is_set(&bus->hw_info, QLINK_HW_CAPAB_HW_BRIDGE) &&
+ bus->bus_ops->data_tx_use_meta_set)
+ bus->bus_ops->data_tx_use_meta_set(bus, true);
+
+ if (bus->hw_info.num_mac > QTNF_MAX_MAC) {
+ pr_err("no support for number of MACs=%u\n",
+ bus->hw_info.num_mac);
+ ret = -ERANGE;
+ goto error;
+ }
+
+ for (i = 0; i < bus->hw_info.num_mac; i++) {
+ ret = qtnf_core_mac_attach(bus, i);
+
+ if (ret) {
+ pr_err("MAC%u: attach failed: %d\n", i, ret);
+ goto error;
+ }
+ }
+
+ bus->netdev_nb.notifier_call = qtnf_core_netdevice_event;
+ ret = register_netdevice_notifier(&bus->netdev_nb);
+ if (ret) {
+ pr_err("failed to register netdev notifier: %d\n", ret);
+ goto error;
+ }
+
+ bus->fw_state = QTNF_FW_STATE_RUNNING;
+ return 0;
+
+error:
+ qtnf_core_detach(bus);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(qtnf_core_attach);
+
+void qtnf_core_detach(struct qtnf_bus *bus)
+{
+ unsigned int macid;
+
+ unregister_netdevice_notifier(&bus->netdev_nb);
+ qtnf_bus_data_rx_stop(bus);
+
+ for (macid = 0; macid < QTNF_MAX_MAC; macid++)
+ qtnf_core_mac_detach(bus, macid);
+
+ if (qtnf_fw_is_up(bus))
+ qtnf_cmd_send_deinit_fw(bus);
+
+ bus->fw_state = QTNF_FW_STATE_DETACHED;
+
+ if (bus->workqueue) {
+ destroy_workqueue(bus->workqueue);
+ bus->workqueue = NULL;
+ }
+
+ if (bus->hprio_workqueue) {
+ destroy_workqueue(bus->hprio_workqueue);
+ bus->hprio_workqueue = NULL;
+ }
+
+ qtnf_trans_free(bus);
+}
+EXPORT_SYMBOL_GPL(qtnf_core_detach);
+
+static inline int qtnf_is_frame_meta_magic_valid(struct qtnf_frame_meta_info *m)
+{
+ return m->magic_s == HBM_FRAME_META_MAGIC_PATTERN_S &&
+ m->magic_e == HBM_FRAME_META_MAGIC_PATTERN_E;
+}
+
+struct net_device *qtnf_classify_skb(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+ struct qtnf_frame_meta_info *meta;
+ struct net_device *ndev = NULL;
+ struct qtnf_wmac *mac;
+ struct qtnf_vif *vif;
+
+ if (unlikely(bus->fw_state != QTNF_FW_STATE_RUNNING))
+ return NULL;
+
+ meta = (struct qtnf_frame_meta_info *)
+ (skb_tail_pointer(skb) - sizeof(*meta));
+
+ if (unlikely(!qtnf_is_frame_meta_magic_valid(meta))) {
+ pr_err_ratelimited("invalid magic 0x%x:0x%x\n",
+ meta->magic_s, meta->magic_e);
+ goto out;
+ }
+
+ if (unlikely(meta->macid >= QTNF_MAX_MAC)) {
+ pr_err_ratelimited("invalid mac(%u)\n", meta->macid);
+ goto out;
+ }
+
+ if (unlikely(meta->ifidx >= QTNF_MAX_INTF)) {
+ pr_err_ratelimited("invalid vif(%u)\n", meta->ifidx);
+ goto out;
+ }
+
+ mac = bus->mac[meta->macid];
+
+ if (unlikely(!mac)) {
+ pr_err_ratelimited("mac(%d) does not exist\n", meta->macid);
+ goto out;
+ }
+
+ vif = &mac->iflist[meta->ifidx];
+
+ if (unlikely(vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED)) {
+ pr_err_ratelimited("vif(%u) does not exists\n", meta->ifidx);
+ goto out;
+ }
+
+ ndev = vif->netdev;
+
+ if (unlikely(!ndev)) {
+ pr_err_ratelimited("netdev for wlan%u.%u does not exists\n",
+ meta->macid, meta->ifidx);
+ goto out;
+ }
+
+ __skb_trim(skb, skb->len - sizeof(*meta));
+ /* Firmware always handles packets that require flooding */
+ qtnfmac_switch_mark_skb_flooded(skb);
+
+out:
+ return ndev;
+}
+EXPORT_SYMBOL_GPL(qtnf_classify_skb);
+
+void qtnf_wake_all_queues(struct net_device *ndev)
+{
+ struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ struct qtnf_wmac *mac;
+ struct qtnf_bus *bus;
+ int macid;
+ int i;
+
+ if (unlikely(!vif || !vif->mac || !vif->mac->bus))
+ return;
+
+ bus = vif->mac->bus;
+
+ for (macid = 0; macid < QTNF_MAX_MAC; macid++) {
+ if (!(bus->hw_info.mac_bitmap & BIT(macid)))
+ continue;
+
+ mac = bus->mac[macid];
+ for (i = 0; i < QTNF_MAX_INTF; i++) {
+ vif = &mac->iflist[i];
+ if (vif->netdev && netif_queue_stopped(vif->netdev))
+ netif_tx_wake_all_queues(vif->netdev);
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(qtnf_wake_all_queues);
+
+struct dentry *qtnf_get_debugfs_dir(void)
+{
+ return qtnf_debugfs_dir;
+}
+EXPORT_SYMBOL_GPL(qtnf_get_debugfs_dir);
+
+static int __init qtnf_core_register(void)
+{
+ qtnf_debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL);
+
+ if (IS_ERR(qtnf_debugfs_dir))
+ qtnf_debugfs_dir = NULL;
+
+ return 0;
+}
+
+static void __exit qtnf_core_exit(void)
+{
+ debugfs_remove(qtnf_debugfs_dir);
+}
+
+module_init(qtnf_core_register);
+module_exit(qtnf_core_exit);
+
+MODULE_AUTHOR("Quantenna Communications");
+MODULE_DESCRIPTION("Quantenna 802.11 wireless LAN FullMAC driver.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.h b/drivers/net/wireless/quantenna/qtnfmac/core.h
new file mode 100644
index 000000000..a377d85c2
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.h
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_CORE_H_
+#define _QTN_FMAC_CORE_H_
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/semaphore.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/etherdevice.h>
+#include <net/sock.h>
+#include <net/cfg80211.h>
+#include <linux/vmalloc.h>
+#include <linux/firmware.h>
+#include <linux/ctype.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+#include "qlink.h"
+#include "trans.h"
+#include "qlink_util.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#define QTNF_MAX_VSIE_LEN 255
+#define QTNF_MAX_INTF 8
+#define QTNF_MAX_EVENT_QUEUE_LEN 255
+#define QTNF_SCAN_TIMEOUT_SEC 15
+
+#define QTNF_DEF_BSS_PRIORITY 0
+#define QTNF_DEF_WDOG_TIMEOUT 5
+#define QTNF_TX_TIMEOUT_TRSHLD 100
+
+extern const struct net_device_ops qtnf_netdev_ops;
+
+struct qtnf_bus;
+struct qtnf_vif;
+
+struct qtnf_sta_node {
+ struct list_head list;
+ u8 mac_addr[ETH_ALEN];
+};
+
+struct qtnf_sta_list {
+ struct list_head head;
+ atomic_t size;
+};
+
+struct qtnf_vif {
+ struct wireless_dev wdev;
+ u8 bssid[ETH_ALEN];
+ u8 mac_addr[ETH_ALEN];
+ u8 vifid;
+ u8 bss_priority;
+ u8 bss_status;
+ u16 mgmt_frames_bitmask;
+ struct net_device *netdev;
+ struct qtnf_wmac *mac;
+
+ struct work_struct reset_work;
+ struct work_struct high_pri_tx_work;
+ struct sk_buff_head high_pri_tx_queue;
+ struct qtnf_sta_list sta_list;
+ unsigned long cons_tx_timeout_cnt;
+ int generation;
+};
+
+struct qtnf_mac_info {
+ u8 bands_cap;
+ u8 num_tx_chain;
+ u8 num_rx_chain;
+ u16 max_ap_assoc_sta;
+ u32 frag_thr;
+ u32 rts_thr;
+ u8 lretry_limit;
+ u8 sretry_limit;
+ u8 coverage_class;
+ u8 radar_detect_widths;
+ u8 max_scan_ssids;
+ u16 max_acl_mac_addrs;
+ struct ieee80211_ht_cap ht_cap_mod_mask;
+ struct ieee80211_vht_cap vht_cap_mod_mask;
+ struct ieee80211_iface_combination *if_comb;
+ size_t n_if_comb;
+ u8 *extended_capabilities;
+ u8 *extended_capabilities_mask;
+ u8 extended_capabilities_len;
+ struct wiphy_wowlan_support *wowlan;
+};
+
+struct qtnf_wmac {
+ u8 macid;
+ u8 wiphy_registered;
+ u8 macaddr[ETH_ALEN];
+ struct qtnf_bus *bus;
+ struct qtnf_mac_info macinfo;
+ struct qtnf_vif iflist[QTNF_MAX_INTF];
+ struct cfg80211_scan_request *scan_req;
+ struct mutex mac_lock; /* lock during wmac specific ops */
+ struct delayed_work scan_timeout;
+ struct ieee80211_regdomain *rd;
+ struct platform_device *pdev;
+};
+
+struct qtnf_hw_info {
+ u32 ql_proto_ver;
+ u8 num_mac;
+ u8 mac_bitmap;
+ u32 fw_ver;
+ u8 total_tx_chain;
+ u8 total_rx_chain;
+ char fw_version[ETHTOOL_FWVERS_LEN];
+ u32 hw_version;
+ u8 hw_capab[QLINK_HW_CAPAB_NUM / BITS_PER_BYTE + 1];
+};
+
+struct qtnf_vif *qtnf_mac_get_free_vif(struct qtnf_wmac *mac);
+struct qtnf_vif *qtnf_mac_get_base_vif(struct qtnf_wmac *mac);
+void qtnf_mac_iface_comb_free(struct qtnf_wmac *mac);
+void qtnf_mac_ext_caps_free(struct qtnf_wmac *mac);
+bool qtnf_slave_radar_get(void);
+bool qtnf_dfs_offload_get(void);
+struct wiphy *qtnf_wiphy_allocate(struct qtnf_bus *bus,
+ struct platform_device *pdev);
+int qtnf_core_net_attach(struct qtnf_wmac *mac, struct qtnf_vif *priv,
+ 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);
+
+struct qtnf_wmac *qtnf_core_get_mac(const struct qtnf_bus *bus, u8 macid);
+struct net_device *qtnf_classify_skb(struct qtnf_bus *bus, struct sk_buff *skb);
+void qtnf_wake_all_queues(struct net_device *ndev);
+
+void qtnf_virtual_intf_cleanup(struct net_device *ndev);
+
+void qtnf_netdev_updown(struct net_device *ndev, bool up);
+void qtnf_scan_done(struct qtnf_wmac *mac, bool aborted);
+struct dentry *qtnf_get_debugfs_dir(void);
+bool qtnf_netdev_is_qtn(const struct net_device *ndev);
+
+static inline struct qtnf_vif *qtnf_netdev_get_priv(struct net_device *dev)
+{
+ return *((void **)netdev_priv(dev));
+}
+
+static inline bool qtnf_hwcap_is_set(const struct qtnf_hw_info *info,
+ unsigned int bit)
+{
+ return qtnf_utils_is_bit_set(info->hw_capab, bit,
+ sizeof(info->hw_capab));
+}
+
+#endif /* _QTN_FMAC_CORE_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/debug.c b/drivers/net/wireless/quantenna/qtnfmac/debug.c
new file mode 100644
index 000000000..2d3574c1f
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/debug.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include "debug.h"
+
+void qtnf_debugfs_init(struct qtnf_bus *bus, const char *name)
+{
+ struct dentry *parent = qtnf_get_debugfs_dir();
+
+ bus->dbg_dir = debugfs_create_dir(name, parent);
+}
+
+void qtnf_debugfs_remove(struct qtnf_bus *bus)
+{
+ debugfs_remove_recursive(bus->dbg_dir);
+ bus->dbg_dir = NULL;
+}
+
+void qtnf_debugfs_add_entry(struct qtnf_bus *bus, const char *name,
+ int (*fn)(struct seq_file *seq, void *data))
+{
+ debugfs_create_devm_seqfile(bus->dev, name, bus->dbg_dir, fn);
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/debug.h b/drivers/net/wireless/quantenna/qtnfmac/debug.h
new file mode 100644
index 000000000..61b45536b
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/debug.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_DEBUG_H_
+#define _QTN_FMAC_DEBUG_H_
+
+#include <linux/debugfs.h>
+
+#include "core.h"
+#include "bus.h"
+
+#ifdef CONFIG_DEBUG_FS
+
+void qtnf_debugfs_init(struct qtnf_bus *bus, const char *name);
+void qtnf_debugfs_remove(struct qtnf_bus *bus);
+void qtnf_debugfs_add_entry(struct qtnf_bus *bus, const char *name,
+ int (*fn)(struct seq_file *seq, void *data));
+
+#else
+
+static inline void qtnf_debugfs_init(struct qtnf_bus *bus, const char *name)
+{
+}
+
+static inline void qtnf_debugfs_remove(struct qtnf_bus *bus)
+{
+}
+
+static inline void
+qtnf_debugfs_add_entry(struct qtnf_bus *bus, const char *name,
+ int (*fn)(struct seq_file *seq, void *data))
+{
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+#endif /* _QTN_FMAC_DEBUG_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/event.c b/drivers/net/wireless/quantenna/qtnfmac/event.c
new file mode 100644
index 000000000..fb5a56b6d
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/event.c
@@ -0,0 +1,797 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/nospec.h>
+
+#include "cfg80211.h"
+#include "core.h"
+#include "qlink.h"
+#include "bus.h"
+#include "trans.h"
+#include "util.h"
+#include "event.h"
+#include "qlink_util.h"
+
+static int
+qtnf_event_handle_sta_assoc(struct qtnf_wmac *mac, struct qtnf_vif *vif,
+ const struct qlink_event_sta_assoc *sta_assoc,
+ u16 len)
+{
+ const u8 *sta_addr;
+ u16 frame_control;
+ struct station_info *sinfo;
+ size_t payload_len;
+ u16 tlv_type;
+ u16 tlv_value_len;
+ const struct qlink_tlv_hdr *tlv;
+ int ret = 0;
+
+ if (unlikely(len < sizeof(*sta_assoc))) {
+ pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
+ mac->macid, vif->vifid, len, sizeof(*sta_assoc));
+ return -EINVAL;
+ }
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_AP) {
+ pr_err("VIF%u.%u: STA_ASSOC event when not in AP mode\n",
+ mac->macid, vif->vifid);
+ return -EPROTO;
+ }
+
+ sinfo = kzalloc_obj(*sinfo);
+ if (!sinfo)
+ return -ENOMEM;
+
+ sta_addr = sta_assoc->sta_addr;
+ frame_control = le16_to_cpu(sta_assoc->frame_control);
+
+ pr_debug("VIF%u.%u: MAC:%pM FC:%x\n", mac->macid, vif->vifid, sta_addr,
+ frame_control);
+
+ 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);
+
+ qlink_for_each_tlv(tlv, sta_assoc->ies, payload_len) {
+ tlv_type = le16_to_cpu(tlv->type);
+ tlv_value_len = le16_to_cpu(tlv->len);
+
+ if (tlv_type == QTN_TLV_ID_IE_SET) {
+ const struct qlink_tlv_ie_set *ie_set;
+ unsigned int ie_len;
+
+ if (tlv_value_len <
+ (sizeof(*ie_set) - sizeof(ie_set->hdr))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ie_set = (const struct qlink_tlv_ie_set *)tlv;
+ ie_len = tlv_value_len -
+ (sizeof(*ie_set) - sizeof(ie_set->hdr));
+
+ if (ie_set->type == QLINK_IE_SET_ASSOC_REQ && ie_len) {
+ sinfo->assoc_req_ies = ie_set->ie_data;
+ sinfo->assoc_req_ies_len = ie_len;
+ }
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, sta_assoc->ies, payload_len)) {
+ pr_err("Malformed TLV buffer\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ cfg80211_new_sta(vif->netdev->ieee80211_ptr, sta_assoc->sta_addr,
+ sinfo, GFP_KERNEL);
+
+out:
+ kfree(sinfo);
+ return ret;
+}
+
+static int
+qtnf_event_handle_sta_deauth(struct qtnf_wmac *mac, struct qtnf_vif *vif,
+ const struct qlink_event_sta_deauth *sta_deauth,
+ u16 len)
+{
+ const u8 *sta_addr;
+ u16 reason;
+
+ if (unlikely(len < sizeof(*sta_deauth))) {
+ pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
+ mac->macid, vif->vifid, len,
+ sizeof(struct qlink_event_sta_deauth));
+ return -EINVAL;
+ }
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_AP) {
+ pr_err("VIF%u.%u: STA_DEAUTH event when not in AP mode\n",
+ mac->macid, vif->vifid);
+ return -EPROTO;
+ }
+
+ sta_addr = sta_deauth->sta_addr;
+ reason = le16_to_cpu(sta_deauth->reason);
+
+ pr_debug("VIF%u.%u: MAC:%pM reason:%x\n", mac->macid, vif->vifid,
+ sta_addr, reason);
+
+ if (qtnf_sta_list_del(vif, sta_addr))
+ cfg80211_del_sta(&vif->wdev, sta_deauth->sta_addr,
+ GFP_KERNEL);
+
+ return 0;
+}
+
+static int
+qtnf_event_handle_bss_join(struct qtnf_vif *vif,
+ const struct qlink_event_bss_join *join_info,
+ u16 len)
+{
+ struct wiphy *wiphy = priv_to_wiphy(vif->mac);
+ enum ieee80211_statuscode status = le16_to_cpu(join_info->status);
+ struct cfg80211_chan_def chandef;
+ struct cfg80211_bss *bss = NULL;
+ u8 *ie = NULL;
+ size_t payload_len;
+ u16 tlv_type;
+ u16 tlv_value_len;
+ const struct qlink_tlv_hdr *tlv;
+ const u8 *rsp_ies = NULL;
+ size_t rsp_ies_len = 0;
+
+ if (unlikely(len < sizeof(*join_info))) {
+ pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
+ vif->mac->macid, vif->vifid, len,
+ sizeof(struct qlink_event_bss_join));
+ return -EINVAL;
+ }
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_STATION) {
+ pr_err("VIF%u.%u: BSS_JOIN event when not in STA mode\n",
+ vif->mac->macid, vif->vifid);
+ return -EPROTO;
+ }
+
+ pr_debug("VIF%u.%u: BSSID:%pM chan:%u status:%u\n",
+ vif->mac->macid, vif->vifid, join_info->bssid,
+ le16_to_cpu(join_info->chan.chan.center_freq), status);
+
+ if (status != WLAN_STATUS_SUCCESS)
+ goto done;
+
+ qlink_chandef_q2cfg(wiphy, &join_info->chan, &chandef);
+ if (!cfg80211_chandef_valid(&chandef)) {
+ pr_warn("MAC%u.%u: bad channel freq=%u cf1=%u cf2=%u bw=%u\n",
+ vif->mac->macid, vif->vifid,
+ chandef.chan ? chandef.chan->center_freq : 0,
+ chandef.center_freq1,
+ chandef.center_freq2,
+ chandef.width);
+ status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+ goto done;
+ }
+
+ bss = cfg80211_get_bss(wiphy, chandef.chan, join_info->bssid,
+ NULL, 0, IEEE80211_BSS_TYPE_ESS,
+ IEEE80211_PRIVACY_ANY);
+ if (!bss) {
+ pr_warn("VIF%u.%u: add missing BSS:%pM chan:%u\n",
+ vif->mac->macid, vif->vifid,
+ join_info->bssid, chandef.chan->hw_value);
+
+ if (!vif->wdev.u.client.ssid_len) {
+ pr_warn("VIF%u.%u: SSID unknown for BSS:%pM\n",
+ vif->mac->macid, vif->vifid,
+ join_info->bssid);
+ status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+ goto done;
+ }
+
+ ie = kzalloc(2 + vif->wdev.u.client.ssid_len, GFP_KERNEL);
+ if (!ie) {
+ pr_warn("VIF%u.%u: IE alloc failed for BSS:%pM\n",
+ vif->mac->macid, vif->vifid,
+ join_info->bssid);
+ status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+ goto done;
+ }
+
+ ie[0] = WLAN_EID_SSID;
+ ie[1] = vif->wdev.u.client.ssid_len;
+ memcpy(ie + 2, vif->wdev.u.client.ssid,
+ vif->wdev.u.client.ssid_len);
+
+ bss = cfg80211_inform_bss(wiphy, chandef.chan,
+ CFG80211_BSS_FTYPE_UNKNOWN,
+ join_info->bssid, 0,
+ WLAN_CAPABILITY_ESS, 100,
+ ie, 2 + vif->wdev.u.client.ssid_len,
+ 0, GFP_KERNEL);
+ if (!bss) {
+ pr_warn("VIF%u.%u: can't connect to unknown BSS: %pM\n",
+ vif->mac->macid, vif->vifid,
+ join_info->bssid);
+ status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+ goto done;
+ }
+ }
+
+ payload_len = len - sizeof(*join_info);
+
+ qlink_for_each_tlv(tlv, join_info->ies, payload_len) {
+ tlv_type = le16_to_cpu(tlv->type);
+ tlv_value_len = le16_to_cpu(tlv->len);
+
+ if (tlv_type == QTN_TLV_ID_IE_SET) {
+ const struct qlink_tlv_ie_set *ie_set;
+ unsigned int ie_len;
+
+ if (tlv_value_len <
+ (sizeof(*ie_set) - sizeof(ie_set->hdr))) {
+ pr_warn("invalid IE_SET TLV\n");
+ status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+ goto done;
+ }
+
+ ie_set = (const struct qlink_tlv_ie_set *)tlv;
+ ie_len = tlv_value_len -
+ (sizeof(*ie_set) - sizeof(ie_set->hdr));
+
+ switch (ie_set->type) {
+ case QLINK_IE_SET_ASSOC_RESP:
+ if (ie_len) {
+ rsp_ies = ie_set->ie_data;
+ rsp_ies_len = ie_len;
+ }
+ break;
+ default:
+ pr_warn("unexpected IE type: %u\n",
+ ie_set->type);
+ break;
+ }
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, join_info->ies, payload_len))
+ pr_warn("Malformed TLV buffer\n");
+done:
+ cfg80211_connect_result(vif->netdev, join_info->bssid, NULL, 0, rsp_ies,
+ rsp_ies_len, status, GFP_KERNEL);
+ if (bss) {
+ if (!ether_addr_equal(vif->bssid, join_info->bssid))
+ ether_addr_copy(vif->bssid, join_info->bssid);
+ cfg80211_put_bss(wiphy, bss);
+ }
+
+ if (status == WLAN_STATUS_SUCCESS)
+ netif_carrier_on(vif->netdev);
+
+ kfree(ie);
+ return 0;
+}
+
+static int
+qtnf_event_handle_bss_leave(struct qtnf_vif *vif,
+ const struct qlink_event_bss_leave *leave_info,
+ u16 len)
+{
+ if (unlikely(len < sizeof(*leave_info))) {
+ pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
+ vif->mac->macid, vif->vifid, len,
+ sizeof(struct qlink_event_bss_leave));
+ return -EINVAL;
+ }
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_STATION) {
+ pr_err("VIF%u.%u: BSS_LEAVE event when not in STA mode\n",
+ vif->mac->macid, vif->vifid);
+ return -EPROTO;
+ }
+
+ pr_debug("VIF%u.%u: disconnected\n", vif->mac->macid, vif->vifid);
+
+ cfg80211_disconnected(vif->netdev, le16_to_cpu(leave_info->reason),
+ NULL, 0, 0, GFP_KERNEL);
+ netif_carrier_off(vif->netdev);
+
+ return 0;
+}
+
+static int
+qtnf_event_handle_mgmt_received(struct qtnf_vif *vif,
+ const struct qlink_event_rxmgmt *rxmgmt,
+ u16 len)
+{
+ const size_t min_len = sizeof(*rxmgmt) +
+ sizeof(struct ieee80211_hdr_3addr);
+ const struct ieee80211_hdr_3addr *frame = (void *)rxmgmt->frame_data;
+ const u16 frame_len = len - sizeof(*rxmgmt);
+ enum nl80211_rxmgmt_flags flags = 0;
+
+ if (unlikely(len < min_len)) {
+ pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
+ vif->mac->macid, vif->vifid, len, min_len);
+ return -EINVAL;
+ }
+
+ if (le32_to_cpu(rxmgmt->flags) & QLINK_RXMGMT_FLAG_ANSWERED)
+ flags |= NL80211_RXMGMT_FLAG_ANSWERED;
+
+ 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), rxmgmt->sig_dbm,
+ rxmgmt->frame_data, frame_len, flags);
+
+ return 0;
+}
+
+static int
+qtnf_event_handle_scan_results(struct qtnf_vif *vif,
+ const struct qlink_event_scan_result *sr,
+ u16 len)
+{
+ struct cfg80211_bss *bss;
+ struct ieee80211_channel *channel;
+ struct wiphy *wiphy = priv_to_wiphy(vif->mac);
+ enum cfg80211_bss_frame_type frame_type = CFG80211_BSS_FTYPE_UNKNOWN;
+ size_t payload_len;
+ u16 tlv_type;
+ u16 tlv_value_len;
+ const struct qlink_tlv_hdr *tlv;
+ const u8 *ies = NULL;
+ size_t ies_len = 0;
+
+ if (len < sizeof(*sr)) {
+ pr_err("VIF%u.%u: payload is too short\n", vif->mac->macid,
+ vif->vifid);
+ return -EINVAL;
+ }
+
+ channel = ieee80211_get_channel(wiphy, le16_to_cpu(sr->freq));
+ if (!channel) {
+ pr_err("VIF%u.%u: channel at %u MHz not found\n",
+ vif->mac->macid, vif->vifid, le16_to_cpu(sr->freq));
+ return -EINVAL;
+ }
+
+ payload_len = len - sizeof(*sr);
+
+ qlink_for_each_tlv(tlv, sr->payload, payload_len) {
+ tlv_type = le16_to_cpu(tlv->type);
+ tlv_value_len = le16_to_cpu(tlv->len);
+
+ if (tlv_type == QTN_TLV_ID_IE_SET) {
+ const struct qlink_tlv_ie_set *ie_set;
+ unsigned int ie_len;
+
+ if (tlv_value_len <
+ (sizeof(*ie_set) - sizeof(ie_set->hdr)))
+ return -EINVAL;
+
+ ie_set = (const struct qlink_tlv_ie_set *)tlv;
+ ie_len = tlv_value_len -
+ (sizeof(*ie_set) - sizeof(ie_set->hdr));
+
+ switch (ie_set->type) {
+ case QLINK_IE_SET_BEACON_IES:
+ frame_type = CFG80211_BSS_FTYPE_BEACON;
+ break;
+ case QLINK_IE_SET_PROBE_RESP_IES:
+ frame_type = CFG80211_BSS_FTYPE_PRESP;
+ break;
+ default:
+ frame_type = CFG80211_BSS_FTYPE_UNKNOWN;
+ }
+
+ if (ie_len) {
+ ies = ie_set->ie_data;
+ ies_len = ie_len;
+ }
+ }
+ }
+
+ if (!qlink_tlv_parsing_ok(tlv, sr->payload, payload_len))
+ return -EINVAL;
+
+ bss = cfg80211_inform_bss(wiphy, channel, frame_type,
+ sr->bssid, get_unaligned_le64(&sr->tsf),
+ le16_to_cpu(sr->capab),
+ le16_to_cpu(sr->bintval), ies, ies_len,
+ DBM_TO_MBM(sr->sig_dbm), GFP_KERNEL);
+ if (!bss)
+ return -ENOMEM;
+
+ cfg80211_put_bss(wiphy, bss);
+
+ return 0;
+}
+
+static int
+qtnf_event_handle_scan_complete(struct qtnf_wmac *mac,
+ const struct qlink_event_scan_complete *status,
+ u16 len)
+{
+ if (len < sizeof(*status)) {
+ pr_err("MAC%u: payload is too short\n", mac->macid);
+ return -EINVAL;
+ }
+
+ qtnf_scan_done(mac, le32_to_cpu(status->flags) & QLINK_SCAN_ABORTED);
+
+ return 0;
+}
+
+static int
+qtnf_event_handle_freq_change(struct qtnf_wmac *mac,
+ const struct qlink_event_freq_change *data,
+ u16 len)
+{
+ struct wiphy *wiphy = priv_to_wiphy(mac);
+ struct cfg80211_chan_def chandef;
+ struct qtnf_vif *vif;
+ int i;
+
+ if (len < sizeof(*data)) {
+ pr_err("MAC%u: payload is too short\n", mac->macid);
+ return -EINVAL;
+ }
+
+ if (!wiphy->registered)
+ return 0;
+
+ qlink_chandef_q2cfg(wiphy, &data->chan, &chandef);
+
+ if (!cfg80211_chandef_valid(&chandef)) {
+ 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;
+ }
+
+ pr_debug("MAC%d: new channel ieee=%u freq1=%u freq2=%u bw=%u\n",
+ mac->macid, chandef.chan->hw_value, chandef.center_freq1,
+ chandef.center_freq2, chandef.width);
+
+ for (i = 0; i < QTNF_MAX_INTF; i++) {
+ vif = &mac->iflist[i];
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED)
+ continue;
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION &&
+ !vif->wdev.connected)
+ continue;
+
+ if (!vif->netdev)
+ continue;
+
+ wiphy_lock(priv_to_wiphy(vif->mac));
+ cfg80211_ch_switch_notify(vif->netdev, &chandef, 0);
+ wiphy_unlock(priv_to_wiphy(vif->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.links[0].cac_started)
+ break;
+
+ cfg80211_cac_event(vif->netdev, &chandef,
+ NL80211_RADAR_CAC_FINISHED, GFP_KERNEL, 0);
+ break;
+ case QLINK_RADAR_CAC_ABORTED:
+ if (!vif->wdev.links[0].cac_started)
+ break;
+
+ cfg80211_cac_event(vif->netdev, &chandef,
+ NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0);
+ break;
+ case QLINK_RADAR_CAC_STARTED:
+ if (vif->wdev.links[0].cac_started)
+ break;
+
+ if (!wiphy_ext_feature_isset(wiphy,
+ NL80211_EXT_FEATURE_DFS_OFFLOAD))
+ break;
+
+ cfg80211_cac_event(vif->netdev, &chandef,
+ NL80211_RADAR_CAC_STARTED, GFP_KERNEL, 0);
+ break;
+ default:
+ pr_warn("%s: unhandled radar event %u\n",
+ vif->netdev->name, ev->event);
+ break;
+ }
+
+ return 0;
+}
+
+static int
+qtnf_event_handle_external_auth(struct qtnf_vif *vif,
+ const struct qlink_event_external_auth *ev,
+ u16 len)
+{
+ struct cfg80211_external_auth_params auth = {0};
+ struct wiphy *wiphy = priv_to_wiphy(vif->mac);
+ int ret;
+
+ 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;
+
+ if (ev->ssid_len) {
+ int len = clamp_val(ev->ssid_len, 0, IEEE80211_MAX_SSID_LEN);
+
+ memcpy(auth.ssid.ssid, ev->ssid, len);
+ auth.ssid.ssid_len = len;
+ }
+
+ auth.key_mgmt_suite = le32_to_cpu(ev->akm_suite);
+ ether_addr_copy(auth.bssid, ev->bssid);
+ auth.action = ev->action;
+
+ pr_debug("%s: external SAE processing: bss=%pM action=%u akm=%u\n",
+ vif->netdev->name, auth.bssid, auth.action,
+ auth.key_mgmt_suite);
+
+ ret = cfg80211_external_auth_request(vif->netdev, &auth, GFP_KERNEL);
+ if (ret)
+ pr_warn("failed to offload external auth request\n");
+
+ return ret;
+}
+
+static int
+qtnf_event_handle_mic_failure(struct qtnf_vif *vif,
+ const struct qlink_event_mic_failure *mic_ev,
+ u16 len)
+{
+ struct wiphy *wiphy = priv_to_wiphy(vif->mac);
+ u8 pairwise;
+
+ if (len < sizeof(*mic_ev)) {
+ pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
+ vif->mac->macid, vif->vifid, len,
+ sizeof(struct qlink_event_mic_failure));
+ return -EINVAL;
+ }
+
+ if (!wiphy->registered || !vif->netdev)
+ return 0;
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_STATION) {
+ pr_err("VIF%u.%u: MIC_FAILURE event when not in STA mode\n",
+ vif->mac->macid, vif->vifid);
+ return -EPROTO;
+ }
+
+ pairwise = mic_ev->pairwise ?
+ NL80211_KEYTYPE_PAIRWISE : NL80211_KEYTYPE_GROUP;
+
+ pr_info("%s: MIC error: src=%pM key_index=%u pairwise=%u\n",
+ vif->netdev->name, mic_ev->src, mic_ev->key_index, pairwise);
+
+ cfg80211_michael_mic_failure(vif->netdev, mic_ev->src, pairwise,
+ mic_ev->key_index, NULL, GFP_KERNEL);
+
+ return 0;
+}
+
+static int
+qtnf_event_handle_update_owe(struct qtnf_vif *vif,
+ const struct qlink_event_update_owe *owe_ev,
+ u16 len)
+{
+ struct wiphy *wiphy = priv_to_wiphy(vif->mac);
+ struct cfg80211_update_owe_info owe_info = {};
+ const u16 ie_len = len - sizeof(*owe_ev);
+ u8 *ie;
+
+ if (len < sizeof(*owe_ev)) {
+ pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
+ vif->mac->macid, vif->vifid, len,
+ sizeof(struct qlink_event_update_owe));
+ return -EINVAL;
+ }
+
+ if (!wiphy->registered || !vif->netdev)
+ return 0;
+
+ if (vif->wdev.iftype != NL80211_IFTYPE_AP) {
+ pr_err("VIF%u.%u: UPDATE_OWE event when not in AP mode\n",
+ vif->mac->macid, vif->vifid);
+ return -EPROTO;
+ }
+
+ ie = kzalloc(ie_len, GFP_KERNEL);
+ if (!ie)
+ return -ENOMEM;
+
+ memcpy(owe_info.peer, owe_ev->peer, ETH_ALEN);
+ memcpy(ie, owe_ev->ies, ie_len);
+ owe_info.ie_len = ie_len;
+ owe_info.ie = ie;
+ owe_info.assoc_link_id = -1;
+
+ pr_info("%s: external OWE processing: peer=%pM\n",
+ vif->netdev->name, owe_ev->peer);
+
+ cfg80211_update_owe_info_event(vif->netdev, &owe_info, GFP_KERNEL);
+ kfree(ie);
+
+ return 0;
+}
+
+static int qtnf_event_parse(struct qtnf_wmac *mac,
+ const struct sk_buff *event_skb)
+{
+ const struct qlink_event *event;
+ struct qtnf_vif *vif = NULL;
+ int ret = -1;
+ u16 event_id;
+ u16 event_len;
+ u8 vifid;
+
+ event = (const struct qlink_event *)event_skb->data;
+ event_id = le16_to_cpu(event->event_id);
+ event_len = le16_to_cpu(event->mhdr.len);
+
+ if (event->vifid >= QTNF_MAX_INTF) {
+ pr_err("invalid vif(%u)\n", event->vifid);
+ return -EINVAL;
+ }
+
+ vifid = array_index_nospec(event->vifid, QTNF_MAX_INTF);
+ vif = &mac->iflist[vifid];
+
+ switch (event_id) {
+ case QLINK_EVENT_STA_ASSOCIATED:
+ ret = qtnf_event_handle_sta_assoc(mac, vif, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_STA_DEAUTH:
+ ret = qtnf_event_handle_sta_deauth(mac, vif,
+ (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_MGMT_RECEIVED:
+ ret = qtnf_event_handle_mgmt_received(vif, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_SCAN_RESULTS:
+ ret = qtnf_event_handle_scan_results(vif, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_SCAN_COMPLETE:
+ ret = qtnf_event_handle_scan_complete(mac, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_BSS_JOIN:
+ ret = qtnf_event_handle_bss_join(vif, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_BSS_LEAVE:
+ ret = qtnf_event_handle_bss_leave(vif, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_FREQ_CHANGE:
+ 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;
+ case QLINK_EVENT_EXTERNAL_AUTH:
+ ret = qtnf_event_handle_external_auth(vif, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_MIC_FAILURE:
+ ret = qtnf_event_handle_mic_failure(vif, (const void *)event,
+ event_len);
+ break;
+ case QLINK_EVENT_UPDATE_OWE:
+ ret = qtnf_event_handle_update_owe(vif, (const void *)event,
+ event_len);
+ break;
+ default:
+ pr_warn("unknown event type: %x\n", event_id);
+ break;
+ }
+
+ return ret;
+}
+
+static int qtnf_event_process_skb(struct qtnf_bus *bus,
+ const struct sk_buff *skb)
+{
+ const struct qlink_event *event;
+ struct qtnf_wmac *mac;
+ int res;
+
+ if (unlikely(!skb || skb->len < sizeof(*event))) {
+ pr_err("invalid event buffer\n");
+ return -EINVAL;
+ }
+
+ event = (struct qlink_event *)skb->data;
+
+ mac = qtnf_core_get_mac(bus, event->macid);
+
+ pr_debug("new event id:%x len:%u mac:%u vif:%u\n",
+ le16_to_cpu(event->event_id), le16_to_cpu(event->mhdr.len),
+ event->macid, event->vifid);
+
+ if (unlikely(!mac))
+ return -ENXIO;
+
+ rtnl_lock();
+ res = qtnf_event_parse(mac, skb);
+ rtnl_unlock();
+
+ return res;
+}
+
+void qtnf_event_work_handler(struct work_struct *work)
+{
+ struct qtnf_bus *bus = container_of(work, struct qtnf_bus, event_work);
+ struct sk_buff_head *event_queue = &bus->trans.event_queue;
+ struct sk_buff *current_event_skb = skb_dequeue(event_queue);
+
+ while (current_event_skb) {
+ qtnf_event_process_skb(bus, current_event_skb);
+ dev_kfree_skb_any(current_event_skb);
+ current_event_skb = skb_dequeue(event_queue);
+ }
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/event.h b/drivers/net/wireless/quantenna/qtnfmac/event.h
new file mode 100644
index 000000000..533ad99d0
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/event.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_EVENT_H_
+#define _QTN_FMAC_EVENT_H_
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "qlink.h"
+
+void qtnf_event_work_handler(struct work_struct *work);
+
+#endif /* _QTN_FMAC_EVENT_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
new file mode 100644
index 000000000..3adcfac28
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2018 Quantenna Communications, Inc. All rights reserved. */
+
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/seq_file.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+
+#include "pcie_priv.h"
+#include "bus.h"
+#include "shm_ipc.h"
+#include "core.h"
+#include "debug.h"
+#include "util.h"
+#include "qtn_hw_ids.h"
+
+#define QTN_SYSCTL_BAR 0
+#define QTN_SHMEM_BAR 2
+#define QTN_DMA_BAR 3
+
+#define QTN_PCIE_MAX_FW_BUFSZ (1 * 1024 * 1024)
+
+static bool use_msi = true;
+module_param(use_msi, bool, 0644);
+MODULE_PARM_DESC(use_msi, "set 0 to use legacy interrupt");
+
+static unsigned int tx_bd_size_param;
+module_param(tx_bd_size_param, uint, 0644);
+MODULE_PARM_DESC(tx_bd_size_param, "Tx descriptors queue size");
+
+static unsigned int rx_bd_size_param;
+module_param(rx_bd_size_param, uint, 0644);
+MODULE_PARM_DESC(rx_bd_size_param, "Rx descriptors queue size");
+
+static u8 flashboot = 1;
+module_param(flashboot, byte, 0644);
+MODULE_PARM_DESC(flashboot, "set to 0 to use FW binary file on FS");
+
+static unsigned int fw_blksize_param = QTN_PCIE_MAX_FW_BUFSZ;
+module_param(fw_blksize_param, uint, 0644);
+MODULE_PARM_DESC(fw_blksize_param, "firmware loading block size in bytes");
+
+#define DRV_NAME "qtnfmac_pcie"
+
+int qtnf_pcie_control_tx(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+ struct qtnf_pcie_bus_priv *priv = get_bus_priv(bus);
+ int ret;
+
+ ret = qtnf_shm_ipc_send(&priv->shm_ipc_ep_in, skb->data, skb->len);
+
+ if (ret == -ETIMEDOUT) {
+ pr_err("EP firmware is dead\n");
+ bus->fw_state = QTNF_FW_STATE_DEAD;
+ }
+
+ return ret;
+}
+
+int qtnf_pcie_alloc_skb_array(struct qtnf_pcie_bus_priv *priv)
+{
+ struct sk_buff **vaddr;
+ int len;
+
+ len = priv->tx_bd_num * sizeof(*priv->tx_skb) +
+ priv->rx_bd_num * sizeof(*priv->rx_skb);
+ vaddr = devm_kzalloc(&priv->pdev->dev, len, GFP_KERNEL);
+
+ if (!vaddr)
+ return -ENOMEM;
+
+ priv->tx_skb = vaddr;
+
+ vaddr += priv->tx_bd_num;
+ priv->rx_skb = vaddr;
+
+ return 0;
+}
+
+static void qtnf_pcie_bringup_fw_async(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_bus_priv *priv = get_bus_priv(bus);
+ struct pci_dev *pdev = priv->pdev;
+
+ get_device(&pdev->dev);
+ schedule_work(&bus->fw_work);
+}
+
+static int qtnf_dbg_mps_show(struct seq_file *s, void *data)
+{
+ struct qtnf_bus *bus = dev_get_drvdata(s->private);
+ struct qtnf_pcie_bus_priv *priv = get_bus_priv(bus);
+
+ seq_printf(s, "%d\n", pcie_get_mps(priv->pdev));
+
+ return 0;
+}
+
+static int qtnf_dbg_msi_show(struct seq_file *s, void *data)
+{
+ struct qtnf_bus *bus = dev_get_drvdata(s->private);
+ struct qtnf_pcie_bus_priv *priv = get_bus_priv(bus);
+
+ seq_printf(s, "%u\n", priv->msi_enabled);
+
+ return 0;
+}
+
+static int qtnf_dbg_shm_stats(struct seq_file *s, void *data)
+{
+ struct qtnf_bus *bus = dev_get_drvdata(s->private);
+ struct qtnf_pcie_bus_priv *priv = get_bus_priv(bus);
+
+ seq_printf(s, "shm_ipc_ep_in.tx_packet_count(%zu)\n",
+ priv->shm_ipc_ep_in.tx_packet_count);
+ seq_printf(s, "shm_ipc_ep_in.rx_packet_count(%zu)\n",
+ priv->shm_ipc_ep_in.rx_packet_count);
+ seq_printf(s, "shm_ipc_ep_out.tx_packet_count(%zu)\n",
+ priv->shm_ipc_ep_out.tx_timeout_count);
+ seq_printf(s, "shm_ipc_ep_out.rx_packet_count(%zu)\n",
+ priv->shm_ipc_ep_out.rx_packet_count);
+
+ return 0;
+}
+
+int qtnf_pcie_fw_boot_done(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_bus_priv *priv = get_bus_priv(bus);
+ char card_id[64];
+ int ret;
+
+ bus->fw_state = QTNF_FW_STATE_BOOT_DONE;
+ ret = qtnf_core_attach(bus);
+ if (ret) {
+ pr_err("failed to attach core\n");
+ } else {
+ snprintf(card_id, sizeof(card_id), "%s:%s",
+ DRV_NAME, pci_name(priv->pdev));
+ qtnf_debugfs_init(bus, card_id);
+ qtnf_debugfs_add_entry(bus, "mps", qtnf_dbg_mps_show);
+ qtnf_debugfs_add_entry(bus, "msi_enabled", qtnf_dbg_msi_show);
+ qtnf_debugfs_add_entry(bus, "shm_stats", qtnf_dbg_shm_stats);
+ }
+
+ return ret;
+}
+
+static void qtnf_tune_pcie_mps(struct pci_dev *pdev)
+{
+ struct pci_dev *parent;
+ int mps_p, mps_o, mps_m, mps;
+ int ret;
+
+ /* current mps */
+ mps_o = pcie_get_mps(pdev);
+
+ /* maximum supported mps */
+ mps_m = 128 << pdev->pcie_mpss;
+
+ /* suggested new mps value */
+ mps = mps_m;
+
+ if (pdev->bus && pdev->bus->self) {
+ /* parent (bus) mps */
+ parent = pdev->bus->self;
+
+ if (pci_is_pcie(parent)) {
+ mps_p = pcie_get_mps(parent);
+ mps = min(mps_m, mps_p);
+ }
+ }
+
+ ret = pcie_set_mps(pdev, mps);
+ if (ret) {
+ pr_err("failed to set mps to %d, keep using current %d\n",
+ mps, mps_o);
+ return;
+ }
+
+ pr_debug("set mps to %d (was %d, max %d)\n", mps, mps_o, mps_m);
+}
+
+static void qtnf_pcie_init_irq(struct qtnf_pcie_bus_priv *priv, bool use_msi)
+{
+ struct pci_dev *pdev = priv->pdev;
+
+ /* fall back to legacy INTx interrupts by default */
+ priv->msi_enabled = 0;
+
+ /* check if MSI capability is available */
+ if (use_msi) {
+ if (!pci_enable_msi(pdev)) {
+ pr_debug("enabled MSI interrupt\n");
+ priv->msi_enabled = 1;
+ } else {
+ pr_warn("failed to enable MSI interrupts");
+ }
+ }
+
+ if (!priv->msi_enabled) {
+ pr_warn("legacy PCIE interrupts enabled\n");
+ pcim_intx(pdev, 1);
+ }
+}
+
+static void __iomem *qtnf_map_bar(struct pci_dev *pdev, u8 index)
+{
+ void __iomem *vaddr;
+ dma_addr_t busaddr;
+ size_t len;
+ int ret;
+
+ ret = pcim_iomap_regions(pdev, 1 << index, "qtnfmac_pcie");
+ if (ret)
+ return IOMEM_ERR_PTR(ret);
+
+ busaddr = pci_resource_start(pdev, index);
+ len = pci_resource_len(pdev, index);
+ vaddr = pcim_iomap_table(pdev)[index];
+ if (!vaddr)
+ return IOMEM_ERR_PTR(-ENOMEM);
+
+ pr_debug("BAR%u vaddr=0x%p busaddr=%pad len=%u\n",
+ index, vaddr, &busaddr, (int)len);
+
+ return vaddr;
+}
+
+static void qtnf_pcie_control_rx_callback(void *arg, const u8 __iomem *buf,
+ size_t len)
+{
+ struct qtnf_pcie_bus_priv *priv = arg;
+ struct qtnf_bus *bus = pci_get_drvdata(priv->pdev);
+ struct sk_buff *skb;
+
+ if (unlikely(len == 0)) {
+ pr_warn("zero length packet received\n");
+ return;
+ }
+
+ skb = __dev_alloc_skb(len, GFP_KERNEL);
+
+ if (unlikely(!skb)) {
+ pr_err("failed to allocate skb\n");
+ return;
+ }
+
+ memcpy_fromio(skb_put(skb, len), buf, len);
+
+ qtnf_trans_handle_rx_ctl_packet(bus, skb);
+}
+
+void qtnf_pcie_init_shm_ipc(struct qtnf_pcie_bus_priv *priv,
+ struct qtnf_shm_ipc_region __iomem *ipc_tx_reg,
+ struct qtnf_shm_ipc_region __iomem *ipc_rx_reg,
+ const struct qtnf_shm_ipc_int *ipc_int)
+{
+ const struct qtnf_shm_ipc_rx_callback rx_callback = {
+ qtnf_pcie_control_rx_callback, priv };
+
+ qtnf_shm_ipc_init(&priv->shm_ipc_ep_in, QTNF_SHM_IPC_OUTBOUND,
+ ipc_tx_reg, priv->workqueue,
+ ipc_int, &rx_callback);
+ qtnf_shm_ipc_init(&priv->shm_ipc_ep_out, QTNF_SHM_IPC_INBOUND,
+ ipc_rx_reg, priv->workqueue,
+ ipc_int, &rx_callback);
+}
+
+static int qtnf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct qtnf_pcie_bus_priv *pcie_priv;
+ struct qtnf_bus *bus;
+ void __iomem *sysctl_bar;
+ void __iomem *epmem_bar;
+ void __iomem *dmareg_bar;
+ unsigned int chipid;
+ int ret;
+
+ if (!pci_is_pcie(pdev)) {
+ pr_err("device %s is not PCI Express\n", pci_name(pdev));
+ return -EIO;
+ }
+
+ qtnf_tune_pcie_mps(pdev);
+
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ pr_err("failed to init PCI device %x\n", pdev->device);
+ return ret;
+ }
+
+ pci_set_master(pdev);
+
+ sysctl_bar = qtnf_map_bar(pdev, QTN_SYSCTL_BAR);
+ if (IS_ERR(sysctl_bar)) {
+ pr_err("failed to map BAR%u\n", QTN_SYSCTL_BAR);
+ return PTR_ERR(sysctl_bar);
+ }
+
+ dmareg_bar = qtnf_map_bar(pdev, QTN_DMA_BAR);
+ if (IS_ERR(dmareg_bar)) {
+ pr_err("failed to map BAR%u\n", QTN_DMA_BAR);
+ return PTR_ERR(dmareg_bar);
+ }
+
+ epmem_bar = qtnf_map_bar(pdev, QTN_SHMEM_BAR);
+ if (IS_ERR(epmem_bar)) {
+ pr_err("failed to map BAR%u\n", QTN_SHMEM_BAR);
+ return PTR_ERR(epmem_bar);
+ }
+
+ chipid = qtnf_chip_id_get(sysctl_bar);
+
+ pr_info("identified device: %s\n", qtnf_chipid_to_string(chipid));
+
+ switch (chipid) {
+ case QTN_CHIP_ID_PEARL:
+ case QTN_CHIP_ID_PEARL_B:
+ case QTN_CHIP_ID_PEARL_C:
+ bus = qtnf_pcie_pearl_alloc(pdev);
+ break;
+ case QTN_CHIP_ID_TOPAZ:
+ bus = qtnf_pcie_topaz_alloc(pdev);
+ break;
+ default:
+ pr_err("unsupported chip ID 0x%x\n", chipid);
+ return -ENOTSUPP;
+ }
+
+ if (!bus)
+ return -ENOMEM;
+
+ pcie_priv = get_bus_priv(bus);
+ pci_set_drvdata(pdev, bus);
+ bus->dev = &pdev->dev;
+ bus->fw_state = QTNF_FW_STATE_DETACHED;
+ pcie_priv->pdev = pdev;
+ pcie_priv->tx_stopped = 0;
+ pcie_priv->flashboot = flashboot;
+
+ if (fw_blksize_param > QTN_PCIE_MAX_FW_BUFSZ)
+ pcie_priv->fw_blksize = QTN_PCIE_MAX_FW_BUFSZ;
+ else
+ pcie_priv->fw_blksize = fw_blksize_param;
+
+ mutex_init(&bus->bus_lock);
+ spin_lock_init(&pcie_priv->tx_lock);
+ spin_lock_init(&pcie_priv->tx_reclaim_lock);
+
+ pcie_priv->tx_full_count = 0;
+ pcie_priv->tx_done_count = 0;
+ pcie_priv->pcie_irq_count = 0;
+ pcie_priv->tx_reclaim_done = 0;
+ pcie_priv->tx_reclaim_req = 0;
+
+ pcie_priv->workqueue = create_singlethread_workqueue("QTNF_PCIE");
+ if (!pcie_priv->workqueue) {
+ pr_err("failed to alloc bus workqueue\n");
+ return -ENODEV;
+ }
+
+ ret = dma_set_mask_and_coherent(&pdev->dev,
+ pcie_priv->dma_mask_get_cb());
+ if (ret) {
+ pr_err("PCIE DMA coherent mask init failed 0x%llx\n",
+ pcie_priv->dma_mask_get_cb());
+ goto error;
+ }
+
+ bus->mux_dev = alloc_netdev_dummy(0);
+ if (!bus->mux_dev) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ qtnf_pcie_init_irq(pcie_priv, use_msi);
+ pcie_priv->sysctl_bar = sysctl_bar;
+ pcie_priv->dmareg_bar = dmareg_bar;
+ pcie_priv->epmem_bar = epmem_bar;
+ pci_save_state(pdev);
+
+ ret = pcie_priv->probe_cb(bus, tx_bd_size_param, rx_bd_size_param);
+ if (ret)
+ goto error_free;
+
+ qtnf_pcie_bringup_fw_async(bus);
+ return 0;
+
+error_free:
+ free_netdev(bus->mux_dev);
+error:
+ destroy_workqueue(pcie_priv->workqueue);
+ pci_set_drvdata(pdev, NULL);
+ return ret;
+}
+
+static void qtnf_pcie_free_shm_ipc(struct qtnf_pcie_bus_priv *priv)
+{
+ qtnf_shm_ipc_free(&priv->shm_ipc_ep_in);
+ qtnf_shm_ipc_free(&priv->shm_ipc_ep_out);
+}
+
+static void qtnf_pcie_remove(struct pci_dev *dev)
+{
+ struct qtnf_pcie_bus_priv *priv;
+ struct qtnf_bus *bus;
+
+ bus = pci_get_drvdata(dev);
+ if (!bus)
+ return;
+
+ priv = get_bus_priv(bus);
+
+ cancel_work_sync(&bus->fw_work);
+
+ if (qtnf_fw_is_attached(bus))
+ qtnf_core_detach(bus);
+
+ netif_napi_del(&bus->mux_napi);
+ destroy_workqueue(priv->workqueue);
+ tasklet_kill(&priv->reclaim_tq);
+ free_netdev(bus->mux_dev);
+
+ qtnf_pcie_free_shm_ipc(priv);
+ qtnf_debugfs_remove(bus);
+ priv->remove_cb(bus);
+ pci_set_drvdata(priv->pdev, NULL);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int qtnf_pcie_suspend(struct device *dev)
+{
+ struct qtnf_pcie_bus_priv *priv;
+ struct qtnf_bus *bus;
+
+ bus = dev_get_drvdata(dev);
+ if (!bus)
+ return -EFAULT;
+
+ priv = get_bus_priv(bus);
+ return priv->suspend_cb(bus);
+}
+
+static int qtnf_pcie_resume(struct device *dev)
+{
+ struct qtnf_pcie_bus_priv *priv;
+ struct qtnf_bus *bus;
+
+ bus = dev_get_drvdata(dev);
+ if (!bus)
+ return -EFAULT;
+
+ priv = get_bus_priv(bus);
+ return priv->resume_cb(bus);
+}
+
+/* Power Management Hooks */
+static SIMPLE_DEV_PM_OPS(qtnf_pcie_pm_ops, qtnf_pcie_suspend,
+ qtnf_pcie_resume);
+#endif
+
+static const struct pci_device_id qtnf_pcie_devid_table[] = {
+ {
+ PCIE_VENDOR_ID_QUANTENNA, PCIE_DEVICE_ID_QSR,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+ },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, qtnf_pcie_devid_table);
+
+static struct pci_driver qtnf_pcie_drv_data = {
+ .name = DRV_NAME,
+ .id_table = qtnf_pcie_devid_table,
+ .probe = qtnf_pcie_probe,
+ .remove = qtnf_pcie_remove,
+#ifdef CONFIG_PM_SLEEP
+ .driver = {
+ .pm = &qtnf_pcie_pm_ops,
+ },
+#endif
+};
+
+module_pci_driver(qtnf_pcie_drv_data)
+
+MODULE_AUTHOR("Quantenna Communications");
+MODULE_DESCRIPTION("Quantenna PCIe bus driver for 802.11 wireless LAN.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
new file mode 100644
index 000000000..2a6a928e1
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2018 Quantenna Communications, Inc. All rights reserved. */
+
+#ifndef _QTN_FMAC_PCIE_H_
+#define _QTN_FMAC_PCIE_H_
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+
+#include "shm_ipc.h"
+#include "bus.h"
+
+#define SKB_BUF_SIZE 2048
+
+#define QTN_FW_DL_TIMEOUT_MS 3000
+#define QTN_FW_QLINK_TIMEOUT_MS 30000
+#define QTN_EP_RESET_WAIT_MS 1000
+
+struct qtnf_pcie_bus_priv {
+ struct pci_dev *pdev;
+
+ int (*probe_cb)(struct qtnf_bus *bus, unsigned int tx_bd_size,
+ unsigned int rx_bd_size);
+ void (*remove_cb)(struct qtnf_bus *bus);
+ int (*suspend_cb)(struct qtnf_bus *bus);
+ int (*resume_cb)(struct qtnf_bus *bus);
+ u64 (*dma_mask_get_cb)(void);
+
+ spinlock_t tx_reclaim_lock;
+ spinlock_t tx_lock;
+
+ struct workqueue_struct *workqueue;
+ struct tasklet_struct reclaim_tq;
+
+ void __iomem *sysctl_bar;
+ void __iomem *epmem_bar;
+ void __iomem *dmareg_bar;
+
+ struct qtnf_shm_ipc shm_ipc_ep_in;
+ struct qtnf_shm_ipc shm_ipc_ep_out;
+
+ u16 tx_bd_num;
+ u16 rx_bd_num;
+
+ struct sk_buff **tx_skb;
+ struct sk_buff **rx_skb;
+
+ unsigned int fw_blksize;
+
+ u32 rx_bd_w_index;
+ u32 rx_bd_r_index;
+
+ u32 tx_bd_w_index;
+ u32 tx_bd_r_index;
+
+ /* diagnostics stats */
+ u32 pcie_irq_count;
+ u32 tx_full_count;
+ u32 tx_done_count;
+ u32 tx_reclaim_done;
+ u32 tx_reclaim_req;
+
+ u8 msi_enabled;
+ u8 tx_stopped;
+ bool flashboot;
+};
+
+int qtnf_pcie_control_tx(struct qtnf_bus *bus, struct sk_buff *skb);
+int qtnf_pcie_alloc_skb_array(struct qtnf_pcie_bus_priv *priv);
+int qtnf_pcie_fw_boot_done(struct qtnf_bus *bus);
+void qtnf_pcie_init_shm_ipc(struct qtnf_pcie_bus_priv *priv,
+ struct qtnf_shm_ipc_region __iomem *ipc_tx_reg,
+ struct qtnf_shm_ipc_region __iomem *ipc_rx_reg,
+ const struct qtnf_shm_ipc_int *ipc_int);
+struct qtnf_bus *qtnf_pcie_pearl_alloc(struct pci_dev *pdev);
+struct qtnf_bus *qtnf_pcie_topaz_alloc(struct pci_dev *pdev);
+
+static inline void qtnf_non_posted_write(u32 val, void __iomem *basereg)
+{
+ writel(val, basereg);
+
+ /* flush posted write */
+ readl(basereg);
+}
+
+#endif /* _QTN_FMAC_PCIE_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c
new file mode 100644
index 000000000..c1a53e1ba
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c
@@ -0,0 +1,1199 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2018 Quantenna Communications */
+
+#include <linux/kernel.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/crc32.h>
+#include <linux/spinlock.h>
+#include <linux/circ_buf.h>
+#include <linux/log2.h>
+
+#include "pcie_priv.h"
+#include "pearl_pcie_regs.h"
+#include "pearl_pcie_ipc.h"
+#include "qtn_hw_ids.h"
+#include "core.h"
+#include "bus.h"
+#include "shm_ipc.h"
+#include "debug.h"
+
+#define PEARL_TX_BD_SIZE_DEFAULT 32
+#define PEARL_RX_BD_SIZE_DEFAULT 256
+
+struct qtnf_pearl_bda {
+ __le16 bda_len;
+ __le16 bda_version;
+ __le32 bda_pci_endian;
+ __le32 bda_ep_state;
+ __le32 bda_rc_state;
+ __le32 bda_dma_mask;
+ __le32 bda_msi_addr;
+ __le32 bda_flashsz;
+ u8 bda_boardname[PCIE_BDA_NAMELEN];
+ __le32 bda_rc_msi_enabled;
+ u8 bda_hhbm_list[PCIE_HHBM_MAX_SIZE];
+ __le32 bda_dsbw_start_index;
+ __le32 bda_dsbw_end_index;
+ __le32 bda_dsbw_total_bytes;
+ __le32 bda_rc_tx_bd_base;
+ __le32 bda_rc_tx_bd_num;
+ u8 bda_pcie_mac[QTN_ENET_ADDR_LENGTH];
+ struct qtnf_shm_ipc_region bda_shm_reg1 __aligned(4096); /* host TX */
+ struct qtnf_shm_ipc_region bda_shm_reg2 __aligned(4096); /* host RX */
+} __packed;
+
+struct qtnf_pearl_tx_bd {
+ __le32 addr;
+ __le32 addr_h;
+ __le32 info;
+ __le32 info_h;
+} __packed;
+
+struct qtnf_pearl_rx_bd {
+ __le32 addr;
+ __le32 addr_h;
+ __le32 info;
+ __le32 info_h;
+ __le32 next_ptr;
+ __le32 next_ptr_h;
+} __packed;
+
+struct qtnf_pearl_fw_hdr {
+ u8 boardflg[8];
+ __le32 fwsize;
+ __le32 seqnum;
+ __le32 type;
+ __le32 pktlen;
+ __le32 crc;
+} __packed;
+
+struct qtnf_pcie_pearl_state {
+ struct qtnf_pcie_bus_priv base;
+
+ /* lock for irq configuration changes */
+ spinlock_t irq_lock;
+
+ struct qtnf_pearl_bda __iomem *bda;
+ void __iomem *pcie_reg_base;
+
+ struct qtnf_pearl_tx_bd *tx_bd_vbase;
+ dma_addr_t tx_bd_pbase;
+
+ struct qtnf_pearl_rx_bd *rx_bd_vbase;
+ dma_addr_t rx_bd_pbase;
+
+ dma_addr_t bd_table_paddr;
+ void *bd_table_vaddr;
+ u32 bd_table_len;
+ u32 pcie_irq_mask;
+ u32 pcie_irq_rx_count;
+ u32 pcie_irq_tx_count;
+ u32 pcie_irq_uf_count;
+};
+
+static inline void qtnf_init_hdp_irqs(struct qtnf_pcie_pearl_state *ps)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ps->irq_lock, flags);
+ ps->pcie_irq_mask = (PCIE_HDP_INT_RX_BITS | PCIE_HDP_INT_TX_BITS);
+ spin_unlock_irqrestore(&ps->irq_lock, flags);
+}
+
+static inline void qtnf_enable_hdp_irqs(struct qtnf_pcie_pearl_state *ps)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ps->irq_lock, flags);
+ writel(ps->pcie_irq_mask, PCIE_HDP_INT_EN(ps->pcie_reg_base));
+ spin_unlock_irqrestore(&ps->irq_lock, flags);
+}
+
+static inline void qtnf_disable_hdp_irqs(struct qtnf_pcie_pearl_state *ps)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ps->irq_lock, flags);
+ writel(0x0, PCIE_HDP_INT_EN(ps->pcie_reg_base));
+ spin_unlock_irqrestore(&ps->irq_lock, flags);
+}
+
+static inline void qtnf_en_rxdone_irq(struct qtnf_pcie_pearl_state *ps)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ps->irq_lock, flags);
+ ps->pcie_irq_mask |= PCIE_HDP_INT_RX_BITS;
+ writel(ps->pcie_irq_mask, PCIE_HDP_INT_EN(ps->pcie_reg_base));
+ spin_unlock_irqrestore(&ps->irq_lock, flags);
+}
+
+static inline void qtnf_dis_rxdone_irq(struct qtnf_pcie_pearl_state *ps)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ps->irq_lock, flags);
+ ps->pcie_irq_mask &= ~PCIE_HDP_INT_RX_BITS;
+ writel(ps->pcie_irq_mask, PCIE_HDP_INT_EN(ps->pcie_reg_base));
+ spin_unlock_irqrestore(&ps->irq_lock, flags);
+}
+
+static inline void qtnf_en_txdone_irq(struct qtnf_pcie_pearl_state *ps)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ps->irq_lock, flags);
+ ps->pcie_irq_mask |= PCIE_HDP_INT_TX_BITS;
+ writel(ps->pcie_irq_mask, PCIE_HDP_INT_EN(ps->pcie_reg_base));
+ spin_unlock_irqrestore(&ps->irq_lock, flags);
+}
+
+static inline void qtnf_dis_txdone_irq(struct qtnf_pcie_pearl_state *ps)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ps->irq_lock, flags);
+ ps->pcie_irq_mask &= ~PCIE_HDP_INT_TX_BITS;
+ writel(ps->pcie_irq_mask, PCIE_HDP_INT_EN(ps->pcie_reg_base));
+ spin_unlock_irqrestore(&ps->irq_lock, flags);
+}
+
+static void qtnf_deassert_intx(struct qtnf_pcie_pearl_state *ps)
+{
+ void __iomem *reg = ps->base.sysctl_bar + PEARL_PCIE_CFG0_OFFSET;
+ u32 cfg;
+
+ cfg = readl(reg);
+ cfg &= ~PEARL_ASSERT_INTX;
+ qtnf_non_posted_write(cfg, reg);
+}
+
+static void qtnf_pearl_reset_ep(struct qtnf_pcie_pearl_state *ps)
+{
+ const u32 data = QTN_PEARL_IPC_IRQ_WORD(QTN_PEARL_LHOST_EP_RESET);
+ void __iomem *reg = ps->base.sysctl_bar +
+ QTN_PEARL_SYSCTL_LHOST_IRQ_OFFSET;
+
+ qtnf_non_posted_write(data, reg);
+ msleep(QTN_EP_RESET_WAIT_MS);
+ pci_restore_state(ps->base.pdev);
+}
+
+static void qtnf_pcie_pearl_ipc_gen_ep_int(void *arg)
+{
+ const struct qtnf_pcie_pearl_state *ps = arg;
+ const u32 data = QTN_PEARL_IPC_IRQ_WORD(QTN_PEARL_LHOST_IPC_IRQ);
+ void __iomem *reg = ps->base.sysctl_bar +
+ QTN_PEARL_SYSCTL_LHOST_IRQ_OFFSET;
+
+ qtnf_non_posted_write(data, reg);
+}
+
+static int qtnf_is_state(__le32 __iomem *reg, u32 state)
+{
+ u32 s = readl(reg);
+
+ return s & state;
+}
+
+static void qtnf_set_state(__le32 __iomem *reg, u32 state)
+{
+ u32 s = readl(reg);
+
+ qtnf_non_posted_write(state | s, reg);
+}
+
+static void qtnf_clear_state(__le32 __iomem *reg, u32 state)
+{
+ u32 s = readl(reg);
+
+ qtnf_non_posted_write(s & ~state, reg);
+}
+
+static int qtnf_poll_state(__le32 __iomem *reg, u32 state, u32 delay_in_ms)
+{
+ u32 timeout = 0;
+
+ while ((qtnf_is_state(reg, state) == 0)) {
+ usleep_range(1000, 1200);
+ if (++timeout > delay_in_ms)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pearl_alloc_bd_table(struct qtnf_pcie_pearl_state *ps)
+{
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ dma_addr_t paddr;
+ void *vaddr;
+ int len;
+
+ len = priv->tx_bd_num * sizeof(struct qtnf_pearl_tx_bd) +
+ priv->rx_bd_num * sizeof(struct qtnf_pearl_rx_bd);
+
+ vaddr = dmam_alloc_coherent(&priv->pdev->dev, len, &paddr, GFP_KERNEL);
+ if (!vaddr)
+ return -ENOMEM;
+
+ /* tx bd */
+
+ ps->bd_table_vaddr = vaddr;
+ ps->bd_table_paddr = paddr;
+ ps->bd_table_len = len;
+
+ ps->tx_bd_vbase = vaddr;
+ ps->tx_bd_pbase = paddr;
+
+ pr_debug("TX descriptor table: vaddr=0x%p paddr=%pad\n", vaddr, &paddr);
+
+ priv->tx_bd_r_index = 0;
+ priv->tx_bd_w_index = 0;
+
+ /* rx bd */
+
+ vaddr = ((struct qtnf_pearl_tx_bd *)vaddr) + priv->tx_bd_num;
+ paddr += priv->tx_bd_num * sizeof(struct qtnf_pearl_tx_bd);
+
+ ps->rx_bd_vbase = vaddr;
+ ps->rx_bd_pbase = paddr;
+
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+ writel(QTN_HOST_HI32(paddr),
+ PCIE_HDP_TX_HOST_Q_BASE_H(ps->pcie_reg_base));
+#endif
+ writel(QTN_HOST_LO32(paddr),
+ PCIE_HDP_TX_HOST_Q_BASE_L(ps->pcie_reg_base));
+ writel(priv->rx_bd_num | (sizeof(struct qtnf_pearl_rx_bd)) << 16,
+ PCIE_HDP_TX_HOST_Q_SZ_CTRL(ps->pcie_reg_base));
+
+ pr_debug("RX descriptor table: vaddr=0x%p paddr=%pad\n", vaddr, &paddr);
+
+ return 0;
+}
+
+static int pearl_skb2rbd_attach(struct qtnf_pcie_pearl_state *ps, u16 index)
+{
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ struct qtnf_pearl_rx_bd *rxbd;
+ struct sk_buff *skb;
+ dma_addr_t paddr;
+
+ skb = netdev_alloc_skb_ip_align(NULL, SKB_BUF_SIZE);
+ if (!skb) {
+ priv->rx_skb[index] = NULL;
+ return -ENOMEM;
+ }
+
+ priv->rx_skb[index] = skb;
+ rxbd = &ps->rx_bd_vbase[index];
+
+ paddr = dma_map_single(&priv->pdev->dev, skb->data, SKB_BUF_SIZE,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(&priv->pdev->dev, paddr)) {
+ pr_err("skb DMA mapping error: %pad\n", &paddr);
+ return -ENOMEM;
+ }
+
+ /* keep rx skb paddrs in rx buffer descriptors for cleanup purposes */
+ rxbd->addr = cpu_to_le32(QTN_HOST_LO32(paddr));
+ rxbd->addr_h = cpu_to_le32(QTN_HOST_HI32(paddr));
+ rxbd->info = 0x0;
+
+ priv->rx_bd_w_index = index;
+
+ /* sync up all descriptor updates */
+ wmb();
+
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+ writel(QTN_HOST_HI32(paddr),
+ PCIE_HDP_HHBM_BUF_PTR_H(ps->pcie_reg_base));
+#endif
+ writel(QTN_HOST_LO32(paddr),
+ PCIE_HDP_HHBM_BUF_PTR(ps->pcie_reg_base));
+
+ writel(index, PCIE_HDP_TX_HOST_Q_WR_PTR(ps->pcie_reg_base));
+ return 0;
+}
+
+static int pearl_alloc_rx_buffers(struct qtnf_pcie_pearl_state *ps)
+{
+ u16 i;
+ int ret = 0;
+
+ memset(ps->rx_bd_vbase, 0x0,
+ ps->base.rx_bd_num * sizeof(struct qtnf_pearl_rx_bd));
+
+ for (i = 0; i < ps->base.rx_bd_num; i++) {
+ ret = pearl_skb2rbd_attach(ps, i);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+/* all rx/tx activity should have ceased before calling this function */
+static void qtnf_pearl_free_xfer_buffers(struct qtnf_pcie_pearl_state *ps)
+{
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ struct qtnf_pearl_tx_bd *txbd;
+ struct qtnf_pearl_rx_bd *rxbd;
+ struct sk_buff *skb;
+ dma_addr_t paddr;
+ int i;
+
+ /* free rx buffers */
+ for (i = 0; i < priv->rx_bd_num; i++) {
+ if (priv->rx_skb && priv->rx_skb[i]) {
+ rxbd = &ps->rx_bd_vbase[i];
+ skb = priv->rx_skb[i];
+ paddr = QTN_HOST_ADDR(le32_to_cpu(rxbd->addr_h),
+ le32_to_cpu(rxbd->addr));
+ dma_unmap_single(&priv->pdev->dev, paddr,
+ SKB_BUF_SIZE, DMA_FROM_DEVICE);
+ dev_kfree_skb_any(skb);
+ priv->rx_skb[i] = NULL;
+ }
+ }
+
+ /* free tx buffers */
+ for (i = 0; i < priv->tx_bd_num; i++) {
+ if (priv->tx_skb && priv->tx_skb[i]) {
+ txbd = &ps->tx_bd_vbase[i];
+ skb = priv->tx_skb[i];
+ paddr = QTN_HOST_ADDR(le32_to_cpu(txbd->addr_h),
+ le32_to_cpu(txbd->addr));
+ dma_unmap_single(&priv->pdev->dev, paddr, skb->len,
+ DMA_TO_DEVICE);
+ dev_kfree_skb_any(skb);
+ priv->tx_skb[i] = NULL;
+ }
+ }
+}
+
+static int pearl_hhbm_init(struct qtnf_pcie_pearl_state *ps)
+{
+ u32 val;
+
+ val = readl(PCIE_HHBM_CONFIG(ps->pcie_reg_base));
+ val |= HHBM_CONFIG_SOFT_RESET;
+ writel(val, PCIE_HHBM_CONFIG(ps->pcie_reg_base));
+ usleep_range(50, 100);
+ val &= ~HHBM_CONFIG_SOFT_RESET;
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+ val |= HHBM_64BIT;
+#endif
+ writel(val, PCIE_HHBM_CONFIG(ps->pcie_reg_base));
+ writel(ps->base.rx_bd_num, PCIE_HHBM_Q_LIMIT_REG(ps->pcie_reg_base));
+
+ return 0;
+}
+
+static int qtnf_pcie_pearl_init_xfer(struct qtnf_pcie_pearl_state *ps,
+ unsigned int tx_bd_size,
+ unsigned int rx_bd_size)
+{
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ int ret;
+ u32 val;
+
+ if (tx_bd_size == 0)
+ tx_bd_size = PEARL_TX_BD_SIZE_DEFAULT;
+
+ val = tx_bd_size * sizeof(struct qtnf_pearl_tx_bd);
+
+ if (!is_power_of_2(tx_bd_size) || val > PCIE_HHBM_MAX_SIZE) {
+ pr_warn("invalid tx_bd_size value %u, use default %u\n",
+ tx_bd_size, PEARL_TX_BD_SIZE_DEFAULT);
+ priv->tx_bd_num = PEARL_TX_BD_SIZE_DEFAULT;
+ } else {
+ priv->tx_bd_num = tx_bd_size;
+ }
+
+ if (rx_bd_size == 0)
+ rx_bd_size = PEARL_RX_BD_SIZE_DEFAULT;
+
+ val = rx_bd_size * sizeof(dma_addr_t);
+
+ if (!is_power_of_2(rx_bd_size) || val > PCIE_HHBM_MAX_SIZE) {
+ pr_warn("invalid rx_bd_size value %u, use default %u\n",
+ rx_bd_size, PEARL_RX_BD_SIZE_DEFAULT);
+ priv->rx_bd_num = PEARL_RX_BD_SIZE_DEFAULT;
+ } else {
+ priv->rx_bd_num = rx_bd_size;
+ }
+
+ priv->rx_bd_w_index = 0;
+ priv->rx_bd_r_index = 0;
+
+ ret = pearl_hhbm_init(ps);
+ if (ret) {
+ pr_err("failed to init h/w queues\n");
+ return ret;
+ }
+
+ ret = qtnf_pcie_alloc_skb_array(priv);
+ if (ret) {
+ pr_err("failed to allocate skb array\n");
+ return ret;
+ }
+
+ ret = pearl_alloc_bd_table(ps);
+ if (ret) {
+ pr_err("failed to allocate bd table\n");
+ return ret;
+ }
+
+ ret = pearl_alloc_rx_buffers(ps);
+ if (ret) {
+ pr_err("failed to allocate rx buffers\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static void qtnf_pearl_data_tx_reclaim(struct qtnf_pcie_pearl_state *ps)
+{
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ struct qtnf_pearl_tx_bd *txbd;
+ struct sk_buff *skb;
+ unsigned long flags;
+ dma_addr_t paddr;
+ u32 tx_done_index;
+ int count = 0;
+ int i;
+
+ spin_lock_irqsave(&priv->tx_reclaim_lock, flags);
+
+ tx_done_index = readl(PCIE_HDP_RX0DMA_CNT(ps->pcie_reg_base))
+ & (priv->tx_bd_num - 1);
+
+ i = priv->tx_bd_r_index;
+
+ while (CIRC_CNT(tx_done_index, i, priv->tx_bd_num)) {
+ skb = priv->tx_skb[i];
+ if (likely(skb)) {
+ txbd = &ps->tx_bd_vbase[i];
+ paddr = QTN_HOST_ADDR(le32_to_cpu(txbd->addr_h),
+ le32_to_cpu(txbd->addr));
+ dma_unmap_single(&priv->pdev->dev, paddr, skb->len,
+ DMA_TO_DEVICE);
+
+ if (skb->dev) {
+ dev_sw_netstats_tx_add(skb->dev, 1, skb->len);
+ if (unlikely(priv->tx_stopped)) {
+ qtnf_wake_all_queues(skb->dev);
+ priv->tx_stopped = 0;
+ }
+ }
+
+ dev_kfree_skb_any(skb);
+ }
+
+ priv->tx_skb[i] = NULL;
+ count++;
+
+ if (++i >= priv->tx_bd_num)
+ i = 0;
+ }
+
+ priv->tx_reclaim_done += count;
+ priv->tx_reclaim_req++;
+ priv->tx_bd_r_index = i;
+
+ spin_unlock_irqrestore(&priv->tx_reclaim_lock, flags);
+}
+
+static int qtnf_tx_queue_ready(struct qtnf_pcie_pearl_state *ps)
+{
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+
+ if (!CIRC_SPACE(priv->tx_bd_w_index, priv->tx_bd_r_index,
+ priv->tx_bd_num)) {
+ qtnf_pearl_data_tx_reclaim(ps);
+
+ if (!CIRC_SPACE(priv->tx_bd_w_index, priv->tx_bd_r_index,
+ priv->tx_bd_num)) {
+ pr_warn_ratelimited("reclaim full Tx queue\n");
+ priv->tx_full_count++;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int qtnf_pcie_skb_send(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+ struct qtnf_pcie_pearl_state *ps = get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ dma_addr_t txbd_paddr, skb_paddr;
+ struct qtnf_pearl_tx_bd *txbd;
+ unsigned long flags;
+ int len, i;
+ u32 info;
+ int ret = 0;
+
+ spin_lock_irqsave(&priv->tx_lock, flags);
+
+ if (!qtnf_tx_queue_ready(ps)) {
+ if (skb->dev) {
+ netif_tx_stop_all_queues(skb->dev);
+ priv->tx_stopped = 1;
+ }
+
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+ return NETDEV_TX_BUSY;
+ }
+
+ i = priv->tx_bd_w_index;
+ priv->tx_skb[i] = skb;
+ len = skb->len;
+
+ skb_paddr = dma_map_single(&priv->pdev->dev, skb->data, skb->len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(&priv->pdev->dev, skb_paddr)) {
+ pr_err("skb DMA mapping error: %pad\n", &skb_paddr);
+ ret = -ENOMEM;
+ goto tx_done;
+ }
+
+ txbd = &ps->tx_bd_vbase[i];
+ txbd->addr = cpu_to_le32(QTN_HOST_LO32(skb_paddr));
+ txbd->addr_h = cpu_to_le32(QTN_HOST_HI32(skb_paddr));
+
+ info = (len & QTN_PCIE_TX_DESC_LEN_MASK) << QTN_PCIE_TX_DESC_LEN_SHIFT;
+ txbd->info = cpu_to_le32(info);
+
+ /* sync up all descriptor updates before passing them to EP */
+ dma_wmb();
+
+ /* write new TX descriptor to PCIE_RX_FIFO on EP */
+ txbd_paddr = ps->tx_bd_pbase + i * sizeof(struct qtnf_pearl_tx_bd);
+
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+ writel(QTN_HOST_HI32(txbd_paddr),
+ PCIE_HDP_HOST_WR_DESC0_H(ps->pcie_reg_base));
+#endif
+ writel(QTN_HOST_LO32(txbd_paddr),
+ PCIE_HDP_HOST_WR_DESC0(ps->pcie_reg_base));
+
+ if (++i >= priv->tx_bd_num)
+ i = 0;
+
+ priv->tx_bd_w_index = i;
+
+tx_done:
+ if (ret) {
+ pr_err_ratelimited("drop skb\n");
+ if (skb->dev)
+ skb->dev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+
+ priv->tx_done_count++;
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+ qtnf_pearl_data_tx_reclaim(ps);
+
+ return NETDEV_TX_OK;
+}
+
+static int qtnf_pcie_data_tx(struct qtnf_bus *bus, struct sk_buff *skb,
+ unsigned int macid, unsigned int vifid)
+{
+ return qtnf_pcie_skb_send(bus, skb);
+}
+
+static int qtnf_pcie_data_tx_meta(struct qtnf_bus *bus, struct sk_buff *skb,
+ unsigned int macid, unsigned int vifid)
+{
+ struct qtnf_frame_meta_info *meta;
+ int tail_need = sizeof(*meta) - skb_tailroom(skb);
+ int ret;
+
+ if (tail_need > 0 && pskb_expand_head(skb, 0, tail_need, GFP_ATOMIC)) {
+ skb->dev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+ }
+
+ meta = skb_put(skb, sizeof(*meta));
+ meta->magic_s = HBM_FRAME_META_MAGIC_PATTERN_S;
+ meta->magic_e = HBM_FRAME_META_MAGIC_PATTERN_E;
+ meta->macid = macid;
+ meta->ifidx = vifid;
+
+ ret = qtnf_pcie_skb_send(bus, skb);
+ if (unlikely(ret == NETDEV_TX_BUSY))
+ __skb_trim(skb, skb->len - sizeof(*meta));
+
+ return ret;
+}
+
+static irqreturn_t qtnf_pcie_pearl_interrupt(int irq, void *data)
+{
+ struct qtnf_bus *bus = (struct qtnf_bus *)data;
+ struct qtnf_pcie_pearl_state *ps = get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ u32 status;
+
+ priv->pcie_irq_count++;
+ status = readl(PCIE_HDP_INT_STATUS(ps->pcie_reg_base));
+
+ qtnf_shm_ipc_irq_handler(&priv->shm_ipc_ep_in);
+ qtnf_shm_ipc_irq_handler(&priv->shm_ipc_ep_out);
+
+ if (!(status & ps->pcie_irq_mask))
+ goto irq_done;
+
+ if (status & PCIE_HDP_INT_RX_BITS)
+ ps->pcie_irq_rx_count++;
+
+ if (status & PCIE_HDP_INT_TX_BITS)
+ ps->pcie_irq_tx_count++;
+
+ if (status & PCIE_HDP_INT_HHBM_UF)
+ ps->pcie_irq_uf_count++;
+
+ if (status & PCIE_HDP_INT_RX_BITS) {
+ qtnf_dis_rxdone_irq(ps);
+ napi_schedule(&bus->mux_napi);
+ }
+
+ if (status & PCIE_HDP_INT_TX_BITS) {
+ qtnf_dis_txdone_irq(ps);
+ tasklet_hi_schedule(&priv->reclaim_tq);
+ }
+
+irq_done:
+ /* H/W workaround: clean all bits, not only enabled */
+ qtnf_non_posted_write(~0U, PCIE_HDP_INT_STATUS(ps->pcie_reg_base));
+
+ if (!priv->msi_enabled)
+ qtnf_deassert_intx(ps);
+
+ return IRQ_HANDLED;
+}
+
+static int qtnf_rx_data_ready(struct qtnf_pcie_pearl_state *ps)
+{
+ u16 index = ps->base.rx_bd_r_index;
+ struct qtnf_pearl_rx_bd *rxbd;
+ u32 descw;
+
+ rxbd = &ps->rx_bd_vbase[index];
+ descw = le32_to_cpu(rxbd->info);
+
+ if (descw & QTN_TXDONE_MASK)
+ return 1;
+
+ return 0;
+}
+
+static int qtnf_pcie_pearl_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct qtnf_bus *bus = container_of(napi, struct qtnf_bus, mux_napi);
+ struct qtnf_pcie_pearl_state *ps = get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+ struct net_device *ndev = NULL;
+ struct sk_buff *skb = NULL;
+ int processed = 0;
+ struct qtnf_pearl_rx_bd *rxbd;
+ dma_addr_t skb_paddr;
+ int consume;
+ u32 descw;
+ u32 psize;
+ u16 r_idx;
+ u16 w_idx;
+ int ret;
+
+ while (processed < budget) {
+ if (!qtnf_rx_data_ready(ps))
+ goto rx_out;
+
+ r_idx = priv->rx_bd_r_index;
+ rxbd = &ps->rx_bd_vbase[r_idx];
+ descw = le32_to_cpu(rxbd->info);
+
+ skb = priv->rx_skb[r_idx];
+ psize = QTN_GET_LEN(descw);
+ consume = 1;
+
+ if (!(descw & QTN_TXDONE_MASK)) {
+ pr_warn("skip invalid rxbd[%d]\n", r_idx);
+ consume = 0;
+ }
+
+ if (!skb) {
+ pr_warn("skip missing rx_skb[%d]\n", r_idx);
+ consume = 0;
+ }
+
+ if (skb && (skb_tailroom(skb) < psize)) {
+ pr_err("skip packet with invalid length: %u > %u\n",
+ psize, skb_tailroom(skb));
+ consume = 0;
+ }
+
+ if (skb) {
+ skb_paddr = QTN_HOST_ADDR(le32_to_cpu(rxbd->addr_h),
+ le32_to_cpu(rxbd->addr));
+ dma_unmap_single(&priv->pdev->dev, skb_paddr,
+ SKB_BUF_SIZE, DMA_FROM_DEVICE);
+ }
+
+ if (consume) {
+ skb_put(skb, psize);
+ ndev = qtnf_classify_skb(bus, skb);
+ if (likely(ndev)) {
+ dev_sw_netstats_rx_add(ndev, skb->len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ napi_gro_receive(napi, skb);
+ } else {
+ pr_debug("drop untagged skb\n");
+ bus->mux_dev->stats.rx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+ } else {
+ if (skb) {
+ bus->mux_dev->stats.rx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+ }
+
+ priv->rx_skb[r_idx] = NULL;
+ if (++r_idx >= priv->rx_bd_num)
+ r_idx = 0;
+
+ priv->rx_bd_r_index = r_idx;
+
+ /* repalce processed buffer by a new one */
+ w_idx = priv->rx_bd_w_index;
+ while (CIRC_SPACE(priv->rx_bd_w_index, priv->rx_bd_r_index,
+ priv->rx_bd_num) > 0) {
+ if (++w_idx >= priv->rx_bd_num)
+ w_idx = 0;
+
+ ret = pearl_skb2rbd_attach(ps, w_idx);
+ if (ret) {
+ pr_err("failed to allocate new rx_skb[%d]\n",
+ w_idx);
+ break;
+ }
+ }
+
+ processed++;
+ }
+
+rx_out:
+ if (processed < budget) {
+ napi_complete(napi);
+ qtnf_en_rxdone_irq(ps);
+ }
+
+ return processed;
+}
+
+static void
+qtnf_pcie_data_tx_timeout(struct qtnf_bus *bus, struct net_device *ndev)
+{
+ struct qtnf_pcie_pearl_state *ps = (void *)get_bus_priv(bus);
+
+ tasklet_hi_schedule(&ps->base.reclaim_tq);
+}
+
+static void qtnf_pcie_data_rx_start(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_pearl_state *ps = (void *)get_bus_priv(bus);
+
+ qtnf_enable_hdp_irqs(ps);
+ napi_enable(&bus->mux_napi);
+}
+
+static void qtnf_pcie_data_rx_stop(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_pearl_state *ps = (void *)get_bus_priv(bus);
+
+ napi_disable(&bus->mux_napi);
+ qtnf_disable_hdp_irqs(ps);
+}
+
+static void qtnf_pearl_tx_use_meta_info_set(struct qtnf_bus *bus, bool use_meta)
+{
+ if (use_meta)
+ bus->bus_ops->data_tx = qtnf_pcie_data_tx_meta;
+ else
+ bus->bus_ops->data_tx = qtnf_pcie_data_tx;
+}
+
+static struct qtnf_bus_ops qtnf_pcie_pearl_bus_ops = {
+ /* control path methods */
+ .control_tx = qtnf_pcie_control_tx,
+
+ /* data path methods */
+ .data_tx = qtnf_pcie_data_tx,
+ .data_tx_timeout = qtnf_pcie_data_tx_timeout,
+ .data_tx_use_meta_set = qtnf_pearl_tx_use_meta_info_set,
+ .data_rx_start = qtnf_pcie_data_rx_start,
+ .data_rx_stop = qtnf_pcie_data_rx_stop,
+};
+
+static int qtnf_dbg_irq_stats(struct seq_file *s, void *data)
+{
+ struct qtnf_bus *bus = dev_get_drvdata(s->private);
+ struct qtnf_pcie_pearl_state *ps = get_bus_priv(bus);
+ u32 reg = readl(PCIE_HDP_INT_EN(ps->pcie_reg_base));
+ u32 status;
+
+ seq_printf(s, "pcie_irq_count(%u)\n", ps->base.pcie_irq_count);
+ seq_printf(s, "pcie_irq_tx_count(%u)\n", ps->pcie_irq_tx_count);
+ status = reg & PCIE_HDP_INT_TX_BITS;
+ seq_printf(s, "pcie_irq_tx_status(%s)\n",
+ (status == PCIE_HDP_INT_TX_BITS) ? "EN" : "DIS");
+ seq_printf(s, "pcie_irq_rx_count(%u)\n", ps->pcie_irq_rx_count);
+ status = reg & PCIE_HDP_INT_RX_BITS;
+ seq_printf(s, "pcie_irq_rx_status(%s)\n",
+ (status == PCIE_HDP_INT_RX_BITS) ? "EN" : "DIS");
+ seq_printf(s, "pcie_irq_uf_count(%u)\n", ps->pcie_irq_uf_count);
+ status = reg & PCIE_HDP_INT_HHBM_UF;
+ seq_printf(s, "pcie_irq_hhbm_uf_status(%s)\n",
+ (status == PCIE_HDP_INT_HHBM_UF) ? "EN" : "DIS");
+
+ return 0;
+}
+
+static int qtnf_dbg_hdp_stats(struct seq_file *s, void *data)
+{
+ struct qtnf_bus *bus = dev_get_drvdata(s->private);
+ struct qtnf_pcie_pearl_state *ps = get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ps->base;
+
+ seq_printf(s, "tx_full_count(%u)\n", priv->tx_full_count);
+ seq_printf(s, "tx_done_count(%u)\n", priv->tx_done_count);
+ seq_printf(s, "tx_reclaim_done(%u)\n", priv->tx_reclaim_done);
+ seq_printf(s, "tx_reclaim_req(%u)\n", priv->tx_reclaim_req);
+
+ seq_printf(s, "tx_bd_r_index(%u)\n", priv->tx_bd_r_index);
+ seq_printf(s, "tx_bd_p_index(%u)\n",
+ readl(PCIE_HDP_RX0DMA_CNT(ps->pcie_reg_base))
+ & (priv->tx_bd_num - 1));
+ seq_printf(s, "tx_bd_w_index(%u)\n", priv->tx_bd_w_index);
+ seq_printf(s, "tx queue len(%u)\n",
+ CIRC_CNT(priv->tx_bd_w_index, priv->tx_bd_r_index,
+ priv->tx_bd_num));
+
+ seq_printf(s, "rx_bd_r_index(%u)\n", priv->rx_bd_r_index);
+ seq_printf(s, "rx_bd_p_index(%u)\n",
+ readl(PCIE_HDP_TX0DMA_CNT(ps->pcie_reg_base))
+ & (priv->rx_bd_num - 1));
+ seq_printf(s, "rx_bd_w_index(%u)\n", priv->rx_bd_w_index);
+ seq_printf(s, "rx alloc queue len(%u)\n",
+ CIRC_SPACE(priv->rx_bd_w_index, priv->rx_bd_r_index,
+ priv->rx_bd_num));
+
+ return 0;
+}
+
+static int qtnf_ep_fw_send(struct pci_dev *pdev, uint32_t size,
+ int blk, const u8 *pblk, const u8 *fw)
+{
+ struct qtnf_bus *bus = pci_get_drvdata(pdev);
+
+ struct qtnf_pearl_fw_hdr *hdr;
+ u8 *pdata;
+
+ int hds = sizeof(*hdr);
+ struct sk_buff *skb = NULL;
+ int len = 0;
+ int ret;
+
+ skb = __dev_alloc_skb(QTN_PCIE_FW_BUFSZ, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ skb->len = QTN_PCIE_FW_BUFSZ;
+ skb->dev = NULL;
+
+ hdr = (struct qtnf_pearl_fw_hdr *)skb->data;
+ memcpy(hdr->boardflg, QTN_PCIE_BOARDFLG, strlen(QTN_PCIE_BOARDFLG));
+ hdr->fwsize = cpu_to_le32(size);
+ hdr->seqnum = cpu_to_le32(blk);
+
+ if (blk)
+ hdr->type = cpu_to_le32(QTN_FW_DSUB);
+ else
+ hdr->type = cpu_to_le32(QTN_FW_DBEGIN);
+
+ pdata = skb->data + hds;
+
+ len = QTN_PCIE_FW_BUFSZ - hds;
+ if (pblk >= (fw + size - len)) {
+ len = fw + size - pblk;
+ hdr->type = cpu_to_le32(QTN_FW_DEND);
+ }
+
+ hdr->pktlen = cpu_to_le32(len);
+ memcpy(pdata, pblk, len);
+ hdr->crc = cpu_to_le32(~crc32(0, pdata, len));
+
+ ret = qtnf_pcie_skb_send(bus, skb);
+
+ return (ret == NETDEV_TX_OK) ? len : 0;
+}
+
+static int
+qtnf_ep_fw_load(struct qtnf_pcie_pearl_state *ps, const u8 *fw, u32 fw_size)
+{
+ int blk_size = QTN_PCIE_FW_BUFSZ - sizeof(struct qtnf_pearl_fw_hdr);
+ int blk_count = fw_size / blk_size + ((fw_size % blk_size) ? 1 : 0);
+ const u8 *pblk = fw;
+ int threshold = 0;
+ int blk = 0;
+ int len;
+
+ pr_debug("FW upload started: fw_addr=0x%p size=%d\n", fw, fw_size);
+
+ while (blk < blk_count) {
+ if (++threshold > 10000) {
+ pr_err("FW upload failed: too many retries\n");
+ return -ETIMEDOUT;
+ }
+
+ len = qtnf_ep_fw_send(ps->base.pdev, fw_size, blk, pblk, fw);
+ if (len <= 0)
+ continue;
+
+ if (!((blk + 1) & QTN_PCIE_FW_DLMASK) ||
+ (blk == (blk_count - 1))) {
+ qtnf_set_state(&ps->bda->bda_rc_state,
+ QTN_RC_FW_SYNC);
+ if (qtnf_poll_state(&ps->bda->bda_ep_state,
+ QTN_EP_FW_SYNC,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("FW upload failed: SYNC timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ qtnf_clear_state(&ps->bda->bda_ep_state,
+ QTN_EP_FW_SYNC);
+
+ if (qtnf_is_state(&ps->bda->bda_ep_state,
+ QTN_EP_FW_RETRY)) {
+ if (blk == (blk_count - 1)) {
+ int last_round =
+ blk_count & QTN_PCIE_FW_DLMASK;
+ blk -= last_round;
+ pblk -= ((last_round - 1) *
+ blk_size + len);
+ } else {
+ blk -= QTN_PCIE_FW_DLMASK;
+ pblk -= QTN_PCIE_FW_DLMASK * blk_size;
+ }
+
+ qtnf_clear_state(&ps->bda->bda_ep_state,
+ QTN_EP_FW_RETRY);
+
+ pr_warn("FW upload retry: block #%d\n", blk);
+ continue;
+ }
+
+ qtnf_pearl_data_tx_reclaim(ps);
+ }
+
+ pblk += len;
+ blk++;
+ }
+
+ pr_debug("FW upload completed: totally sent %d blocks\n", blk);
+ return 0;
+}
+
+static void qtnf_pearl_fw_work_handler(struct work_struct *work)
+{
+ struct qtnf_bus *bus = container_of(work, struct qtnf_bus, fw_work);
+ struct qtnf_pcie_pearl_state *ps = (void *)get_bus_priv(bus);
+ u32 state = QTN_RC_FW_LOADRDY | QTN_RC_FW_QLINK;
+ const char *fwname = QTN_PCI_PEARL_FW_NAME;
+ struct pci_dev *pdev = ps->base.pdev;
+ const struct firmware *fw;
+ int ret;
+
+ if (ps->base.flashboot) {
+ state |= QTN_RC_FW_FLASHBOOT;
+ } else {
+ ret = request_firmware(&fw, fwname, &pdev->dev);
+ if (ret < 0) {
+ pr_err("failed to get firmware %s\n", fwname);
+ goto fw_load_exit;
+ }
+ }
+
+ qtnf_set_state(&ps->bda->bda_rc_state, state);
+
+ if (qtnf_poll_state(&ps->bda->bda_ep_state, QTN_EP_FW_LOADRDY,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("card is not ready\n");
+
+ if (!ps->base.flashboot)
+ release_firmware(fw);
+
+ goto fw_load_exit;
+ }
+
+ qtnf_clear_state(&ps->bda->bda_ep_state, QTN_EP_FW_LOADRDY);
+
+ if (ps->base.flashboot) {
+ pr_info("booting firmware from flash\n");
+
+ } else {
+ pr_info("starting firmware upload: %s\n", fwname);
+
+ ret = qtnf_ep_fw_load(ps, fw->data, fw->size);
+ release_firmware(fw);
+ if (ret) {
+ pr_err("firmware upload error\n");
+ goto fw_load_exit;
+ }
+ }
+
+ if (qtnf_poll_state(&ps->bda->bda_ep_state, QTN_EP_FW_DONE,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("firmware bringup timed out\n");
+ goto fw_load_exit;
+ }
+
+ if (qtnf_poll_state(&ps->bda->bda_ep_state,
+ QTN_EP_FW_QLINK_DONE, QTN_FW_QLINK_TIMEOUT_MS)) {
+ pr_err("firmware runtime failure\n");
+ goto fw_load_exit;
+ }
+
+ pr_info("firmware is up and running\n");
+
+ ret = qtnf_pcie_fw_boot_done(bus);
+ if (ret)
+ goto fw_load_exit;
+
+ qtnf_debugfs_add_entry(bus, "hdp_stats", qtnf_dbg_hdp_stats);
+ qtnf_debugfs_add_entry(bus, "irq_stats", qtnf_dbg_irq_stats);
+
+fw_load_exit:
+ put_device(&pdev->dev);
+}
+
+static void qtnf_pearl_reclaim_tasklet_fn(struct tasklet_struct *t)
+{
+ struct qtnf_pcie_pearl_state *ps = from_tasklet(ps, t, base.reclaim_tq);
+
+ qtnf_pearl_data_tx_reclaim(ps);
+ qtnf_en_txdone_irq(ps);
+}
+
+static u64 qtnf_pearl_dma_mask_get(void)
+{
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+ return DMA_BIT_MASK(64);
+#else
+ return DMA_BIT_MASK(32);
+#endif
+}
+
+static int qtnf_pcie_pearl_probe(struct qtnf_bus *bus, unsigned int tx_bd_size,
+ unsigned int rx_bd_size)
+{
+ struct qtnf_shm_ipc_int ipc_int;
+ struct qtnf_pcie_pearl_state *ps = get_bus_priv(bus);
+ struct pci_dev *pdev = ps->base.pdev;
+ int ret;
+
+ bus->bus_ops = &qtnf_pcie_pearl_bus_ops;
+ spin_lock_init(&ps->irq_lock);
+ INIT_WORK(&bus->fw_work, qtnf_pearl_fw_work_handler);
+
+ ps->pcie_reg_base = ps->base.dmareg_bar;
+ ps->bda = ps->base.epmem_bar;
+ writel(ps->base.msi_enabled, &ps->bda->bda_rc_msi_enabled);
+
+ ret = qtnf_pcie_pearl_init_xfer(ps, tx_bd_size, rx_bd_size);
+ if (ret) {
+ pr_err("PCIE xfer init failed\n");
+ return ret;
+ }
+
+ /* init default irq settings */
+ qtnf_init_hdp_irqs(ps);
+
+ /* start with disabled irqs */
+ qtnf_disable_hdp_irqs(ps);
+
+ ret = devm_request_irq(&pdev->dev, pdev->irq,
+ &qtnf_pcie_pearl_interrupt, 0,
+ "qtnf_pearl_irq", (void *)bus);
+ if (ret) {
+ pr_err("failed to request pcie irq %d\n", pdev->irq);
+ qtnf_pearl_free_xfer_buffers(ps);
+ return ret;
+ }
+
+ tasklet_setup(&ps->base.reclaim_tq, qtnf_pearl_reclaim_tasklet_fn);
+ netif_napi_add_weight(bus->mux_dev, &bus->mux_napi,
+ qtnf_pcie_pearl_rx_poll, 10);
+
+ ipc_int.fn = qtnf_pcie_pearl_ipc_gen_ep_int;
+ ipc_int.arg = ps;
+ qtnf_pcie_init_shm_ipc(&ps->base, &ps->bda->bda_shm_reg1,
+ &ps->bda->bda_shm_reg2, &ipc_int);
+
+ return 0;
+}
+
+static void qtnf_pcie_pearl_remove(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_pearl_state *ps = get_bus_priv(bus);
+
+ qtnf_pearl_reset_ep(ps);
+ qtnf_pearl_free_xfer_buffers(ps);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int qtnf_pcie_pearl_suspend(struct qtnf_bus *bus)
+{
+ return -EOPNOTSUPP;
+}
+
+static int qtnf_pcie_pearl_resume(struct qtnf_bus *bus)
+{
+ return 0;
+}
+#endif
+
+struct qtnf_bus *qtnf_pcie_pearl_alloc(struct pci_dev *pdev)
+{
+ struct qtnf_bus *bus;
+ struct qtnf_pcie_pearl_state *ps;
+
+ bus = devm_kzalloc(&pdev->dev, sizeof(*bus) + sizeof(*ps), GFP_KERNEL);
+ if (!bus)
+ return NULL;
+
+ ps = get_bus_priv(bus);
+ ps->base.probe_cb = qtnf_pcie_pearl_probe;
+ ps->base.remove_cb = qtnf_pcie_pearl_remove;
+ ps->base.dma_mask_get_cb = qtnf_pearl_dma_mask_get;
+#ifdef CONFIG_PM_SLEEP
+ ps->base.resume_cb = qtnf_pcie_pearl_resume;
+ ps->base.suspend_cb = qtnf_pcie_pearl_suspend;
+#endif
+
+ return bus;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_ipc.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_ipc.h
new file mode 100644
index 000000000..634480fe6
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_ipc.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications */
+
+#ifndef _QTN_FMAC_PCIE_IPC_H_
+#define _QTN_FMAC_PCIE_IPC_H_
+
+#include <linux/types.h>
+
+#include "shm_ipc_defs.h"
+
+/* bitmap for EP status and flags: updated by EP, read by RC */
+#define QTN_EP_HAS_UBOOT BIT(0)
+#define QTN_EP_HAS_FIRMWARE BIT(1)
+#define QTN_EP_REQ_UBOOT BIT(2)
+#define QTN_EP_REQ_FIRMWARE BIT(3)
+#define QTN_EP_ERROR_UBOOT BIT(4)
+#define QTN_EP_ERROR_FIRMWARE BIT(5)
+
+#define QTN_EP_FW_LOADRDY BIT(8)
+#define QTN_EP_FW_SYNC BIT(9)
+#define QTN_EP_FW_RETRY BIT(10)
+#define QTN_EP_FW_QLINK_DONE BIT(15)
+#define QTN_EP_FW_DONE BIT(16)
+
+/* bitmap for RC status and flags: updated by RC, read by EP */
+#define QTN_RC_PCIE_LINK BIT(0)
+#define QTN_RC_NET_LINK BIT(1)
+#define QTN_RC_FW_FLASHBOOT BIT(5)
+#define QTN_RC_FW_QLINK BIT(7)
+#define QTN_RC_FW_LOADRDY BIT(8)
+#define QTN_RC_FW_SYNC BIT(9)
+
+#define PCIE_HDP_INT_RX_BITS (0 \
+ | PCIE_HDP_INT_EP_TXDMA \
+ | PCIE_HDP_INT_EP_TXEMPTY \
+ | PCIE_HDP_INT_HHBM_UF \
+ )
+
+#define PCIE_HDP_INT_TX_BITS (0 \
+ | PCIE_HDP_INT_EP_RXDMA \
+ )
+
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+#define QTN_HOST_HI32(a) ((u32)(((u64)a) >> 32))
+#define QTN_HOST_LO32(a) ((u32)(((u64)a) & 0xffffffffUL))
+#define QTN_HOST_ADDR(h, l) ((((u64)h) << 32) | ((u64)l))
+#else
+#define QTN_HOST_HI32(a) 0
+#define QTN_HOST_LO32(a) ((u32)(((u32)a) & 0xffffffffUL))
+#define QTN_HOST_ADDR(h, l) ((u32)l)
+#endif
+
+#define QTN_PCIE_BDA_VERSION 0x1002
+
+#define PCIE_BDA_NAMELEN 32
+#define PCIE_HHBM_MAX_SIZE 2048
+
+#define QTN_PCIE_BOARDFLG "PCIEQTN"
+#define QTN_PCIE_FW_DLMASK 0xF
+#define QTN_PCIE_FW_BUFSZ 2048
+
+#define QTN_ENET_ADDR_LENGTH 6
+
+#define QTN_TXDONE_MASK ((u32)0x80000000)
+#define QTN_GET_LEN(x) ((x) & 0xFFFF)
+
+#define QTN_PCIE_TX_DESC_LEN_MASK 0xFFFF
+#define QTN_PCIE_TX_DESC_LEN_SHIFT 0
+#define QTN_PCIE_TX_DESC_PORT_MASK 0xF
+#define QTN_PCIE_TX_DESC_PORT_SHIFT 16
+#define QTN_PCIE_TX_DESC_TQE_BIT BIT(24)
+
+#define QTN_EP_LHOST_TQE_PORT 4
+
+enum qtnf_fw_loadtype {
+ QTN_FW_DBEGIN,
+ QTN_FW_DSUB,
+ QTN_FW_DEND,
+ QTN_FW_CTRL
+};
+
+#endif /* _QTN_FMAC_PCIE_IPC_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_regs.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_regs.h
new file mode 100644
index 000000000..6e9a5c61d
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie_regs.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015 Quantenna Communications */
+
+#ifndef __PEARL_PCIE_H
+#define __PEARL_PCIE_H
+
+/* Pearl PCIe HDP registers */
+#define PCIE_HDP_CTRL(base) ((base) + 0x2c00)
+#define PCIE_HDP_AXI_CTRL(base) ((base) + 0x2c04)
+#define PCIE_HDP_HOST_WR_DESC0(base) ((base) + 0x2c10)
+#define PCIE_HDP_HOST_WR_DESC0_H(base) ((base) + 0x2c14)
+#define PCIE_HDP_HOST_WR_DESC1(base) ((base) + 0x2c18)
+#define PCIE_HDP_HOST_WR_DESC1_H(base) ((base) + 0x2c1c)
+#define PCIE_HDP_HOST_WR_DESC2(base) ((base) + 0x2c20)
+#define PCIE_HDP_HOST_WR_DESC2_H(base) ((base) + 0x2c24)
+#define PCIE_HDP_HOST_WR_DESC3(base) ((base) + 0x2c28)
+#define PCIE_HDP_HOST_WR_DESC4_H(base) ((base) + 0x2c2c)
+#define PCIE_HDP_RX_INT_CTRL(base) ((base) + 0x2c30)
+#define PCIE_HDP_TX_INT_CTRL(base) ((base) + 0x2c34)
+#define PCIE_HDP_INT_STATUS(base) ((base) + 0x2c38)
+#define PCIE_HDP_INT_EN(base) ((base) + 0x2c3c)
+#define PCIE_HDP_RX_DESC0_PTR(base) ((base) + 0x2c40)
+#define PCIE_HDP_RX_DESC0_NOE(base) ((base) + 0x2c44)
+#define PCIE_HDP_RX_DESC1_PTR(base) ((base) + 0x2c48)
+#define PCIE_HDP_RX_DESC1_NOE(base) ((base) + 0x2c4c)
+#define PCIE_HDP_RX_DESC2_PTR(base) ((base) + 0x2c50)
+#define PCIE_HDP_RX_DESC2_NOE(base) ((base) + 0x2c54)
+#define PCIE_HDP_RX_DESC3_PTR(base) ((base) + 0x2c58)
+#define PCIE_HDP_RX_DESC3_NOE(base) ((base) + 0x2c5c)
+
+#define PCIE_HDP_TX0_BASE_ADDR(base) ((base) + 0x2c60)
+#define PCIE_HDP_TX1_BASE_ADDR(base) ((base) + 0x2c64)
+#define PCIE_HDP_TX0_Q_CTRL(base) ((base) + 0x2c70)
+#define PCIE_HDP_TX1_Q_CTRL(base) ((base) + 0x2c74)
+#define PCIE_HDP_CFG0(base) ((base) + 0x2c80)
+#define PCIE_HDP_CFG1(base) ((base) + 0x2c84)
+#define PCIE_HDP_CFG2(base) ((base) + 0x2c88)
+#define PCIE_HDP_CFG3(base) ((base) + 0x2c8c)
+#define PCIE_HDP_CFG4(base) ((base) + 0x2c90)
+#define PCIE_HDP_CFG5(base) ((base) + 0x2c94)
+#define PCIE_HDP_CFG6(base) ((base) + 0x2c98)
+#define PCIE_HDP_CFG7(base) ((base) + 0x2c9c)
+#define PCIE_HDP_CFG8(base) ((base) + 0x2ca0)
+#define PCIE_HDP_CFG9(base) ((base) + 0x2ca4)
+#define PCIE_HDP_CFG10(base) ((base) + 0x2ca8)
+#define PCIE_HDP_CFG11(base) ((base) + 0x2cac)
+#define PCIE_INT(base) ((base) + 0x2cb0)
+#define PCIE_INT_MASK(base) ((base) + 0x2cb4)
+#define PCIE_MSI_MASK(base) ((base) + 0x2cb8)
+#define PCIE_MSI_PNDG(base) ((base) + 0x2cbc)
+#define PCIE_PRI_CFG(base) ((base) + 0x2cc0)
+#define PCIE_PHY_CR(base) ((base) + 0x2cc4)
+#define PCIE_HDP_CTAG_CTRL(base) ((base) + 0x2cf4)
+#define PCIE_HDP_HHBM_BUF_PTR(base) ((base) + 0x2d00)
+#define PCIE_HDP_HHBM_BUF_PTR_H(base) ((base) + 0x2d04)
+#define PCIE_HDP_HHBM_BUF_FIFO_NOE(base) ((base) + 0x2d04)
+#define PCIE_HDP_RX0DMA_CNT(base) ((base) + 0x2d10)
+#define PCIE_HDP_RX1DMA_CNT(base) ((base) + 0x2d14)
+#define PCIE_HDP_RX2DMA_CNT(base) ((base) + 0x2d18)
+#define PCIE_HDP_RX3DMA_CNT(base) ((base) + 0x2d1c)
+#define PCIE_HDP_TX0DMA_CNT(base) ((base) + 0x2d20)
+#define PCIE_HDP_TX1DMA_CNT(base) ((base) + 0x2d24)
+#define PCIE_HDP_RXDMA_CTRL(base) ((base) + 0x2d28)
+#define PCIE_HDP_TX_HOST_Q_SZ_CTRL(base) ((base) + 0x2d2c)
+#define PCIE_HDP_TX_HOST_Q_BASE_L(base) ((base) + 0x2d30)
+#define PCIE_HDP_TX_HOST_Q_BASE_H(base) ((base) + 0x2d34)
+#define PCIE_HDP_TX_HOST_Q_WR_PTR(base) ((base) + 0x2d38)
+#define PCIE_HDP_TX_HOST_Q_RD_PTR(base) ((base) + 0x2d3c)
+#define PCIE_HDP_TX_HOST_Q_STS(base) ((base) + 0x2d40)
+
+/* Pearl PCIe HBM pool registers */
+#define PCIE_HHBM_CSR_REG(base) ((base) + 0x2e00)
+#define PCIE_HHBM_Q_BASE_REG(base) ((base) + 0x2e04)
+#define PCIE_HHBM_Q_LIMIT_REG(base) ((base) + 0x2e08)
+#define PCIE_HHBM_Q_WR_REG(base) ((base) + 0x2e0c)
+#define PCIE_HHBM_Q_RD_REG(base) ((base) + 0x2e10)
+#define PCIE_HHBM_POOL_DATA_0_H(base) ((base) + 0x2e90)
+#define PCIE_HHBM_CONFIG(base) ((base) + 0x2f9c)
+#define PCIE_HHBM_POOL_REQ_0(base) ((base) + 0x2f10)
+#define PCIE_HHBM_POOL_DATA_0(base) ((base) + 0x2f40)
+#define PCIE_HHBM_WATERMARK_MASKED_INT(base) ((base) + 0x2f68)
+#define PCIE_HHBM_WATERMARK_INT(base) ((base) + 0x2f6c)
+#define PCIE_HHBM_POOL_WATERMARK(base) ((base) + 0x2f70)
+#define PCIE_HHBM_POOL_OVERFLOW_CNT(base) ((base) + 0x2f90)
+#define PCIE_HHBM_POOL_UNDERFLOW_CNT(base) ((base) + 0x2f94)
+#define HBM_INT_STATUS(base) ((base) + 0x2f9c)
+#define PCIE_HHBM_POOL_CNFIG(base) ((base) + 0x2f9c)
+
+/* Pearl PCIe HBM bit field definitions */
+#define HHBM_CONFIG_SOFT_RESET (BIT(8))
+#define HHBM_WR_REQ (BIT(0))
+#define HHBM_RD_REQ (BIT(1))
+#define HHBM_DONE (BIT(31))
+#define HHBM_64BIT (BIT(10))
+
+/* PCIe HDP interrupt status definition */
+#define PCIE_HDP_INT_EP_RXDMA (BIT(0))
+#define PCIE_HDP_INT_HBM_UF (BIT(1))
+#define PCIE_HDP_INT_RX_LEN_ERR (BIT(2))
+#define PCIE_HDP_INT_RX_HDR_LEN_ERR (BIT(3))
+#define PCIE_HDP_INT_EP_TXDMA (BIT(12))
+#define PCIE_HDP_INT_HHBM_UF (BIT(13))
+#define PCIE_HDP_INT_EP_TXEMPTY (BIT(15))
+#define PCIE_HDP_INT_IPC (BIT(29))
+
+/* PCIe interrupt status definition */
+#define PCIE_INT_MSI (BIT(24))
+#define PCIE_INT_INTX (BIT(23))
+
+/* PCIe legacy INTx */
+#define PEARL_PCIE_CFG0_OFFSET (0x6C)
+#define PEARL_ASSERT_INTX (BIT(9))
+
+/* SYS CTL regs */
+#define QTN_PEARL_SYSCTL_LHOST_IRQ_OFFSET (0x001C)
+
+#define QTN_PEARL_IPC_IRQ_WORD(irq) (BIT(irq) | BIT(irq + 16))
+#define QTN_PEARL_LHOST_IPC_IRQ (6)
+#define QTN_PEARL_LHOST_EP_RESET (7)
+
+#endif /* __PEARL_PCIE_H */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
new file mode 100644
index 000000000..a3a285f17
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
@@ -0,0 +1,1234 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2018 Quantenna Communications */
+
+#include <linux/kernel.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/crc32.h>
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+#include <linux/circ_buf.h>
+
+#include "pcie_priv.h"
+#include "topaz_pcie_regs.h"
+#include "topaz_pcie_ipc.h"
+#include "qtn_hw_ids.h"
+#include "core.h"
+#include "bus.h"
+#include "shm_ipc.h"
+#include "debug.h"
+
+#define TOPAZ_TX_BD_SIZE_DEFAULT 128
+#define TOPAZ_RX_BD_SIZE_DEFAULT 256
+
+struct qtnf_topaz_tx_bd {
+ __le32 addr;
+ __le32 info;
+} __packed;
+
+struct qtnf_topaz_rx_bd {
+ __le32 addr;
+ __le32 info;
+} __packed;
+
+struct qtnf_extra_bd_params {
+ __le32 param1;
+ __le32 param2;
+ __le32 param3;
+ __le32 param4;
+} __packed;
+
+#define QTNF_BD_PARAM_OFFSET(n) offsetof(struct qtnf_extra_bd_params, param##n)
+
+struct vmac_pkt_info {
+ __le32 addr;
+ __le32 info;
+};
+
+struct qtnf_topaz_bda {
+ __le16 bda_len;
+ __le16 bda_version;
+ __le32 bda_bootstate;
+ __le32 bda_dma_mask;
+ __le32 bda_dma_offset;
+ __le32 bda_flags;
+ __le32 bda_img;
+ __le32 bda_img_size;
+ __le32 bda_ep2h_irqstatus;
+ __le32 bda_h2ep_irqstatus;
+ __le32 bda_msi_addr;
+ u8 reserved1[56];
+ __le32 bda_flashsz;
+ u8 bda_boardname[PCIE_BDA_NAMELEN];
+ __le32 bda_pci_pre_status;
+ __le32 bda_pci_endian;
+ __le32 bda_pci_post_status;
+ __le32 bda_h2ep_txd_budget;
+ __le32 bda_ep2h_txd_budget;
+ __le32 bda_rc_rx_bd_base;
+ __le32 bda_rc_rx_bd_num;
+ __le32 bda_rc_tx_bd_base;
+ __le32 bda_rc_tx_bd_num;
+ u8 bda_ep_link_state;
+ u8 bda_rc_link_state;
+ u8 bda_rc_msi_enabled;
+ u8 reserved2;
+ __le32 bda_ep_next_pkt;
+ struct vmac_pkt_info request[QTN_PCIE_RC_TX_QUEUE_LEN];
+ struct qtnf_shm_ipc_region bda_shm_reg1 __aligned(4096);
+ struct qtnf_shm_ipc_region bda_shm_reg2 __aligned(4096);
+} __packed;
+
+struct qtnf_pcie_topaz_state {
+ struct qtnf_pcie_bus_priv base;
+ struct qtnf_topaz_bda __iomem *bda;
+
+ dma_addr_t dma_msi_dummy;
+ u32 dma_msi_imwr;
+
+ struct qtnf_topaz_tx_bd *tx_bd_vbase;
+ struct qtnf_topaz_rx_bd *rx_bd_vbase;
+
+ __le32 __iomem *ep_next_rx_pkt;
+ __le32 __iomem *txqueue_wake;
+ __le32 __iomem *ep_pmstate;
+
+ unsigned long rx_pkt_count;
+};
+
+static void qtnf_deassert_intx(struct qtnf_pcie_topaz_state *ts)
+{
+ void __iomem *reg = ts->base.sysctl_bar + TOPAZ_PCIE_CFG0_OFFSET;
+ u32 cfg;
+
+ cfg = readl(reg);
+ cfg &= ~TOPAZ_ASSERT_INTX;
+ qtnf_non_posted_write(cfg, reg);
+}
+
+static inline int qtnf_topaz_intx_asserted(struct qtnf_pcie_topaz_state *ts)
+{
+ void __iomem *reg = ts->base.sysctl_bar + TOPAZ_PCIE_CFG0_OFFSET;
+ u32 cfg = readl(reg);
+
+ return !!(cfg & TOPAZ_ASSERT_INTX);
+}
+
+static void qtnf_topaz_reset_ep(struct qtnf_pcie_topaz_state *ts)
+{
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_RST_EP_IRQ),
+ TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+ msleep(QTN_EP_RESET_WAIT_MS);
+ pci_restore_state(ts->base.pdev);
+}
+
+static void setup_rx_irqs(struct qtnf_pcie_topaz_state *ts)
+{
+ void __iomem *reg = PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(ts->base.dmareg_bar);
+
+ ts->dma_msi_imwr = readl(reg);
+}
+
+static void enable_rx_irqs(struct qtnf_pcie_topaz_state *ts)
+{
+ void __iomem *reg = PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(ts->base.dmareg_bar);
+
+ qtnf_non_posted_write(ts->dma_msi_imwr, reg);
+}
+
+static void disable_rx_irqs(struct qtnf_pcie_topaz_state *ts)
+{
+ void __iomem *reg = PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(ts->base.dmareg_bar);
+
+ qtnf_non_posted_write(QTN_HOST_LO32(ts->dma_msi_dummy), reg);
+}
+
+static void qtnf_topaz_ipc_gen_ep_int(void *arg)
+{
+ struct qtnf_pcie_topaz_state *ts = arg;
+
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_CTRL_IRQ),
+ TOPAZ_CTL_M2L_INT(ts->base.sysctl_bar));
+}
+
+static int qtnf_is_state(__le32 __iomem *reg, u32 state)
+{
+ u32 s = readl(reg);
+
+ return (s == state);
+}
+
+static void qtnf_set_state(__le32 __iomem *reg, u32 state)
+{
+ qtnf_non_posted_write(state, reg);
+}
+
+static int qtnf_poll_state(__le32 __iomem *reg, u32 state, u32 delay_in_ms)
+{
+ u32 timeout = 0;
+
+ while ((qtnf_is_state(reg, state) == 0)) {
+ usleep_range(1000, 1200);
+ if (++timeout > delay_in_ms)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int topaz_alloc_bd_table(struct qtnf_pcie_topaz_state *ts,
+ struct qtnf_topaz_bda __iomem *bda)
+{
+ struct qtnf_extra_bd_params __iomem *extra_params;
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+ dma_addr_t paddr;
+ void *vaddr;
+ int len;
+ int i;
+
+ /* bd table */
+
+ len = priv->tx_bd_num * sizeof(struct qtnf_topaz_tx_bd) +
+ priv->rx_bd_num * sizeof(struct qtnf_topaz_rx_bd) +
+ sizeof(struct qtnf_extra_bd_params);
+
+ vaddr = dmam_alloc_coherent(&priv->pdev->dev, len, &paddr, GFP_KERNEL);
+ if (!vaddr)
+ return -ENOMEM;
+
+ /* tx bd */
+
+ ts->tx_bd_vbase = vaddr;
+ qtnf_non_posted_write(paddr, &bda->bda_rc_tx_bd_base);
+
+ for (i = 0; i < priv->tx_bd_num; i++)
+ ts->tx_bd_vbase[i].info |= cpu_to_le32(QTN_BD_EMPTY);
+
+ pr_debug("TX descriptor table: vaddr=0x%p paddr=%pad\n", vaddr, &paddr);
+
+ priv->tx_bd_r_index = 0;
+ priv->tx_bd_w_index = 0;
+
+ /* rx bd */
+
+ vaddr = ((struct qtnf_topaz_tx_bd *)vaddr) + priv->tx_bd_num;
+ paddr += priv->tx_bd_num * sizeof(struct qtnf_topaz_tx_bd);
+
+ ts->rx_bd_vbase = vaddr;
+ qtnf_non_posted_write(paddr, &bda->bda_rc_rx_bd_base);
+
+ pr_debug("RX descriptor table: vaddr=0x%p paddr=%pad\n", vaddr, &paddr);
+
+ /* extra shared params */
+
+ vaddr = ((struct qtnf_topaz_rx_bd *)vaddr) + priv->rx_bd_num;
+ paddr += priv->rx_bd_num * sizeof(struct qtnf_topaz_rx_bd);
+
+ extra_params = (struct qtnf_extra_bd_params __iomem *)vaddr;
+
+ ts->ep_next_rx_pkt = &extra_params->param1;
+ qtnf_non_posted_write(paddr + QTNF_BD_PARAM_OFFSET(1),
+ &bda->bda_ep_next_pkt);
+ ts->txqueue_wake = &extra_params->param2;
+ ts->ep_pmstate = &extra_params->param3;
+ ts->dma_msi_dummy = paddr + QTNF_BD_PARAM_OFFSET(4);
+
+ return 0;
+}
+
+static int
+topaz_skb2rbd_attach(struct qtnf_pcie_topaz_state *ts, u16 index, u32 wrap)
+{
+ struct qtnf_topaz_rx_bd *rxbd = &ts->rx_bd_vbase[index];
+ struct sk_buff *skb;
+ dma_addr_t paddr;
+
+ skb = netdev_alloc_skb_ip_align(NULL, SKB_BUF_SIZE);
+ if (!skb) {
+ ts->base.rx_skb[index] = NULL;
+ return -ENOMEM;
+ }
+
+ ts->base.rx_skb[index] = skb;
+
+ paddr = dma_map_single(&ts->base.pdev->dev, skb->data, SKB_BUF_SIZE,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(&ts->base.pdev->dev, paddr)) {
+ pr_err("skb mapping error: %pad\n", &paddr);
+ return -ENOMEM;
+ }
+
+ rxbd->addr = cpu_to_le32(QTN_HOST_LO32(paddr));
+ rxbd->info = cpu_to_le32(QTN_BD_EMPTY | wrap);
+
+ ts->base.rx_bd_w_index = index;
+
+ return 0;
+}
+
+static int topaz_alloc_rx_buffers(struct qtnf_pcie_topaz_state *ts)
+{
+ u16 i;
+ int ret = 0;
+
+ memset(ts->rx_bd_vbase, 0x0,
+ ts->base.rx_bd_num * sizeof(struct qtnf_topaz_rx_bd));
+
+ for (i = 0; i < ts->base.rx_bd_num; i++) {
+ ret = topaz_skb2rbd_attach(ts, i, 0);
+ if (ret)
+ break;
+ }
+
+ ts->rx_bd_vbase[ts->base.rx_bd_num - 1].info |=
+ cpu_to_le32(QTN_BD_WRAP);
+
+ return ret;
+}
+
+/* all rx/tx activity should have ceased before calling this function */
+static void qtnf_topaz_free_xfer_buffers(struct qtnf_pcie_topaz_state *ts)
+{
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+ struct qtnf_topaz_rx_bd *rxbd;
+ struct qtnf_topaz_tx_bd *txbd;
+ struct sk_buff *skb;
+ dma_addr_t paddr;
+ int i;
+
+ /* free rx buffers */
+ for (i = 0; i < priv->rx_bd_num; i++) {
+ if (priv->rx_skb && priv->rx_skb[i]) {
+ rxbd = &ts->rx_bd_vbase[i];
+ skb = priv->rx_skb[i];
+ paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(rxbd->addr));
+ dma_unmap_single(&priv->pdev->dev, paddr,
+ SKB_BUF_SIZE, DMA_FROM_DEVICE);
+ dev_kfree_skb_any(skb);
+ priv->rx_skb[i] = NULL;
+ rxbd->addr = 0;
+ rxbd->info = 0;
+ }
+ }
+
+ /* free tx buffers */
+ for (i = 0; i < priv->tx_bd_num; i++) {
+ if (priv->tx_skb && priv->tx_skb[i]) {
+ txbd = &ts->tx_bd_vbase[i];
+ skb = priv->tx_skb[i];
+ paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(txbd->addr));
+ dma_unmap_single(&priv->pdev->dev, paddr,
+ SKB_BUF_SIZE, DMA_TO_DEVICE);
+ dev_kfree_skb_any(skb);
+ priv->tx_skb[i] = NULL;
+ txbd->addr = 0;
+ txbd->info = 0;
+ }
+ }
+}
+
+static int qtnf_pcie_topaz_init_xfer(struct qtnf_pcie_topaz_state *ts,
+ unsigned int tx_bd_size,
+ unsigned int rx_bd_size)
+{
+ struct qtnf_topaz_bda __iomem *bda = ts->bda;
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+ int ret;
+
+ if (tx_bd_size == 0)
+ tx_bd_size = TOPAZ_TX_BD_SIZE_DEFAULT;
+
+ /* check TX BD queue max length according to struct qtnf_topaz_bda */
+ if (tx_bd_size > QTN_PCIE_RC_TX_QUEUE_LEN) {
+ pr_warn("TX BD queue cannot exceed %d\n",
+ QTN_PCIE_RC_TX_QUEUE_LEN);
+ tx_bd_size = QTN_PCIE_RC_TX_QUEUE_LEN;
+ }
+
+ priv->tx_bd_num = tx_bd_size;
+ qtnf_non_posted_write(priv->tx_bd_num, &bda->bda_rc_tx_bd_num);
+
+ if (rx_bd_size == 0)
+ rx_bd_size = TOPAZ_RX_BD_SIZE_DEFAULT;
+
+ if (rx_bd_size > TOPAZ_RX_BD_SIZE_DEFAULT) {
+ pr_warn("RX BD queue cannot exceed %d\n",
+ TOPAZ_RX_BD_SIZE_DEFAULT);
+ rx_bd_size = TOPAZ_RX_BD_SIZE_DEFAULT;
+ }
+
+ priv->rx_bd_num = rx_bd_size;
+ qtnf_non_posted_write(priv->rx_bd_num, &bda->bda_rc_rx_bd_num);
+
+ priv->rx_bd_w_index = 0;
+ priv->rx_bd_r_index = 0;
+
+ ret = qtnf_pcie_alloc_skb_array(priv);
+ if (ret) {
+ pr_err("failed to allocate skb array\n");
+ return ret;
+ }
+
+ ret = topaz_alloc_bd_table(ts, bda);
+ if (ret) {
+ pr_err("failed to allocate bd table\n");
+ return ret;
+ }
+
+ ret = topaz_alloc_rx_buffers(ts);
+ if (ret) {
+ pr_err("failed to allocate rx buffers\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static void qtnf_topaz_data_tx_reclaim(struct qtnf_pcie_topaz_state *ts)
+{
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+ struct qtnf_topaz_tx_bd *txbd;
+ struct sk_buff *skb;
+ unsigned long flags;
+ dma_addr_t paddr;
+ u32 tx_done_index;
+ int count = 0;
+ int i;
+
+ spin_lock_irqsave(&priv->tx_reclaim_lock, flags);
+
+ tx_done_index = readl(ts->ep_next_rx_pkt);
+ i = priv->tx_bd_r_index;
+
+ if (CIRC_CNT(priv->tx_bd_w_index, tx_done_index, priv->tx_bd_num))
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_DONE_IRQ),
+ TOPAZ_LH_IPC4_INT(priv->sysctl_bar));
+
+ while (CIRC_CNT(tx_done_index, i, priv->tx_bd_num)) {
+ skb = priv->tx_skb[i];
+
+ if (likely(skb)) {
+ txbd = &ts->tx_bd_vbase[i];
+ paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(txbd->addr));
+ dma_unmap_single(&priv->pdev->dev, paddr, skb->len,
+ DMA_TO_DEVICE);
+
+ if (skb->dev) {
+ dev_sw_netstats_tx_add(skb->dev, 1, skb->len);
+ if (unlikely(priv->tx_stopped)) {
+ qtnf_wake_all_queues(skb->dev);
+ priv->tx_stopped = 0;
+ }
+ }
+
+ dev_kfree_skb_any(skb);
+ }
+
+ priv->tx_skb[i] = NULL;
+ count++;
+
+ if (++i >= priv->tx_bd_num)
+ i = 0;
+ }
+
+ priv->tx_reclaim_done += count;
+ priv->tx_reclaim_req++;
+ priv->tx_bd_r_index = i;
+
+ spin_unlock_irqrestore(&priv->tx_reclaim_lock, flags);
+}
+
+static void qtnf_try_stop_xmit(struct qtnf_bus *bus, struct net_device *ndev)
+{
+ struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+
+ if (ndev) {
+ netif_tx_stop_all_queues(ndev);
+ ts->base.tx_stopped = 1;
+ }
+
+ writel(0x0, ts->txqueue_wake);
+
+ /* sync up tx queue status before generating interrupt */
+ dma_wmb();
+
+ /* send irq to card: tx stopped */
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_STOP_IRQ),
+ TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+
+ /* schedule reclaim attempt */
+ tasklet_hi_schedule(&ts->base.reclaim_tq);
+}
+
+static void qtnf_try_wake_xmit(struct qtnf_bus *bus, struct net_device *ndev)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+ int ready;
+
+ ready = readl(ts->txqueue_wake);
+ if (ready) {
+ netif_wake_queue(ndev);
+ } else {
+ /* re-send irq to card: tx stopped */
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_STOP_IRQ),
+ TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+ }
+}
+
+static int qtnf_tx_queue_ready(struct qtnf_pcie_topaz_state *ts)
+{
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+
+ if (!CIRC_SPACE(priv->tx_bd_w_index, priv->tx_bd_r_index,
+ priv->tx_bd_num)) {
+ qtnf_topaz_data_tx_reclaim(ts);
+
+ if (!CIRC_SPACE(priv->tx_bd_w_index, priv->tx_bd_r_index,
+ priv->tx_bd_num)) {
+ priv->tx_full_count++;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int qtnf_pcie_data_tx(struct qtnf_bus *bus, struct sk_buff *skb,
+ unsigned int macid, unsigned int vifid)
+{
+ struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+ struct qtnf_topaz_bda __iomem *bda = ts->bda;
+ struct qtnf_topaz_tx_bd *txbd;
+ dma_addr_t skb_paddr;
+ unsigned long flags;
+ int ret = 0;
+ int len;
+ int i;
+
+ spin_lock_irqsave(&priv->tx_lock, flags);
+
+ if (!qtnf_tx_queue_ready(ts)) {
+ qtnf_try_stop_xmit(bus, skb->dev);
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+ return NETDEV_TX_BUSY;
+ }
+
+ i = priv->tx_bd_w_index;
+ priv->tx_skb[i] = skb;
+ len = skb->len;
+
+ skb_paddr = dma_map_single(&priv->pdev->dev, skb->data, skb->len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(&priv->pdev->dev, skb_paddr)) {
+ ret = -ENOMEM;
+ goto tx_done;
+ }
+
+ txbd = &ts->tx_bd_vbase[i];
+ txbd->addr = cpu_to_le32(QTN_HOST_LO32(skb_paddr));
+
+ writel(QTN_HOST_LO32(skb_paddr), &bda->request[i].addr);
+ writel(len | QTN_PCIE_TX_VALID_PKT, &bda->request[i].info);
+
+ /* sync up descriptor updates before generating interrupt */
+ dma_wmb();
+
+ /* generate irq to card: tx done */
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_DONE_IRQ),
+ TOPAZ_LH_IPC4_INT(priv->sysctl_bar));
+
+ if (++i >= priv->tx_bd_num)
+ i = 0;
+
+ priv->tx_bd_w_index = i;
+
+tx_done:
+ if (ret) {
+ if (skb->dev)
+ skb->dev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+
+ priv->tx_done_count++;
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+ qtnf_topaz_data_tx_reclaim(ts);
+
+ return NETDEV_TX_OK;
+}
+
+static irqreturn_t qtnf_pcie_topaz_interrupt(int irq, void *data)
+{
+ struct qtnf_bus *bus = (struct qtnf_bus *)data;
+ struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+
+ if (!priv->msi_enabled && !qtnf_topaz_intx_asserted(ts))
+ return IRQ_NONE;
+
+ if (!priv->msi_enabled)
+ qtnf_deassert_intx(ts);
+
+ priv->pcie_irq_count++;
+
+ qtnf_shm_ipc_irq_handler(&priv->shm_ipc_ep_in);
+ qtnf_shm_ipc_irq_handler(&priv->shm_ipc_ep_out);
+
+ if (napi_schedule_prep(&bus->mux_napi)) {
+ disable_rx_irqs(ts);
+ __napi_schedule(&bus->mux_napi);
+ }
+
+ tasklet_hi_schedule(&priv->reclaim_tq);
+
+ return IRQ_HANDLED;
+}
+
+static int qtnf_rx_data_ready(struct qtnf_pcie_topaz_state *ts)
+{
+ u16 index = ts->base.rx_bd_r_index;
+ struct qtnf_topaz_rx_bd *rxbd;
+ u32 descw;
+
+ rxbd = &ts->rx_bd_vbase[index];
+ descw = le32_to_cpu(rxbd->info);
+
+ if (descw & QTN_BD_EMPTY)
+ return 0;
+
+ return 1;
+}
+
+static int qtnf_topaz_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct qtnf_bus *bus = container_of(napi, struct qtnf_bus, mux_napi);
+ struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+ struct net_device *ndev = NULL;
+ struct sk_buff *skb = NULL;
+ int processed = 0;
+ struct qtnf_topaz_rx_bd *rxbd;
+ dma_addr_t skb_paddr;
+ int consume;
+ u32 descw;
+ u32 poffset;
+ u32 psize;
+ u16 r_idx;
+ u16 w_idx;
+ int ret;
+
+ while (processed < budget) {
+ if (!qtnf_rx_data_ready(ts))
+ goto rx_out;
+
+ r_idx = priv->rx_bd_r_index;
+ rxbd = &ts->rx_bd_vbase[r_idx];
+ descw = le32_to_cpu(rxbd->info);
+
+ skb = priv->rx_skb[r_idx];
+ poffset = QTN_GET_OFFSET(descw);
+ psize = QTN_GET_LEN(descw);
+ consume = 1;
+
+ if (descw & QTN_BD_EMPTY) {
+ pr_warn("skip invalid rxbd[%d]\n", r_idx);
+ consume = 0;
+ }
+
+ if (!skb) {
+ pr_warn("skip missing rx_skb[%d]\n", r_idx);
+ consume = 0;
+ }
+
+ if (skb && (skb_tailroom(skb) < psize)) {
+ pr_err("skip packet with invalid length: %u > %u\n",
+ psize, skb_tailroom(skb));
+ consume = 0;
+ }
+
+ if (skb) {
+ skb_paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(rxbd->addr));
+ dma_unmap_single(&priv->pdev->dev, skb_paddr,
+ SKB_BUF_SIZE, DMA_FROM_DEVICE);
+ }
+
+ if (consume) {
+ skb_reserve(skb, poffset);
+ skb_put(skb, psize);
+ ndev = qtnf_classify_skb(bus, skb);
+ if (likely(ndev)) {
+ dev_sw_netstats_rx_add(ndev, skb->len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ netif_receive_skb(skb);
+ } else {
+ pr_debug("drop untagged skb\n");
+ bus->mux_dev->stats.rx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+ } else {
+ if (skb) {
+ bus->mux_dev->stats.rx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+ }
+
+ /* notify card about recv packets once per several packets */
+ if (((++ts->rx_pkt_count) & RX_DONE_INTR_MSK) == 0)
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_RX_DONE_IRQ),
+ TOPAZ_LH_IPC4_INT(priv->sysctl_bar));
+
+ priv->rx_skb[r_idx] = NULL;
+ if (++r_idx >= priv->rx_bd_num)
+ r_idx = 0;
+
+ priv->rx_bd_r_index = r_idx;
+
+ /* repalce processed buffer by a new one */
+ w_idx = priv->rx_bd_w_index;
+ while (CIRC_SPACE(priv->rx_bd_w_index, priv->rx_bd_r_index,
+ priv->rx_bd_num) > 0) {
+ if (++w_idx >= priv->rx_bd_num)
+ w_idx = 0;
+
+ ret = topaz_skb2rbd_attach(ts, w_idx,
+ descw & QTN_BD_WRAP);
+ if (ret) {
+ pr_err("failed to allocate new rx_skb[%d]\n",
+ w_idx);
+ break;
+ }
+ }
+
+ processed++;
+ }
+
+rx_out:
+ if (processed < budget) {
+ napi_complete(napi);
+ enable_rx_irqs(ts);
+ }
+
+ return processed;
+}
+
+static void
+qtnf_pcie_data_tx_timeout(struct qtnf_bus *bus, struct net_device *ndev)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+ qtnf_try_wake_xmit(bus, ndev);
+ tasklet_hi_schedule(&ts->base.reclaim_tq);
+}
+
+static void qtnf_pcie_data_rx_start(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+ napi_enable(&bus->mux_napi);
+ enable_rx_irqs(ts);
+}
+
+static void qtnf_pcie_data_rx_stop(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+ disable_rx_irqs(ts);
+ napi_disable(&bus->mux_napi);
+}
+
+static struct qtnf_bus_ops qtnf_pcie_topaz_bus_ops = {
+ /* control path methods */
+ .control_tx = qtnf_pcie_control_tx,
+
+ /* data path methods */
+ .data_tx = qtnf_pcie_data_tx,
+ .data_tx_timeout = qtnf_pcie_data_tx_timeout,
+ .data_rx_start = qtnf_pcie_data_rx_start,
+ .data_rx_stop = qtnf_pcie_data_rx_stop,
+};
+
+static int qtnf_dbg_irq_stats(struct seq_file *s, void *data)
+{
+ struct qtnf_bus *bus = dev_get_drvdata(s->private);
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+ seq_printf(s, "pcie_irq_count(%u)\n", ts->base.pcie_irq_count);
+
+ return 0;
+}
+
+static int qtnf_dbg_pkt_stats(struct seq_file *s, void *data)
+{
+ struct qtnf_bus *bus = dev_get_drvdata(s->private);
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+ struct qtnf_pcie_bus_priv *priv = &ts->base;
+ u32 tx_done_index = readl(ts->ep_next_rx_pkt);
+
+ seq_printf(s, "tx_full_count(%u)\n", priv->tx_full_count);
+ seq_printf(s, "tx_done_count(%u)\n", priv->tx_done_count);
+ seq_printf(s, "tx_reclaim_done(%u)\n", priv->tx_reclaim_done);
+ seq_printf(s, "tx_reclaim_req(%u)\n", priv->tx_reclaim_req);
+
+ seq_printf(s, "tx_bd_r_index(%u)\n", priv->tx_bd_r_index);
+ seq_printf(s, "tx_done_index(%u)\n", tx_done_index);
+ seq_printf(s, "tx_bd_w_index(%u)\n", priv->tx_bd_w_index);
+
+ seq_printf(s, "tx host queue len(%u)\n",
+ CIRC_CNT(priv->tx_bd_w_index, priv->tx_bd_r_index,
+ priv->tx_bd_num));
+ seq_printf(s, "tx reclaim queue len(%u)\n",
+ CIRC_CNT(tx_done_index, priv->tx_bd_r_index,
+ priv->tx_bd_num));
+ seq_printf(s, "tx card queue len(%u)\n",
+ CIRC_CNT(priv->tx_bd_w_index, tx_done_index,
+ priv->tx_bd_num));
+
+ seq_printf(s, "rx_bd_r_index(%u)\n", priv->rx_bd_r_index);
+ seq_printf(s, "rx_bd_w_index(%u)\n", priv->rx_bd_w_index);
+ seq_printf(s, "rx alloc queue len(%u)\n",
+ CIRC_SPACE(priv->rx_bd_w_index, priv->rx_bd_r_index,
+ priv->rx_bd_num));
+
+ return 0;
+}
+
+static void qtnf_reset_dma_offset(struct qtnf_pcie_topaz_state *ts)
+{
+ struct qtnf_topaz_bda __iomem *bda = ts->bda;
+ u32 offset = readl(&bda->bda_dma_offset);
+
+ if ((offset & PCIE_DMA_OFFSET_ERROR_MASK) != PCIE_DMA_OFFSET_ERROR)
+ return;
+
+ writel(0x0, &bda->bda_dma_offset);
+}
+
+static int qtnf_pcie_endian_detect(struct qtnf_pcie_topaz_state *ts)
+{
+ struct qtnf_topaz_bda __iomem *bda = ts->bda;
+ u32 timeout = 0;
+ u32 endian;
+ int ret = 0;
+
+ writel(QTN_PCI_ENDIAN_DETECT_DATA, &bda->bda_pci_endian);
+
+ /* flush endian modifications before status update */
+ dma_wmb();
+
+ writel(QTN_PCI_ENDIAN_VALID_STATUS, &bda->bda_pci_pre_status);
+
+ while (readl(&bda->bda_pci_post_status) !=
+ QTN_PCI_ENDIAN_VALID_STATUS) {
+ usleep_range(1000, 1200);
+ if (++timeout > QTN_FW_DL_TIMEOUT_MS) {
+ pr_err("card endianness detection timed out\n");
+ ret = -ETIMEDOUT;
+ goto endian_out;
+ }
+ }
+
+ /* do not read before status is updated */
+ dma_rmb();
+
+ endian = readl(&bda->bda_pci_endian);
+ WARN(endian != QTN_PCI_LITTLE_ENDIAN,
+ "%s: unexpected card endianness", __func__);
+
+endian_out:
+ writel(0, &bda->bda_pci_pre_status);
+ writel(0, &bda->bda_pci_post_status);
+ writel(0, &bda->bda_pci_endian);
+
+ return ret;
+}
+
+static int qtnf_pre_init_ep(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+ struct qtnf_topaz_bda __iomem *bda = ts->bda;
+ u32 flags;
+ int ret;
+
+ ret = qtnf_pcie_endian_detect(ts);
+ if (ret < 0) {
+ pr_err("failed to detect card endianness\n");
+ return ret;
+ }
+
+ writeb(ts->base.msi_enabled, &ts->bda->bda_rc_msi_enabled);
+ qtnf_reset_dma_offset(ts);
+
+ /* notify card about driver type and boot mode */
+ flags = readl(&bda->bda_flags) | QTN_BDA_HOST_QLINK_DRV;
+
+ if (ts->base.flashboot)
+ flags |= QTN_BDA_FLASH_BOOT;
+ else
+ flags &= ~QTN_BDA_FLASH_BOOT;
+
+ writel(flags, &bda->bda_flags);
+
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_HOST_RDY);
+ if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_TARGET_RDY,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("card is not ready to boot...\n");
+ return -ETIMEDOUT;
+ }
+
+ return ret;
+}
+
+static int qtnf_post_init_ep(struct qtnf_pcie_topaz_state *ts)
+{
+ struct pci_dev *pdev = ts->base.pdev;
+
+ setup_rx_irqs(ts);
+ disable_rx_irqs(ts);
+
+ if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_QLINK_DONE,
+ QTN_FW_QLINK_TIMEOUT_MS))
+ return -ETIMEDOUT;
+
+ enable_irq(pdev->irq);
+ return 0;
+}
+
+static int
+qtnf_ep_fw_load(struct qtnf_pcie_topaz_state *ts, const u8 *fw, u32 fw_size)
+{
+ struct qtnf_topaz_bda __iomem *bda = ts->bda;
+ struct pci_dev *pdev = ts->base.pdev;
+ u32 remaining = fw_size;
+ u8 *curr = (u8 *)fw;
+ u32 blksize;
+ u32 nblocks;
+ u32 offset;
+ u32 count;
+ u32 size;
+ dma_addr_t paddr;
+ void *data;
+ int ret = 0;
+
+ pr_debug("FW upload started: fw_addr = 0x%p, size=%d\n", fw, fw_size);
+
+ blksize = ts->base.fw_blksize;
+
+ if (blksize < PAGE_SIZE)
+ blksize = PAGE_SIZE;
+
+ while (blksize >= PAGE_SIZE) {
+ pr_debug("allocating %u bytes to upload FW\n", blksize);
+ data = dma_alloc_coherent(&pdev->dev, blksize,
+ &paddr, GFP_KERNEL);
+ if (data)
+ break;
+ blksize /= 2;
+ }
+
+ if (!data) {
+ pr_err("failed to allocate DMA buffer for FW upload\n");
+ ret = -ENOMEM;
+ goto fw_load_out;
+ }
+
+ nblocks = NBLOCKS(fw_size, blksize);
+ offset = readl(&bda->bda_dma_offset);
+
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_HOST_LOAD);
+ if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_EP_RDY,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("card is not ready to download FW\n");
+ ret = -ETIMEDOUT;
+ goto fw_load_map;
+ }
+
+ for (count = 0 ; count < nblocks; count++) {
+ size = (remaining > blksize) ? blksize : remaining;
+
+ memcpy(data, curr, size);
+ qtnf_non_posted_write(paddr + offset, &bda->bda_img);
+ qtnf_non_posted_write(size, &bda->bda_img_size);
+
+ pr_debug("chunk[%u] VA[0x%p] PA[%pad] sz[%u]\n",
+ count, (void *)curr, &paddr, size);
+
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_RDY);
+ if (qtnf_poll_state(&ts->bda->bda_bootstate,
+ QTN_BDA_FW_BLOCK_DONE,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("confirmation for block #%d timed out\n", count);
+ ret = -ETIMEDOUT;
+ goto fw_load_map;
+ }
+
+ remaining = (remaining < size) ? remaining : (remaining - size);
+ curr += size;
+ }
+
+ /* upload completion mark: zero-sized block */
+ qtnf_non_posted_write(0, &bda->bda_img);
+ qtnf_non_posted_write(0, &bda->bda_img_size);
+
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_RDY);
+ if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_DONE,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("confirmation for the last block timed out\n");
+ ret = -ETIMEDOUT;
+ goto fw_load_map;
+ }
+
+ /* RC is done */
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_END);
+ if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_LOAD_DONE,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("confirmation for FW upload completion timed out\n");
+ ret = -ETIMEDOUT;
+ goto fw_load_map;
+ }
+
+ pr_debug("FW upload completed: totally sent %d blocks\n", count);
+
+fw_load_map:
+ dma_free_coherent(&pdev->dev, blksize, data, paddr);
+
+fw_load_out:
+ return ret;
+}
+
+static int qtnf_topaz_fw_upload(struct qtnf_pcie_topaz_state *ts,
+ const char *fwname)
+{
+ const struct firmware *fw;
+ struct pci_dev *pdev = ts->base.pdev;
+ int ret;
+
+ if (qtnf_poll_state(&ts->bda->bda_bootstate,
+ QTN_BDA_FW_LOAD_RDY,
+ QTN_FW_DL_TIMEOUT_MS)) {
+ pr_err("%s: card is not ready\n", fwname);
+ return -1;
+ }
+
+ pr_info("starting firmware upload: %s\n", fwname);
+
+ ret = request_firmware(&fw, fwname, &pdev->dev);
+ if (ret < 0) {
+ pr_err("%s: request_firmware error %d\n", fwname, ret);
+ return -1;
+ }
+
+ ret = qtnf_ep_fw_load(ts, fw->data, fw->size);
+ release_firmware(fw);
+
+ if (ret)
+ pr_err("%s: FW upload error\n", fwname);
+
+ return ret;
+}
+
+static void qtnf_topaz_fw_work_handler(struct work_struct *work)
+{
+ struct qtnf_bus *bus = container_of(work, struct qtnf_bus, fw_work);
+ struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+ int bootloader_needed = readl(&ts->bda->bda_flags) & QTN_BDA_XMIT_UBOOT;
+ struct pci_dev *pdev = ts->base.pdev;
+ int ret;
+
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_TARGET_BOOT);
+
+ if (bootloader_needed) {
+ ret = qtnf_topaz_fw_upload(ts, QTN_PCI_TOPAZ_BOOTLD_NAME);
+ if (ret)
+ goto fw_load_exit;
+
+ ret = qtnf_pre_init_ep(bus);
+ if (ret)
+ goto fw_load_exit;
+
+ qtnf_set_state(&ts->bda->bda_bootstate,
+ QTN_BDA_FW_TARGET_BOOT);
+ }
+
+ if (ts->base.flashboot) {
+ pr_info("booting firmware from flash\n");
+
+ ret = qtnf_poll_state(&ts->bda->bda_bootstate,
+ QTN_BDA_FW_FLASH_BOOT,
+ QTN_FW_DL_TIMEOUT_MS);
+ if (ret)
+ goto fw_load_exit;
+ } else {
+ ret = qtnf_topaz_fw_upload(ts, QTN_PCI_TOPAZ_FW_NAME);
+ if (ret)
+ goto fw_load_exit;
+
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_START);
+ ret = qtnf_poll_state(&ts->bda->bda_bootstate,
+ QTN_BDA_FW_CONFIG,
+ QTN_FW_QLINK_TIMEOUT_MS);
+ if (ret) {
+ pr_err("FW bringup timed out\n");
+ goto fw_load_exit;
+ }
+
+ qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_RUN);
+ ret = qtnf_poll_state(&ts->bda->bda_bootstate,
+ QTN_BDA_FW_RUNNING,
+ QTN_FW_QLINK_TIMEOUT_MS);
+ if (ret) {
+ pr_err("card bringup timed out\n");
+ goto fw_load_exit;
+ }
+ }
+
+ ret = qtnf_post_init_ep(ts);
+ if (ret) {
+ pr_err("FW runtime failure\n");
+ goto fw_load_exit;
+ }
+
+ pr_info("firmware is up and running\n");
+
+ ret = qtnf_pcie_fw_boot_done(bus);
+ if (ret)
+ goto fw_load_exit;
+
+ qtnf_debugfs_add_entry(bus, "pkt_stats", qtnf_dbg_pkt_stats);
+ qtnf_debugfs_add_entry(bus, "irq_stats", qtnf_dbg_irq_stats);
+
+fw_load_exit:
+ put_device(&pdev->dev);
+}
+
+static void qtnf_reclaim_tasklet_fn(struct tasklet_struct *t)
+{
+ struct qtnf_pcie_topaz_state *ts = from_tasklet(ts, t, base.reclaim_tq);
+
+ qtnf_topaz_data_tx_reclaim(ts);
+}
+
+static u64 qtnf_topaz_dma_mask_get(void)
+{
+ return DMA_BIT_MASK(32);
+}
+
+static int qtnf_pcie_topaz_probe(struct qtnf_bus *bus,
+ unsigned int tx_bd_num, unsigned int rx_bd_num)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+ struct pci_dev *pdev = ts->base.pdev;
+ struct qtnf_shm_ipc_int ipc_int;
+ unsigned long irqflags;
+ int ret;
+
+ bus->bus_ops = &qtnf_pcie_topaz_bus_ops;
+ INIT_WORK(&bus->fw_work, qtnf_topaz_fw_work_handler);
+ ts->bda = ts->base.epmem_bar;
+
+ /* assign host msi irq before card init */
+ if (ts->base.msi_enabled)
+ irqflags = IRQF_NOBALANCING | IRQF_NO_AUTOEN;
+ else
+ irqflags = IRQF_NOBALANCING | IRQF_SHARED | IRQF_NO_AUTOEN;
+
+ ret = devm_request_irq(&pdev->dev, pdev->irq,
+ &qtnf_pcie_topaz_interrupt,
+ irqflags, "qtnf_topaz_irq", (void *)bus);
+ if (ret) {
+ pr_err("failed to request pcie irq %d\n", pdev->irq);
+ return ret;
+ }
+
+ ret = qtnf_pre_init_ep(bus);
+ if (ret) {
+ pr_err("failed to init card\n");
+ return ret;
+ }
+
+ ret = qtnf_pcie_topaz_init_xfer(ts, tx_bd_num, rx_bd_num);
+ if (ret) {
+ pr_err("PCIE xfer init failed\n");
+ return ret;
+ }
+
+ tasklet_setup(&ts->base.reclaim_tq, qtnf_reclaim_tasklet_fn);
+ netif_napi_add_weight(bus->mux_dev, &bus->mux_napi,
+ qtnf_topaz_rx_poll, 10);
+
+ ipc_int.fn = qtnf_topaz_ipc_gen_ep_int;
+ ipc_int.arg = ts;
+ qtnf_pcie_init_shm_ipc(&ts->base, &ts->bda->bda_shm_reg1,
+ &ts->bda->bda_shm_reg2, &ipc_int);
+
+ return 0;
+}
+
+static void qtnf_pcie_topaz_remove(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+ qtnf_topaz_reset_ep(ts);
+ qtnf_topaz_free_xfer_buffers(ts);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int qtnf_pcie_topaz_suspend(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+ struct pci_dev *pdev = ts->base.pdev;
+
+ writel((u32 __force)PCI_D3hot, ts->ep_pmstate);
+ dma_wmb();
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_PM_EP_IRQ),
+ TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+
+ pci_save_state(pdev);
+ pci_enable_wake(pdev, PCI_D3hot, 1);
+ pci_set_power_state(pdev, PCI_D3hot);
+
+ return 0;
+}
+
+static int qtnf_pcie_topaz_resume(struct qtnf_bus *bus)
+{
+ struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+ struct pci_dev *pdev = ts->base.pdev;
+
+ pci_set_power_state(pdev, PCI_D0);
+ pci_restore_state(pdev);
+ pci_enable_wake(pdev, PCI_D0, 0);
+
+ writel((u32 __force)PCI_D0, ts->ep_pmstate);
+ dma_wmb();
+ writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_PM_EP_IRQ),
+ TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+
+ return 0;
+}
+#endif
+
+struct qtnf_bus *qtnf_pcie_topaz_alloc(struct pci_dev *pdev)
+{
+ struct qtnf_bus *bus;
+ struct qtnf_pcie_topaz_state *ts;
+
+ bus = devm_kzalloc(&pdev->dev, sizeof(*bus) + sizeof(*ts), GFP_KERNEL);
+ if (!bus)
+ return NULL;
+
+ ts = get_bus_priv(bus);
+ ts->base.probe_cb = qtnf_pcie_topaz_probe;
+ ts->base.remove_cb = qtnf_pcie_topaz_remove;
+ ts->base.dma_mask_get_cb = qtnf_topaz_dma_mask_get;
+#ifdef CONFIG_PM_SLEEP
+ ts->base.resume_cb = qtnf_pcie_topaz_resume;
+ ts->base.suspend_cb = qtnf_pcie_topaz_suspend;
+#endif
+
+ return bus;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h
new file mode 100644
index 000000000..eb30e9d08
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2018 Quantenna Communications */
+
+#ifndef _QTN_FMAC_PCIE_IPC_H_
+#define _QTN_FMAC_PCIE_IPC_H_
+
+#include <linux/types.h>
+
+#include "shm_ipc_defs.h"
+
+/* EP/RC status and flags */
+#define QTN_BDA_PCIE_INIT 0x01
+#define QTN_BDA_PCIE_RDY 0x02
+#define QTN_BDA_FW_LOAD_RDY 0x03
+#define QTN_BDA_FW_LOAD_DONE 0x04
+#define QTN_BDA_FW_START 0x05
+#define QTN_BDA_FW_RUN 0x06
+#define QTN_BDA_FW_HOST_RDY 0x07
+#define QTN_BDA_FW_TARGET_RDY 0x11
+#define QTN_BDA_FW_TARGET_BOOT 0x12
+#define QTN_BDA_FW_FLASH_BOOT 0x13
+#define QTN_BDA_FW_QLINK_DONE 0x14
+#define QTN_BDA_FW_HOST_LOAD 0x08
+#define QTN_BDA_FW_BLOCK_DONE 0x09
+#define QTN_BDA_FW_BLOCK_RDY 0x0A
+#define QTN_BDA_FW_EP_RDY 0x0B
+#define QTN_BDA_FW_BLOCK_END 0x0C
+#define QTN_BDA_FW_CONFIG 0x0D
+#define QTN_BDA_FW_RUNNING 0x0E
+#define QTN_BDA_PCIE_FAIL 0x82
+#define QTN_BDA_FW_LOAD_FAIL 0x85
+
+#define QTN_BDA_RCMODE BIT(1)
+#define QTN_BDA_MSI BIT(2)
+#define QTN_BDA_HOST_CALCMD BIT(3)
+#define QTN_BDA_FLASH_PRESENT BIT(4)
+#define QTN_BDA_FLASH_BOOT BIT(5)
+#define QTN_BDA_XMIT_UBOOT BIT(6)
+#define QTN_BDA_HOST_QLINK_DRV BIT(7)
+#define QTN_BDA_TARGET_FBOOT_ERR BIT(8)
+#define QTN_BDA_TARGET_FWLOAD_ERR BIT(9)
+#define QTN_BDA_HOST_NOFW_ERR BIT(12)
+#define QTN_BDA_HOST_MEMALLOC_ERR BIT(13)
+#define QTN_BDA_HOST_MEMMAP_ERR BIT(14)
+#define QTN_BDA_VER(x) (((x) >> 4) & 0xFF)
+#define QTN_BDA_ERROR_MASK 0xFF00
+
+/* registers and shmem address macros */
+#if BITS_PER_LONG == 64
+#define QTN_HOST_HI32(a) ((u32)(((u64)a) >> 32))
+#define QTN_HOST_LO32(a) ((u32)(((u64)a) & 0xffffffffUL))
+#define QTN_HOST_ADDR(h, l) ((((u64)h) << 32) | ((u64)l))
+#elif BITS_PER_LONG == 32
+#define QTN_HOST_HI32(a) 0
+#define QTN_HOST_LO32(a) ((u32)(((u32)a) & 0xffffffffUL))
+#define QTN_HOST_ADDR(h, l) ((u32)l)
+#else
+#error Unexpected BITS_PER_LONG value
+#endif
+
+#define QTN_PCIE_BDA_VERSION 0x1001
+
+#define PCIE_BDA_NAMELEN 32
+
+#define QTN_PCIE_RC_TX_QUEUE_LEN 256
+#define QTN_PCIE_TX_VALID_PKT 0x80000000
+#define QTN_PCIE_PKT_LEN_MASK 0xffff
+
+#define QTN_BD_EMPTY ((uint32_t)0x00000001)
+#define QTN_BD_WRAP ((uint32_t)0x00000002)
+#define QTN_BD_MASK_LEN ((uint32_t)0xFFFF0000)
+#define QTN_BD_MASK_OFFSET ((uint32_t)0x0000FF00)
+
+#define QTN_GET_LEN(x) (((x) >> 16) & 0xFFFF)
+#define QTN_GET_OFFSET(x) (((x) >> 8) & 0xFF)
+#define QTN_SET_LEN(len) (((len) & 0xFFFF) << 16)
+#define QTN_SET_OFFSET(of) (((of) & 0xFF) << 8)
+
+#define RX_DONE_INTR_MSK ((0x1 << 6) - 1)
+
+#define PCIE_DMA_OFFSET_ERROR 0xFFFF
+#define PCIE_DMA_OFFSET_ERROR_MASK 0xFFFF
+
+#define QTN_PCI_ENDIAN_DETECT_DATA 0x12345678
+#define QTN_PCI_ENDIAN_REVERSE_DATA 0x78563412
+#define QTN_PCI_ENDIAN_VALID_STATUS 0x3c3c3c3c
+#define QTN_PCI_ENDIAN_INVALID_STATUS 0
+#define QTN_PCI_LITTLE_ENDIAN 0
+#define QTN_PCI_BIG_ENDIAN 0xffffffff
+
+#define NBLOCKS(size, blksize) \
+ ((size) / (blksize) + (((size) % (blksize) > 0) ? 1 : 0))
+
+#endif /* _QTN_FMAC_PCIE_IPC_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h
new file mode 100644
index 000000000..4782e1ed3
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2018 Quantenna Communications */
+
+#ifndef __TOPAZ_PCIE_H
+#define __TOPAZ_PCIE_H
+
+/* Topaz PCIe DMA registers */
+#define PCIE_DMA_WR_INTR_STATUS(base) ((base) + 0x9bc)
+#define PCIE_DMA_WR_INTR_MASK(base) ((base) + 0x9c4)
+#define PCIE_DMA_WR_INTR_CLR(base) ((base) + 0x9c8)
+#define PCIE_DMA_WR_ERR_STATUS(base) ((base) + 0x9cc)
+#define PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(base) ((base) + 0x9D0)
+#define PCIE_DMA_WR_DONE_IMWR_ADDR_HIGH(base) ((base) + 0x9d4)
+
+#define PCIE_DMA_RD_INTR_STATUS(base) ((base) + 0x310)
+#define PCIE_DMA_RD_INTR_MASK(base) ((base) + 0x319)
+#define PCIE_DMA_RD_INTR_CLR(base) ((base) + 0x31c)
+#define PCIE_DMA_RD_ERR_STATUS_LOW(base) ((base) + 0x324)
+#define PCIE_DMA_RD_ERR_STATUS_HIGH(base) ((base) + 0x328)
+#define PCIE_DMA_RD_DONE_IMWR_ADDR_LOW(base) ((base) + 0x33c)
+#define PCIE_DMA_RD_DONE_IMWR_ADDR_HIGH(base) ((base) + 0x340)
+
+/* Topaz LHost IPC4 interrupt */
+#define TOPAZ_LH_IPC4_INT(base) ((base) + 0x13C)
+#define TOPAZ_LH_IPC4_INT_MASK(base) ((base) + 0x140)
+
+#define TOPAZ_RC_TX_DONE_IRQ (0)
+#define TOPAZ_RC_RST_EP_IRQ (1)
+#define TOPAZ_RC_TX_STOP_IRQ (2)
+#define TOPAZ_RC_RX_DONE_IRQ (3)
+#define TOPAZ_RC_PM_EP_IRQ (4)
+
+/* Topaz LHost M2L interrupt */
+#define TOPAZ_CTL_M2L_INT(base) ((base) + 0x2C)
+#define TOPAZ_CTL_M2L_INT_MASK(base) ((base) + 0x30)
+
+#define TOPAZ_RC_CTRL_IRQ (6)
+
+#define TOPAZ_IPC_IRQ_WORD(irq) (BIT(irq) | BIT(irq + 16))
+
+/* PCIe legacy INTx */
+#define TOPAZ_PCIE_CFG0_OFFSET (0x6C)
+#define TOPAZ_ASSERT_INTX BIT(9)
+
+#endif /* __TOPAZ_PCIE_H */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
new file mode 100644
index 000000000..eae35b678
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -0,0 +1,1891 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_QLINK_H_
+#define _QTN_QLINK_H_
+
+#include <linux/ieee80211.h>
+
+#define QLINK_PROTO_VER_MAJOR_M 0xFFFF
+#define QLINK_PROTO_VER_MAJOR_S 16
+#define QLINK_PROTO_VER_MINOR_M 0xFFFF
+#define QLINK_VER_MINOR(_ver) ((_ver) & QLINK_PROTO_VER_MINOR_M)
+#define QLINK_VER_MAJOR(_ver) \
+ (((_ver) >> QLINK_PROTO_VER_MAJOR_S) & QLINK_PROTO_VER_MAJOR_M)
+#define QLINK_VER(_maj, _min) (((_maj) << QLINK_PROTO_VER_MAJOR_S) | (_min))
+
+#define QLINK_PROTO_VER_MAJOR 18
+#define QLINK_PROTO_VER_MINOR 1
+#define QLINK_PROTO_VER \
+ QLINK_VER(QLINK_PROTO_VER_MAJOR, QLINK_PROTO_VER_MINOR)
+
+#define QLINK_ALIGN 4
+
+#define QLINK_MACID_RSVD 0xFF
+#define QLINK_VIFID_RSVD 0xFF
+
+/* Common QLINK protocol messages definitions.
+ */
+
+/**
+ * enum qlink_msg_type - QLINK message types
+ *
+ * Used to distinguish between message types of QLINK protocol.
+ *
+ * @QLINK_MSG_TYPE_CMD: Message is carrying data of a command sent from
+ * driver to wireless hardware.
+ * @QLINK_MSG_TYPE_CMDRSP: Message is carrying data of a response to a command.
+ * Sent from wireless HW to driver in reply to previously issued command.
+ * @QLINK_MSG_TYPE_EVENT: Data for an event originated in wireless hardware and
+ * sent asynchronously to driver.
+ */
+enum qlink_msg_type {
+ QLINK_MSG_TYPE_CMD = 1,
+ QLINK_MSG_TYPE_CMDRSP = 2,
+ QLINK_MSG_TYPE_EVENT = 3
+};
+
+/**
+ * struct qlink_msg_header - common QLINK protocol message header
+ *
+ * Portion of QLINK protocol header common for all message types.
+ *
+ * @type: Message type, one of &enum qlink_msg_type.
+ * @len: Total length of message including all headers.
+ */
+struct qlink_msg_header {
+ __le16 type;
+ __le16 len;
+} __packed;
+
+/* Generic definitions of data and information carried in QLINK messages
+ */
+
+/**
+ * enum qlink_hw_capab - device capabilities.
+ *
+ * @QLINK_HW_CAPAB_REG_UPDATE: device can update it's regulatory region.
+ * @QLINK_HW_CAPAB_STA_INACT_TIMEOUT: device implements a logic to kick-out
+ * associated STAs due to inactivity. Inactivity timeout period is taken
+ * from QLINK_CMD_START_AP parameters.
+ * @QLINK_HW_CAPAB_DFS_OFFLOAD: device implements DFS offload functionality
+ * @QLINK_HW_CAPAB_SCAN_RANDOM_MAC_ADDR: device supports MAC Address
+ * Randomization in probe requests.
+ * @QLINK_HW_CAPAB_OBSS_SCAN: device can perform OBSS scanning.
+ * @QLINK_HW_CAPAB_HW_BRIDGE: device has hardware switch capabilities.
+ */
+enum qlink_hw_capab {
+ QLINK_HW_CAPAB_REG_UPDATE = 0,
+ QLINK_HW_CAPAB_STA_INACT_TIMEOUT,
+ QLINK_HW_CAPAB_DFS_OFFLOAD,
+ QLINK_HW_CAPAB_SCAN_RANDOM_MAC_ADDR,
+ QLINK_HW_CAPAB_PWR_MGMT,
+ QLINK_HW_CAPAB_OBSS_SCAN,
+ QLINK_HW_CAPAB_SCAN_DWELL,
+ QLINK_HW_CAPAB_SAE,
+ QLINK_HW_CAPAB_HW_BRIDGE,
+ QLINK_HW_CAPAB_NUM
+};
+
+/**
+ * enum qlink_driver_capab - host driver capabilities.
+ *
+ */
+enum qlink_driver_capab {
+ QLINK_DRV_CAPAB_NUM = 0
+};
+
+enum qlink_iface_type {
+ QLINK_IFTYPE_AP = 1,
+ QLINK_IFTYPE_STATION = 2,
+ QLINK_IFTYPE_ADHOC = 3,
+ QLINK_IFTYPE_MONITOR = 4,
+ QLINK_IFTYPE_WDS = 5,
+ QLINK_IFTYPE_AP_VLAN = 6,
+};
+
+/**
+ * struct qlink_intf_info - information on virtual interface.
+ *
+ * Data describing a single virtual interface.
+ *
+ * @if_type: Mode of interface operation, one of &enum qlink_iface_type
+ * @vlanid: VLAN ID for AP_VLAN interface type
+ * @mac_addr: MAC address of virtual interface.
+ */
+struct qlink_intf_info {
+ __le16 if_type;
+ __le16 vlanid;
+ u8 mac_addr[ETH_ALEN];
+ u8 use4addr;
+ u8 rsvd[1];
+} __packed;
+
+enum qlink_sta_flags {
+ QLINK_STA_FLAG_INVALID = 0,
+ QLINK_STA_FLAG_AUTHORIZED = BIT(0),
+ QLINK_STA_FLAG_SHORT_PREAMBLE = BIT(1),
+ QLINK_STA_FLAG_WME = BIT(2),
+ QLINK_STA_FLAG_MFP = BIT(3),
+ QLINK_STA_FLAG_AUTHENTICATED = BIT(4),
+ QLINK_STA_FLAG_TDLS_PEER = BIT(5),
+ QLINK_STA_FLAG_ASSOCIATED = BIT(6),
+};
+
+enum qlink_channel_width {
+ QLINK_CHAN_WIDTH_5 = 0,
+ QLINK_CHAN_WIDTH_10,
+ QLINK_CHAN_WIDTH_20_NOHT,
+ QLINK_CHAN_WIDTH_20,
+ QLINK_CHAN_WIDTH_40,
+ QLINK_CHAN_WIDTH_80,
+ QLINK_CHAN_WIDTH_80P80,
+ QLINK_CHAN_WIDTH_160,
+};
+
+/**
+ * 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];
+} __packed;
+
+#define QLINK_MAX_NR_CIPHER_SUITES 5
+#define QLINK_MAX_NR_AKM_SUITES 2
+
+struct qlink_auth_encr {
+ __le32 wpa_versions;
+ __le32 cipher_group;
+ __le32 n_ciphers_pairwise;
+ __le32 ciphers_pairwise[QLINK_MAX_NR_CIPHER_SUITES];
+ __le32 n_akm_suites;
+ __le32 akm_suites[QLINK_MAX_NR_AKM_SUITES];
+ __le16 control_port_ethertype;
+ u8 auth_type;
+ u8 privacy;
+ u8 control_port;
+ u8 control_port_no_encrypt;
+ 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;
+
+/**
+ * enum qlink_sr_ctrl_flags - control flags for spatial reuse parameter set
+ *
+ * @QLINK_SR_PSR_DISALLOWED: indicates whether or not PSR-based spatial reuse
+ * transmissions are allowed for STAs associated with the AP
+ * @QLINK_SR_NON_SRG_OBSS_PD_SR_DISALLOWED: indicates whether or not
+ * Non-SRG OBSS PD spatial reuse transmissions are allowed for STAs associated
+ * with the AP
+ * @NON_SRG_OFFSET_PRESENT: indicates whether or not Non-SRG OBSS PD Max offset
+ * field is valid in the element
+ * @QLINK_SR_SRG_INFORMATION_PRESENT: indicates whether or not SRG OBSS PD
+ * Min/Max offset fields ore valid in the element
+ */
+enum qlink_sr_ctrl_flags {
+ QLINK_SR_PSR_DISALLOWED = BIT(0),
+ QLINK_SR_NON_SRG_OBSS_PD_SR_DISALLOWED = BIT(1),
+ QLINK_SR_NON_SRG_OFFSET_PRESENT = BIT(2),
+ QLINK_SR_SRG_INFORMATION_PRESENT = BIT(3),
+};
+
+/**
+ * struct qlink_sr_params - spatial reuse parameters
+ *
+ * @sr_control: spatial reuse control field; flags contained in this field are
+ * defined in @qlink_sr_ctrl_flags
+ * @non_srg_obss_pd_max: added to -82 dBm to generate the value of the
+ * Non-SRG OBSS PD Max parameter
+ * @srg_obss_pd_min_offset: added to -82 dBm to generate the value of the
+ * SRG OBSS PD Min parameter
+ * @srg_obss_pd_max_offset: added to -82 dBm to generate the value of the
+ * SRG PBSS PD Max parameter
+ */
+struct qlink_sr_params {
+ u8 sr_control;
+ u8 non_srg_obss_pd_max;
+ u8 srg_obss_pd_min_offset;
+ u8 srg_obss_pd_max_offset;
+} __packed;
+
+/* QLINK Command messages related definitions
+ */
+
+/**
+ * enum qlink_cmd_type - list of supported commands
+ *
+ * Commands are QLINK messages of type @QLINK_MSG_TYPE_CMD, sent by driver to
+ * wireless network device for processing. Device is expected to send back a
+ * reply message of type &QLINK_MSG_TYPE_CMDRSP, containing at least command
+ * execution status (one of &enum qlink_cmd_result). Reply message
+ * may also contain data payload specific to the command type.
+ *
+ * @QLINK_CMD_SEND_FRAME: send specified frame over the air; firmware will
+ * encapsulate 802.3 packet into 802.11 frame automatically.
+ * @QLINK_CMD_BAND_INFO_GET: for the specified MAC and specified band, get
+ * the band's description including number of operational channels and
+ * info on each channel, HT/VHT capabilities, supported rates etc.
+ * This command is generic to a specified MAC, interface index must be set
+ * to QLINK_VIFID_RSVD in command header.
+ * @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.
+ * @QLINK_CMD_TXPWR: get or set current channel transmit power for
+ * the specified MAC.
+ * @QLINK_CMD_NDEV_EVENT: signalizes changes made with a corresponding network
+ * device.
+ */
+enum qlink_cmd_type {
+ QLINK_CMD_FW_INIT = 0x0001,
+ QLINK_CMD_FW_DEINIT = 0x0002,
+ QLINK_CMD_REGISTER_MGMT = 0x0003,
+ QLINK_CMD_SEND_FRAME = 0x0004,
+ QLINK_CMD_MGMT_SET_APPIE = 0x0005,
+ QLINK_CMD_PHY_PARAMS_SET = 0x0012,
+ QLINK_CMD_GET_HW_INFO = 0x0013,
+ QLINK_CMD_MAC_INFO = 0x0014,
+ QLINK_CMD_ADD_INTF = 0x0015,
+ QLINK_CMD_DEL_INTF = 0x0016,
+ QLINK_CMD_CHANGE_INTF = 0x0017,
+ QLINK_CMD_UPDOWN_INTF = 0x0018,
+ QLINK_CMD_REG_NOTIFY = 0x0019,
+ 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_SET_MAC_ACL = 0x0023,
+ QLINK_CMD_GET_STA_INFO = 0x0030,
+ QLINK_CMD_ADD_KEY = 0x0040,
+ QLINK_CMD_DEL_KEY = 0x0041,
+ QLINK_CMD_SET_DEFAULT_KEY = 0x0042,
+ QLINK_CMD_SET_DEFAULT_MGMT_KEY = 0x0043,
+ QLINK_CMD_CHANGE_STA = 0x0051,
+ QLINK_CMD_DEL_STA = 0x0052,
+ QLINK_CMD_SCAN = 0x0053,
+ QLINK_CMD_CHAN_STATS = 0x0054,
+ QLINK_CMD_NDEV_EVENT = 0x0055,
+ QLINK_CMD_CONNECT = 0x0060,
+ QLINK_CMD_DISCONNECT = 0x0061,
+ QLINK_CMD_PM_SET = 0x0062,
+ QLINK_CMD_WOWLAN_SET = 0x0063,
+ QLINK_CMD_EXTERNAL_AUTH = 0x0066,
+ QLINK_CMD_TXPWR = 0x0067,
+ QLINK_CMD_UPDATE_OWE = 0x0068,
+};
+
+/**
+ * struct qlink_cmd - QLINK command message header
+ *
+ * Header used for QLINK messages of QLINK_MSG_TYPE_CMD type.
+ *
+ * @mhdr: Common QLINK message header.
+ * @cmd_id: command id, one of &enum qlink_cmd_type.
+ * @seq_num: sequence number of command message, used for matching with
+ * response message.
+ * @macid: index of physical radio device the command is destined to or
+ * QLINK_MACID_RSVD if not applicable.
+ * @vifid: index of virtual wireless interface on specified @macid the command
+ * is destined to or QLINK_VIFID_RSVD if not applicable.
+ */
+struct qlink_cmd {
+ struct qlink_msg_header mhdr;
+ __le16 cmd_id;
+ __le16 seq_num;
+ u8 macid;
+ u8 vifid;
+ u8 rsvd[2];
+} __packed;
+
+/**
+ * struct qlink_cmd_init_fw - data for QLINK_CMD_FW_INIT
+ *
+ * Initialize firmware based on specified host configuration. This is the first
+ * command sent to wifi card and it's fixed part should never be changed, any
+ * additions must be done by appending TLVs.
+ * If wifi card can not operate with a specified parameters it will return
+ * error.
+ *
+ * @qlink_proto_ver: QLINK protocol version used by host driver.
+ */
+struct qlink_cmd_init_fw {
+ struct qlink_cmd chdr;
+ __le32 qlink_proto_ver;
+ u8 var_info[];
+} __packed;
+
+/**
+ * struct qlink_cmd_manage_intf - interface management command
+ *
+ * Data for interface management commands QLINK_CMD_ADD_INTF, QLINK_CMD_DEL_INTF
+ * and QLINK_CMD_CHANGE_INTF.
+ *
+ * @intf_info: interface description.
+ */
+struct qlink_cmd_manage_intf {
+ struct qlink_cmd chdr;
+ struct qlink_intf_info intf_info;
+} __packed;
+
+enum qlink_mgmt_frame_type {
+ QLINK_MGMT_FRAME_ASSOC_REQ = 0x00,
+ QLINK_MGMT_FRAME_ASSOC_RESP = 0x01,
+ QLINK_MGMT_FRAME_REASSOC_REQ = 0x02,
+ QLINK_MGMT_FRAME_REASSOC_RESP = 0x03,
+ QLINK_MGMT_FRAME_PROBE_REQ = 0x04,
+ QLINK_MGMT_FRAME_PROBE_RESP = 0x05,
+ QLINK_MGMT_FRAME_BEACON = 0x06,
+ QLINK_MGMT_FRAME_ATIM = 0x07,
+ QLINK_MGMT_FRAME_DISASSOC = 0x08,
+ QLINK_MGMT_FRAME_AUTH = 0x09,
+ QLINK_MGMT_FRAME_DEAUTH = 0x0A,
+ QLINK_MGMT_FRAME_ACTION = 0x0B,
+
+ QLINK_MGMT_FRAME_TYPE_COUNT
+};
+
+/**
+ * struct qlink_cmd_mgmt_frame_register - data for QLINK_CMD_REGISTER_MGMT
+ *
+ * @frame_type: MGMT frame type the registration request describes, one of
+ * &enum qlink_mgmt_frame_type.
+ * @do_register: 0 - unregister, otherwise register for reception of specified
+ * MGMT frame type.
+ */
+struct qlink_cmd_mgmt_frame_register {
+ struct qlink_cmd chdr;
+ __le16 frame_type;
+ u8 do_register;
+ u8 rsvd[1];
+} __packed;
+
+/**
+ * @QLINK_FRAME_TX_FLAG_8023: frame has a 802.3 header; if not set, frame
+ * is a 802.11 encapsulated.
+ */
+enum qlink_frame_tx_flags {
+ QLINK_FRAME_TX_FLAG_OFFCHAN = BIT(0),
+ QLINK_FRAME_TX_FLAG_NO_CCK = BIT(1),
+ QLINK_FRAME_TX_FLAG_ACK_NOWAIT = BIT(2),
+ QLINK_FRAME_TX_FLAG_8023 = BIT(3),
+};
+
+/**
+ * struct qlink_cmd_frame_tx - data for QLINK_CMD_SEND_FRAME command
+ *
+ * @cookie: opaque request identifier.
+ * @freq: Frequency to use for frame transmission.
+ * @flags: Transmission flags, one of &enum qlink_frame_tx_flags.
+ * @frame_data: frame to transmit.
+ */
+struct qlink_cmd_frame_tx {
+ struct qlink_cmd chdr;
+ __le32 cookie;
+ __le16 freq;
+ __le16 flags;
+ u8 frame_data[];
+} __packed;
+
+/**
+ * struct qlink_cmd_get_sta_info - data for QLINK_CMD_GET_STA_INFO command
+ *
+ * @sta_addr: MAC address of the STA statistics is requested for.
+ */
+struct qlink_cmd_get_sta_info {
+ struct qlink_cmd chdr;
+ u8 sta_addr[ETH_ALEN];
+ u8 rsvd[2];
+} __packed;
+
+/**
+ * struct qlink_cmd_add_key - data for QLINK_CMD_ADD_KEY command.
+ *
+ * @key_index: index of the key being installed.
+ * @pairwise: whether to use pairwise key.
+ * @addr: MAC address of a STA key is being installed to.
+ * @cipher: cipher suite.
+ * @vlanid: VLAN ID for AP_VLAN interface type
+ * @key_data: key data itself.
+ */
+struct qlink_cmd_add_key {
+ struct qlink_cmd chdr;
+ u8 key_index;
+ u8 pairwise;
+ u8 addr[ETH_ALEN];
+ __le32 cipher;
+ __le16 vlanid;
+ u8 rsvd[2];
+ u8 key_data[];
+} __packed;
+
+/**
+ * struct qlink_cmd_del_key_req - data for QLINK_CMD_DEL_KEY command
+ *
+ * @key_index: index of the key being removed.
+ * @pairwise: whether to use pairwise key.
+ * @addr: MAC address of a STA for which a key is removed.
+ */
+struct qlink_cmd_del_key {
+ struct qlink_cmd chdr;
+ u8 key_index;
+ u8 pairwise;
+ u8 addr[ETH_ALEN];
+} __packed;
+
+/**
+ * struct qlink_cmd_set_def_key - data for QLINK_CMD_SET_DEFAULT_KEY command
+ *
+ * @key_index: index of the key to be set as default one.
+ * @unicast: key is unicast.
+ * @multicast: key is multicast.
+ */
+struct qlink_cmd_set_def_key {
+ struct qlink_cmd chdr;
+ u8 key_index;
+ u8 unicast;
+ u8 multicast;
+ u8 rsvd[1];
+} __packed;
+
+/**
+ * struct qlink_cmd_set_def_mgmt_key - data for QLINK_CMD_SET_DEFAULT_MGMT_KEY
+ *
+ * @key_index: index of the key to be set as default MGMT key.
+ */
+struct qlink_cmd_set_def_mgmt_key {
+ struct qlink_cmd chdr;
+ u8 key_index;
+ u8 rsvd[3];
+} __packed;
+
+/**
+ * struct qlink_cmd_change_sta - data for QLINK_CMD_CHANGE_STA command
+ *
+ * @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;
+ struct qlink_sta_info_state flag_update;
+ __le16 if_type;
+ __le16 vlanid;
+ u8 sta_addr[ETH_ALEN];
+ u8 rsvd[2];
+} __packed;
+
+/**
+ * struct qlink_cmd_del_sta - data for QLINK_CMD_DEL_STA command.
+ *
+ * See &struct station_del_parameters
+ */
+struct qlink_cmd_del_sta {
+ struct qlink_cmd chdr;
+ __le16 reason_code;
+ u8 sta_addr[ETH_ALEN];
+ u8 subtype;
+ u8 rsvd[3];
+} __packed;
+
+enum qlink_sta_connect_flags {
+ QLINK_STA_CONNECT_DISABLE_HT = BIT(0),
+ QLINK_STA_CONNECT_DISABLE_VHT = BIT(1),
+ QLINK_STA_CONNECT_USE_RRM = BIT(2),
+};
+
+/**
+ * struct qlink_cmd_connect - data for QLINK_CMD_CONNECT command
+ *
+ * @bssid: BSSID of the BSS to connect to.
+ * @bssid_hint: recommended AP BSSID for initial connection to the BSS or
+ * 00:00:00:00:00:00 if not specified.
+ * @prev_bssid: previous BSSID, if specified (not 00:00:00:00:00:00) indicates
+ * a request to reassociate.
+ * @bg_scan_period: period of background scan.
+ * @flags: one of &enum qlink_sta_connect_flags.
+ * @ht_capa: HT Capabilities overrides.
+ * @ht_capa_mask: The bits of ht_capa which are to be used.
+ * @vht_capa: VHT Capability overrides
+ * @vht_capa_mask: The bits of vht_capa which are to be used.
+ * @aen: authentication information.
+ * @mfp: whether to use management frame protection.
+ * @payload: variable portion of connection request.
+ */
+struct qlink_cmd_connect {
+ struct qlink_cmd chdr;
+ u8 bssid[ETH_ALEN];
+ u8 bssid_hint[ETH_ALEN];
+ u8 prev_bssid[ETH_ALEN];
+ __le16 bg_scan_period;
+ __le32 flags;
+ struct ieee80211_ht_cap ht_capa;
+ struct ieee80211_ht_cap ht_capa_mask;
+ struct ieee80211_vht_cap vht_capa;
+ struct ieee80211_vht_cap vht_capa_mask;
+ struct qlink_auth_encr aen;
+ u8 mfp;
+ u8 pbss;
+ u8 rsvd[2];
+ u8 payload[];
+} __packed;
+
+/**
+ * struct qlink_cmd_external_auth - data for QLINK_CMD_EXTERNAL_AUTH command
+ *
+ * @bssid: BSSID of the BSS to connect to
+ * @status: authentication status code
+ * @payload: variable portion of connection request.
+ */
+struct qlink_cmd_external_auth {
+ struct qlink_cmd chdr;
+ u8 peer[ETH_ALEN];
+ __le16 status;
+ u8 payload[];
+} __packed;
+
+/**
+ * struct qlink_cmd_disconnect - data for QLINK_CMD_DISCONNECT command
+ *
+ * @reason: code of the reason of disconnect, see &enum ieee80211_reasoncode.
+ */
+struct qlink_cmd_disconnect {
+ struct qlink_cmd chdr;
+ __le16 reason;
+ u8 rsvd[2];
+} __packed;
+
+/**
+ * struct qlink_cmd_updown - data for QLINK_CMD_UPDOWN_INTF command
+ *
+ * @if_up: bring specified interface DOWN (if_up==0) or UP (otherwise).
+ * Interface is specified in common command header @chdr.
+ */
+struct qlink_cmd_updown {
+ struct qlink_cmd chdr;
+ u8 if_up;
+ u8 rsvd[3];
+} __packed;
+
+/**
+ * enum qlink_band - a list of frequency bands
+ *
+ * @QLINK_BAND_2GHZ: 2.4GHz band
+ * @QLINK_BAND_5GHZ: 5GHz band
+ * @QLINK_BAND_60GHZ: 60GHz band
+ */
+enum qlink_band {
+ QLINK_BAND_2GHZ = BIT(0),
+ QLINK_BAND_5GHZ = BIT(1),
+ QLINK_BAND_60GHZ = BIT(2),
+};
+
+/**
+ * struct qlink_cmd_band_info_get - data for QLINK_CMD_BAND_INFO_GET command
+ *
+ * @band: a PHY band for which information is queried, one of @enum qlink_band
+ */
+struct qlink_cmd_band_info_get {
+ struct qlink_cmd chdr;
+ u8 band;
+ u8 rsvd[3];
+} __packed;
+
+/**
+ * struct qlink_cmd_get_chan_stats - data for QLINK_CMD_CHAN_STATS command
+ *
+ * @channel_freq: channel center frequency
+ */
+struct qlink_cmd_get_chan_stats {
+ struct qlink_cmd chdr;
+ __le32 channel_freq;
+} __packed;
+
+/**
+ * enum qlink_reg_initiator - Indicates the initiator of a reg domain request
+ *
+ * See &enum nl80211_reg_initiator for more info.
+ */
+enum qlink_reg_initiator {
+ QLINK_REGDOM_SET_BY_CORE,
+ QLINK_REGDOM_SET_BY_USER,
+ QLINK_REGDOM_SET_BY_DRIVER,
+ QLINK_REGDOM_SET_BY_COUNTRY_IE,
+};
+
+/**
+ * enum qlink_user_reg_hint_type - type of user regulatory hint
+ *
+ * See &enum nl80211_user_reg_hint_type for more info.
+ */
+enum qlink_user_reg_hint_type {
+ QLINK_USER_REG_HINT_USER = 0,
+ QLINK_USER_REG_HINT_CELL_BASE = 1,
+ QLINK_USER_REG_HINT_INDOOR = 2,
+};
+
+/**
+ * struct qlink_cmd_reg_notify - data for QLINK_CMD_REG_NOTIFY command
+ *
+ * @alpha2: the ISO / IEC 3166 alpha2 country code.
+ * @initiator: which entity sent the request, one of &enum qlink_reg_initiator.
+ * @user_reg_hint_type: type of hint for QLINK_REGDOM_SET_BY_USER request, one
+ * of &enum qlink_user_reg_hint_type.
+ * @num_channels: number of &struct qlink_tlv_channel in a variable portion of a
+ * payload.
+ * @dfs_region: one of &enum qlink_dfs_regions.
+ * @slave_radar: whether slave device should enable radar detection.
+ * @dfs_offload: enable or disable DFS offload to firmware.
+ * @info: variable portion of regulatory notifier callback.
+ */
+struct qlink_cmd_reg_notify {
+ struct qlink_cmd chdr;
+ u8 alpha2[2];
+ u8 initiator;
+ u8 user_reg_hint_type;
+ u8 num_channels;
+ u8 dfs_region;
+ u8 slave_radar;
+ u8 dfs_offload;
+ u8 info[];
+} __packed;
+
+/**
+ * enum qlink_chan_sw_flags - channel switch control flags
+ *
+ * @QLINK_CHAN_SW_RADAR_REQUIRED: whether radar detection is required on a new
+ * channel.
+ * @QLINK_CHAN_SW_BLOCK_TX: whether transmissions should be blocked while
+ * changing a channel.
+ */
+enum qlink_chan_sw_flags {
+ QLINK_CHAN_SW_RADAR_REQUIRED = BIT(0),
+ QLINK_CHAN_SW_BLOCK_TX = BIT(1),
+};
+
+/**
+ * struct qlink_cmd_chan_switch - data for QLINK_CMD_CHAN_SWITCH command
+ *
+ * @channel: channel to switch to.
+ * @flags: flags to control channel switch, bitmap of &enum qlink_chan_sw_flags.
+ * @beacon_count: number of beacons until switch
+ */
+struct qlink_cmd_chan_switch {
+ struct qlink_cmd chdr;
+ struct qlink_chandef channel;
+ __le64 flags;
+ __le32 n_counter_offsets_beacon;
+ __le32 n_counter_offsets_presp;
+ u8 beacon_count;
+ u8 rsvd[3];
+} __packed;
+
+/**
+ * enum qlink_hidden_ssid - values for %NL80211_ATTR_HIDDEN_SSID
+ *
+ * Refer to &enum nl80211_hidden_ssid
+ */
+enum qlink_hidden_ssid {
+ QLINK_HIDDEN_SSID_NOT_IN_USE,
+ QLINK_HIDDEN_SSID_ZERO_LEN,
+ QLINK_HIDDEN_SSID_ZERO_CONTENTS
+};
+
+/**
+ * struct qlink_cmd_start_ap - data for QLINK_CMD_START_AP command
+ *
+ * @beacon_interval: beacon interval
+ * @inactivity_timeout: station's inactivity period in seconds
+ * @dtim_period: DTIM period
+ * @hidden_ssid: whether to hide the SSID, one of &enum qlink_hidden_ssid
+ * @smps_mode: SMPS mode
+ * @ht_required: stations must support HT
+ * @vht_required: stations must support VHT
+ * @aen: encryption info
+ * @sr_params: spatial reuse parameters
+ * @twt_responder: enable Target Wake Time
+ * @info: variable configurations
+ */
+struct qlink_cmd_start_ap {
+ struct qlink_cmd chdr;
+ __le16 beacon_interval;
+ __le16 inactivity_timeout;
+ u8 dtim_period;
+ u8 hidden_ssid;
+ u8 smps_mode;
+ u8 p2p_ctwindow;
+ u8 p2p_opp_ps;
+ u8 pbss;
+ u8 ht_required;
+ u8 vht_required;
+ struct qlink_auth_encr aen;
+ struct qlink_sr_params sr_params;
+ u8 twt_responder;
+ u8 rsvd[3];
+ u8 info[];
+} __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;
+
+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[];
+} __packed;
+
+/**
+ * enum qlink_pm_mode - Power Management mode
+ *
+ * @QLINK_PM_OFF: normal mode, no power saving enabled
+ * @QLINK_PM_AUTO_STANDBY: enable auto power save mode
+ */
+enum qlink_pm_mode {
+ QLINK_PM_OFF = 0,
+ QLINK_PM_AUTO_STANDBY = 1,
+};
+
+/**
+ * struct qlink_cmd_pm_set - data for QLINK_CMD_PM_SET command
+ *
+ * @pm_standby timer: period of network inactivity in seconds before
+ * putting a radio in power save mode
+ * @pm_mode: power management mode
+ */
+struct qlink_cmd_pm_set {
+ struct qlink_cmd chdr;
+ __le32 pm_standby_timer;
+ u8 pm_mode;
+ u8 rsvd[3];
+} __packed;
+
+/**
+ * enum qlink_txpwr_op - transmit power operation type
+ * @QLINK_TXPWR_SET: set tx power
+ * @QLINK_TXPWR_GET: get current tx power setting
+ */
+enum qlink_txpwr_op {
+ QLINK_TXPWR_SET,
+ QLINK_TXPWR_GET
+};
+
+/**
+ * struct qlink_cmd_txpwr - get or set current transmit power
+ *
+ * @txpwr: new transmit power setting, in mBm
+ * @txpwr_setting: transmit power setting type, one of
+ * &enum nl80211_tx_power_setting
+ * @op_type: type of operation, one of &enum qlink_txpwr_op
+ */
+struct qlink_cmd_txpwr {
+ struct qlink_cmd chdr;
+ __le32 txpwr;
+ u8 txpwr_setting;
+ u8 op_type;
+ u8 rsvd[2];
+} __packed;
+
+/**
+ * enum qlink_wowlan_trigger
+ *
+ * @QLINK_WOWLAN_TRIG_DISCONNECT: wakeup on disconnect
+ * @QLINK_WOWLAN_TRIG_MAGIC_PKT: wakeup on magic packet
+ * @QLINK_WOWLAN_TRIG_PATTERN_PKT: wakeup on user-defined packet
+ */
+enum qlink_wowlan_trigger {
+ QLINK_WOWLAN_TRIG_DISCONNECT = BIT(0),
+ QLINK_WOWLAN_TRIG_MAGIC_PKT = BIT(1),
+ QLINK_WOWLAN_TRIG_PATTERN_PKT = BIT(2),
+};
+
+/**
+ * struct qlink_cmd_wowlan_set - data for QLINK_CMD_WOWLAN_SET command
+ *
+ * @triggers: requested bitmask of WoWLAN triggers
+ */
+struct qlink_cmd_wowlan_set {
+ struct qlink_cmd chdr;
+ __le32 triggers;
+ u8 data[];
+} __packed;
+
+enum qlink_ndev_event_type {
+ QLINK_NDEV_EVENT_CHANGEUPPER,
+};
+
+/**
+ * struct qlink_cmd_ndev_event - data for QLINK_CMD_NDEV_EVENT command
+ *
+ * @event: type of event, one of &enum qlink_ndev_event_type
+ */
+struct qlink_cmd_ndev_event {
+ struct qlink_cmd chdr;
+ __le16 event;
+ u8 rsvd[2];
+} __packed;
+
+enum qlink_ndev_upper_type {
+ QLINK_NDEV_UPPER_TYPE_NONE,
+ QLINK_NDEV_UPPER_TYPE_BRIDGE,
+};
+
+/**
+ * struct qlink_cmd_ndev_changeupper - data for QLINK_NDEV_EVENT_CHANGEUPPER
+ *
+ * @br_domain: layer 2 broadcast domain ID that ndev is a member of
+ * @upper_type: type of upper device, one of &enum qlink_ndev_upper_type
+ */
+struct qlink_cmd_ndev_changeupper {
+ struct qlink_cmd_ndev_event nehdr;
+ __le64 flags;
+ __le32 br_domain;
+ __le32 netspace_id;
+ __le16 vlanid;
+ u8 upper_type;
+ u8 rsvd[1];
+} __packed;
+
+/**
+ * enum qlink_scan_flags - scan request control flags
+ *
+ * Scan flags are used to control QLINK_CMD_SCAN behavior.
+ *
+ * @QLINK_SCAN_FLAG_FLUSH: flush cache before scanning.
+ */
+enum qlink_scan_flags {
+ QLINK_SCAN_FLAG_FLUSH = BIT(0),
+ QLINK_SCAN_FLAG_DURATION_MANDATORY = BIT(1),
+};
+
+/**
+ * struct qlink_cmd_scan - data for QLINK_CMD_SCAN command
+ *
+ * @flags: scan flags, a bitmap of &enum qlink_scan_flags.
+ * @n_ssids: number of WLAN_EID_SSID TLVs expected in variable portion of the
+ * command.
+ * @n_channels: number of QTN_TLV_ID_CHANNEL TLVs expected in variable payload.
+ * @active_dwell: time spent on a single channel for an active scan.
+ * @passive_dwell: time spent on a single channel for a passive scan.
+ * @sample_duration: total duration of sampling a single channel during a scan
+ * including off-channel dwell time and operating channel time.
+ * @bssid: specific BSSID to scan for or a broadcast BSSID.
+ * @scan_width: channel width to use, one of &enum qlink_channel_width.
+ */
+struct qlink_cmd_scan {
+ struct qlink_cmd chdr;
+ __le64 flags;
+ __le16 n_ssids;
+ __le16 n_channels;
+ __le16 active_dwell;
+ __le16 passive_dwell;
+ __le16 sample_duration;
+ u8 bssid[ETH_ALEN];
+ u8 scan_width;
+ u8 rsvd[3];
+ u8 var_info[];
+} __packed;
+
+/**
+ * struct qlink_cmd_update_owe - data for QLINK_CMD_UPDATE_OWE_INFO command
+ *
+ * @peer: MAC of the peer device for which OWE processing has been completed
+ * @status: OWE external processing status code
+ * @ies: IEs for the peer constructed by the user space
+ */
+struct qlink_cmd_update_owe {
+ struct qlink_cmd chdr;
+ u8 peer[ETH_ALEN];
+ __le16 status;
+ u8 ies[];
+} __packed;
+
+/* QLINK Command Responses messages related definitions
+ */
+
+enum qlink_cmd_result {
+ QLINK_CMD_RESULT_OK = 0,
+ QLINK_CMD_RESULT_INVALID,
+ QLINK_CMD_RESULT_ENOTSUPP,
+ QLINK_CMD_RESULT_ENOTFOUND,
+ QLINK_CMD_RESULT_EALREADY,
+ QLINK_CMD_RESULT_EADDRINUSE,
+ QLINK_CMD_RESULT_EADDRNOTAVAIL,
+ QLINK_CMD_RESULT_EBUSY,
+};
+
+/**
+ * struct qlink_resp - QLINK command response message header
+ *
+ * Header used for QLINK messages of QLINK_MSG_TYPE_CMDRSP type.
+ *
+ * @mhdr: see &struct qlink_msg_header.
+ * @cmd_id: command ID the response corresponds to, one of &enum qlink_cmd_type.
+ * @seq_num: sequence number of command message, used for matching with
+ * response message.
+ * @result: result of the command execution, one of &enum qlink_cmd_result.
+ * @macid: index of physical radio device the response is sent from or
+ * QLINK_MACID_RSVD if not applicable.
+ * @vifid: index of virtual wireless interface on specified @macid the response
+ * is sent from or QLINK_VIFID_RSVD if not applicable.
+ */
+struct qlink_resp {
+ struct qlink_msg_header mhdr;
+ __le16 cmd_id;
+ __le16 seq_num;
+ __le16 result;
+ u8 macid;
+ u8 vifid;
+} __packed;
+
+/**
+ * struct qlink_resp_init_fw - response for QLINK_CMD_FW_INIT
+ *
+ * @qlink_proto_ver: QLINK protocol version used by wifi card firmware.
+ */
+struct qlink_resp_init_fw {
+ struct qlink_resp rhdr;
+ __le32 qlink_proto_ver;
+} __packed;
+
+/**
+ * enum qlink_dfs_regions - regulatory DFS regions
+ *
+ * Corresponds to &enum nl80211_dfs_regions.
+ */
+enum qlink_dfs_regions {
+ QLINK_DFS_UNSET = 0,
+ QLINK_DFS_FCC = 1,
+ QLINK_DFS_ETSI = 2,
+ QLINK_DFS_JP = 3,
+};
+
+/**
+ * struct qlink_resp_get_mac_info - response for QLINK_CMD_MAC_INFO command
+ *
+ * Data describing specific physical device providing wireless MAC
+ * functionality.
+ *
+ * @dev_mac: MAC address of physical WMAC device (used for first BSS on
+ * specified WMAC).
+ * @num_tx_chain: Number of transmit chains used by WMAC.
+ * @num_rx_chain: Number of receive chains used by WMAC.
+ * @vht_cap_mod_mask: mask specifying which VHT capabilities can be altered.
+ * @ht_cap_mod_mask: mask specifying which HT capabilities can be altered.
+ * @max_scan_ssids: maximum number of SSIDs the device can scan for in any scan.
+ * @bands_cap: wireless bands WMAC can operate in, bitmap of &enum qlink_band.
+ * @max_ap_assoc_sta: Maximum number of associations supported by WMAC.
+ * @radar_detect_widths: bitmask of channels BW for which WMAC can detect radar.
+ * @alpha2: country code ID firmware is configured to.
+ * @n_reg_rules: number of regulatory rules TLVs in variable portion of the
+ * message.
+ * @dfs_region: regulatory DFS region, one of &enum qlink_dfs_regions.
+ * @var_info: variable-length WMAC info data.
+ */
+struct qlink_resp_get_mac_info {
+ struct qlink_resp rhdr;
+ u8 dev_mac[ETH_ALEN];
+ u8 num_tx_chain;
+ u8 num_rx_chain;
+ struct ieee80211_vht_cap vht_cap_mod_mask;
+ struct ieee80211_ht_cap ht_cap_mod_mask;
+
+ __le16 max_ap_assoc_sta;
+ __le32 hw_version;
+ __le32 probe_resp_offload;
+ __le32 bss_select_support;
+ __le16 n_addresses;
+ __le16 radar_detect_widths;
+ __le16 max_remain_on_channel_duration;
+ __le16 max_acl_mac_addrs;
+
+ __le32 frag_threshold;
+ __le32 rts_threshold;
+ u8 retry_short;
+ u8 retry_long;
+ u8 coverage_class;
+
+ u8 max_scan_ssids;
+ u8 max_sched_scan_reqs;
+ u8 max_sched_scan_ssids;
+ u8 max_match_sets;
+ u8 max_adj_channel_rssi_comp;
+
+ __le16 max_scan_ie_len;
+ __le16 max_sched_scan_ie_len;
+ __le32 max_sched_scan_plans;
+ __le32 max_sched_scan_plan_interval;
+ __le32 max_sched_scan_plan_iterations;
+
+ u8 n_cipher_suites;
+ u8 n_akm_suites;
+ u8 max_num_pmkids;
+ u8 num_iftype_ext_capab;
+ u8 extended_capabilities_len;
+ u8 max_data_retry_count;
+ u8 n_iface_combinations;
+ u8 max_num_csa_counters;
+
+ u8 bands_cap;
+ u8 alpha2[2];
+ u8 n_reg_rules;
+ u8 dfs_region;
+ u8 rsvd[3];
+ u8 var_info[];
+} __packed;
+
+/**
+ * struct qlink_resp_get_hw_info - response for QLINK_CMD_GET_HW_INFO command
+ *
+ * Description of wireless hardware capabilities and features.
+ *
+ * @fw_ver: wireless hardware firmware version.
+ * @num_mac: Number of separate physical radio devices provided by hardware.
+ * @mac_bitmap: Bitmap of MAC IDs that are active and can be used in firmware.
+ * @total_tx_chains: total number of transmit chains used by device.
+ * @total_rx_chains: total number of receive chains.
+ * @info: variable-length HW info.
+ */
+struct qlink_resp_get_hw_info {
+ struct qlink_resp rhdr;
+ __le32 fw_ver;
+ __le32 bld_tmstamp;
+ __le32 plat_id;
+ __le32 hw_ver;
+ u8 num_mac;
+ u8 mac_bitmap;
+ u8 total_tx_chain;
+ u8 total_rx_chain;
+ u8 info[];
+} __packed;
+
+/**
+ * struct qlink_resp_manage_intf - response for interface management commands
+ *
+ * Response data for QLINK_CMD_ADD_INTF and QLINK_CMD_CHANGE_INTF commands.
+ *
+ * @rhdr: Common Command Response message header.
+ * @intf_info: interface description.
+ */
+struct qlink_resp_manage_intf {
+ struct qlink_resp rhdr;
+ 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),
+ QLINK_STA_INFO_RATE_FLAG_HE_MCS = BIT(4),
+};
+
+/**
+ * struct qlink_resp_get_sta_info - response for QLINK_CMD_GET_STA_INFO command
+ *
+ * Response data containing statistics for specified STA.
+ *
+ * @sta_addr: MAC address of STA the response carries statistic for.
+ * @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[];
+} __packed;
+
+/**
+ * struct qlink_resp_band_info_get - response for QLINK_CMD_BAND_INFO_GET cmd
+ *
+ * @band: frequency band that the response describes, one of @enum qlink_band.
+ * @num_chans: total number of channels info TLVs contained in reply.
+ * @num_bitrates: total number of bitrate TLVs contained in reply.
+ * @info: variable-length info portion.
+ */
+struct qlink_resp_band_info_get {
+ struct qlink_resp rhdr;
+ u8 band;
+ u8 num_chans;
+ u8 num_bitrates;
+ u8 rsvd[1];
+ u8 info[];
+} __packed;
+
+/**
+ * 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_resp rhdr;
+ __le32 chan_freq;
+ u8 info[];
+} __packed;
+
+/**
+ * struct qlink_resp_channel_get - response for QLINK_CMD_CHAN_GET command
+ *
+ * @chan: definition of current operating channel.
+ */
+struct qlink_resp_channel_get {
+ struct qlink_resp rhdr;
+ struct qlink_chandef chan;
+} __packed;
+
+/**
+ * struct qlink_resp_txpwr - response for QLINK_CMD_TXPWR command
+ *
+ * This response is intended for QLINK_TXPWR_GET operation and does not
+ * contain any meaningful information in case of QLINK_TXPWR_SET operation.
+ *
+ * @txpwr: current transmit power setting, in mBm
+ */
+struct qlink_resp_txpwr {
+ struct qlink_resp rhdr;
+ __le32 txpwr;
+} __packed;
+
+/* QLINK Events messages related definitions
+ */
+
+enum qlink_event_type {
+ QLINK_EVENT_STA_ASSOCIATED = 0x0021,
+ QLINK_EVENT_STA_DEAUTH = 0x0022,
+ QLINK_EVENT_MGMT_RECEIVED = 0x0023,
+ QLINK_EVENT_SCAN_RESULTS = 0x0024,
+ QLINK_EVENT_SCAN_COMPLETE = 0x0025,
+ QLINK_EVENT_BSS_JOIN = 0x0026,
+ QLINK_EVENT_BSS_LEAVE = 0x0027,
+ QLINK_EVENT_FREQ_CHANGE = 0x0028,
+ QLINK_EVENT_RADAR = 0x0029,
+ QLINK_EVENT_EXTERNAL_AUTH = 0x0030,
+ QLINK_EVENT_MIC_FAILURE = 0x0031,
+ QLINK_EVENT_UPDATE_OWE = 0x0032,
+};
+
+/**
+ * struct qlink_event - QLINK event message header
+ *
+ * Header used for QLINK messages of QLINK_MSG_TYPE_EVENT type.
+ *
+ * @mhdr: Common QLINK message header.
+ * @event_id: Specifies specific event ID, one of &enum qlink_event_type.
+ * @macid: index of physical radio device the event was generated on or
+ * QLINK_MACID_RSVD if not applicable.
+ * @vifid: index of virtual wireless interface on specified @macid the event
+ * was generated on or QLINK_VIFID_RSVD if not applicable.
+ */
+struct qlink_event {
+ struct qlink_msg_header mhdr;
+ __le16 event_id;
+ u8 macid;
+ u8 vifid;
+} __packed;
+
+/**
+ * struct qlink_event_sta_assoc - data for QLINK_EVENT_STA_ASSOCIATED event
+ *
+ * @sta_addr: Address of a STA for which new association event was generated
+ * @frame_control: control bits from 802.11 ASSOC_REQUEST header.
+ * @payload: IEs from association request.
+ */
+struct qlink_event_sta_assoc {
+ struct qlink_event ehdr;
+ u8 sta_addr[ETH_ALEN];
+ __le16 frame_control;
+ u8 ies[];
+} __packed;
+
+/**
+ * struct qlink_event_sta_deauth - data for QLINK_EVENT_STA_DEAUTH event
+ *
+ * @sta_addr: Address of a deauthenticated STA.
+ * @reason: reason for deauthentication.
+ */
+struct qlink_event_sta_deauth {
+ struct qlink_event ehdr;
+ u8 sta_addr[ETH_ALEN];
+ __le16 reason;
+} __packed;
+
+/**
+ * struct qlink_event_bss_join - data for QLINK_EVENT_BSS_JOIN event
+ *
+ * @chan: new operating channel definition
+ * @bssid: BSSID of a BSS which interface tried to joined.
+ * @status: status of joining attempt, see &enum ieee80211_statuscode.
+ */
+struct qlink_event_bss_join {
+ struct qlink_event ehdr;
+ struct qlink_chandef chan;
+ u8 bssid[ETH_ALEN];
+ __le16 status;
+ u8 ies[];
+} __packed;
+
+/**
+ * struct qlink_event_bss_leave - data for QLINK_EVENT_BSS_LEAVE event
+ *
+ * @reason: reason of disconnecting from BSS.
+ */
+struct qlink_event_bss_leave {
+ struct qlink_event ehdr;
+ __le16 reason;
+ u8 rsvd[2];
+} __packed;
+
+/**
+ * struct qlink_event_freq_change - data for QLINK_EVENT_FREQ_CHANGE event
+ *
+ * @chan: new operating channel definition
+ */
+struct qlink_event_freq_change {
+ struct qlink_event ehdr;
+ struct qlink_chandef chan;
+} __packed;
+
+enum qlink_rxmgmt_flags {
+ QLINK_RXMGMT_FLAG_ANSWERED = 1 << 0,
+};
+
+/**
+ * struct qlink_event_rxmgmt - data for QLINK_EVENT_MGMT_RECEIVED event
+ *
+ * @freq: Frequency on which the frame was received in MHz.
+ * @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 flags;
+ s8 sig_dbm;
+ u8 rsvd[3];
+ u8 frame_data[];
+} __packed;
+
+/**
+ * struct qlink_event_scan_result - data for QLINK_EVENT_SCAN_RESULTS event
+ *
+ * @tsf: TSF timestamp indicating when scan results were generated.
+ * @freq: Center frequency of the channel where BSS for which the scan result
+ * event was generated was discovered.
+ * @capab: capabilities field.
+ * @bintval: beacon interval announced by discovered BSS.
+ * @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.
+ * @payload: IEs that are announced by discovered BSS in its MGMt frames.
+ */
+struct qlink_event_scan_result {
+ struct qlink_event ehdr;
+ __le64 tsf;
+ __le16 freq;
+ __le16 capab;
+ __le16 bintval;
+ s8 sig_dbm;
+ u8 ssid_len;
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ u8 bssid[ETH_ALEN];
+ u8 rsvd[2];
+ u8 payload[];
+} __packed;
+
+/**
+ * enum qlink_scan_complete_flags - indicates result of scan request.
+ *
+ * @QLINK_SCAN_NONE: Scan request was processed.
+ * @QLINK_SCAN_ABORTED: Scan was aborted.
+ */
+enum qlink_scan_complete_flags {
+ QLINK_SCAN_NONE = 0,
+ QLINK_SCAN_ABORTED = BIT(0),
+};
+
+/**
+ * struct qlink_event_scan_complete - data for QLINK_EVENT_SCAN_COMPLETE event
+ *
+ * @flags: flags indicating the status of pending scan request,
+ * see &enum qlink_scan_complete_flags.
+ */
+struct qlink_event_scan_complete {
+ struct qlink_event ehdr;
+ __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,
+ QLINK_RADAR_CAC_STARTED,
+};
+
+/**
+ * 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;
+
+/**
+ * struct qlink_event_external_auth - data for QLINK_EVENT_EXTERNAL_AUTH event
+ *
+ * @ssid: SSID announced by BSS
+ * @ssid_len: SSID length
+ * @bssid: BSSID of the BSS to connect to
+ * @akm_suite: AKM suite for external authentication
+ * @action: action type/trigger for external authentication
+ */
+struct qlink_event_external_auth {
+ struct qlink_event ehdr;
+ __le32 akm_suite;
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ u8 bssid[ETH_ALEN];
+ u8 ssid_len;
+ u8 action;
+} __packed;
+
+/**
+ * struct qlink_event_mic_failure - data for QLINK_EVENT_MIC_FAILURE event
+ *
+ * @src: source MAC address of the frame
+ * @key_index: index of the key being reported
+ * @pairwise: whether the key is pairwise or group
+ */
+struct qlink_event_mic_failure {
+ struct qlink_event ehdr;
+ u8 src[ETH_ALEN];
+ u8 key_index;
+ u8 pairwise;
+} __packed;
+
+/**
+ * struct qlink_event_update_owe - data for QLINK_EVENT_UPDATE_OWE event
+ *
+ * @peer: MAC addr of the peer device for which OWE processing needs to be done
+ * @ies: IEs from the peer
+ */
+struct qlink_event_update_owe {
+ struct qlink_event ehdr;
+ u8 peer[ETH_ALEN];
+ u8 rsvd[2];
+ u8 ies[];
+} __packed;
+
+/* QLINK TLVs (Type-Length Values) definitions
+ */
+
+/**
+ * enum qlink_tlv_id - list of TLVs that Qlink messages can carry
+ *
+ * @QTN_TLV_ID_BITMAP: a data representing a bitmap that is used together with
+ * other TLVs:
+ * &enum qlink_sta_info used to indicate which statistic carried in
+ * 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.
+ * @QTN_TLV_ID_IFTYPE_DATA: supported band data.
+ */
+enum qlink_tlv_id {
+ QTN_TLV_ID_FRAG_THRESH = 0x0201,
+ QTN_TLV_ID_RTS_THRESH = 0x0202,
+ QTN_TLV_ID_SRETRY_LIMIT = 0x0203,
+ QTN_TLV_ID_LRETRY_LIMIT = 0x0204,
+ QTN_TLV_ID_REG_RULE = 0x0207,
+ QTN_TLV_ID_CHANNEL = 0x020F,
+ QTN_TLV_ID_CHANDEF = 0x0210,
+ QTN_TLV_ID_BITMAP = 0x0211,
+ QTN_TLV_ID_STA_STATS = 0x0212,
+ QTN_TLV_ID_COVERAGE_CLASS = 0x0213,
+ QTN_TLV_ID_IFACE_LIMIT = 0x0214,
+ QTN_TLV_ID_CHANNEL_STATS = 0x0216,
+ QTN_TLV_ID_KEY = 0x0302,
+ QTN_TLV_ID_SEQ = 0x0303,
+ QTN_TLV_ID_IE_SET = 0x0305,
+ QTN_TLV_ID_EXT_CAPABILITY_MASK = 0x0306,
+ QTN_TLV_ID_ACL_DATA = 0x0307,
+ QTN_TLV_ID_BUILD_NAME = 0x0401,
+ QTN_TLV_ID_BUILD_REV = 0x0402,
+ QTN_TLV_ID_BUILD_TYPE = 0x0403,
+ QTN_TLV_ID_BUILD_LABEL = 0x0404,
+ QTN_TLV_ID_HW_ID = 0x0405,
+ QTN_TLV_ID_CALIBRATION_VER = 0x0406,
+ QTN_TLV_ID_UBOOT_VER = 0x0407,
+ QTN_TLV_ID_RANDOM_MAC_ADDR = 0x0408,
+ QTN_TLV_ID_WOWLAN_CAPAB = 0x0410,
+ QTN_TLV_ID_WOWLAN_PATTERN = 0x0411,
+ QTN_TLV_ID_IFTYPE_DATA = 0x0418,
+};
+
+struct qlink_tlv_hdr {
+ /* New members MUST be added within the struct_group() macro below. */
+ __struct_group(qlink_tlv_hdr_fixed, __hdr, __packed,
+ __le16 type;
+ __le16 len;
+ );
+ u8 val[];
+} __packed;
+static_assert(offsetof(struct qlink_tlv_hdr, val) == sizeof(struct qlink_tlv_hdr_fixed),
+ "struct member likely outside of __struct_group()");
+
+struct qlink_iface_limit {
+ __le16 max_num;
+ __le16 type;
+} __packed;
+
+struct qlink_iface_limit_record {
+ __le16 max_interfaces;
+ u8 num_different_channels;
+ u8 n_limits;
+ struct qlink_iface_limit limits[];
+} __packed;
+
+#define QLINK_RSSI_OFFSET 120
+
+/**
+ * enum qlink_reg_rule_flags - regulatory rule flags
+ *
+ * See description of &enum nl80211_reg_rule_flags
+ */
+enum qlink_reg_rule_flags {
+ QLINK_RRF_NO_OFDM = BIT(0),
+ QLINK_RRF_NO_CCK = BIT(1),
+ QLINK_RRF_NO_INDOOR = BIT(2),
+ QLINK_RRF_NO_OUTDOOR = BIT(3),
+ QLINK_RRF_DFS = BIT(4),
+ QLINK_RRF_PTP_ONLY = BIT(5),
+ QLINK_RRF_PTMP_ONLY = BIT(6),
+ QLINK_RRF_NO_IR = BIT(7),
+ QLINK_RRF_AUTO_BW = BIT(8),
+ QLINK_RRF_IR_CONCURRENT = BIT(9),
+ QLINK_RRF_NO_HT40MINUS = BIT(10),
+ QLINK_RRF_NO_HT40PLUS = BIT(11),
+ QLINK_RRF_NO_80MHZ = BIT(12),
+ QLINK_RRF_NO_160MHZ = BIT(13),
+};
+
+/**
+ * struct qlink_tlv_reg_rule - data for QTN_TLV_ID_REG_RULE TLV
+ *
+ * Regulatory rule description.
+ *
+ * @start_freq_khz: start frequency of the range the rule is attributed to.
+ * @end_freq_khz: end frequency of the range the rule is attributed to.
+ * @max_bandwidth_khz: max bandwidth that channels in specified range can be
+ * configured to.
+ * @max_antenna_gain: max antenna gain that can be used in the specified
+ * frequency range, dBi.
+ * @max_eirp: maximum EIRP.
+ * @flags: regulatory rule flags in &enum qlink_reg_rule_flags.
+ * @dfs_cac_ms: DFS CAC period.
+ */
+struct qlink_tlv_reg_rule {
+ struct qlink_tlv_hdr_fixed hdr;
+ __le32 start_freq_khz;
+ __le32 end_freq_khz;
+ __le32 max_bandwidth_khz;
+ __le32 max_antenna_gain;
+ __le32 max_eirp;
+ __le32 flags;
+ __le32 dfs_cac_ms;
+} __packed;
+
+enum qlink_channel_flags {
+ QLINK_CHAN_DISABLED = BIT(0),
+ QLINK_CHAN_NO_IR = BIT(1),
+ QLINK_CHAN_RADAR = BIT(3),
+ QLINK_CHAN_NO_HT40PLUS = BIT(4),
+ QLINK_CHAN_NO_HT40MINUS = BIT(5),
+ QLINK_CHAN_NO_OFDM = BIT(6),
+ QLINK_CHAN_NO_80MHZ = BIT(7),
+ QLINK_CHAN_NO_160MHZ = BIT(8),
+ QLINK_CHAN_INDOOR_ONLY = BIT(9),
+ QLINK_CHAN_IR_CONCURRENT = BIT(10),
+ QLINK_CHAN_NO_20MHZ = BIT(11),
+ QLINK_CHAN_NO_10MHZ = BIT(12),
+};
+
+enum qlink_dfs_state {
+ QLINK_DFS_USABLE,
+ QLINK_DFS_UNAVAILABLE,
+ 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_fixed hdr;
+ struct qlink_channel chan;
+} __packed;
+
+/**
+ * struct qlink_tlv_chandef - data for QTN_TLV_ID_CHANDEF TLV
+ *
+ * Channel definition.
+ *
+ * @chan: channel definition data.
+ */
+struct qlink_tlv_chandef {
+ struct qlink_tlv_hdr_fixed hdr;
+ struct qlink_chandef chdef;
+} __packed;
+
+enum qlink_ie_set_type {
+ QLINK_IE_SET_UNKNOWN,
+ QLINK_IE_SET_ASSOC_REQ,
+ QLINK_IE_SET_ASSOC_RESP,
+ QLINK_IE_SET_PROBE_REQ,
+ QLINK_IE_SET_SCAN,
+ QLINK_IE_SET_BEACON_HEAD,
+ QLINK_IE_SET_BEACON_TAIL,
+ QLINK_IE_SET_BEACON_IES,
+ QLINK_IE_SET_PROBE_RESP,
+ QLINK_IE_SET_PROBE_RESP_IES,
+};
+
+/**
+ * struct qlink_tlv_ie_set - data for QTN_TLV_ID_IE_SET
+ *
+ * @type: type of MGMT frame IEs belong to, one of &enum qlink_ie_set_type.
+ * @flags: for future use.
+ * @ie_data: IEs data.
+ */
+struct qlink_tlv_ie_set {
+ struct qlink_tlv_hdr_fixed hdr;
+ u8 type;
+ u8 flags;
+ u8 rsvd[2];
+ u8 ie_data[];
+} __packed;
+
+/**
+ * struct qlink_tlv_ext_ie - extension IE
+ *
+ * @eid_ext: element ID extension, one of &enum ieee80211_eid_ext.
+ * @ie_data: IEs data.
+ */
+struct qlink_tlv_ext_ie {
+ struct qlink_tlv_hdr_fixed hdr;
+ u8 eid_ext;
+ u8 rsvd[3];
+ u8 ie_data[];
+} __packed;
+
+#define IEEE80211_HE_PPE_THRES_MAX_LEN 25
+struct qlink_sband_iftype_data {
+ __le16 types_mask;
+ struct ieee80211_he_cap_elem he_cap_elem;
+ struct ieee80211_he_mcs_nss_supp he_mcs_nss_supp;
+ u8 ppe_thres[IEEE80211_HE_PPE_THRES_MAX_LEN];
+} __packed;
+
+/**
+ * struct qlink_tlv_iftype_data - data for QTN_TLV_ID_IFTYPE_DATA
+ *
+ * @n_iftype_data: number of entries in iftype_data.
+ * @iftype_data: interface type data entries.
+ */
+struct qlink_tlv_iftype_data {
+ struct qlink_tlv_hdr_fixed hdr;
+ u8 n_iftype_data;
+ u8 rsvd[3];
+ struct qlink_sband_iftype_data iftype_data[];
+} __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 primary channel was busy.
+ * @cca_busy_ext: amount of time the secondary channel was busy.
+ * @time_scan: amount of radio spent scanning on the channel.
+ * @chan_noise: channel noise.
+ */
+struct qlink_chan_stats {
+ __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;
+
+/**
+ * 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_BITMAP.
+ *
+ * @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_BITMAP.
+ */
+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];
+};
+
+/**
+ * struct qlink_random_mac_addr - data for QTN_TLV_ID_RANDOM_MAC_ADDR TLV
+ *
+ * Specifies MAC address mask/value for generation random MAC address
+ * during scan.
+ *
+ * @mac_addr: MAC address used with randomisation
+ * @mac_addr_mask: MAC address mask used with randomisation, bits that
+ * are 0 in the mask should be randomised, bits that are 1 should
+ * be taken from the @mac_addr
+ */
+struct qlink_random_mac_addr {
+ u8 mac_addr[ETH_ALEN];
+ u8 mac_addr_mask[ETH_ALEN];
+} __packed;
+
+/**
+ * struct qlink_wowlan_capab_data - data for QTN_TLV_ID_WOWLAN_CAPAB TLV
+ *
+ * WoWLAN capabilities supported by cards.
+ *
+ * @version: version of WoWLAN data structure, to ensure backward
+ * compatibility for firmwares with limited WoWLAN support
+ * @len: Total length of WoWLAN data
+ * @data: supported WoWLAN features
+ */
+struct qlink_wowlan_capab_data {
+ __le16 version;
+ __le16 len;
+ u8 data[];
+} __packed;
+
+/**
+ * struct qlink_wowlan_support - supported WoWLAN capabilities
+ *
+ * @n_patterns: number of supported wakeup patterns
+ * @pattern_max_len: maximum length of each pattern
+ * @pattern_min_len: minimum length of each pattern
+ */
+struct qlink_wowlan_support {
+ __le32 n_patterns;
+ __le32 pattern_max_len;
+ __le32 pattern_min_len;
+} __packed;
+
+#endif /* _QTN_QLINK_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
new file mode 100644
index 000000000..30b60d6ae
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include <linux/nl80211.h>
+
+#include "qlink_util.h"
+
+u16 qlink_iface_type_to_nl_mask(u16 qlink_type)
+{
+ u16 result = 0;
+
+ switch (qlink_type) {
+ case QLINK_IFTYPE_AP:
+ result |= BIT(NL80211_IFTYPE_AP);
+ break;
+ case QLINK_IFTYPE_STATION:
+ result |= BIT(NL80211_IFTYPE_STATION);
+ break;
+ case QLINK_IFTYPE_ADHOC:
+ result |= BIT(NL80211_IFTYPE_ADHOC);
+ break;
+ case QLINK_IFTYPE_MONITOR:
+ result |= BIT(NL80211_IFTYPE_MONITOR);
+ break;
+ case QLINK_IFTYPE_WDS:
+ result |= BIT(NL80211_IFTYPE_WDS);
+ break;
+ case QLINK_IFTYPE_AP_VLAN:
+ result |= BIT(NL80211_IFTYPE_AP_VLAN);
+ break;
+ }
+
+ return result;
+}
+
+u8 qlink_chan_width_mask_to_nl(u16 qlink_mask)
+{
+ u8 result = 0;
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_5))
+ result |= BIT(NL80211_CHAN_WIDTH_5);
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_10))
+ result |= BIT(NL80211_CHAN_WIDTH_10);
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_20_NOHT))
+ result |= BIT(NL80211_CHAN_WIDTH_20_NOHT);
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_20))
+ result |= BIT(NL80211_CHAN_WIDTH_20);
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_40))
+ result |= BIT(NL80211_CHAN_WIDTH_40);
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_80))
+ result |= BIT(NL80211_CHAN_WIDTH_80);
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_80P80))
+ result |= BIT(NL80211_CHAN_WIDTH_80P80);
+
+ if (qlink_mask & BIT(QLINK_CHAN_WIDTH_160))
+ result |= BIT(NL80211_CHAN_WIDTH_160);
+
+ return result;
+}
+
+static enum nl80211_chan_width qlink_chanwidth_to_nl(u8 qlw)
+{
+ switch (qlw) {
+ case QLINK_CHAN_WIDTH_20_NOHT:
+ return NL80211_CHAN_WIDTH_20_NOHT;
+ case QLINK_CHAN_WIDTH_20:
+ return NL80211_CHAN_WIDTH_20;
+ case QLINK_CHAN_WIDTH_40:
+ return NL80211_CHAN_WIDTH_40;
+ case QLINK_CHAN_WIDTH_80:
+ return NL80211_CHAN_WIDTH_80;
+ case QLINK_CHAN_WIDTH_80P80:
+ return NL80211_CHAN_WIDTH_80P80;
+ case QLINK_CHAN_WIDTH_160:
+ return NL80211_CHAN_WIDTH_160;
+ case QLINK_CHAN_WIDTH_5:
+ return NL80211_CHAN_WIDTH_5;
+ case QLINK_CHAN_WIDTH_10:
+ return NL80211_CHAN_WIDTH_10;
+ default:
+ return -1;
+ }
+}
+
+static u8 qlink_chanwidth_nl_to_qlink(enum nl80211_chan_width nlwidth)
+{
+ switch (nlwidth) {
+ case NL80211_CHAN_WIDTH_20_NOHT:
+ return QLINK_CHAN_WIDTH_20_NOHT;
+ case NL80211_CHAN_WIDTH_20:
+ return QLINK_CHAN_WIDTH_20;
+ case NL80211_CHAN_WIDTH_40:
+ return QLINK_CHAN_WIDTH_40;
+ case NL80211_CHAN_WIDTH_80:
+ return QLINK_CHAN_WIDTH_80;
+ case NL80211_CHAN_WIDTH_80P80:
+ return QLINK_CHAN_WIDTH_80P80;
+ case NL80211_CHAN_WIDTH_160:
+ return QLINK_CHAN_WIDTH_160;
+ case NL80211_CHAN_WIDTH_5:
+ return QLINK_CHAN_WIDTH_5;
+ case NL80211_CHAN_WIDTH_10:
+ return QLINK_CHAN_WIDTH_10;
+ default:
+ return -1;
+ }
+}
+
+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);
+ chdef->edmg.bw_config = 0;
+ chdef->edmg.channels = 0;
+}
+
+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);
+}
+
+enum qlink_hidden_ssid qlink_hidden_ssid_nl2q(enum nl80211_hidden_ssid nl_val)
+{
+ switch (nl_val) {
+ case NL80211_HIDDEN_SSID_ZERO_LEN:
+ return QLINK_HIDDEN_SSID_ZERO_LEN;
+ case NL80211_HIDDEN_SSID_ZERO_CONTENTS:
+ return QLINK_HIDDEN_SSID_ZERO_CONTENTS;
+ case NL80211_HIDDEN_SSID_NOT_IN_USE:
+ default:
+ 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;
+}
+
+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));
+}
+
+enum qlink_band qlink_utils_band_cfg2q(enum nl80211_band band)
+{
+ switch (band) {
+ case NL80211_BAND_2GHZ:
+ return QLINK_BAND_2GHZ;
+ case NL80211_BAND_5GHZ:
+ return QLINK_BAND_5GHZ;
+ case NL80211_BAND_60GHZ:
+ return QLINK_BAND_60GHZ;
+ default:
+ return -EINVAL;
+ }
+}
+
+enum qlink_dfs_state qlink_utils_dfs_state_cfg2q(enum nl80211_dfs_state state)
+{
+ switch (state) {
+ case NL80211_DFS_USABLE:
+ return QLINK_DFS_USABLE;
+ case NL80211_DFS_AVAILABLE:
+ return QLINK_DFS_AVAILABLE;
+ case NL80211_DFS_UNAVAILABLE:
+ default:
+ return QLINK_DFS_UNAVAILABLE;
+ }
+}
+
+u32 qlink_utils_chflags_cfg2q(u32 cfgflags)
+{
+ u32 flags = 0;
+
+ if (cfgflags & IEEE80211_CHAN_DISABLED)
+ flags |= QLINK_CHAN_DISABLED;
+
+ if (cfgflags & IEEE80211_CHAN_NO_IR)
+ flags |= QLINK_CHAN_NO_IR;
+
+ if (cfgflags & IEEE80211_CHAN_RADAR)
+ flags |= QLINK_CHAN_RADAR;
+
+ if (cfgflags & IEEE80211_CHAN_NO_HT40PLUS)
+ flags |= QLINK_CHAN_NO_HT40PLUS;
+
+ if (cfgflags & IEEE80211_CHAN_NO_HT40MINUS)
+ flags |= QLINK_CHAN_NO_HT40MINUS;
+
+ if (cfgflags & IEEE80211_CHAN_NO_80MHZ)
+ flags |= QLINK_CHAN_NO_80MHZ;
+
+ if (cfgflags & IEEE80211_CHAN_NO_160MHZ)
+ flags |= QLINK_CHAN_NO_160MHZ;
+
+ return flags;
+}
+
+static u32 qtnf_reg_rule_flags_parse(u32 qflags)
+{
+ u32 flags = 0;
+
+ if (qflags & QLINK_RRF_NO_OFDM)
+ flags |= NL80211_RRF_NO_OFDM;
+
+ if (qflags & QLINK_RRF_NO_CCK)
+ flags |= NL80211_RRF_NO_CCK;
+
+ if (qflags & QLINK_RRF_NO_INDOOR)
+ flags |= NL80211_RRF_NO_INDOOR;
+
+ if (qflags & QLINK_RRF_NO_OUTDOOR)
+ flags |= NL80211_RRF_NO_OUTDOOR;
+
+ if (qflags & QLINK_RRF_DFS)
+ flags |= NL80211_RRF_DFS;
+
+ if (qflags & QLINK_RRF_PTP_ONLY)
+ flags |= NL80211_RRF_PTP_ONLY;
+
+ if (qflags & QLINK_RRF_PTMP_ONLY)
+ flags |= NL80211_RRF_PTMP_ONLY;
+
+ if (qflags & QLINK_RRF_NO_IR)
+ flags |= NL80211_RRF_NO_IR;
+
+ if (qflags & QLINK_RRF_AUTO_BW)
+ flags |= NL80211_RRF_AUTO_BW;
+
+ if (qflags & QLINK_RRF_IR_CONCURRENT)
+ flags |= NL80211_RRF_IR_CONCURRENT;
+
+ if (qflags & QLINK_RRF_NO_HT40MINUS)
+ flags |= NL80211_RRF_NO_HT40MINUS;
+
+ if (qflags & QLINK_RRF_NO_HT40PLUS)
+ flags |= NL80211_RRF_NO_HT40PLUS;
+
+ if (qflags & QLINK_RRF_NO_80MHZ)
+ flags |= NL80211_RRF_NO_80MHZ;
+
+ if (qflags & QLINK_RRF_NO_160MHZ)
+ flags |= NL80211_RRF_NO_160MHZ;
+
+ return flags;
+}
+
+void qlink_utils_regrule_q2nl(struct ieee80211_reg_rule *rule,
+ const struct qlink_tlv_reg_rule *tlv)
+{
+ rule->freq_range.start_freq_khz = le32_to_cpu(tlv->start_freq_khz);
+ rule->freq_range.end_freq_khz = le32_to_cpu(tlv->end_freq_khz);
+ rule->freq_range.max_bandwidth_khz =
+ le32_to_cpu(tlv->max_bandwidth_khz);
+ rule->power_rule.max_antenna_gain = le32_to_cpu(tlv->max_antenna_gain);
+ rule->power_rule.max_eirp = le32_to_cpu(tlv->max_eirp);
+ rule->dfs_cac_ms = le32_to_cpu(tlv->dfs_cac_ms);
+ rule->flags = qtnf_reg_rule_flags_parse(le32_to_cpu(tlv->flags));
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
new file mode 100644
index 000000000..230a10a41
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink_util.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_QLINK_UTIL_H_
+#define _QTN_FMAC_QLINK_UTIL_H_
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <net/cfg80211.h>
+
+#include "qlink.h"
+
+static inline void
+qtnf_cmd_skb_put_buffer(struct sk_buff *skb, const u8 *buf_src, size_t len)
+{
+ skb_put_data(skb, buf_src, len);
+}
+
+static inline void qtnf_cmd_skb_put_tlv_arr(struct sk_buff *skb,
+ u16 tlv_id, const u8 arr[],
+ size_t arr_len)
+{
+ struct qlink_tlv_hdr *hdr;
+
+ hdr = skb_put(skb, sizeof(*hdr) + round_up(arr_len, QLINK_ALIGN));
+ hdr->type = cpu_to_le16(tlv_id);
+ hdr->len = cpu_to_le16(arr_len);
+ memcpy(hdr->val, arr, arr_len);
+}
+
+static inline void qtnf_cmd_skb_put_tlv_u32(struct sk_buff *skb,
+ u16 tlv_id, u32 value)
+{
+ struct qlink_tlv_hdr *hdr = skb_put(skb, sizeof(*hdr) + sizeof(value));
+ __le32 tmp = cpu_to_le32(value);
+
+ hdr->type = cpu_to_le16(tlv_id);
+ hdr->len = cpu_to_le16(sizeof(value));
+ memcpy(hdr->val, &tmp, sizeof(tmp));
+}
+
+u16 qlink_iface_type_to_nl_mask(u16 qlink_type);
+u8 qlink_chan_width_mask_to_nl(u16 qlink_mask);
+void qlink_chandef_q2cfg(struct wiphy *wiphy,
+ const struct qlink_chandef *qch,
+ struct cfg80211_chan_def *chdef);
+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);
+void qlink_acl_data_cfg2q(const struct cfg80211_acl_data *acl,
+ struct qlink_acl_data *qacl);
+enum qlink_band qlink_utils_band_cfg2q(enum nl80211_band band);
+enum qlink_dfs_state qlink_utils_dfs_state_cfg2q(enum nl80211_dfs_state state);
+u32 qlink_utils_chflags_cfg2q(u32 cfgflags);
+void qlink_utils_regrule_q2nl(struct ieee80211_reg_rule *rule,
+ const struct qlink_tlv_reg_rule *tlv_rule);
+
+#define qlink_for_each_tlv(_tlv, _start, _datalen) \
+ for (_tlv = (const struct qlink_tlv_hdr *)(_start); \
+ (const u8 *)(_start) + (_datalen) - (const u8 *)_tlv >= \
+ (int)sizeof(*_tlv) && \
+ (const u8 *)(_start) + (_datalen) - (const u8 *)_tlv >= \
+ (int)sizeof(*_tlv) + le16_to_cpu(_tlv->len); \
+ _tlv = (const struct qlink_tlv_hdr *)(_tlv->val + \
+ round_up(le16_to_cpu(_tlv->len), QLINK_ALIGN)))
+
+#define qlink_tlv_parsing_ok(_tlv_last, _start, _datalen) \
+ ((const u8 *)(_tlv_last) == \
+ (const u8 *)(_start) + round_up(_datalen, QLINK_ALIGN))
+
+#endif /* _QTN_FMAC_QLINK_UTIL_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h b/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
new file mode 100644
index 000000000..82d879950
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_HW_IDS_H_
+#define _QTN_HW_IDS_H_
+
+#include <linux/pci_ids.h>
+
+#define PCIE_VENDOR_ID_QUANTENNA (0x1bb5)
+
+/* PCIE Device IDs */
+
+#define PCIE_DEVICE_ID_QSR (0x0008)
+
+#define QTN_REG_SYS_CTRL_CSR 0x14
+#define QTN_CHIP_ID_MASK 0xF0
+#define QTN_CHIP_ID_TOPAZ 0x40
+#define QTN_CHIP_ID_PEARL 0x50
+#define QTN_CHIP_ID_PEARL_B 0x60
+#define QTN_CHIP_ID_PEARL_C 0x70
+
+/* FW names */
+
+#define QTN_PCI_PEARL_FW_NAME "qtn/fmac_qsr10g.img"
+#define QTN_PCI_TOPAZ_FW_NAME "qtn/fmac_qsr1000.img"
+#define QTN_PCI_TOPAZ_BOOTLD_NAME "qtn/uboot_qsr1000.img"
+
+static inline unsigned int qtnf_chip_id_get(const void __iomem *regs_base)
+{
+ u32 board_rev = readl(regs_base + QTN_REG_SYS_CTRL_CSR);
+
+ return board_rev & QTN_CHIP_ID_MASK;
+}
+
+#endif /* _QTN_HW_IDS_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/shm_ipc.c b/drivers/net/wireless/quantenna/qtnfmac/shm_ipc.c
new file mode 100644
index 000000000..ff678951d
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/shm_ipc.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include <linux/types.h>
+#include <linux/io.h>
+
+#include "shm_ipc.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) "qtnfmac shm_ipc: %s: " fmt, __func__
+
+static bool qtnf_shm_ipc_has_new_data(struct qtnf_shm_ipc *ipc)
+{
+ const u32 flags = readl(&ipc->shm_region->headroom.hdr.flags);
+
+ return (flags & QTNF_SHM_IPC_NEW_DATA);
+}
+
+static void qtnf_shm_handle_new_data(struct qtnf_shm_ipc *ipc)
+{
+ size_t size;
+ bool rx_buff_ok = true;
+ struct qtnf_shm_ipc_region_header __iomem *shm_reg_hdr;
+
+ shm_reg_hdr = &ipc->shm_region->headroom.hdr;
+
+ size = readw(&shm_reg_hdr->data_len);
+
+ if (unlikely(size == 0 || size > QTN_IPC_MAX_DATA_SZ)) {
+ pr_err("wrong rx packet size: %zu\n", size);
+ rx_buff_ok = false;
+ }
+
+ if (likely(rx_buff_ok)) {
+ ipc->rx_packet_count++;
+ ipc->rx_callback.fn(ipc->rx_callback.arg,
+ ipc->shm_region->data, size);
+ }
+
+ writel(QTNF_SHM_IPC_ACK, &shm_reg_hdr->flags);
+ readl(&shm_reg_hdr->flags); /* flush PCIe write */
+
+ ipc->interrupt.fn(ipc->interrupt.arg);
+}
+
+static void qtnf_shm_ipc_irq_work(struct work_struct *work)
+{
+ struct qtnf_shm_ipc *ipc = container_of(work, struct qtnf_shm_ipc,
+ irq_work);
+
+ while (qtnf_shm_ipc_has_new_data(ipc))
+ qtnf_shm_handle_new_data(ipc);
+}
+
+static void qtnf_shm_ipc_irq_inbound_handler(struct qtnf_shm_ipc *ipc)
+{
+ u32 flags;
+
+ flags = readl(&ipc->shm_region->headroom.hdr.flags);
+
+ if (flags & QTNF_SHM_IPC_NEW_DATA)
+ queue_work(ipc->workqueue, &ipc->irq_work);
+}
+
+static void qtnf_shm_ipc_irq_outbound_handler(struct qtnf_shm_ipc *ipc)
+{
+ u32 flags;
+
+ if (!READ_ONCE(ipc->waiting_for_ack))
+ return;
+
+ flags = readl(&ipc->shm_region->headroom.hdr.flags);
+
+ if (flags & QTNF_SHM_IPC_ACK) {
+ WRITE_ONCE(ipc->waiting_for_ack, 0);
+ complete(&ipc->tx_completion);
+ }
+}
+
+int qtnf_shm_ipc_init(struct qtnf_shm_ipc *ipc,
+ enum qtnf_shm_ipc_direction direction,
+ struct qtnf_shm_ipc_region __iomem *shm_region,
+ struct workqueue_struct *workqueue,
+ const struct qtnf_shm_ipc_int *interrupt,
+ const struct qtnf_shm_ipc_rx_callback *rx_callback)
+{
+ BUILD_BUG_ON(offsetof(struct qtnf_shm_ipc_region, data) !=
+ QTN_IPC_REG_HDR_SZ);
+ BUILD_BUG_ON(sizeof(struct qtnf_shm_ipc_region) > QTN_IPC_REG_SZ);
+
+ ipc->shm_region = shm_region;
+ ipc->direction = direction;
+ ipc->interrupt = *interrupt;
+ ipc->rx_callback = *rx_callback;
+ ipc->tx_packet_count = 0;
+ ipc->rx_packet_count = 0;
+ ipc->workqueue = workqueue;
+ ipc->waiting_for_ack = 0;
+ ipc->tx_timeout_count = 0;
+
+ switch (direction) {
+ case QTNF_SHM_IPC_OUTBOUND:
+ ipc->irq_handler = qtnf_shm_ipc_irq_outbound_handler;
+ break;
+ case QTNF_SHM_IPC_INBOUND:
+ ipc->irq_handler = qtnf_shm_ipc_irq_inbound_handler;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ INIT_WORK(&ipc->irq_work, qtnf_shm_ipc_irq_work);
+ init_completion(&ipc->tx_completion);
+
+ return 0;
+}
+
+void qtnf_shm_ipc_free(struct qtnf_shm_ipc *ipc)
+{
+ complete_all(&ipc->tx_completion);
+}
+
+int qtnf_shm_ipc_send(struct qtnf_shm_ipc *ipc, const u8 *buf, size_t size)
+{
+ int ret = 0;
+ struct qtnf_shm_ipc_region_header __iomem *shm_reg_hdr;
+
+ shm_reg_hdr = &ipc->shm_region->headroom.hdr;
+
+ if (unlikely(size > QTN_IPC_MAX_DATA_SZ))
+ return -E2BIG;
+
+ ipc->tx_packet_count++;
+
+ writew(size, &shm_reg_hdr->data_len);
+ memcpy_toio(ipc->shm_region->data, buf, size);
+
+ /* sync previous writes before proceeding */
+ dma_wmb();
+
+ WRITE_ONCE(ipc->waiting_for_ack, 1);
+
+ /* sync previous memory write before announcing new data ready */
+ wmb();
+
+ writel(QTNF_SHM_IPC_NEW_DATA, &shm_reg_hdr->flags);
+ readl(&shm_reg_hdr->flags); /* flush PCIe write */
+
+ ipc->interrupt.fn(ipc->interrupt.arg);
+
+ if (!wait_for_completion_timeout(&ipc->tx_completion,
+ QTN_SHM_IPC_ACK_TIMEOUT)) {
+ ret = -ETIMEDOUT;
+ ipc->tx_timeout_count++;
+ pr_err("TX ACK timeout\n");
+ }
+
+ /* now we're not waiting for ACK even in case of timeout */
+ WRITE_ONCE(ipc->waiting_for_ack, 0);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/shm_ipc.h b/drivers/net/wireless/quantenna/qtnfmac/shm_ipc.h
new file mode 100644
index 000000000..52cac5439
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/shm_ipc.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_SHM_IPC_H_
+#define _QTN_FMAC_SHM_IPC_H_
+
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+#include "shm_ipc_defs.h"
+
+#define QTN_SHM_IPC_ACK_TIMEOUT (2 * HZ)
+
+struct qtnf_shm_ipc_int {
+ void (*fn)(void *arg);
+ void *arg;
+};
+
+struct qtnf_shm_ipc_rx_callback {
+ void (*fn)(void *arg, const u8 __iomem *buf, size_t len);
+ void *arg;
+};
+
+enum qtnf_shm_ipc_direction {
+ QTNF_SHM_IPC_OUTBOUND = BIT(0),
+ QTNF_SHM_IPC_INBOUND = BIT(1),
+};
+
+struct qtnf_shm_ipc {
+ struct qtnf_shm_ipc_region __iomem *shm_region;
+ enum qtnf_shm_ipc_direction direction;
+ size_t tx_packet_count;
+ size_t rx_packet_count;
+
+ size_t tx_timeout_count;
+
+ u8 waiting_for_ack;
+
+ struct qtnf_shm_ipc_int interrupt;
+ struct qtnf_shm_ipc_rx_callback rx_callback;
+
+ void (*irq_handler)(struct qtnf_shm_ipc *ipc);
+
+ struct workqueue_struct *workqueue;
+ struct work_struct irq_work;
+ struct completion tx_completion;
+};
+
+int qtnf_shm_ipc_init(struct qtnf_shm_ipc *ipc,
+ enum qtnf_shm_ipc_direction direction,
+ struct qtnf_shm_ipc_region __iomem *shm_region,
+ struct workqueue_struct *workqueue,
+ const struct qtnf_shm_ipc_int *interrupt,
+ const struct qtnf_shm_ipc_rx_callback *rx_callback);
+void qtnf_shm_ipc_free(struct qtnf_shm_ipc *ipc);
+int qtnf_shm_ipc_send(struct qtnf_shm_ipc *ipc, const u8 *buf, size_t size);
+
+static inline void qtnf_shm_ipc_irq_handler(struct qtnf_shm_ipc *ipc)
+{
+ ipc->irq_handler(ipc);
+}
+
+#endif /* _QTN_FMAC_SHM_IPC_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/shm_ipc_defs.h b/drivers/net/wireless/quantenna/qtnfmac/shm_ipc_defs.h
new file mode 100644
index 000000000..78be70df1
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/shm_ipc_defs.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_SHM_IPC_DEFS_H_
+#define _QTN_FMAC_SHM_IPC_DEFS_H_
+
+#include <linux/types.h>
+
+#define QTN_IPC_REG_HDR_SZ (32)
+#define QTN_IPC_REG_SZ (4096)
+#define QTN_IPC_MAX_DATA_SZ (QTN_IPC_REG_SZ - QTN_IPC_REG_HDR_SZ)
+
+enum qtnf_shm_ipc_region_flags {
+ QTNF_SHM_IPC_NEW_DATA = BIT(0),
+ QTNF_SHM_IPC_ACK = BIT(1),
+};
+
+struct qtnf_shm_ipc_region_header {
+ __le32 flags;
+ __le16 data_len;
+} __packed;
+
+union qtnf_shm_ipc_region_headroom {
+ struct qtnf_shm_ipc_region_header hdr;
+ u8 headroom[QTN_IPC_REG_HDR_SZ];
+} __packed;
+
+struct qtnf_shm_ipc_region {
+ union qtnf_shm_ipc_region_headroom headroom;
+ u8 data[QTN_IPC_MAX_DATA_SZ];
+} __packed;
+
+#endif /* _QTN_FMAC_SHM_IPC_DEFS_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/switchdev.h b/drivers/net/wireless/quantenna/qtnfmac/switchdev.h
new file mode 100644
index 000000000..b962e670c
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/switchdev.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2019 Quantenna Communications. All rights reserved. */
+
+#ifndef QTNFMAC_SWITCHDEV_H_
+#define QTNFMAC_SWITCHDEV_H_
+
+#include <linux/skbuff.h>
+
+#ifdef CONFIG_NET_SWITCHDEV
+
+static inline void qtnfmac_switch_mark_skb_flooded(struct sk_buff *skb)
+{
+ skb->offload_fwd_mark = 1;
+}
+
+#else
+
+static inline void qtnfmac_switch_mark_skb_flooded(struct sk_buff *skb)
+{
+}
+
+#endif
+
+#endif /* QTNFMAC_SWITCHDEV_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/trans.c b/drivers/net/wireless/quantenna/qtnfmac/trans.c
new file mode 100644
index 000000000..95356e280
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/trans.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include <linux/types.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "commands.h"
+#include "event.h"
+#include "bus.h"
+
+#define QTNF_DEF_SYNC_CMD_TIMEOUT (5 * HZ)
+
+int qtnf_trans_send_cmd_with_resp(struct qtnf_bus *bus, struct sk_buff *cmd_skb,
+ struct sk_buff **response_skb)
+{
+ struct qtnf_cmd_ctl_node *ctl_node = &bus->trans.curr_cmd;
+ struct qlink_cmd *cmd = (void *)cmd_skb->data;
+ int ret = 0;
+ long status;
+ bool resp_not_handled = true;
+ struct sk_buff *resp_skb = NULL;
+
+ if (unlikely(!response_skb)) {
+ dev_kfree_skb(cmd_skb);
+ return -EFAULT;
+ }
+
+ spin_lock(&ctl_node->resp_lock);
+ ctl_node->seq_num++;
+ cmd->seq_num = cpu_to_le16(ctl_node->seq_num);
+ WARN(ctl_node->resp_skb, "qtnfmac: response skb not empty\n");
+ ctl_node->waiting_for_resp = true;
+ spin_unlock(&ctl_node->resp_lock);
+
+ ret = qtnf_bus_control_tx(bus, cmd_skb);
+ dev_kfree_skb(cmd_skb);
+
+ if (unlikely(ret))
+ goto out;
+
+ status = wait_for_completion_interruptible_timeout(
+ &ctl_node->cmd_resp_completion,
+ QTNF_DEF_SYNC_CMD_TIMEOUT);
+
+ spin_lock(&ctl_node->resp_lock);
+ resp_not_handled = ctl_node->waiting_for_resp;
+ resp_skb = ctl_node->resp_skb;
+ ctl_node->resp_skb = NULL;
+ ctl_node->waiting_for_resp = false;
+ spin_unlock(&ctl_node->resp_lock);
+
+ if (unlikely(status <= 0)) {
+ if (status == 0) {
+ ret = -ETIMEDOUT;
+ pr_err("response timeout\n");
+ } else {
+ ret = -EINTR;
+ pr_debug("interrupted\n");
+ }
+ }
+
+ if (unlikely(!resp_skb || resp_not_handled)) {
+ if (!ret)
+ ret = -EFAULT;
+
+ goto out;
+ }
+
+ ret = 0;
+ *response_skb = resp_skb;
+
+out:
+ if (unlikely(resp_skb && resp_not_handled))
+ dev_kfree_skb(resp_skb);
+
+ return ret;
+}
+
+static void qtnf_trans_signal_cmdresp(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+ struct qtnf_cmd_ctl_node *ctl_node = &bus->trans.curr_cmd;
+ const struct qlink_resp *resp = (const struct qlink_resp *)skb->data;
+ const u16 recvd_seq_num = le16_to_cpu(resp->seq_num);
+
+ spin_lock(&ctl_node->resp_lock);
+
+ if (unlikely(!ctl_node->waiting_for_resp)) {
+ pr_err("unexpected response\n");
+ goto out_err;
+ }
+
+ if (unlikely(recvd_seq_num != ctl_node->seq_num)) {
+ pr_err("seq num mismatch\n");
+ goto out_err;
+ }
+
+ ctl_node->resp_skb = skb;
+ ctl_node->waiting_for_resp = false;
+
+ spin_unlock(&ctl_node->resp_lock);
+
+ complete(&ctl_node->cmd_resp_completion);
+ return;
+
+out_err:
+ spin_unlock(&ctl_node->resp_lock);
+ dev_kfree_skb(skb);
+}
+
+static int qtnf_trans_event_enqueue(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+ struct qtnf_qlink_transport *trans = &bus->trans;
+
+ if (likely(skb_queue_len(&trans->event_queue) <
+ trans->event_queue_max_len)) {
+ skb_queue_tail(&trans->event_queue, skb);
+ queue_work(bus->workqueue, &bus->event_work);
+ } else {
+ pr_warn("event dropped due to queue overflow\n");
+ dev_kfree_skb(skb);
+ return -1;
+ }
+
+ return 0;
+}
+
+void qtnf_trans_init(struct qtnf_bus *bus)
+{
+ struct qtnf_qlink_transport *trans = &bus->trans;
+
+ init_completion(&trans->curr_cmd.cmd_resp_completion);
+ spin_lock_init(&trans->curr_cmd.resp_lock);
+
+ spin_lock(&trans->curr_cmd.resp_lock);
+ trans->curr_cmd.seq_num = 0;
+ trans->curr_cmd.waiting_for_resp = false;
+ trans->curr_cmd.resp_skb = NULL;
+ spin_unlock(&trans->curr_cmd.resp_lock);
+
+ /* Init event handling related fields */
+ skb_queue_head_init(&trans->event_queue);
+ trans->event_queue_max_len = QTNF_MAX_EVENT_QUEUE_LEN;
+}
+
+static void qtnf_trans_free_events(struct qtnf_bus *bus)
+{
+ struct sk_buff_head *event_queue = &bus->trans.event_queue;
+ struct sk_buff *current_event_skb = skb_dequeue(event_queue);
+
+ while (current_event_skb) {
+ dev_kfree_skb_any(current_event_skb);
+ current_event_skb = skb_dequeue(event_queue);
+ }
+}
+
+void qtnf_trans_free(struct qtnf_bus *bus)
+{
+ if (!bus) {
+ pr_err("invalid bus pointer\n");
+ return;
+ }
+
+ qtnf_trans_free_events(bus);
+}
+
+int qtnf_trans_handle_rx_ctl_packet(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+ const struct qlink_msg_header *header = (void *)skb->data;
+ int ret = -1;
+
+ if (unlikely(skb->len < sizeof(*header))) {
+ pr_warn("packet is too small: %u\n", skb->len);
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ if (unlikely(skb->len != le16_to_cpu(header->len))) {
+ pr_warn("cmd reply length mismatch: %u != %u\n",
+ skb->len, le16_to_cpu(header->len));
+ dev_kfree_skb(skb);
+ return -EFAULT;
+ }
+
+ switch (le16_to_cpu(header->type)) {
+ case QLINK_MSG_TYPE_CMDRSP:
+ if (unlikely(skb->len < sizeof(struct qlink_cmd))) {
+ pr_warn("cmd reply too short: %u\n", skb->len);
+ dev_kfree_skb(skb);
+ break;
+ }
+
+ qtnf_trans_signal_cmdresp(bus, skb);
+ break;
+ case QLINK_MSG_TYPE_EVENT:
+ if (unlikely(skb->len < sizeof(struct qlink_event))) {
+ pr_warn("event too short: %u\n", skb->len);
+ dev_kfree_skb(skb);
+ break;
+ }
+
+ ret = qtnf_trans_event_enqueue(bus, skb);
+ break;
+ default:
+ pr_warn("unknown packet type: %x\n", le16_to_cpu(header->type));
+ dev_kfree_skb(skb);
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(qtnf_trans_handle_rx_ctl_packet);
diff --git a/drivers/net/wireless/quantenna/qtnfmac/trans.h b/drivers/net/wireless/quantenna/qtnfmac/trans.h
new file mode 100644
index 000000000..c0b76f871
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/trans.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#ifndef _QTN_FMAC_TRANS_H_
+#define _QTN_FMAC_TRANS_H_
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/mutex.h>
+
+#include "qlink.h"
+
+#define QTNF_CMD_FLAG_RESP_REQ BIT(0)
+
+#define QTNF_MAX_CMD_BUF_SIZE 2048
+#define QTNF_DEF_CMD_HROOM 4
+
+struct qtnf_bus;
+
+struct qtnf_cmd_ctl_node {
+ struct completion cmd_resp_completion;
+ struct sk_buff *resp_skb;
+ u16 seq_num;
+ bool waiting_for_resp;
+ spinlock_t resp_lock; /* lock for resp_skb & waiting_for_resp changes */
+};
+
+struct qtnf_qlink_transport {
+ struct qtnf_cmd_ctl_node curr_cmd;
+ struct sk_buff_head event_queue;
+ size_t event_queue_max_len;
+};
+
+void qtnf_trans_init(struct qtnf_bus *bus);
+void qtnf_trans_free(struct qtnf_bus *bus);
+
+int qtnf_trans_send_next_cmd(struct qtnf_bus *bus);
+int qtnf_trans_handle_rx_ctl_packet(struct qtnf_bus *bus, struct sk_buff *skb);
+int qtnf_trans_send_cmd_with_resp(struct qtnf_bus *bus,
+ struct sk_buff *cmd_skb,
+ struct sk_buff **response_skb);
+
+#endif /* _QTN_FMAC_TRANS_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/util.c b/drivers/net/wireless/quantenna/qtnfmac/util.c
new file mode 100644
index 000000000..4ad66cc18
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/util.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
+
+#include "util.h"
+#include "qtn_hw_ids.h"
+
+void qtnf_sta_list_init(struct qtnf_sta_list *list)
+{
+ if (unlikely(!list))
+ return;
+
+ INIT_LIST_HEAD(&list->head);
+ atomic_set(&list->size, 0);
+}
+
+struct qtnf_sta_node *qtnf_sta_list_lookup(struct qtnf_sta_list *list,
+ const u8 *mac)
+{
+ struct qtnf_sta_node *node;
+
+ if (unlikely(!mac))
+ return NULL;
+
+ list_for_each_entry(node, &list->head, list) {
+ if (ether_addr_equal(node->mac_addr, mac))
+ return node;
+ }
+
+ return NULL;
+}
+
+struct qtnf_sta_node *qtnf_sta_list_lookup_index(struct qtnf_sta_list *list,
+ size_t index)
+{
+ struct qtnf_sta_node *node;
+
+ if (qtnf_sta_list_size(list) <= index)
+ return NULL;
+
+ list_for_each_entry(node, &list->head, list) {
+ if (index-- == 0)
+ return node;
+ }
+
+ return NULL;
+}
+
+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))
+ return NULL;
+
+ node = qtnf_sta_list_lookup(list, mac);
+
+ if (node)
+ goto done;
+
+ node = kzalloc_obj(*node);
+ if (unlikely(!node))
+ goto done;
+
+ 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_vif *vif, const u8 *mac)
+{
+ struct qtnf_sta_list *list = &vif->sta_list;
+ struct qtnf_sta_node *node;
+ bool ret = false;
+
+ node = qtnf_sta_list_lookup(list, mac);
+
+ if (node) {
+ list_del(&node->list);
+ atomic_dec(&list->size);
+ kfree(node);
+ ++vif->generation;
+ ret = true;
+ }
+
+ return ret;
+}
+
+void qtnf_sta_list_free(struct qtnf_sta_list *list)
+{
+ struct qtnf_sta_node *node, *tmp;
+
+ atomic_set(&list->size, 0);
+
+ list_for_each_entry_safe(node, tmp, &list->head, list) {
+ list_del(&node->list);
+ kfree(node);
+ }
+
+ INIT_LIST_HEAD(&list->head);
+}
+
+const char *qtnf_chipid_to_string(unsigned long chip_id)
+{
+ switch (chip_id) {
+ case QTN_CHIP_ID_TOPAZ:
+ return "Topaz";
+ case QTN_CHIP_ID_PEARL:
+ return "Pearl revA";
+ case QTN_CHIP_ID_PEARL_B:
+ return "Pearl revB";
+ case QTN_CHIP_ID_PEARL_C:
+ return "Pearl revC";
+ default:
+ return "unknown";
+ }
+}
+EXPORT_SYMBOL_GPL(qtnf_chipid_to_string);
diff --git a/drivers/net/wireless/quantenna/qtnfmac/util.h b/drivers/net/wireless/quantenna/qtnfmac/util.h
new file mode 100644
index 000000000..a14b7078a
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/util.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2015 Quantenna Communications. All rights reserved. */
+
+#ifndef QTNFMAC_UTIL_H
+#define QTNFMAC_UTIL_H
+
+#include <linux/kernel.h>
+#include "core.h"
+
+const char *qtnf_chipid_to_string(unsigned long chip_id);
+
+void qtnf_sta_list_init(struct qtnf_sta_list *list);
+
+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_vif *vif,
+ 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);
+
+static inline size_t qtnf_sta_list_size(const struct qtnf_sta_list *list)
+{
+ return atomic_read(&list->size);
+}
+
+static inline bool qtnf_sta_list_empty(const struct qtnf_sta_list *list)
+{
+ return list_empty(&list->head);
+}
+
+#endif /* QTNFMAC_UTIL_H */