Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -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.
@@ -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
@@ -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 */
File diff suppressed because it is too large Load Diff
@@ -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_ */
File diff suppressed because it is too large Load Diff
@@ -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_ */
@@ -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");
@@ -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_ */
@@ -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);
}
@@ -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_ */
@@ -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);
}
}
@@ -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_ */
@@ -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");
@@ -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_ */
File diff suppressed because it is too large Load Diff
@@ -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_ */
@@ -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 */
File diff suppressed because it is too large Load Diff
@@ -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_ */
@@ -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 */
File diff suppressed because it is too large Load Diff
@@ -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));
}
@@ -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_ */
@@ -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_ */
@@ -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;
}
@@ -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_ */
@@ -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_ */
@@ -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_ */
@@ -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);
@@ -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_ */
@@ -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);
@@ -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 */