From fbe5a76d8c9ff1cf3f906a3c863928fc1adcbc95 Mon Sep 17 00:00:00 2001 From: Karthikeyan Kathirvel Date: Tue, 16 Feb 2021 13:44:39 +0530 Subject: [PATCH] ath11k: Add mesh nss offload support - New capability advertising nss offload support for mesh type - Mesh obj vap and link vap registration/clean up - Command/event handling - New .ch files in ath11k for nss mesh offload related debugs - Tx/Rx data path on mesh link vap uses native wifi format - Mesh obj vap handls packets in ether format. No Tx on Mesh obj vap is expected as packets transmitted in slow path is supposed to be encapsulated in 802.11 format. - New mac80211-driver callbacks for mesh vap, mpath and mpp configurations. Signed-off-by: Vasanthakumar Thiagarajan Change-Id: Ib6950344286ba18fab43586262c62dcd09557614 Co-developed-by: Karthikeyan Kathirvel Signed-off-by: Karthikeyan Kathirvel Signed-off-by: Vasanthakumar Thiagarajan --- drivers/net/wireless/ath/ath11k/Makefile | 2 +- drivers/net/wireless/ath/ath11k/debug_nss.c | 343 +++++++ drivers/net/wireless/ath/ath11k/debug_nss.h | 24 + drivers/net/wireless/ath/ath11k/dp.h | 16 +- drivers/net/wireless/ath/ath11k/dp_rx.c | 170 ++- drivers/net/wireless/ath/ath11k/dp_rx.h | 2 + drivers/net/wireless/ath/ath11k/mac.c | 36 +- drivers/net/wireless/ath/ath11k/nss.c | 1482 ++++++++++++++++++++++++--- drivers/net/wireless/ath/ath11k/nss.h | 63 +- include/net/mac80211.h | 106 ++ net/mac80211/cfg.c | 19 +- net/mac80211/debug.h | 10 + net/mac80211/debugfs.c | 1 + net/mac80211/driver-ops.c | 20 + net/mac80211/driver-ops.h | 7 + net/mac80211/mesh.h | 5 + net/mac80211/mesh_hwmp.c | 273 +++++ net/mac80211/mesh_pathtbl.c | 167 ++- net/mac80211/rx.c | 8 +- 19 files changed, 2548 insertions(+), 206 deletions(-) create mode 100644 drivers/net/wireless/ath/ath11k/debug_nss.c create mode 100644 drivers/net/wireless/ath/ath11k/debug_nss.h --- a/drivers/net/wireless/ath/ath11k/Makefile +++ b/drivers/net/wireless/ath/ath11k/Makefile @@ -20,7 +20,7 @@ ath11k-y += core.o \ wow.o \ vendor.o -ath11k-$(CPTCFG_ATH11K_DEBUGFS) += debugfs.o debugfs_htt_stats.o debugfs_sta.o +ath11k-$(CPTCFG_ATH11K_DEBUGFS) += debugfs.o debugfs_htt_stats.o debugfs_sta.o debug_nss.o ath11k-$(CPTCFG_NL80211_TESTMODE) += testmode.o ath11k-$(CPTCFG_ATH11K_TRACING) += trace.o ath11k-$(CONFIG_THERMAL) += thermal.o --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/debug_nss.c @@ -0,0 +1,924 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#ifdef CPTCFG_ATH11K_NSS_SUPPORT + +#include +#include "dp_rx.h" +#include "nss.h" +#include "debug.h" +#include "debug_nss.h" + +static unsigned int +debug_nss_fill_mpp_dump(struct ath11k_vif *arvif, char *buf, ssize_t size) +{ + struct arvif_nss *nss = &arvif->nss; + struct ath11k *ar = arvif->ar; + struct ath11k_nss_mpp_entry *entry, *tmp; + LIST_HEAD(local_entry); + unsigned int len = 0; + int i; + + len += scnprintf(buf + len, size - len, "\nProxy path table\n"); + len += scnprintf(buf + len, size - len, "dest_mac_addr\t\tmesh_dest_mac\t\tflags\n"); + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail_init(&nss->mpp_dump, &local_entry); + spin_unlock_bh(&ar->nss.dump_lock); + + list_for_each_entry_safe(entry, tmp, &local_entry, list) { + for (i = 0; i < entry->num_entries; i++) + len += scnprintf(buf + len, size - len, "%pM\t%pM\t0x%x\n", + entry->mpp[i].dest_mac_addr, + entry->mpp[i].mesh_dest_mac, entry->mpp[i].flags); + list_del(&entry->list); + kfree(entry); + } + + return len; +} + +static int ath11k_nss_dump_mpp_open(struct inode *inode, struct file *file) +{ + struct ath11k_vif *arvif = inode->i_private; + struct ath11k *ar = arvif->ar; + unsigned long time_left; + struct ath11k_nss_dbg_priv_data *priv_data; + int ret; + ssize_t size = 100; + char *buf; + + mutex_lock(&ar->conf_mutex); + + reinit_completion(&arvif->nss.dump_mpp_complete); + + priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) { + mutex_unlock(&ar->conf_mutex); + return -ENOMEM; + } + + priv_data->arvif = arvif; + ret = ath11k_nss_dump_mpp_request(arvif); + if (ret) { + ath11k_warn(ar->ab, "failed to send dump mpp command %d\n", ret); + goto err_unlock; + } + + time_left = wait_for_completion_timeout(&arvif->nss.dump_mpp_complete, + ATH11K_NSS_MPATH_DUMP_TIMEOUT); + if (time_left == 0) { + ret = -ETIMEDOUT; + goto err_unlock; + } + + mutex_unlock(&ar->conf_mutex); + + size += (arvif->nss.mpp_dump_num_entries * 200 + 10 * 100); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + priv_data->buf = buf; + + priv_data->len= debug_nss_fill_mpp_dump(arvif, buf, size); + + file->private_data = priv_data; + + return 0; + +err_unlock: + kfree(priv_data); + mutex_unlock(&ar->conf_mutex); + return ret; +} + +static int ath11k_nss_dump_mpp_release(struct inode *inode, struct file *file) +{ + struct ath11k_nss_dbg_priv_data *priv_data = file->private_data; + + kfree(priv_data->buf); + kfree(priv_data); + return 0; +} + +static ssize_t ath11k_nss_dump_mpp_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_nss_dbg_priv_data *priv_data = file->private_data; + struct ath11k_vif *arvif = priv_data->arvif; + char *buf = priv_data->buf; + struct ath11k *ar = arvif->ar; + int ret; + + mutex_lock(&ar->conf_mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, priv_data->len); + + mutex_unlock(&ar->conf_mutex); + + return ret; +} + +static const struct file_operations fops_nss_dump_mpp_table = { + .open = ath11k_nss_dump_mpp_open, + .read = ath11k_nss_dump_mpp_read, + .release = ath11k_nss_dump_mpp_release, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static unsigned int +debug_nss_fill_mpath_dump(struct ath11k_vif *arvif, char *buf, ssize_t size) +{ + struct arvif_nss *nss = &arvif->nss; + struct ath11k *ar = arvif->ar; + struct ath11k_nss_mpath_entry *entry, *tmp; + LIST_HEAD(local_entry); + unsigned int len = 0; + u64 expiry_time; + int i; + + len += scnprintf(buf + len, size - len, "\nmpath table\n"); + len += scnprintf(buf + len, size - len, "dest_mac_addr\t\tnext_hop_mac\t\tmetric\t" + "expiry_time\thop_count\tflags\tlink_vap_id\n"); + + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail_init(&nss->mpath_dump, &local_entry); + spin_unlock_bh(&ar->nss.dump_lock); + + list_for_each_entry_safe(entry, tmp, &local_entry, list) { + for (i = 0; i < entry->num_entries; i++) { + memcpy(&expiry_time, entry->mpath[i].expiry_time, sizeof(u64)); + len += scnprintf(buf + len, size - len, "%pM\t%pM\t%u\t%llu\t\t\t%d\t0x%x\t%d\n", + entry->mpath[i].dest_mac_addr, + entry->mpath[i].next_hop_mac_addr, entry->mpath[i].metric, + expiry_time, entry->mpath[i].hop_count, + entry->mpath[i].flags, entry->mpath[i].link_vap_id); + } + kfree(entry); + } + + return len; +} + +static int ath11k_nss_dump_mpath_open(struct inode *inode, struct file *file) +{ + struct ath11k_vif *arvif = inode->i_private; + struct ath11k *ar = arvif->ar; + unsigned long time_left; + struct ath11k_nss_dbg_priv_data *priv_data; + ssize_t size = 200; + char *buf; + int ret; + + reinit_completion(&arvif->nss.dump_mpath_complete); + + priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + mutex_lock(&ar->conf_mutex); + + priv_data->arvif = arvif; + ret = ath11k_nss_dump_mpath_request(arvif); + if (ret) { + ath11k_warn(ar->ab, "failed to send dump mpath command %d\n", ret); + goto err_unlock; + } + + time_left = wait_for_completion_timeout(&arvif->nss.dump_mpath_complete, + ATH11K_NSS_MPATH_DUMP_TIMEOUT); + if (time_left == 0) { + ret = -ETIMEDOUT; + goto err_unlock; + } + + mutex_unlock(&ar->conf_mutex); + + size += (arvif->nss.mpath_dump_num_entries * 200 + 10 * 100); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + priv_data->buf = buf; + + priv_data->len = debug_nss_fill_mpath_dump(arvif, buf, size); + + file->private_data = priv_data; + + return 0; + +err_unlock: + mutex_unlock(&ar->conf_mutex); + return ret; +} + +static int ath11k_nss_dump_mpath_release(struct inode *inode, struct file *file) +{ + struct ath11k_nss_dbg_priv_data *priv_data = file->private_data; + + kfree(priv_data->buf); + kfree(priv_data); + return 0; + +} + +static ssize_t ath11k_nss_dump_mpath_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_nss_dbg_priv_data *priv_data = file->private_data; + struct ath11k_vif *arvif = priv_data->arvif; + char *buf = priv_data->buf; + struct ath11k *ar = arvif->ar; + int ret; + + mutex_lock(&ar->conf_mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, priv_data->len); + + mutex_unlock(&ar->conf_mutex); + + return ret; +} + +static const struct file_operations fops_nss_dump_mpath_table = { + .open = ath11k_nss_dump_mpath_open, + .read = ath11k_nss_dump_mpath_read, + .release = ath11k_nss_dump_mpath_release, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_mpath_add(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct ieee80211_mesh_path_offld path = {0}; + u8 buf[128] = {0}; + int ret; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%u %hhu %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %hhu %hhu", + &path.metric, + &path.hop_count, + &path.mesh_da[0], + &path.mesh_da[1], + &path.mesh_da[2], + &path.mesh_da[3], + &path.mesh_da[4], + &path.mesh_da[5], + &path.next_hop[0], + &path.next_hop[1], + &path.next_hop[2], + &path.next_hop[3], + &path.next_hop[4], + &path.next_hop[5], + &path.block_mesh_fwd, + &path.metadata_type); + + + path.flags |= IEEE80211_MESH_PATH_ACTIVE | IEEE80211_MESH_PATH_RESOLVED; + + if (ret != 16) + return -EINVAL; + + /* Configure the mpath */ + ret = ath11k_nss_mesh_config_path(arvif->ar, arvif, + IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPATH, + &path); + if(ret) { + ath11k_warn(arvif->ar->ab, "failed to configure mpath ret %d\n", ret); + return -EINVAL; + } + + return ret ? ret : count; + +} + +static const struct file_operations fops_nss_mpath_add = { + .open = simple_open, + .write = ath11k_nss_mpath_add, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_mpp_add(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct ieee80211_mesh_path_offld path = {0}; + u8 buf[128] = {0}; + int ret; + + if (!arvif->ar->ab->nss.debug_mode) { + ret = -EPERM; + return ret; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &path.da[0], + &path.da[1], + &path.da[2], + &path.da[3], + &path.da[4], + &path.da[5], + &path.mesh_da[0], + &path.mesh_da[1], + &path.mesh_da[2], + &path.mesh_da[3], + &path.mesh_da[4], + &path.mesh_da[5]); + + path.flags |= IEEE80211_MESH_PATH_ACTIVE | IEEE80211_MESH_PATH_RESOLVED; + + if (ret != 12) + return -EINVAL; + + /* Configure the mpp */ + ret = ath11k_nss_mesh_config_path(arvif->ar, arvif, + IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPP, + &path); + if(ret) { + ath11k_warn(arvif->ar->ab, "failed to configure mpp ret %d\n", ret); + return -EINVAL; + } + + return ret ? ret : count; + +} + +static const struct file_operations fops_nss_mpp_add = { + .open = simple_open, + .write = ath11k_nss_mpp_add, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_mpath_update(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct ieee80211_mesh_path_offld path = {0}; + u8 buf[128] = {0}; + int ret; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%u %hhu %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %hhu %lu %hhu %hhu", + &path.metric, + &path.hop_count, + &path.mesh_da[0], + &path.mesh_da[1], + &path.mesh_da[2], + &path.mesh_da[3], + &path.mesh_da[4], + &path.mesh_da[5], + &path.next_hop[0], + &path.next_hop[1], + &path.next_hop[2], + &path.next_hop[3], + &path.next_hop[4], + &path.next_hop[5], + &path.old_next_hop[0], + &path.old_next_hop[1], + &path.old_next_hop[2], + &path.old_next_hop[3], + &path.old_next_hop[4], + &path.old_next_hop[5], + &path.mesh_gate, + &path.exp_time, + &path.block_mesh_fwd, + &path.metadata_type); + + + path.flags |= IEEE80211_MESH_PATH_ACTIVE | IEEE80211_MESH_PATH_RESOLVED; + + if (ret != 24) + return -EINVAL; + + /* Configure the mpath */ + ret = ath11k_nss_mesh_config_path(arvif->ar, arvif, + IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPATH, + &path); + if(ret) { + ath11k_warn(arvif->ar->ab, "failed to configure mpath ret %d\n", ret); + return -EINVAL; + } + + return ret ? ret : count; + +} + +static const struct file_operations fops_nss_mpath_update = { + .open = simple_open, + .write = ath11k_nss_mpath_update, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_mpp_update(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct ieee80211_mesh_path_offld path = {0}; + u8 buf[128] = {0}; + int ret; + + if (!arvif->ar->ab->nss.debug_mode) { + ret = -EPERM; + return ret; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &path.da[0], + &path.da[1], + &path.da[2], + &path.da[3], + &path.da[4], + &path.da[5], + &path.mesh_da[0], + &path.mesh_da[1], + &path.mesh_da[2], + &path.mesh_da[3], + &path.mesh_da[4], + &path.mesh_da[5]); + + path.flags |= IEEE80211_MESH_PATH_ACTIVE | IEEE80211_MESH_PATH_RESOLVED; + + if (ret != 12) + return -EINVAL; + + /* Configure the mpp */ + ret = ath11k_nss_mesh_config_path(arvif->ar, arvif, + IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPP, + &path); + if(ret) { + ath11k_warn(arvif->ar->ab, "failed to configure mpp ret %d\n", ret); + return -EINVAL; + } + + return ret ? ret : count; + +} + +static const struct file_operations fops_nss_mpp_update = { + .open = simple_open, + .write = ath11k_nss_mpp_update, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +static ssize_t ath11k_nss_mpath_delete(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct ieee80211_mesh_path_offld path = {0}; + u8 buf[128] = {0}; + int ret; + + if (!arvif->ar->ab->nss.debug_mode) { + ret = -EPERM; + return ret; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &path.mesh_da[0], + &path.mesh_da[1], + &path.mesh_da[2], + &path.mesh_da[3], + &path.mesh_da[4], + &path.mesh_da[5], + &path.next_hop[0], + &path.next_hop[1], + &path.next_hop[2], + &path.next_hop[3], + &path.next_hop[4], + &path.next_hop[5]); + + path.flags |= IEEE80211_MESH_PATH_DELETED; + + if (ret != 12) + return -EINVAL; + + /* Configure the mpath */ + ret = ath11k_nss_mesh_config_path(arvif->ar, arvif, + IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPATH, + &path); + if(ret) { + ath11k_warn(arvif->ar->ab, "failed to configure mpath ret %d\n", ret); + return -EINVAL; + } + + return ret ? ret : count; + +} + +static const struct file_operations fops_nss_mpath_del = { + .open = simple_open, + .write = ath11k_nss_mpath_delete, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_mpp_delete(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct ieee80211_mesh_path_offld path = {0}; + u8 buf[128] = {0}; + int ret; + + if (!arvif->ar->ab->nss.debug_mode) { + ret = -EPERM; + return ret; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &path.da[0], + &path.da[1], + &path.da[2], + &path.da[3], + &path.da[4], + &path.da[5], + &path.mesh_da[0], + &path.mesh_da[1], + &path.mesh_da[2], + &path.mesh_da[3], + &path.mesh_da[4], + &path.mesh_da[5]); + + path.flags |= IEEE80211_MESH_PATH_DELETED; + + if (ret != 12) + return -EINVAL; + + /* Configure the mpp */ + ret = ath11k_nss_mesh_config_path(arvif->ar, arvif, + IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPP, + &path); + if(ret) { + ath11k_warn(arvif->ar->ab, "failed to configure mpp ret %d\n", ret); + return -EINVAL; + } + + return ret ? ret : count; + +} + +static const struct file_operations fops_nss_mpp_del = { + .open = simple_open, + .write = ath11k_nss_mpp_delete, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +static ssize_t ath11k_nss_assoc_link(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + u8 buf[128] = {0}; + int ret; + u32 assoc_link = 0; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%u", &assoc_link); + + if (ret != 1) + return -EINVAL; + + arvif->ar->ab->nss.debug_mode = true; + arvif->vif->driver_flags |= IEEE80211_VIF_NSS_OFFLOAD_DEBUG_MODE; + + ret = ath11k_nss_assoc_link_arvif_to_ifnum(arvif, assoc_link); + + return ret ? ret : count; + +} + +static const struct file_operations fops_nss_assoc_link = { + .open = simple_open, + .write = ath11k_nss_assoc_link, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_links(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[512] = {0}; + struct arvif_nss *nss; + int len = 0; + + list_for_each_entry(nss, &mesh_vaps, list) + len += scnprintf(buf + len, sizeof(buf) - len, "link id %d\n", + nss->if_num); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_nss_links = { + .open = simple_open, + .read = ath11k_nss_links, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_vap_link_id(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct arvif_nss *nss = &arvif->nss; + char buf[512] = {0}; + int len = 0; + + len = scnprintf(buf, sizeof(buf) - len, "link id %d\n", + nss->if_num); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_nss_vap_link_id = { + .open = simple_open, + .read = ath11k_nss_vap_link_id, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_read_mpp_mode(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[512] = {0}; + int len = 0; + + len = scnprintf(buf, sizeof(buf) - len, "%s\n",mpp_mode ? + "Host Assisted Learning" : "NSS Independent Learning"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath11k_nss_write_mpp_mode(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + u8 buf[128] = {0}; + int ret; + u32 mppath_mode = 0; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%u", &mppath_mode); + + if (ret != 1) + return -EINVAL; + + mpp_mode = mppath_mode; + + ret = 0; + + return ret ? ret : count; +} + +static const struct file_operations fops_nss_mpp_mode = { + .open = simple_open, + .write = ath11k_nss_write_mpp_mode, + .read = ath11k_nss_read_mpp_mode, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_write_excep_flags(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + u8 buf[128] = {0}; + int ret; + struct nss_wifi_mesh_exception_flag_msg msg; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %hhu", + &msg.dest_mac_addr[0], + &msg.dest_mac_addr[1], + &msg.dest_mac_addr[2], + &msg.dest_mac_addr[3], + &msg.dest_mac_addr[4], + &msg.dest_mac_addr[5], + &msg.exception); + + if (ret != 7) + return -EINVAL; + + ret = ath11k_nss_mesh_exception_flags(arvif, &msg); + + return ret ? ret : count; +} + +static const struct file_operations fops_nss_excep_flags = { + .open = simple_open, + .write = ath11k_nss_write_excep_flags, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_write_metadata_type(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct ath11k *ar = arvif->ar; + u8 buf[128] = {0}; + int ret; + u8 pkt_type; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + + ret = sscanf(buf, "%hhu", &pkt_type); + mutex_lock(&ar->conf_mutex); + arvif->nss.metadata_type = pkt_type ? NSS_WIFI_MESH_PRE_HEADER_80211 : NSS_WIFI_MESH_PRE_HEADER_NONE; + mutex_unlock(&ar->conf_mutex); + + if (ret != 1) + return -EINVAL; + + ret = 0; + + return ret ? ret : count; +} + +static const struct file_operations fops_nss_metadata_type = { + .open = simple_open, + .write = ath11k_nss_write_metadata_type, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_nss_write_exc_rate_limit(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + u8 buf[128] = {0}; + int ret; + struct nss_wifi_mesh_rate_limit_config nss_exc_cfg; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + + ret = sscanf(buf, "%u %u %u", + &nss_exc_cfg.exception_num, + &nss_exc_cfg.enable, + &nss_exc_cfg.rate_limit); + + if (ret != 3) + return -EINVAL; + + ret = ath11k_nss_exc_rate_config(arvif, &nss_exc_cfg); + + return ret ? ret : count; +} + +static const struct file_operations fops_nss_exc_rate_limit = { + .open = simple_open, + .write = ath11k_nss_write_exc_rate_limit, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath11k_debugfs_nss_mesh_vap_create(struct ath11k_vif *arvif) +{ + struct dentry *debugfs_nss_mesh_dir, *debugfs_dbg_infra; + + debugfs_nss_mesh_dir = debugfs_create_dir("nss_mesh", arvif->vif->debugfs_dir); + debugfs_dbg_infra = debugfs_create_dir("dbg_infra", debugfs_nss_mesh_dir); + + debugfs_create_file("dump_nss_mpath_table", 0600, + debugfs_nss_mesh_dir, arvif, + &fops_nss_dump_mpath_table); + + debugfs_create_file("dump_nss_mpp_table", 0600, + debugfs_nss_mesh_dir, arvif, + &fops_nss_dump_mpp_table); + + debugfs_create_file("mpath_add", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_mpath_add); + + debugfs_create_file("mpath_update", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_mpath_update); + + debugfs_create_file("mpath_del", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_mpath_del); + + debugfs_create_file("mpp_add", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_mpp_add); + + debugfs_create_file("mpp_update", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_mpp_update); + + debugfs_create_file("mpp_del", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_mpp_del); + + debugfs_create_file("assoc_link", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_assoc_link); + + debugfs_create_file("vap_linkid", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_vap_link_id); + + debugfs_create_file("excep_flags", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_excep_flags); + + debugfs_create_file("metadata_type", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_metadata_type); + + debugfs_create_file("exc_rate_limit", 0200, + debugfs_dbg_infra, arvif, + &fops_nss_exc_rate_limit); +} + +void ath11k_debugfs_nss_soc_create(struct ath11k_base *ab) +{ + struct dentry *debugfs_dbg_infra; + + debugfs_dbg_infra = debugfs_create_dir("dbg_infra", debugfs_ath11k); + + debugfs_create_file("links", 0200, + debugfs_dbg_infra, ab, + &fops_nss_links); + + debugfs_create_file("mpp_mode", 0600, + debugfs_dbg_infra, ab, + &fops_nss_mpp_mode); +} + +#endif --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/debug_nss.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: BSD-3-Clause-Clear */ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef ATH11K_DEBUG_NSS_H +#define ATH11K_DEBUG_NSS_H + +#include +#include + +#define ATH11K_NSS_MPATH_DUMP_TIMEOUT (2 * HZ) + +struct ath11k_vif; +extern enum nss_wifi_mesh_mpp_learning_mode mpp_mode; +extern struct list_head mesh_vaps; +struct ath11k_nss_dbg_priv_data { + struct ath11k_vif *arvif; + char *buf; + unsigned int len; +}; + +#ifdef CPTCFG_MAC80211_DEBUGFS +void ath11k_debugfs_nss_mesh_vap_create(struct ath11k_vif *arvif); +void ath11k_debugfs_nss_soc_create(struct ath11k_base *ab); +#else +static inline void ath11k_debugfs_nss_mesh_vap_create(struct ath11k_vif *arvif) +{ +} +static inline void ath11k_debugfs_nss_soc_create(struct ath11k_base *ab) +{ +} +#endif + +#endif --- a/drivers/net/wireless/ath/ath11k/dp.h +++ b/drivers/net/wireless/ath/ath11k/dp.h @@ -1575,15 +1575,29 @@ struct htt_ppdu_stats_usr_cmn_array { struct htt_tx_ppdu_stats_info tx_ppdu_info[0]; } __packed; +#define HTT_PPDU_STATS_CMPLTN_FLUSH_INFO_FLOW_TYPE GENMASK(7, 0) +#define HTT_PPDU_STATS_CMPLTN_FLUSH_INFO_NUM_MPDU GENMASK(16, 8) +#define HTT_PPDU_STATS_CMPLTN_FLUSH_INFO_NUM_MSDU GENMASK(30, 17) + +struct htt_ppdu_stats_cmpltn_flush { + u32 drop_reason; + u32 info; + u8 tid_num; + u8 queue_type; + u16 sw_peer_id; +} __packed; + struct htt_ppdu_user_stats { u16 peer_id; u16 delay_ba; u32 tlv_flags; bool is_valid_peer_id; + bool rate_stats_updated; struct htt_ppdu_stats_user_rate rate; struct htt_ppdu_stats_usr_cmpltn_cmn cmpltn_cmn; struct htt_ppdu_stats_usr_cmpltn_ack_ba_status ack_ba; struct htt_ppdu_stats_user_common common; + struct htt_ppdu_stats_cmpltn_flush cmpltn_flush; }; #define HTT_PPDU_STATS_MAX_USERS 37 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -1405,6 +1405,68 @@ static int ath11k_htt_tlv_ppdu_stats_par return 0; } +static void ath11k_dp_ppdu_stats_flush_tlv_parse(struct ath11k_base *ab, + struct htt_ppdu_stats_cmpltn_flush *msg) +{ + struct ath11k *ar; + struct ieee80211_sta *sta; + struct ath11k_sta *arsta; + struct ath11k_peer *peer = NULL; + struct ieee80211_tx_status status; + + if (!ab->nss.mesh_nss_offload_enabled) + return; + + rcu_read_lock(); + + spin_lock_bh(&ab->base_lock); + peer = ath11k_peer_find_by_id(ab, msg->sw_peer_id); + if (!peer) + goto exit; + + if (peer->vif->type != NL80211_IFTYPE_MESH_POINT) + goto exit; + + if (ether_addr_equal(peer->addr, peer->vif->addr)) + goto exit; + + sta = peer->sta; + arsta = (struct ath11k_sta *)sta->drv_priv; + + memset(&status, 0, sizeof(status)); + + status.sta = sta; + status.rate = &arsta->last_txrate; + status.mpdu_fail = FIELD_GET(HTT_PPDU_STATS_CMPLTN_FLUSH_INFO_NUM_MPDU, + msg->info); + ar = arsta->arvif->ar; + ieee80211s_update_metric_ppdu(ar->hw, &status); + +exit: + spin_unlock_bh(&ab->base_lock); + rcu_read_unlock(); +} + +static int ath11k_htt_tlv_ppdu_soc_stats_parse(struct ath11k_base *ab, + u16 tag, u16 len, const void *ptr, + void *data) +{ + switch (tag) { + case HTT_PPDU_STATS_TAG_USR_COMPLTN_FLUSH: + if (len < sizeof(struct htt_ppdu_stats_cmpltn_flush)) { + ath11k_warn(ab, "Invalid len %d for the tag 0x%x\n", + len, tag); + return -EINVAL; + } + ath11k_dp_ppdu_stats_flush_tlv_parse(ab, ptr); + break; + default: + break; + } + + return 0; +} + static void ath11k_update_per_peer_tx_stats(struct ath11k *ar, struct htt_ppdu_stats *ppdu_stats, u8 user) @@ -1431,6 +1493,9 @@ ath11k_update_per_peer_tx_stats(struct a if (!(usr_stats->tlv_flags & BIT(HTT_PPDU_STATS_TAG_USR_RATE))) return; + if (usr_stats->rate_stats_updated) + return; + if (usr_stats->tlv_flags & BIT(HTT_PPDU_STATS_TAG_USR_COMPLTN_COMMON)) is_ampdu = HTT_USR_CMPLTN_IS_AMPDU(usr_stats->cmpltn_cmn.flags); @@ -1568,6 +1633,8 @@ ath11k_update_per_peer_tx_stats(struct a ath11k_debugfs_sta_add_tx_stats(arsta, peer_stats, rate_idx); } + usr_stats->rate_stats_updated = true; + spin_unlock_bh(&ab->base_lock); rcu_read_unlock(); } @@ -1655,8 +1722,10 @@ int ath11k_dp_htt_tlv_iter(struct ath11k int ret = -EINVAL; struct htt_ppdu_stats_info * ppdu_info = NULL; - ppdu_info = (struct htt_ppdu_stats_info *)data; - ppdu_info->tlv_bitmap = 0; + if (data) { + ppdu_info = (struct htt_ppdu_stats_info *)data; + ppdu_info->tlv_bitmap = 0; + } while (len > 0) { if (len < sizeof(*tlv)) { ath11k_err(ab, "htt tlv parse failure at byte %zd (%zu bytes left, %zu expected)\n", @@ -1685,6 +1754,66 @@ int ath11k_dp_htt_tlv_iter(struct ath11k return 0; } +static void +ath11k_dp_rx_ppdu_stats_update_tx_comp_status(struct ath11k *ar, + struct htt_ppdu_stats_info *ppdu_info) +{ + struct ath11k_base *ab = ar->ab; + struct ieee80211_sta *sta; + struct ath11k_sta *arsta; + struct ath11k_peer *peer = NULL; + struct htt_ppdu_user_stats* usr_stats = NULL; + struct ieee80211_tx_status status; + u32 peer_id = 0; + int i; + + lockdep_assert_held(&ar->data_lock); + + if (!ar->ab->nss.mesh_nss_offload_enabled) + return; + + ath11k_htt_update_ppdu_stats(ar, &ppdu_info->ppdu_stats); + + rcu_read_lock(); + + for (i = 0; i < ppdu_info->ppdu_stats.common.num_users; i++) { + usr_stats = &ppdu_info->ppdu_stats.user_stats[i]; + peer_id = usr_stats->peer_id; + spin_lock_bh(&ab->base_lock); + peer = ath11k_peer_find_by_id(ab, peer_id); + if (!peer) { + spin_unlock_bh(&ab->base_lock); + continue; + } + + if (peer->vif->type != NL80211_IFTYPE_MESH_POINT) { + spin_unlock_bh(&ab->base_lock); + goto exit; + } + + if (ether_addr_equal(peer->addr, peer->vif->addr)) { + spin_unlock_bh(&ab->base_lock); + continue; + } + + sta = peer->sta; + arsta = (struct ath11k_sta *)sta->drv_priv; + + memset(&status, 0, sizeof(status)); + + status.sta = sta; + status.rate = &arsta->last_txrate; + status.mpdu_succ = usr_stats->cmpltn_cmn.mpdu_success; + + ieee80211s_update_metric_ppdu(ar->hw, &status); + + spin_unlock_bh(&ab->base_lock); + } + +exit: + rcu_read_unlock(); +} + static int ath11k_htt_pull_ppdu_stats(struct ath11k_base *ab, struct sk_buff *skb) { @@ -1703,6 +1832,15 @@ static int ath11k_htt_pull_ppdu_stats(st pdev_id = FIELD_GET(HTT_T2H_PPDU_STATS_INFO_PDEV_ID, msg->info); ppdu_id = msg->ppdu_id; + if (pdev_id == 0) { + ret = ath11k_dp_htt_tlv_iter(ab, msg->data, len, + ath11k_htt_tlv_ppdu_soc_stats_parse, + NULL); + if (ret) + ath11k_warn(ab, "failed to parse tlv %d\n", ret); + return ret; + } + rcu_read_lock(); ar = ath11k_mac_get_ar_by_pdev_id(ab, pdev_id); if (!ar) { @@ -1774,6 +1912,12 @@ static int ath11k_htt_pull_ppdu_stats(st } } + /* Stats update for mesh interface used when nss-offload in mesh is enabled */ + if ((ppdu_info->frame_type == HTT_STATS_PPDU_FTYPE_DATA && + (ppdu_info->tlv_bitmap & (1 << HTT_PPDU_STATS_TAG_USR_RATE)) && + ppdu_info->tlv_bitmap & (1 << HTT_PPDU_STATS_TAG_USR_COMPLTN_COMMON))) + ath11k_dp_rx_ppdu_stats_update_tx_comp_status(ar, ppdu_info); + spin_unlock_bh(&ar->data_lock); exit: rcu_read_unlock(); @@ -3031,6 +3175,23 @@ static void ath11k_dp_rx_process_receive rcu_read_unlock(); } +void ath11k_dp_rx_from_nss(struct ath11k *ar, struct sk_buff *msdu, + struct napi_struct *napi) +{ + struct ieee80211_rx_status rx_status = {0}; + struct ath11k_skb_rxcb *rxcb; + bool fast_rx = false; + + rxcb = ATH11K_SKB_RXCB(msdu); + + ath11k_dp_rx_h_ppdu(ar, rxcb->rx_desc, &rx_status); + ath11k_dp_rx_h_mpdu(ar, msdu, rxcb->rx_desc, &rx_status, &fast_rx); + + rx_status.flag |= RX_FLAG_SKIP_MONITOR | RX_FLAG_DUP_VALIDATED; + + ath11k_dp_rx_deliver_msdu(ar, napi, msdu, &rx_status); +} + int ath11k_dp_process_rx(struct ath11k_base *ab, int ring_id, struct napi_struct *napi, int budget) { --- a/drivers/net/wireless/ath/ath11k/dp_rx.h +++ b/drivers/net/wireless/ath/ath11k/dp_rx.h @@ -159,4 +159,6 @@ bool ath11k_dp_rx_h_attn_is_mcbc(struct struct hal_rx_desc *desc); u16 ath11k_dp_rx_h_mpdu_start_peer_id(struct ath11k_base *ab, struct hal_rx_desc *desc); +void ath11k_dp_rx_from_nss(struct ath11k *ar, struct sk_buff *msdu, + struct napi_struct *napi); #endif /* ATH11K_DP_RX_H */ --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -3092,6 +3092,16 @@ static void ath11k_mac_op_nss_bss_info_c ath11k_warn(ar->ab, "failed to set ap_isolate in nss %d\n", ret); } + if (changed & (BSS_CHANGED_NSS_MESH_TTL | + BSS_CHANGED_NSS_MESH_REFRESH_TIME | + BSS_CHANGED_NSS_MESH_FWD_ENABLED)) { + ret = ath11k_nss_mesh_config_update(vif, changed); + if (ret) + ath11k_warn(ar->ab, + "failed to update mesh nss offload configuration %d\n", + ret); + } + mutex_unlock(&ar->conf_mutex); } @@ -6056,7 +6066,7 @@ static void ath11k_mac_op_tx(struct ieee skb_cb->flags |= ATH11K_SKB_TX_STATUS; if (ar->ab->nss.enabled) - ret = ath11k_nss_tx(arvif,skb); + ret = ath11k_nss_tx(arvif, skb); else ret = ath11k_dp_tx(ar, arvif, skb, (control->sta) ? control->sta->drv_priv : NULL); @@ -8449,6 +8459,28 @@ static void ath11k_mac_op_sta_statistics ath11k_nss_update_sta_stats(arvif, sinfo, sta); } +#ifdef CPTCFG_MAC80211_MESH +static void +ath11k_mac_op_config_mesh_offload_path(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_mesh_path_offld_cmd cmd, + struct ieee80211_mesh_path_offld *path) +{ + struct ath11k *ar = hw->priv; + struct ath11k_vif *arvif = (void *)vif->drv_priv; + int ret; + + if (arvif->ar->ab->nss.debug_mode) { + ret = 0; + return; + } + + ret = ath11k_nss_mesh_config_path(ar, arvif, cmd, path); + if (ret) + ath11k_warn(ar->ab, "failed to configure path entry to mesh table %d\n", ret); +} +#endif + #define ATH11K_WLAN_PRIO_MAX 0x63 #define ATH11K_WLAN_PRIO_WEIGHT 0xff @@ -8573,6 +8605,9 @@ static const struct ieee80211_ops ath11k #ifdef CPTCFG_ATH11K_DEBUGFS .sta_add_debugfs = ath11k_debugfs_sta_op_add, #endif +#ifdef CPTCFG_MAC80211_MESH + .config_mesh_offload_path = ath11k_mac_op_config_mesh_offload_path, +#endif }; static void ath11k_mac_update_ch_list(struct ath11k *ar, @@ -9068,6 +9103,8 @@ static int __ath11k_mac_register(struct ieee80211_hw_set(ar->hw, SUPPORTS_NSS_OFFLOAD); wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_VLAN_OFFLOAD); + if (ab->nss.mesh_nss_offload_enabled) + ieee80211_hw_set(ar->hw, SUPPORTS_MESH_NSS_OFFLOAD); } ret = ieee80211_register_hw(ar->hw); --- a/drivers/net/wireless/ath/ath11k/nss.c +++ b/drivers/net/wireless/ath/ath11k/nss.c @@ -5,6 +5,7 @@ #include "mac.h" #include "nss.h" +#include "debug_nss.h" #include "core.h" #include "peer.h" #include "dp_tx.h" @@ -14,6 +15,9 @@ #include "wmi.h" #include "../../../../../net/mac80211/sta_info.h" +enum nss_wifi_mesh_mpp_learning_mode mpp_mode = NSS_WIFI_MESH_MPP_LEARNING_MODE_INDEPENDENT_NSS; +LIST_HEAD(mesh_vaps); + /*-----------------------------ATH11K-NSS Helpers--------------------------*/ static enum ath11k_nss_opmode @@ -32,6 +36,30 @@ ath11k_nss_get_vdev_opmode(struct ath11k return ATH11K_NSS_OPMODE_UNKNOWN; } +static struct ath11k_vif *ath11k_nss_get_arvif_from_dev(struct net_device *dev) +{ + struct wireless_dev *wdev; + struct ieee80211_vif *vif; + struct ath11k_vif *arvif; + + if (!dev) + return NULL; + + wdev = dev->ieee80211_ptr; + if (!wdev) + return NULL; + + vif = wdev_to_ieee80211_vif(wdev); + if (!vif) + return NULL; + + arvif = (struct ath11k_vif *)vif->drv_priv; + if (!arvif) + return NULL; + + return arvif; +} + static void ath11k_nss_wifili_stats_sync(struct ath11k_base *ab, struct nss_wifili_stats_sync_msg *wlsoc_stats) { @@ -248,6 +276,9 @@ void ath11k_nss_wifili_event_receive(str switch (msg_type) { case NSS_WIFILI_INIT_MSG: + ab->nss.response = response; + complete(&ab->nss.complete); + break; case NSS_WIFILI_PDEV_INIT_MSG: case NSS_WIFILI_START_MSG: case NSS_WIFILI_SOC_RESET_MSG: @@ -256,7 +287,6 @@ void ath11k_nss_wifili_event_receive(str ab->nss.response = response; complete(&ab->nss.complete); break; - case NSS_WIFILI_PEER_CREATE_MSG: if (response != NSS_CMN_RESPONSE_EMSG) break; @@ -326,6 +356,13 @@ void ath11k_nss_wifili_event_receive(str ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "nss wifili peer 4addr event received %d response %d error %d\n", msg_type, response, error); break; + case NSS_WIFILI_SEND_MESH_CAPABILITY_INFO: + complete(&ab->nss.complete); + if (response != NSS_CMN_RESPONSE_EMSG) + ab->nss.mesh_nss_offload_enabled = true; + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "nss wifili mesh capability response %d\n", + ab->nss.mesh_nss_offload_enabled); + break; default: ath11k_dbg(ab, ATH11K_DBG_NSS, "unhandled event %d\n", msg_type); break; @@ -413,7 +450,9 @@ ath11k_nss_wifili_ext_callback_fn(struct ath11k_nss_process_mic_error(ab, skb); break; default: - kfree(skb); + ath11k_dbg(ab, ATH11K_DBG_NSS, "unknown packet type received in wifili ext cb %d", + wepm->pkt_type); + dev_kfree_skb_any(skb); break; } } @@ -728,8 +767,6 @@ ath11k_nss_vdev_special_data_receive(str { struct nss_wifi_vdev_per_packet_metadata *wifi_metadata = NULL; struct nss_wifi_vdev_wds_per_packet_metadata *wds_metadata = NULL; - struct wireless_dev *wdev; - struct ieee80211_vif *vif; struct ath11k_vif *arvif; struct ath11k_base *ab; bool drop = false; @@ -737,24 +774,7 @@ ath11k_nss_vdev_special_data_receive(str int data_offs = 0; int ret = 0; - if (!dev) { - dev_kfree_skb_any(skb); - return; - } - - wdev = dev->ieee80211_ptr; - if (!wdev) { - dev_kfree_skb_any(skb); - return; - } - - vif = wdev_to_ieee80211_vif(wdev); - if (!vif) { - dev_kfree_skb_any(skb); - return; - } - - arvif = (struct ath11k_vif *)vif->drv_priv; + arvif = ath11k_nss_get_arvif_from_dev(dev); if (!arvif) { dev_kfree_skb_any(skb); return; @@ -875,17 +895,1071 @@ ath11k_nss_ext_vdev_data_receive(struct int data_offs = 0; int ret; - if (!dev) { + if (!dev) { + dev_kfree_skb_any(skb); + return; + } + + wdev = dev->ieee80211_ptr; + if (!wdev) { + dev_kfree_skb_any(skb); + return; + } + + vif = wdev_to_ieee80211_vif(wdev); + if (!vif) { + dev_kfree_skb_any(skb); + return; + } + + arvif = (struct ath11k_vif *)vif->drv_priv; + if (!arvif) { + dev_kfree_skb_any(skb); + return; + } + + ab = arvif->ar->ab; + + skb->dev = dev; + + /* log the original skb received from nss */ + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "dp rx msdu from nss ext : ", + skb->data, skb->len); + + ret = ath11k_nss_undecap(arvif, skb, &data_offs, ð_decap); + if (ret) { + ath11k_warn(ab, "error in nss ext rx undecap, type %d err %d\n", + arvif->nss.decap, ret); + dev_kfree_skb_any(skb); + return; + } + + ath11k_nss_deliver_rx(arvif->vif, skb, eth_decap, data_offs, napi); +} + +/*------Mesh offload------*/ + +void ath11k_nss_mesh_wifili_event_receive(void *app_data, + struct nss_cmn_msg *cmn_msg) +{ + struct nss_wifi_mesh_msg *msg = (struct nss_wifi_mesh_msg *)cmn_msg; + struct ath11k_base *ab = app_data; + u32 msg_type = msg->cm.type; + enum nss_cmn_response response = msg->cm.response; + u32 error = msg->cm.error; + + if (!ab) + return; + + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "nss mesh event received %d response %d error %d\n", + msg_type, response, error); + + switch (msg_type) { + case NSS_WIFI_MESH_MSG_MPATH_ADD: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab,"failed to add an entry to mpath table mesh_da %pM vdev_id %d\n", + (&msg->msg.mpath_add)->dest_mac_addr, + (&msg->msg.mpath_add)->link_vap_id); + break; + case NSS_WIFI_MESH_MSG_MPATH_UPDATE: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab, "failed to update mpath entry mesh_da %pM vdev_id %d" + "next_hop %pM metric %d flags 0x%u hop_count %d" + "exp_time %u mesh_gate %u\n", + (&msg->msg.mpath_update)->dest_mac_addr, + (&msg->msg.mpath_update)->link_vap_id, + (&msg->msg.mpath_update)->next_hop_mac_addr, + (&msg->msg.mpath_update)->metric, + (&msg->msg.mpath_update)->path_flags, + (&msg->msg.mpath_update)->hop_count, + (&msg->msg.mpath_update)->expiry_time, + (&msg->msg.mpath_update)->is_mesh_gate); + break; + case NSS_WIFI_MESH_MSG_MPATH_DELETE: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab,"failed to remove mpath entry mesh_da %pM" + "vdev_id %d\n", + (&msg->msg.mpath_del)->mesh_dest_mac_addr, + (&msg->msg.mpath_del)->link_vap_id); + break; + case NSS_WIFI_MESH_MSG_PROXY_PATH_ADD: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab,"failed to add proxy entry da %pM mesh_da %pM \n", + (&msg->msg.proxy_add_msg)->dest_mac_addr, + (&msg->msg.proxy_add_msg)->mesh_dest_mac); + break; + case NSS_WIFI_MESH_MSG_PROXY_PATH_UPDATE: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab,"failed to update proxy path da %pM mesh_da %pM\n", + (&msg->msg.proxy_update_msg)->dest_mac_addr, + (&msg->msg.proxy_update_msg)->mesh_dest_mac); + break; + case NSS_WIFI_MESH_MSG_PROXY_PATH_DELETE: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab,"failed to remove proxy path entry da %pM mesh_da %pM\n", + (&msg->msg.proxy_del_msg)->dest_mac_addr, + (&msg->msg.proxy_del_msg)->mesh_dest_mac_addr); + break; + case NSS_WIFI_MESH_MSG_EXCEPTION_FLAG: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab,"failed to add the exception da %pM\n", + (&msg->msg.exception_msg)->dest_mac_addr); + break; + default: + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "unhandled event %d\n", msg_type); + break; + } +} + +static void nss_mesh_convert_path_flags(u16 *dest, u16 *src, bool to_nss) +{ + if (to_nss) { + if (*src & IEEE80211_MESH_PATH_ACTIVE) + *dest |= NSS_WIFI_MESH_PATH_FLAG_ACTIVE; + if (*src & IEEE80211_MESH_PATH_RESOLVING) + *dest |= NSS_WIFI_MESH_PATH_FLAG_RESOLVING; + if (*src & IEEE80211_MESH_PATH_RESOLVED) + *dest |= NSS_WIFI_MESH_PATH_FLAG_RESOLVED; + if (*src & IEEE80211_MESH_PATH_FIXED) + *dest |= NSS_WIFI_MESH_PATH_FLAG_FIXED; + } else { + if (*src & NSS_WIFI_MESH_PATH_FLAG_ACTIVE) + *dest |= IEEE80211_MESH_PATH_ACTIVE; + if (*src & NSS_WIFI_MESH_PATH_FLAG_RESOLVING) + *dest |= IEEE80211_MESH_PATH_RESOLVING; + if (*src & NSS_WIFI_MESH_PATH_FLAG_RESOLVED) + *dest |= IEEE80211_MESH_PATH_RESOLVED; + if (*src & NSS_WIFI_MESH_PATH_FLAG_FIXED) + *dest |= IEEE80211_MESH_PATH_FIXED; + } +} + +static void ath11k_nss_mesh_mpath_refresh(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_path_refresh_msg *refresh_msg; + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + refresh_msg = &msg->msg.path_refresh_msg; + ether_addr_copy(path.mesh_da, refresh_msg->dest_mac_addr); + ether_addr_copy(path.next_hop, refresh_msg->next_hop_mac_addr); + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Mesh path refresh event from nss, mDA %pM next_hop %pM link_vdev %d\n", + refresh_msg->dest_mac_addr, refresh_msg->next_hop_mac_addr, + refresh_msg->link_vap_id); + + + if (ab->nss.debug_mode) + return; + + ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path, + IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_REFRESH); + if (ret) + ath11k_warn(ab, "failed to notify mpath refresh nss event %d\n", ret); +} + +static void ath11k_nss_mesh_path_not_found(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_mpath_not_found_msg *err_msg; + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + err_msg = &msg->msg.mpath_not_found_msg; + ether_addr_copy(path.da, err_msg->dest_mac_addr); + if (err_msg->is_mesh_forward_path) + ether_addr_copy(path.ta, err_msg->transmitter_mac_addr); + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Mesh path not found event from nss, (m)DA %pM ta %pM link vap %d\n", + err_msg->dest_mac_addr, err_msg->transmitter_mac_addr, err_msg->link_vap_id); + + + if (ab->nss.debug_mode) + return; + + ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path, + IEEE80211_MESH_PATH_OFFLD_ACTION_PATH_NOT_FOUND); + if (ret) + ath11k_warn(ab, "failed to notify mpath not found nss event %d\n", ret); +} + +static void ath11k_nss_mesh_path_delete(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_mpath_del_msg *del_msg = &msg->msg.mpath_del; + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + ether_addr_copy(path.mesh_da, del_msg->mesh_dest_mac_addr); + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Mesh path delete event from nss, mDA %pM vap_id %d\n", + del_msg->mesh_dest_mac_addr, del_msg->link_vap_id); + + ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path, + IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_DEL); + if (ret) + ath11k_warn(ab, "failed to notify mpath delete nss event %d\n", ret); +} + +static void ath11k_nss_mesh_path_expiry(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_path_expiry_msg *exp_msg = &msg->msg.path_expiry_msg; + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + ether_addr_copy(path.mesh_da, exp_msg->mesh_dest_mac_addr); + ether_addr_copy(path.next_hop, exp_msg->next_hop_mac_addr); + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Mesh path delete event from nss, mDA %pM next_hop %pM if_num %d\n", + exp_msg->mesh_dest_mac_addr, exp_msg->next_hop_mac_addr, + arvif->nss.if_num); + + ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path, + IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_EXP); + if (ret) + ath11k_warn(ab, "failed to notify mpath expiry nss event %d\n", ret); +} + +static void ath11k_nss_mesh_mpp_learn(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) + { + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_proxy_path_learn_msg *learn_msg; + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + learn_msg = &msg->msg.proxy_learn_msg; + + ether_addr_copy(path.mesh_da, learn_msg->mesh_dest_mac); + ether_addr_copy(path.da, learn_msg->dest_mac_addr); + nss_mesh_convert_path_flags(&path.flags, &learn_msg->path_flags, false); + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Mesh proxy learn event from nss, mDA %pM da %pM flags 0x%x if_num %d\n", + learn_msg->mesh_dest_mac, learn_msg->dest_mac_addr, + learn_msg->path_flags, arvif->nss.if_num); + + ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path, + IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_LEARN); + if (ret) + ath11k_warn(ab, "failed to notify proxy learn event %d\n", ret); +} + +static void ath11k_nss_mesh_mpp_add(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_proxy_path_add_msg *add_msg = &msg->msg.proxy_add_msg; + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + ether_addr_copy(path.mesh_da, add_msg->mesh_dest_mac); + ether_addr_copy(path.da, add_msg->dest_mac_addr); + nss_mesh_convert_path_flags(&path.flags, &add_msg->path_flags, false); + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Mesh proxy add event from nss, mDA %pM da %pM flags 0x%x if_num %d\n", + add_msg->mesh_dest_mac, add_msg->dest_mac_addr, add_msg->path_flags, + arvif->nss.if_num); + + ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path, + IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_ADD); + if (ret) + ath11k_warn(ab, "failed to notify proxy add event %d\n", ret); +} + +static void ath11k_nss_mesh_mpp_update(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_proxy_path_update_msg *umsg; + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + umsg = &msg->msg.proxy_update_msg; + ether_addr_copy(path.mesh_da, umsg->mesh_dest_mac); + ether_addr_copy(path.da, umsg->dest_mac_addr); + nss_mesh_convert_path_flags(&path.flags, &umsg->path_flags, false); + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Mesh proxy update event from nss, mDA %pM da %pM flags 0x%x if_num %d\n", + umsg->mesh_dest_mac, umsg->dest_mac_addr, umsg->path_flags, arvif->nss.if_num); + + ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path, + IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_UPDATE); + if (ret) + ath11k_warn(ab, "failed to notify proxy update event %d\n", ret); +} + +static int +ath11k_nss_mesh_process_path_table_dump_msg(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct nss_wifi_mesh_path_table_dump *mpath_dump = &msg->msg.mpath_table_dump; + struct ath11k_nss_mpath_entry *entry; + struct ath11k *ar = arvif->ar; + ssize_t len; + + len = sizeof(struct nss_wifi_mesh_path_dump_entry) * mpath_dump->num_entries; + entry = kzalloc(sizeof(*entry) + len, GFP_ATOMIC); + if (!entry) + return -ENOMEM; + + memcpy(entry->mpath, mpath_dump->path_entry, len); + entry->num_entries = mpath_dump->num_entries; + spin_lock_bh(&ar->nss.dump_lock); + list_add_tail(&entry->list, &arvif->nss.mpath_dump); + arvif->nss.mpath_dump_num_entries += mpath_dump->num_entries; + spin_unlock_bh(&ar->nss.dump_lock); + + if (!mpath_dump->more_events) + complete(&arvif->nss.dump_mpath_complete); + + return 0; +} + +static int +ath11k_nss_mesh_process_mpp_table_dump_msg(struct ath11k_vif *arvif, + struct nss_wifi_mesh_msg *msg) +{ + struct nss_wifi_mesh_proxy_path_table_dump *mpp_dump; + struct ath11k_nss_mpp_entry *entry, *tmp; + struct ath11k *ar = arvif->ar; + struct arvif_nss *nss = &arvif->nss; + ssize_t len; + LIST_HEAD(local_entry_exp_update); + + mpp_dump = &msg->msg.proxy_path_table_dump; + + len = sizeof(struct nss_wifi_mesh_proxy_path_dump_entry) * mpp_dump->num_entries; + entry = kzalloc(sizeof(*entry) + len, GFP_ATOMIC); + if (!entry) + return -ENOMEM; + + memcpy(entry->mpp, mpp_dump->path_entry, len); + entry->num_entries = mpp_dump->num_entries; + spin_lock_bh(&ar->nss.dump_lock); + list_add_tail(&entry->list, &arvif->nss.mpp_dump); + arvif->nss.mpp_dump_num_entries += mpp_dump->num_entries; + spin_unlock_bh(&ar->nss.dump_lock); + + if (!mpp_dump->more_events) { + if (arvif->nss.mpp_aging) { + arvif->nss.mpp_aging = false; + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail_init(&nss->mpp_dump, &local_entry_exp_update); + spin_unlock_bh(&ar->nss.dump_lock); + + list_for_each_entry_safe(entry, tmp, &local_entry_exp_update, list) { + if (entry->mpp->time_diff > ATH11K_MPP_EXPIRY_TIMER_INTERVAL_MS) + continue; + mesh_nss_offld_proxy_path_exp_update(arvif->vif, + entry->mpp->dest_mac_addr, + entry->mpp->mesh_dest_mac, + entry->mpp->time_diff); + } + /* If mpp_dump_req is true dont free the entry + * since it will get freed in debug_nss_fill_mpp_dump + * both mpp_aging and mpp_dump_req will be true during + * simultaneous accessing of mpp dump entry. So this will + * gain the reuse of same dump result for both mpp_aging + * and mpp_dump_req */ + if (!arvif->nss.mpp_dump_req) { + list_for_each_entry_safe(entry, tmp, &local_entry_exp_update, list) + kfree(entry); + } else { + /* Adding back to global nss dump tbl to reuse the same + * tbl for mpp dump request + */ + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail_init(&local_entry_exp_update, &nss->mpp_dump); + spin_unlock_bh(&ar->nss.dump_lock); + } + } + + if (arvif->nss.mpp_dump_req) { + complete(&arvif->nss.dump_mpp_complete); + arvif->nss.mpp_dump_req = false; + } + } + + return 0; +} + +int ath11k_nss_mesh_exception_flags(struct ath11k_vif *arvif, + struct nss_wifi_mesh_exception_flag_msg *nss_msg) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + nss_tx_status_t status; + int ret = 0; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + status = nss_wifi_meshmgr_mesh_path_exception(arvif->nss.mesh_handle, nss_msg, + msg_cb, arvif->ar->ab); + + if (status != NSS_TX_SUCCESS) { + ath11k_warn(arvif->ar->ab, "failed to set the exception flags\n"); + ret = -EINVAL; + } + + return ret; +} + +int ath11k_nss_exc_rate_config(struct ath11k_vif *arvif, + struct nss_wifi_mesh_rate_limit_config *nss_exc_cfg) +{ + nss_tx_status_t status; + int ret = 0; + + status = nss_wifi_meshmgr_config_mesh_exception_sync(arvif->nss.mesh_handle, nss_exc_cfg); + + if (status != NSS_TX_SUCCESS) { + ath11k_warn(arvif->ar->ab, "failed to set the exception rate ctrl\n"); + ret = -EINVAL; + } + + return ret; +} + +static void ath11k_nss_mesh_obj_vdev_event_receive(struct net_device *dev, + struct nss_wifi_mesh_msg *msg) +{ + struct ath11k_base *ab; + struct ath11k_vif *arvif; + int ret; + + arvif = ath11k_nss_get_arvif_from_dev(dev); + if (!arvif) + return; + + ab = arvif->ar->ab; + + switch (msg->cm.type) { + case NSS_WIFI_MESH_MSG_PATH_REFRESH: + ath11k_nss_mesh_mpath_refresh(arvif, msg); + break; + case NSS_WIFI_MESH_MSG_PATH_NOT_FOUND: + ath11k_nss_mesh_path_not_found(arvif, msg); + break; + case NSS_WIFI_MESH_MSG_MPATH_DELETE: + ath11k_nss_mesh_path_delete(arvif, msg); + break; + case NSS_WIFI_MESH_MSG_PATH_EXPIRY: + ath11k_nss_mesh_path_expiry(arvif, msg); + break; + case NSS_WIFI_MESH_MSG_PROXY_PATH_LEARN: + ath11k_nss_mesh_mpp_learn(arvif, msg); + break; + case NSS_WIFI_MESH_MSG_PROXY_PATH_ADD: + ath11k_nss_mesh_mpp_add(arvif, msg); + break; + case NSS_WIFI_MESH_MSG_PROXY_PATH_UPDATE: + ath11k_nss_mesh_mpp_update(arvif, msg); + break; + case NSS_WIFI_MESH_MSG_PATH_TABLE_DUMP: + ret = ath11k_nss_mesh_process_path_table_dump_msg(arvif, msg); + if (ret) + ath11k_warn(arvif->ar->ab, "failed mpath table dump message %d\n", + ret); + break; + case NSS_WIFI_MESH_MSG_PROXY_PATH_TABLE_DUMP: + ret = ath11k_nss_mesh_process_mpp_table_dump_msg(arvif, msg); + if (ret) + ath11k_warn(arvif->ar->ab, "failed mpp table dump message %d\n", + ret); + break; + default: + ath11k_dbg(ab, ATH11K_DBG_NSS, "unknown message type on mesh obj vap %d\n", + msg->cm.type); + break; + } +} + +static int ath11k_nss_mesh_mpath_add(struct ath11k_vif *arvif, + struct ieee80211_mesh_path_offld *path) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + struct nss_wifi_mesh_mpath_add_msg *msg; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "add mpath for mesh_da %pM on radio %d\n", + path->mesh_da, ar->pdev->pdev_id); + + msg = kzalloc(sizeof(struct nss_wifi_mesh_mpath_add_msg), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + ether_addr_copy(msg->dest_mac_addr, path->mesh_da); + ether_addr_copy(msg->next_hop_mac_addr, path->next_hop); + msg->hop_count = path->hop_count; + msg->metric = path->metric; + nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true); + msg->link_vap_id = arvif->nss.if_num; + msg->block_mesh_fwd = path->block_mesh_fwd; + msg->metadata_type = path->metadata_type ? NSS_WIFI_MESH_PRE_HEADER_80211: NSS_WIFI_MESH_PRE_HEADER_NONE; + + status = nss_wifi_meshmgr_mesh_path_add(arvif->nss.mesh_handle, msg, + msg_cb, ar->ab); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, + "failed to add mpath entry mesh_da %pM radio_id %d status %d\n", + path->mesh_da, arvif->nss.if_num, status); + ret = -EINVAL; + } + + kfree(msg); + + return ret; +} + +static int ath11k_nss_mesh_mpath_update(struct ath11k_vif *arvif, + struct ieee80211_mesh_path_offld *path) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + struct nss_wifi_mesh_mpath_update_msg *msg; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, + "update mpath mesh_da %pM radio %d next_hop %pM old_next_hop %pM " + "metric %d flags 0x%x hop_count %d " + "exp_time %lu mesh_gate %d\n", + path->mesh_da, ar->pdev->pdev_id, path->next_hop, path->old_next_hop, + path->metric, path->flags, path->hop_count, path->exp_time, + path->mesh_gate); + + msg = kzalloc(sizeof(struct nss_wifi_mesh_mpath_update_msg), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + ether_addr_copy(msg->dest_mac_addr, path->mesh_da); + ether_addr_copy(msg->next_hop_mac_addr, path->next_hop); + ether_addr_copy(msg->old_next_hop_mac_addr, path->old_next_hop); + msg->hop_count = path->hop_count; + msg->metric = path->metric; + nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true); + msg->link_vap_id = arvif->nss.if_num; + msg->is_mesh_gate = path->mesh_gate; + msg->expiry_time = path->exp_time; + msg->block_mesh_fwd = path->block_mesh_fwd; + msg->metadata_type = path->metadata_type ? NSS_WIFI_MESH_PRE_HEADER_80211: NSS_WIFI_MESH_PRE_HEADER_NONE; + + msg->update_flags = NSS_WIFI_MESH_PATH_UPDATE_FLAG_NEXTHOP | + NSS_WIFI_MESH_PATH_UPDATE_FLAG_HOPCOUNT | + NSS_WIFI_MESH_PATH_UPDATE_FLAG_METRIC | + NSS_WIFI_MESH_PATH_UPDATE_FLAG_MESH_FLAGS | + NSS_WIFI_MESH_PATH_UPDATE_FLAG_BLOCK_MESH_FWD | + NSS_WIFI_MESH_PATH_UPDATE_FLAG_METADATA_ENABLE_VALID; + + status = nss_wifi_meshmgr_mesh_path_update(arvif->nss.mesh_handle, msg, + msg_cb, ar->ab); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, + "failed to update mpath entry mesh_da %pM radio_id %d status %d\n", + path->mesh_da, arvif->nss.if_num, status); + ret = -EINVAL; + } + + kfree(msg); + + return ret; +} + +static int ath11k_nss_mesh_mpath_del(struct ath11k_vif *arvif, + struct ieee80211_mesh_path_offld *path) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + struct nss_wifi_mesh_mpath_del_msg *msg; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "del mpath for mesh_da %pM on radio %d\n", + path->mesh_da, ar->pdev->pdev_id); + + msg = kzalloc(sizeof(struct nss_wifi_mesh_mpath_del_msg), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + ether_addr_copy(msg->mesh_dest_mac_addr, path->mesh_da); + ether_addr_copy(msg->next_hop_mac_addr, path->next_hop); + msg->link_vap_id = arvif->nss.if_num; + + status = nss_wifi_meshmgr_mesh_path_delete(arvif->nss.mesh_handle, + msg, msg_cb, ar->ab); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, + "failed to del mpath entry mesh_da %pM radio_id %d status %d\n", + path->mesh_da, arvif->nss.if_num, status); + ret = -EINVAL; + } + + kfree(msg); + + return ret; +} + +static int ath11k_nss_mesh_mpp_add_cmd(struct ath11k_vif *arvif, + struct ieee80211_mesh_path_offld *path) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + struct nss_wifi_mesh_proxy_path_add_msg *msg; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "add mpp mesh_da %pM da %pM\n", + path->mesh_da, path->da); + + msg = kzalloc(sizeof(struct nss_wifi_mesh_proxy_path_add_msg), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + ether_addr_copy(msg->dest_mac_addr, path->da); + ether_addr_copy(msg->mesh_dest_mac, path->mesh_da); + nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true); + + status = nss_wifi_meshmgr_mesh_proxy_path_add(arvif->nss.mesh_handle, + msg, msg_cb, ar->ab); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, + "failed to add mpp entry da %pM mesh_da %pM status %d\n", + path->da, path->mesh_da, status); + ret = -EINVAL; + } + + kfree(msg); + + return ret; +} + +static int ath11k_nss_mesh_mpp_update_cmd(struct ath11k_vif *arvif, + struct ieee80211_mesh_path_offld *path) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + struct nss_wifi_mesh_proxy_path_update_msg *msg; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "update mpp da %pM mesh_da %pM on vap_id %d\n", + path->da, path->mesh_da, arvif->nss.if_num); + + msg = kzalloc(sizeof(struct nss_wifi_mesh_proxy_path_update_msg), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + ether_addr_copy(msg->dest_mac_addr, path->da); + ether_addr_copy(msg->mesh_dest_mac, path->mesh_da); + nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true); + msg->bitmap = NSS_WIFI_MESH_PATH_UPDATE_FLAG_NEXTHOP | + NSS_WIFI_MESH_PATH_UPDATE_FLAG_HOPCOUNT; + + status = nss_wifi_meshmgr_mesh_proxy_path_update(arvif->nss.mesh_handle, + msg, msg_cb, ar->ab); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, + "failed to update mpp da %pM mesh_da %pM status %d\n", + path->da, path->mesh_da, status); + ret = -EINVAL; + } + + kfree(msg); + + return ret; +} + +static int ath11k_nss_mesh_mpp_del_cmd(struct ath11k_vif *arvif, + struct ieee80211_mesh_path_offld *path) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + struct nss_wifi_mesh_proxy_path_del_msg *msg; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "del mpath for mesh_da %pM\n", + path->mesh_da); + + msg = kzalloc(sizeof(struct nss_wifi_mesh_proxy_path_del_msg), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + ether_addr_copy(msg->dest_mac_addr, path->da); + ether_addr_copy(msg->mesh_dest_mac_addr, path->mesh_da); + + status = nss_wifi_meshmgr_mesh_proxy_path_delete(arvif->nss.mesh_handle, msg, + msg_cb, ar->ab); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, + "failed to add mpath entry mesh_da %pM status %d\n", + path->mesh_da, status); + ret = -EINVAL; + } + + kfree(msg); + + return ret; +} + +int ath11k_nss_mesh_config_path(struct ath11k *ar, struct ath11k_vif *arvif, + enum ieee80211_mesh_path_offld_cmd cmd, + struct ieee80211_mesh_path_offld *path) +{ + int ret; + + + if (!ar->ab->nss.enabled) + return 0; + + switch (cmd) { + case IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPATH: + ret = ath11k_nss_mesh_mpath_add(arvif, path); + break; + case IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPATH: + ret = ath11k_nss_mesh_mpath_update(arvif, path); + break; + case IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPATH: + ret = ath11k_nss_mesh_mpath_del(arvif, path); + break; + case IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPP: + ret = ath11k_nss_mesh_mpp_add_cmd(arvif, path); + break; + case IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPP: + ret = ath11k_nss_mesh_mpp_update_cmd(arvif, path); + break; + case IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPP: + ret = ath11k_nss_mesh_mpp_del_cmd(arvif, path); + break; + default: + ath11k_warn(ar->ab, "unknown mesh path table command type %d\n", cmd); + return -EINVAL; + } + + return ret; +} + +int ath11k_nss_mesh_config_update(struct ieee80211_vif *vif, int changed) +{ + struct ath11k_vif *arvif = ath11k_vif_to_arvif(vif); + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_config_msg *nss_msg; + struct arvif_nss *nss = &arvif->nss; + nss_tx_status_t status; + int ret = 0; + + if (!ab->nss.enabled) + return 0; + + if (!ab->nss.mesh_nss_offload_enabled) + return -ENOTSUPP; + + if (!changed) + return 0; + + nss_msg = kzalloc(sizeof(*nss_msg), GFP_KERNEL); + if (!nss_msg) + return -ENOMEM; + + if (changed & BSS_CHANGED_NSS_MESH_TTL) { + nss_msg->ttl = vif->bss_conf.nss_offld_ttl; + nss->mesh_ttl = vif->bss_conf.nss_offld_ttl; + nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_TTL_VALID; + } + + if (changed & BSS_CHANGED_NSS_MESH_REFRESH_TIME) { + nss_msg->mesh_path_refresh_time = + vif->bss_conf.nss_offld_mpath_refresh_time; + nss->mpath_refresh_time = + vif->bss_conf.nss_offld_mpath_refresh_time; + nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_MPATH_REFRESH_VALID; + } + + if (changed & BSS_CHANGED_NSS_MESH_FWD_ENABLED) { + nss_msg->block_mesh_forwarding = + vif->bss_conf.nss_offld_mesh_forward_enabled; + nss->mesh_forward_enabled = + vif->bss_conf.nss_offld_mesh_forward_enabled; + nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_BLOCK_MESH_FWD_VALID; + nss_msg->metadata_type = arvif->nss.metadata_type; + nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_METADATA_ENABLE_VALID; + } + + status = nss_wifi_meshmgr_mesh_config_update_sync(arvif->nss.mesh_handle, + nss_msg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "failed to configure nss mesh obj vdev nss_err:%d\n", + status); + ret = -EINVAL; + } + + kfree(nss_msg); + + return ret; +} + +int ath11k_nss_dump_mpath_request(struct ath11k_vif *arvif) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct ath11k *ar = arvif->ar; + struct arvif_nss *nss = &arvif->nss; + struct ath11k_nss_mpath_entry *entry, *tmp; + LIST_HEAD(local_entry); + nss_tx_status_t status; + + /* Clean up any stale entries from old events */ + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail(&nss->mpath_dump, &local_entry); + arvif->nss.mpath_dump_num_entries = 0; + spin_unlock_bh(&ar->nss.dump_lock); + + list_for_each_entry_safe(entry, tmp, &local_entry, list) + kfree(entry); + + status = nss_wifi_meshmgr_dump_mesh_path_sync(arvif->nss.mesh_handle); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "failed to send mpath dump command on mesh obj vdev nss_err:%d\n", + status); + return -EINVAL; + } + + return 0; +} + +int ath11k_nss_dump_mpp_request(struct ath11k_vif *arvif) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct ath11k *ar = arvif->ar; + struct arvif_nss *nss = &arvif->nss; + struct ath11k_nss_mpp_entry *entry, *tmp; + LIST_HEAD(local_entry); + nss_wifi_meshmgr_status_t status; + + if (!arvif->nss.mpp_aging) { + /* Clean up any stale entries from old events */ + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail_init(&nss->mpp_dump, &local_entry); + arvif->nss.mpp_dump_num_entries = 0; + spin_unlock_bh(&ar->nss.dump_lock); + + list_for_each_entry_safe(entry, tmp, &local_entry, list) { + list_del(&entry->list); + kfree(entry); + } + } + + arvif->nss.mpp_dump_req = true; + + status = nss_wifi_meshmgr_dump_mesh_proxy_path_sync(arvif->nss.mesh_handle); + if (status != NSS_WIFI_MESHMGR_SUCCESS) { + if (status == NSS_WIFI_MESHMGR_FAILURE_ONESHOT_ALREADY_ATTACHED) + return 0; + ath11k_warn(ab, "failed to send mpp dump command on mesh obj vdev nss_err:%d\n", + status); + return -EINVAL; + } + + return 0; +} + +int ath11k_nss_mpp_timer_cb(struct timer_list *timer) +{ + nss_wifi_mesh_msg_callback_t msg_cb; + struct arvif_nss *nss = from_timer(nss, timer,mpp_expiry_timer); + struct ath11k_vif *arvif = container_of(nss, struct ath11k_vif, nss); + struct ath11k_base *ab = arvif->ar->ab; + LIST_HEAD(local_entry); + nss_tx_status_t status; + + msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive; + + if (!arvif->nss.mpp_dump_req) + arvif->nss.mpp_dump_num_entries = 0; + arvif->nss.mpp_aging = true; + + status = nss_wifi_meshmgr_dump_mesh_proxy_path(arvif->nss.mesh_handle, msg_cb, ab); + if (status != NSS_TX_SUCCESS) + ath11k_warn(ab, "failed to send mpp dump command from timer nss_err:%d\n", + status); + + mod_timer(&nss->mpp_expiry_timer, + jiffies + msecs_to_jiffies(ATH11K_MPP_EXPIRY_TIMER_INTERVAL_MS)); + + return 0; +} + +static void +ath11k_nss_mesh_obj_vdev_data_receive(struct net_device *dev, struct sk_buff *skb, + struct napi_struct *napi) +{ + struct ath11k_vif *arvif; + struct ath11k_base *ab; + char dump_msg[100] = {0}; + struct nss_wifi_mesh_per_packet_metadata *wifi_metadata = NULL; + + arvif = ath11k_nss_get_arvif_from_dev(dev); + if (!arvif) { + dev_kfree_skb_any(skb); + return; + } + + ab = arvif->ar->ab; + + skb->dev = dev; + + snprintf(dump_msg, sizeof(dump_msg), "nss mesh obj vdev: link id %d ", + arvif->nss.if_num); + + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "dp rx msdu from nss", dump_msg, + skb->data, skb->len); + + if (arvif->nss.metadata_type == NSS_WIFI_MESH_PRE_HEADER_80211) { + wifi_metadata = (struct nss_wifi_mesh_per_packet_metadata *)(skb->data - + (sizeof(struct nss_wifi_mesh_per_packet_metadata))); + + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, + "exception from nss on mesh obj vap: pkt_type %d\n", + wifi_metadata->pkt_type); + switch (wifi_metadata->pkt_type) { + case NSS_WIFI_MESH_PRE_HEADER_80211: + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", + "wifi header from nss on mesh obj vdev: ", + skb->data - sizeof(*wifi_metadata), sizeof(*wifi_metadata) + skb->len); + dev_kfree_skb_any(skb); + break; + default: + dev_kfree_skb_any(skb); + } + + return; + } + + ath11k_nss_deliver_rx(arvif->vif, skb, true, 0, napi); +} + +static void +ath11k_nss_mesh_obj_ext_data_callback(struct net_device *dev, struct sk_buff *skb, + __attribute__((unused)) struct napi_struct *napi) +{ + struct ath11k_vif *arvif; + struct ath11k_base *ab; + struct nss_wifi_mesh_encap_ext_pkt_metadata *wifi_metadata = NULL; + int metadata_len; + + arvif = ath11k_nss_get_arvif_from_dev(dev); + if (!arvif) { + dev_kfree_skb_any(skb); + return; + } + + ab = arvif->ar->ab; + + skb->dev = dev; + + metadata_len = NSS_WIFI_MESH_ENCAP_METADATA_OFFSET_TYPE + + sizeof(struct nss_wifi_mesh_encap_ext_pkt_metadata); + + /* msdu from nss should contain metadata in headroom + * any msdu which has invalid or not contains metadata + * will be treated as invalid msdu and dropping it. + */ + if (!(metadata_len < skb_headroom(skb))) { + ath11k_warn(ab, "msdu from nss is having invalid headroom %d\n", skb_headroom(skb)); + dev_kfree_skb_any(skb); + return; + } + + dma_unmap_single(ab->dev, virt_to_phys(skb->head), + metadata_len, + DMA_FROM_DEVICE); + + wifi_metadata = (struct nss_wifi_mesh_encap_ext_pkt_metadata *)(skb->head + + NSS_WIFI_MESH_ENCAP_METADATA_OFFSET_TYPE); + + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "msdu from nss ext_data _cb on mesh obj vdev"); + + switch (wifi_metadata->pkt_type) { + case NSS_WIFI_MESH_ENCAP_EXT_DATA_PKT_TYPE_MPATH_NOT_FOUND_EXC: + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "msdu from nss ext_data for mpath not found : ", + skb->data, skb->len); + skb->protocol = eth_type_trans(skb, dev); + skb_reset_network_header(skb); + dev_queue_xmit(skb); + break; + default: + ath11k_warn(ab, "unknown packet type received in mesh obj ext data %d", + wifi_metadata->pkt_type); + dev_kfree_skb_any(skb); + } +} + +static void +ath11k_nss_mesh_link_vdev_data_receive(void *dev, + struct sk_buff *skb, + struct napi_struct *napi) +{ + struct ieee80211_vif *vif; + struct ath11k_vif *arvif; + struct ath11k_base *ab; + struct wireless_dev *wdev = (struct wireless_dev *)dev; + + vif = wdev_to_ieee80211_vif(wdev); + if (!vif) { dev_kfree_skb_any(skb); return; } - wdev = dev->ieee80211_ptr; - if (!wdev) { + arvif = (struct ath11k_vif *)vif->drv_priv; + if (!arvif) { dev_kfree_skb_any(skb); return; } + ab = arvif->ar->ab; + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "msdu from nss data_receive_cb on mesh link vdev: ", + skb->data, skb->len); + /* data callback for mesh link vap is not expected */ + dev_kfree_skb_any(skb); +} + +static void +ath11k_nss_mesh_link_vdev_special_data_receive(void *dev, + struct sk_buff *skb, + __attribute__((unused)) struct napi_struct *napi) +{ + struct ieee80211_vif *vif; + struct ath11k_base *ab; + struct nss_wifi_vdev_per_packet_metadata *wifi_metadata = NULL; + struct ath11k_skb_rxcb *rxcb; + struct ath11k_vif *arvif; + struct wireless_dev *wdev = (struct wireless_dev *)dev; + vif = wdev_to_ieee80211_vif(wdev); if (!vif) { dev_kfree_skb_any(skb); @@ -900,21 +1974,46 @@ ath11k_nss_ext_vdev_data_receive(struct ab = arvif->ar->ab; - skb->dev = dev; + wifi_metadata = (struct nss_wifi_vdev_per_packet_metadata *)(skb->head + + NSS_WIFI_VDEV_PER_PACKET_METADATA_OFFSET); - /* log the original skb received from nss */ - ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "dp rx msdu from nss ext : ", - skb->data, skb->len); + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, + "dp special data from nss on mesh link vap: pkt_type %d\n", + wifi_metadata->pkt_type); - ret = ath11k_nss_undecap(arvif, skb, &data_offs, ð_decap); - if (ret) { - ath11k_warn(ab, "error in nss ext rx undecap, type %d err %d\n", - arvif->nss.decap, ret); + switch (wifi_metadata->pkt_type) { + case NSS_WIFI_VDEV_MESH_EXT_DATA_PKT_TYPE_RX_SPL_PACKET: + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", + "special packet meta data from nss on mesh link vdev: ", + wifi_metadata, + sizeof(struct nss_wifi_vdev_per_packet_metadata)); + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", + "special packet payload from nss on mesh link vdev: ", + skb->data, skb->len); dev_kfree_skb_any(skb); - return; + break; + case NSS_WIFI_VDEV_EXT_DATA_PKT_TYPE_MCBC_RX: + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", + "mcast packet exception from nss on mesh link vdev: ", + skb->data, skb->len); + rxcb = ATH11K_SKB_RXCB(skb); + rxcb->rx_desc = (struct hal_rx_desc *)skb->head; + rxcb->is_first_msdu = rxcb->is_last_msdu = true; + rxcb->is_continuation = false; + ath11k_dp_rx_from_nss(arvif->ar, skb, napi); + break; + case NSS_WIFI_VDEV_EXT_DATA_PKT_TYPE_MESH: + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", + "static exception path from nss on mesh link vdev: ", + skb->data, skb->len); + dev_kfree_skb_any(skb); + break; + default: + ath11k_warn(ab, "unknown packet type received in mesh link vdev %d", + wifi_metadata->pkt_type); + dev_kfree_skb_any(skb); + break; } - - ath11k_nss_deliver_rx(arvif->vif, skb, eth_decap, data_offs, napi); } int ath11k_nss_tx(struct ath11k_vif *arvif, struct sk_buff *skb) @@ -923,8 +2022,9 @@ int ath11k_nss_tx(struct ath11k_vif *arv nss_tx_status_t status; int encap_type = ath11k_dp_tx_get_encap_type(arvif, skb); struct ath11k_soc_dp_stats *soc_stats = &ar->ab->soc_stats; + char dump_msg[100] = {0}; - if(encap_type != arvif->nss.encap) { + if (!arvif->ar->ab->nss.debug_mode && encap_type != arvif->nss.encap) { ath11k_warn(ar->ab, "encap mismatch in nss tx skb encap type %d" \ "vif encap type %d\n", encap_type, arvif->nss.encap); goto drop; @@ -939,16 +2039,41 @@ int ath11k_nss_tx(struct ath11k_vif *arv ath11k_nss_tx_encap_nwifi(skb); send: - ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, - arvif->vif->type == NL80211_IFTYPE_AP_VLAN ? "ext vdev" : "", - "nss tx msdu: ", skb->data, skb->len); - - if (arvif->vif->type == NL80211_IFTYPE_AP_VLAN) + if (arvif->vif->type == NL80211_IFTYPE_AP_VLAN) { + ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "ext vdev", + "nss tx msdu: ", skb->data, skb->len); status = nss_wifi_ext_vdev_tx_buf(arvif->nss.ctx, skb, arvif->nss.if_num); - else - status = nss_wifi_vdev_tx_buf(arvif->ar->nss.ctx, skb, - arvif->nss.if_num); + } else { + if (arvif->ar->ab->nss.debug_mode) { + if (encap_type == HAL_TCL_ENCAP_TYPE_ETHERNET && + !is_multicast_ether_addr(skb->data)) { + snprintf(dump_msg, sizeof(dump_msg), + "nss tx ucast msdu: %d ", + arvif->nss.mesh_handle); + ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "mesh", + dump_msg, skb->data, skb->len); + status = nss_wifi_meshmgr_tx_buf(arvif->nss.mesh_handle, + skb); + } else { + snprintf(dump_msg, sizeof(dump_msg), + "nss tx mcast msdu: %d ", + arvif->nss.if_num); + ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "mesh", + dump_msg, skb->data, skb->len); + status = nss_wifi_vdev_tx_buf(arvif->ar->nss.ctx, skb, + arvif->nss.if_num); + } + } else { + snprintf(dump_msg, sizeof(dump_msg), + "nss tx msdu: %d ", + arvif->nss.if_num); + ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "", + dump_msg, skb->data, skb->len); + status = nss_wifi_vdev_tx_buf(arvif->ar->nss.ctx, skb, + arvif->nss.if_num); + } + } if (status != NSS_TX_SUCCESS) { ath11k_dbg(ar->ab, (ATH11K_DBG_NSS | ATH11K_DBG_DP_TX), @@ -1035,6 +2160,9 @@ static int ath11k_nss_vdev_configure(str vdev_cfg = &vdev_msg->msg.vdev_config; + if (arvif->vif->type == NL80211_IFTYPE_MESH_POINT) + vdev_cfg->vap_ext_mode = WIFI_VDEV_EXT_MODE_MESH_LINK; + vdev_cfg->radio_ifnum = ar->nss.if_num; vdev_cfg->vdev_id = arvif->vdev_id; @@ -1075,6 +2203,37 @@ free: return ret; } +static int ath11k_nss_mesh_obj_assoc_link_vap(struct ath11k_vif *arvif) +{ + struct nss_wifi_mesh_assoc_link_vap *msg; + struct ath11k_base *ab = arvif->ar->ab; + nss_tx_status_t status; + int ret; + + msg = kzalloc(sizeof(struct nss_wifi_mesh_assoc_link_vap), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + msg->link_vap_id = arvif->nss.if_num; + + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "nss mesh assoc link vap %d, mesh handle %d\n", + arvif->nss.if_num, arvif->nss.mesh_handle); + + status = nss_wifi_meshmgr_assoc_link_vap_sync(arvif->nss.mesh_handle, msg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "failed mesh obj vdev tx msg for assoc link vap nss_err:%d\n", + status); + ret = -EINVAL; + goto free; + } + + ret = 0; +free: + kfree(msg); + + return ret; +} + static void ath11k_nss_vdev_unregister(struct ath11k_vif *arvif) { struct ath11k_base *ab = arvif->ar->ab; @@ -1086,6 +2245,12 @@ static void ath11k_nss_vdev_unregister(s ath11k_dbg(ab, ATH11K_DBG_NSS, "unregistered nss vdev %d \n", arvif->nss.if_num); break; + case NL80211_IFTYPE_MESH_POINT: + nss_unregister_wifi_vdev_if(arvif->nss.if_num); + ath11k_dbg(ab, ATH11K_DBG_NSS, + "unregistered nss mesh vdevs mesh link %d\n", + arvif->nss.if_num); + break; default: ath11k_warn(ab, "unsupported interface type %d for nss vdev unregister\n", arvif->vif->type); @@ -1093,6 +2258,76 @@ static void ath11k_nss_vdev_unregister(s } } +static int ath11k_nss_mesh_alloc_register(struct ath11k_vif *arvif, + struct net_device *netdev) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct nss_wifi_mesh_config_msg *nss_msg; + struct arvif_nss *nss = &arvif->nss; + int ret = 0; + + nss->mesh_ttl = ATH11K_MESH_DEFAULT_ELEMENT_TTL; + nss->mpath_refresh_time = 1000; /* msecs */ + nss->mesh_forward_enabled = true; + + nss_msg = kzalloc(sizeof(*nss_msg), GFP_KERNEL); + if (!nss_msg) + return -ENOMEM; + + nss_msg->ttl = nss->mesh_ttl; + nss_msg->mesh_path_refresh_time = nss->mpath_refresh_time; + nss_msg->mpp_learning_mode = mpp_mode; + nss_msg->block_mesh_forwarding = 0; + ether_addr_copy(nss_msg->local_mac_addr, arvif->vif->addr); + nss_msg->config_flags = + NSS_WIFI_MESH_CONFIG_FLAG_TTL_VALID | + NSS_WIFI_MESH_CONFIG_FLAG_MPATH_REFRESH_VALID | + NSS_WIFI_MESH_CONFIG_FLAG_MPP_LEARNING_MODE_VALID | + NSS_WIFI_MESH_CONFIG_FLAG_BLOCK_MESH_FWD_VALID | + NSS_WIFI_MESH_CONFIG_FLAG_LOCAL_MAC_VALID; + + arvif->nss.mesh_handle = nss_wifi_meshmgr_if_create_sync(netdev, nss_msg, + ath11k_nss_mesh_obj_vdev_data_receive, + ath11k_nss_mesh_obj_ext_data_callback, + ath11k_nss_mesh_obj_vdev_event_receive); + if (arvif->nss.mesh_handle == NSS_WIFI_MESH_HANDLE_INVALID) { + ath11k_warn(ab, "failed to create meshmgr\n"); + ret = -EINVAL; + } + + kfree(nss_msg); + + return ret; +} + +static int ath11k_nss_mesh_vdev_register(struct ath11k_vif *arvif, + struct net_device *netdev) +{ + struct ath11k *ar = arvif->ar; + struct ath11k_base *ab = ar->ab; + nss_tx_status_t status; + u32 features = 0; + + status = nss_register_wifi_vdev_if(ar->nss.ctx, + arvif->nss.if_num, + ath11k_nss_mesh_link_vdev_data_receive, + ath11k_nss_mesh_link_vdev_special_data_receive, + ath11k_nss_vdev_event_receive, + (struct net_device *)netdev->ieee80211_ptr, + features); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "failed to register nss mesh link vdev if_num %d nss_err:%d\n", + arvif->nss.if_num, status); + nss_unregister_wifi_vdev_if(arvif->nss.if_num); + return -EINVAL; + } + + ath11k_dbg(ab, ATH11K_DBG_NSS, "registered nss mesh link vdev if_num %d\n", + arvif->nss.if_num); + + return 0; +} + static int ath11k_nss_vdev_register(struct ath11k_vif *arvif, struct net_device *netdev) { @@ -1120,6 +2355,13 @@ static int ath11k_nss_vdev_register(stru arvif->nss.if_num); break; + case NL80211_IFTYPE_MESH_POINT: + if (!ab->nss.mesh_nss_offload_enabled) + return -ENOTSUPP; + + if (ath11k_nss_mesh_vdev_register(arvif, netdev)) + return -EINVAL; + break; default: ath11k_warn(ab, "unsupported interface type %d for nss vdev register\n", arvif->vif->type); @@ -1129,6 +2371,60 @@ static int ath11k_nss_vdev_register(stru return 0; } +static void ath11k_nss_mesh_vdev_free(struct ath11k_vif *arvif) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct ath11k *ar = arvif->ar; + struct ath11k_nss_mpath_entry *mpath_entry, *mpath_tmp; + struct ath11k_nss_mpp_entry *mpp_entry, *mpp_tmp; + struct arvif_nss *nss = &arvif->nss, *nss_entry, *nss_tmp; + LIST_HEAD(mpath_local_entry); + LIST_HEAD(mpp_local_entry); + nss_tx_status_t status; + + del_timer_sync(&nss->mpp_expiry_timer); + + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail_init(&nss->mpath_dump, &mpath_local_entry); + spin_unlock_bh(&ar->nss.dump_lock); + + list_for_each_entry_safe(mpath_entry, mpath_tmp, &mpath_local_entry, list) { + list_del(&mpath_entry->list); + kfree(mpath_entry); + } + + spin_lock_bh(&ar->nss.dump_lock); + list_splice_tail_init(&nss->mpp_dump, &mpp_local_entry); + spin_unlock_bh(&ar->nss.dump_lock); + + list_for_each_entry_safe(mpp_entry, mpp_tmp, &mpp_local_entry, list) { + list_del(&mpp_entry->list); + kfree(mpp_entry); + } + + list_for_each_entry_safe(nss_entry, nss_tmp, &mesh_vaps, list) + list_del(&nss_entry->list); + + status = nss_dynamic_interface_dealloc_node( + arvif->nss.if_num, + NSS_DYNAMIC_INTERFACE_TYPE_VAP); + if (status != NSS_TX_SUCCESS) + ath11k_warn(ab, "failed to free nss mesh link vdev nss_err:%d\n", + status); + else + ath11k_dbg(ab, ATH11K_DBG_NSS, + "nss mesh link vdev interface deallocated\n"); + + status = nss_wifi_meshmgr_if_destroy_sync(arvif->nss.mesh_handle); + + if (status != NSS_TX_SUCCESS) + ath11k_warn(ab, "failed to free nss mesh object vdev nss_err:%d\n", + status); + else + ath11k_dbg(ab, ATH11K_DBG_NSS, + "nss mesh object vdev interface deallocated\n"); +} + void ath11k_nss_vdev_free(struct ath11k_vif *arvif) { struct ath11k_base *ab = arvif->ar->ab; @@ -1148,6 +2444,9 @@ void ath11k_nss_vdev_free(struct ath11k_ "nss vdev interface deallocated\n"); return; + case NL80211_IFTYPE_MESH_POINT: + ath11k_nss_mesh_vdev_free(arvif); + return; default: ath11k_warn(ab, "unsupported interface type %d for nss vdev dealloc\n", arvif->vif->type); @@ -1155,11 +2454,92 @@ void ath11k_nss_vdev_free(struct ath11k_ } } -static int ath11k_nss_vdev_alloc(struct ath11k_vif *arvif) +struct arvif_nss *ath11k_nss_find_arvif_by_if_num(int if_num) +{ + struct arvif_nss *nss; + + list_for_each_entry(nss, &mesh_vaps, list) { + if (if_num == nss->if_num) + return nss; + } + return NULL; +} + +int ath11k_nss_assoc_link_arvif_to_ifnum(struct ath11k_vif *arvif, int if_num) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct ath11k_vif *arvif_link; + struct wireless_dev *wdev; + struct arvif_nss *nss; + int ret; + + wdev = ieee80211_vif_to_wdev_relaxed(arvif->vif); + if (!wdev) { + ath11k_warn(ab, "ath11k_nss: wdev is null\n"); + return -EINVAL; + } + + if (!wdev->netdev) { + ath11k_warn(ab, "ath11k_nss: netdev is null\n"); + return -EINVAL; + } + + nss = ath11k_nss_find_arvif_by_if_num(if_num); + if (!nss) { + ath11k_warn(ab, "ath11k_nss: unable to find if_num %d\n",if_num); + return -EINVAL; + } + + arvif_link = container_of(nss, struct ath11k_vif, nss); + + ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, + "assoc link vap ifnum %d to mesh handle of link id %d\n", + arvif_link->nss.if_num, arvif->nss.if_num); + + arvif_link->nss.mesh_handle = arvif->nss.mesh_handle; + + ret = ath11k_nss_mesh_obj_assoc_link_vap(arvif_link); + if (ret) + ath11k_warn(ab, "failed to associate link vap to mesh vap %d\n", ret); + + return 0; +} + +static int ath11k_nss_mesh_vdev_alloc(struct ath11k_vif *arvif, + struct net_device *netdev) +{ + struct ath11k_base *ab = arvif->ar->ab; + int if_num; + + if (!ab->nss.mesh_nss_offload_enabled) + return -ENOTSUPP; + + if_num = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_VAP); + if (if_num < 0) { + ath11k_warn(ab, "failed to allocate nss mesh link vdev\n"); + return -EINVAL; + } + + arvif->nss.if_num = if_num; + + INIT_LIST_HEAD(&arvif->nss.list); + list_add_tail(&arvif->nss.list, &mesh_vaps); + + INIT_LIST_HEAD(&arvif->nss.mpath_dump); + init_completion(&arvif->nss.dump_mpath_complete); + INIT_LIST_HEAD(&arvif->nss.mpp_dump); + init_completion(&arvif->nss.dump_mpp_complete); + + return 0; +} + +static int ath11k_nss_vdev_alloc(struct ath11k_vif *arvif, + struct net_device *netdev) { struct ath11k_base *ab = arvif->ar->ab; enum nss_dynamic_interface_type if_type; int if_num; + int ret; /* Initialize completion for verifying NSS message response */ init_completion(&arvif->nss.complete); @@ -1181,6 +2561,14 @@ static int ath11k_nss_vdev_alloc(struct arvif->nss.if_num); break; + case NL80211_IFTYPE_MESH_POINT: + ret = ath11k_nss_mesh_vdev_alloc(arvif, netdev); + if (ret) { + ath11k_warn(ab, "failed to allocate nss vdev of mesh type %d\n", + ret); + return ret; + } + break; default: ath11k_warn(ab, "unsupported interface type %d for nss vdev alloc\n", arvif->vif->type); @@ -1218,7 +2606,7 @@ int ath11k_nss_vdev_create(struct ath11k return -EINVAL; } - ret = ath11k_nss_vdev_alloc(arvif); + ret = ath11k_nss_vdev_alloc(arvif, wdev->netdev); if (ret) return ret; @@ -1234,6 +2622,43 @@ int ath11k_nss_vdev_create(struct ath11k goto unregister_vdev; break; + case NL80211_IFTYPE_MESH_POINT: + ret = ath11k_nss_mesh_alloc_register(arvif, wdev->netdev); + if (ret) { + ath11k_warn(ab, "failed to alloc and register mesh vap %d\n", ret); + goto unregister_vdev; + } + + ret = ath11k_nss_vdev_configure(arvif); + if (ret) { + ath11k_warn(ab, "failed to configure nss mesh link vdev\n"); + goto unregister_vdev; + } + + ret = ath11k_nss_mesh_obj_assoc_link_vap(arvif); + if (ret) { + ath11k_warn(ab, "failed to associate link vap to mesh vap %d\n", ret); + goto unregister_vdev; + } + + ret = ath11k_nss_vdev_set_cmd(arvif, + NSS_WIFI_VDEV_CFG_MCBC_EXC_TO_HOST_CMD, 1); + if (ret) { + ath11k_warn(ab, "failed to enable mcast/bcast exception %d\n", ret); + goto unregister_vdev; + } + + ath11k_debugfs_nss_mesh_vap_create(arvif); + + /* This timer cb is called at specified + * interval to update mpp exp timeout */ + timer_setup(&arvif->nss.mpp_expiry_timer, + ath11k_nss_mpp_timer_cb, 0); + + /* Start the initial timer in 2 secs */ + mod_timer(&arvif->nss.mpp_expiry_timer, + jiffies + msecs_to_jiffies(2 * HZ)); + break; default: ret = -ENOTSUPP; goto unregister_vdev; @@ -1290,6 +2715,14 @@ int ath11k_nss_vdev_up(struct ath11k_vif if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR) return 0; + if (arvif->vif->type == NL80211_IFTYPE_MESH_POINT) { + status = nss_wifi_meshmgr_if_up(arvif->nss.mesh_handle); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss mesh vdev up error %d\n", status); + return -EINVAL; + } + } + vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC); if (!vdev_msg) return -ENOMEM; @@ -1342,6 +2775,14 @@ int ath11k_nss_vdev_down(struct ath11k_v if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR) return 0; + if (arvif->vif->type == NL80211_IFTYPE_MESH_POINT) { + status = nss_wifi_meshmgr_if_down(arvif->nss.mesh_handle); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss mesh vdev up error %d\n", status); + return -EINVAL; + } + } + vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC); if (!vdev_msg) return -ENOMEM; @@ -2722,6 +4163,49 @@ static int ath11k_nss_get_dynamic_interf } } +static int ath11k_nss_mesh_capability(struct ath11k_base *ab) +{ + struct nss_wifili_msg *wlmsg = NULL; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int ret = 0; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + reinit_completion(&ab->nss.complete); + + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_SEND_MESH_CAPABILITY_INFO, + sizeof(struct nss_wifili_mesh_capability_info), + msg_cb, NULL); + + status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss failed to get mesh capability msg %d\n", status); + ret = -EINVAL; + goto free; + } + + ret = wait_for_completion_timeout(&ab->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ab, "timeout while waiting for mesh capability check\n"); + ret = -ETIMEDOUT; + goto free; + } + + kfree(wlmsg); + return 0; + +free: + kfree(wlmsg); + return ret; +} + static int ath11k_nss_init(struct ath11k_base *ab) { struct nss_wifili_init_msg *wim = NULL; @@ -2855,6 +4339,15 @@ static int ath11k_nss_init(struct ath11k kfree(wlmsg); + /* Create a mesh links read debugfs entry */ + ath11k_debugfs_nss_soc_create(ab); + + /* Check for mesh capability */ + ret = ath11k_nss_mesh_capability(ab); + + if (ret) + ath11k_err(ab, "Mesh offload is not enabled %d\n", ret); + ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS Init Message TX Success %p %d\n", ab->nss.ctx, ab->nss.if_num); return 0; --- a/drivers/net/wireless/ath/ath11k/nss.h +++ b/drivers/net/wireless/ath/ath11k/nss.h @@ -8,6 +8,8 @@ #include #include +#include +#include "../../../../../net/mac80211/mesh.h" struct ath11k; struct ath11k_base; @@ -19,8 +21,9 @@ struct hal_rx_mon_ppdu_info; /* NSS DBG macro is not included as part of debug enum to avoid * frequent changes during upgrade*/ -#define ATH11K_DBG_NSS 0x40000000 -#define ATH11K_DBG_NSS_WDS 0x80000000 +#define ATH11K_DBG_NSS 0x20000000 +#define ATH11K_DBG_NSS_WDS 0x40000000 +#define ATH11K_DBG_NSS_MESH 0x80000000 /* WIFILI Supported Target Types */ #define ATH11K_WIFILI_TARGET_TYPE_UNKNOWN 0xFF @@ -57,6 +60,7 @@ struct hal_rx_mon_ppdu_info; /* Timeout for waiting for response from NSS on TX msg */ #define ATH11K_NSS_MSG_TIMEOUT_MS 5000 +#define ATH11K_MESH_DEFAULT_ELEMENT_TTL 31 /* Init Flags */ #define WIFILI_NSS_CCE_DISABLED 0x1 #define WIFILI_ADDTL_MEM_SEG_SET 0x000000002 @@ -100,6 +104,8 @@ do { \ u64_stats_update_end(&tstats->syncp); \ } while (0) +#define ATH11K_MPP_EXPIRY_TIMER_INTERVAL_MS 60 * HZ + enum ath11k_nss_opmode { ATH11K_NSS_OPMODE_UNKNOWN, ATH11K_NSS_OPMODE_AP, @@ -146,10 +152,24 @@ struct ath11k_nss_peer { struct completion complete; }; +struct ath11k_nss_mpath_entry { + struct list_head list; + u32 num_entries; + struct nss_wifi_mesh_path_dump_entry mpath[0]; +}; + +struct ath11k_nss_mpp_entry { + struct list_head list; + u32 num_entries; + struct nss_wifi_mesh_proxy_path_dump_entry mpp[0]; +}; + /* Structure to hold the vif related info for nss offload support */ struct arvif_nss { /* dynamic ifnum allocated by nss driver for vif */ int if_num; + /* mesh handle for mesh obj vap */ + nss_wifi_mesh_handle_t mesh_handle; /* Used for completion status for vdev config nss messages */ struct completion complete; /* Keep the copy of encap type for nss */ @@ -169,6 +189,25 @@ struct arvif_nss { /* WDS cfg should be done only once for ext vdev */ bool wds_cfg_done; bool created; + + bool mpp_aging; + bool mpp_dump_req; + struct timer_list mpp_expiry_timer; + u8 mesh_ttl; + bool mesh_forward_enabled; + u32 metadata_type; + u32 mpath_refresh_time; + + struct list_head list; + struct list_head mpath_dump; + /* total number of mpath entries in all of the mpath_dump list */ + u32 mpath_dump_num_entries; + struct completion dump_mpath_complete; + + struct list_head mpp_dump; + /* total number of mpp entries in all of the mpp_dump list */ + u32 mpp_dump_num_entries; + struct completion dump_mpp_complete; }; /* Structure to hold the pdev/radio related info for nss offload support */ @@ -177,6 +216,8 @@ struct ath11k_nss { int if_num; /* Radio/pdev Context obtained on pdev register */ void* ctx; + /* protects stats from nss */ + spinlock_t dump_lock; }; /* Structure to hold the soc related info for nss offload support */ @@ -185,11 +226,15 @@ struct ath11k_soc_nss { bool enabled; /* turn on/off nss stats support in ath11k */ bool stats_enabled; + /* Mesh offload support as advertised by nss */ + bool mesh_nss_offload_enabled; #ifdef CPTCFG_ATH11K_NSS_SUPPORT /* soc nss ctx */ void* ctx; /* if_num to be used for soc related nss messages */ int if_num; + /* debug mode to disable the regular mesh configuration from mac80211 */ + bool debug_mode; /* Completion to nss message response */ struct completion complete; /* Response to nss messages are stored here on msg callback @@ -245,6 +290,19 @@ void ath11k_nss_update_sta_rxrate(struct int ath11k_nss_setup(struct ath11k_base *ab); int ath11k_nss_teardown(struct ath11k_base *ab); void ath11k_nss_ext_rx_stats(struct ath11k_base *ab, struct htt_rx_ring_tlv_filter *tlv_filter); +int ath11k_nss_dump_mpath_request(struct ath11k_vif *arvif); +int ath11k_nss_dump_mpp_request(struct ath11k_vif *arvif); +#ifdef CPTCFG_MAC80211_MESH +int ath11k_nss_mesh_config_path(struct ath11k *ar, struct ath11k_vif *arvif, + enum ieee80211_mesh_path_offld_cmd cmd, + struct ieee80211_mesh_path_offld *path); +#endif +int ath11k_nss_mesh_config_update(struct ieee80211_vif *vif, int changed); +int ath11k_nss_assoc_link_arvif_to_ifnum(struct ath11k_vif *arvif, int if_num); +int ath11k_nss_mesh_exception_flags(struct ath11k_vif *arvif, + struct nss_wifi_mesh_exception_flag_msg *nss_msg); +int ath11k_nss_exc_rate_config(struct ath11k_vif *arvif, + struct nss_wifi_mesh_rate_limit_config *nss_exc_cfg); #else static inline int ath11k_nss_tx(struct ath11k_vif *arvif, struct sk_buff *skb) { @@ -407,5 +465,35 @@ void ath11k_nss_ext_rx_stats(struct ath1 { return; } + +#ifdef CPTCFG_MAC80211_MESH +static inline int +ath11k_nss_mesh_config_path(struct ath11k *ar, struct ath11k_vif *arvif, + enum ieee80211_mesh_path_offld_cmd cmd, + struct ieee80211_mesh_path_offld *path) +{ + return 0; +} +#endif +static inline int +ath11k_nss_mesh_config_update(struct ieee80211_vif *vif, int changed) +{ + return 0; +} +static int ath11k_nss_assoc_link_arvif_to_ifnum(struct ath11k_vif *arvif, + int if_num) +{ + return 0; +} +static int ath11k_nss_mesh_exception_flags(struct ath11k_vif *arvif, + struct nss_wifi_mesh_exception_flag_msg *nss_msg) +{ + return 0; +} +int ath11k_nss_exc_rate_config(struct ath11k_vif *arvif, + struct nss_wifi_mesh_rate_limit_config *nss_exc_cfg) +{ + return 0; +} #endif /* CPTCFG_ATH11K_NSS_SUPPORT */ #endif --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -127,6 +127,9 @@ struct device; +struct ieee80211_mesh_path_offld; +enum ieee80211_mesh_path_offld_cmd; + /** * enum ieee80211_max_queues - maximum number of queues * @@ -366,11 +369,17 @@ enum ieee80211_bss_change { * to indicate which NSS BSS parameter changed. * * @BSS_CHANGED_NSS_AP_ISOLATE: AP Isolate feature in NSS mode + * @BSS_CHANGED_NSS_MESH_TTL: TTL update in NSS mesh mode + * @BSS_CHANGED_NSS_MESH_REFRESH_TIME: Mesh refresh time in NSS mesh mode + * @BSS_CHANGED_NSS_MESH_FWD_ENABLED: NSS offload mesh forward enabled * */ enum ieee80211_nss_bss_change { BSS_CHANGED_NSS_AP_ISOLATE = BIT(0), + BSS_CHANGED_NSS_MESH_TTL = BIT(1), + BSS_CHANGED_NSS_MESH_REFRESH_TIME = BIT(2), + BSS_CHANGED_NSS_MESH_FWD_ENABLED = BIT(3), }; /* @@ -721,6 +730,11 @@ struct ieee80211_bss_conf { bool nss_ap_isolate; struct ieee80211_multiple_bssid multiple_bssid; enum nl80211_beacon_tx_mode beacon_tx_mode; + + /* Mesh configuration for nss offload */ + u8 nss_offld_ttl; + bool nss_offld_mesh_forward_enabled; + u32 nss_offld_mpath_refresh_time; }; /** @@ -1150,6 +1164,8 @@ ieee80211_info_get_tx_time_est(struct ie * @skb: Packet skb (can be NULL if not provided by the driver) * @rate: The TX rate that was used when sending the packet * @free_list: list where processed skbs are stored to be free'd by the driver + * @mpdu_succ: Number of mpdus successfully transmitted + * @mpdu_fail: Number of mpdus failed */ struct ieee80211_tx_status { struct ieee80211_sta *sta; @@ -1161,6 +1177,8 @@ struct ieee80211_tx_status { #else struct sk_buff_head *free_list; #endif + u32 mpdu_succ; + u32 mpdu_fail; }; /** @@ -1661,12 +1679,14 @@ struct ieee80211_channel_switch { * @IEEE80211_VIF_GET_NOA_UPDATE: request to handle NOA attributes * and send P2P_PS notification to the driver if NOA changed, even * this is not pure P2P vif. + * @IEEE80211_HW_NSS_OFFLOAD_DEBUG_MODE: It enables the debug mode of nss offload. */ enum ieee80211_vif_flags { IEEE80211_VIF_BEACON_FILTER = BIT(0), IEEE80211_VIF_SUPPORTS_CQM_RSSI = BIT(1), IEEE80211_VIF_SUPPORTS_UAPSD = BIT(2), IEEE80211_VIF_GET_NOA_UPDATE = BIT(3), + IEEE80211_VIF_NSS_OFFLOAD_DEBUG_MODE = BIT(4), }; @@ -2529,6 +2549,7 @@ enum ieee80211_hw_flags { IEEE80211_HW_SUPPORTS_CONC_MON_RX_DECAP, IEEE80211_HW_SUPPORTS_NSS_OFFLOAD, IEEE80211_HW_SUPPORTS_MULTI_BSSID_AP, + IEEE80211_HW_SUPPORTS_MESH_NSS_OFFLOAD, /* keep last, obviously */ NUM_IEEE80211_HW_FLAGS @@ -3974,6 +3995,8 @@ enum ieee80211_reconfig_type { * @set_sar_specs: Update the SAR (TX power) settings. * @sta_set_decap_offload: Called to notify the driver when a station is allowed * to use rx decapsulation offload + * @config_mesh_offload_path: Configure mesh path table when driver supports mesh offload. + * This calback must be atomic. */ struct ieee80211_ops { void (*tx)(struct ieee80211_hw *hw, @@ -4297,6 +4320,12 @@ struct ieee80211_ops { void (*sta_set_decap_offload)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, bool enabled); +#ifdef CPTCFG_MAC80211_MESH + void (*config_mesh_offload_path)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_mesh_path_offld_cmd cmd, + struct ieee80211_mesh_path_offld *path); +#endif }; /** @@ -6989,4 +7018,100 @@ void ieeee80211_obss_color_collision_notify(struct ieee80211_vif *vif, u64 color_bitmap); +/* Defines for Mesh NSS offload */ + +enum ieee80211_mesh_path_offld_cmd { + IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPATH, + IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPATH, + IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPATH, + IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPP, + IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPP, + IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPP, +}; + +enum ieee80211_mesh_path_offld_action { + IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_REFRESH = BIT(0), + IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_DEL = BIT(1), + IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_EXP = BIT(2), + IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_LEARN = BIT(3), + IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_ADD = BIT(4), + IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_UPDATE = BIT(5), + IEEE80211_MESH_PATH_OFFLD_ACTION_PATH_NOT_FOUND = BIT(6), +}; + +/* Duplicate defines to make it available to driver */ +enum ieee80211_mesh_path_flags { + IEEE80211_MESH_PATH_ACTIVE = BIT(0), + IEEE80211_MESH_PATH_RESOLVING = BIT(1), + IEEE80211_MESH_PATH_SN_VALID = BIT(2), + IEEE80211_MESH_PATH_FIXED = BIT(3), + IEEE80211_MESH_PATH_RESOLVED = BIT(4), + IEEE80211_MESH_PATH_REQ_QUEUED = BIT(5), + IEEE80211_MESH_PATH_DELETED = BIT(6), +}; + +struct ieee80211_mesh_path_offld { + u8 mesh_da[ETH_ALEN]; + u8 da[ETH_ALEN]; + u8 next_hop[ETH_ALEN]; + u8 old_next_hop[ETH_ALEN]; + u8 ta[ETH_ALEN]; + u32 metric; + unsigned long exp_time; + u8 hop_count; + u16 flags; /* See &enum ieee80211_mesh_path_flags */ + u8 mesh_gate; + u8 block_mesh_fwd; + u8 metadata_type; +}; + +#ifdef CPTCFG_MAC80211_MESH +/** ieee80211_mesh_path_offld_change_notify - Notify mesh path change event. + * @vif: Mesh interface on which the event is being reported. + * @path: Mesh path which got changed. Please note not all the entries in the + * path will have valid information. Based on the action code, it will be + * processed. + * @action: Type of the event. + */ +int ieee80211_mesh_path_offld_change_notify(struct ieee80211_vif *vif, + struct ieee80211_mesh_path_offld *path, + enum ieee80211_mesh_path_offld_action action); + +/** ieee80211s_update_metric_ppdu - Upate tx PPDU stats for 11s metric computation + * + * @hw: the hardware the frame was transmitted by + * @st: tx status information +*/ +void ieee80211s_update_metric_ppdu(struct ieee80211_hw *hw, + struct ieee80211_tx_status *st); + +/** mesh_nss_offld_proxy_path_exp_update - update the expiry time from nss + * @vif Mesh interface on which the event is being reported. + * @mac: dest_mac_addr of the mesh proxy path + * @time_diff: This is the time diff since the mesh peer is active + */ +void mesh_nss_offld_proxy_path_exp_update(struct ieee80211_vif *vif, u8* da, + u8* mesh_da, u32 time_diff); +#else +static inline int +ieee80211_mesh_path_offld_change_notify(struct ieee80211_vif *vif, + struct ieee80211_mesh_path_offld *path, + enum ieee80211_mesh_path_offld_action action) +{ + return 0; +} + +static inline void +ieee80211s_update_metric_ppdu(struct ieee80211_hw *hw, + struct ieee80211_tx_status *st) +{ +} + +static inline void +mesh_nss_offld_proxy_path_exp_update(struct ieee80211_vif *vif, u8* da, + u8* mesh_da, u32 time_diff) +{ +} +#endif + #endif /* MAC80211_H */ --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -2289,6 +2289,7 @@ static int ieee80211_update_mesh_config( struct mesh_config *conf; struct ieee80211_sub_if_data *sdata; struct ieee80211_if_mesh *ifmsh; + u32 nss_changed = 0; sdata = IEEE80211_DEV_TO_SUB_IF(dev); ifmsh = &sdata->u.mesh; @@ -2305,8 +2306,11 @@ static int ieee80211_update_mesh_config( conf->dot11MeshMaxPeerLinks = nconf->dot11MeshMaxPeerLinks; if (_chg_mesh_attr(NL80211_MESHCONF_MAX_RETRIES, mask)) conf->dot11MeshMaxRetries = nconf->dot11MeshMaxRetries; - if (_chg_mesh_attr(NL80211_MESHCONF_TTL, mask)) + if (_chg_mesh_attr(NL80211_MESHCONF_TTL, mask)) { conf->dot11MeshTTL = nconf->dot11MeshTTL; + sdata->vif.bss_conf.nss_offld_ttl = nconf->dot11MeshTTL; + nss_changed |= BSS_CHANGED_NSS_MESH_TTL; + } if (_chg_mesh_attr(NL80211_MESHCONF_ELEMENT_TTL, mask)) conf->element_ttl = nconf->element_ttl; if (_chg_mesh_attr(NL80211_MESHCONF_AUTO_OPEN_PLINKS, mask)) { @@ -2320,8 +2324,12 @@ static int ieee80211_update_mesh_config( if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES, mask)) conf->dot11MeshHWMPmaxPREQretries = nconf->dot11MeshHWMPmaxPREQretries; - if (_chg_mesh_attr(NL80211_MESHCONF_PATH_REFRESH_TIME, mask)) + if (_chg_mesh_attr(NL80211_MESHCONF_PATH_REFRESH_TIME, mask)) { conf->path_refresh_time = nconf->path_refresh_time; + sdata->vif.bss_conf.nss_offld_mpath_refresh_time = + nconf->path_refresh_time; + nss_changed |= BSS_CHANGED_NSS_MESH_REFRESH_TIME; + } if (_chg_mesh_attr(NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT, mask)) conf->min_discovery_timeout = nconf->min_discovery_timeout; if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT, mask)) @@ -2356,8 +2364,12 @@ static int ieee80211_update_mesh_config( if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_RANN_INTERVAL, mask)) conf->dot11MeshHWMPRannInterval = nconf->dot11MeshHWMPRannInterval; - if (_chg_mesh_attr(NL80211_MESHCONF_FORWARDING, mask)) + if (_chg_mesh_attr(NL80211_MESHCONF_FORWARDING, mask)) { conf->dot11MeshForwarding = nconf->dot11MeshForwarding; + sdata->vif.bss_conf.nss_offld_mesh_forward_enabled = + nconf->dot11MeshForwarding; + nss_changed |= BSS_CHANGED_NSS_MESH_FWD_ENABLED; + } if (_chg_mesh_attr(NL80211_MESHCONF_RSSI_THRESHOLD, mask)) { /* our RSSI threshold implementation is supported only for * devices that report signal in dBm. @@ -2398,6 +2410,7 @@ static int ieee80211_update_mesh_config( conf->dot11MeshConnectedToAuthServer = nconf->dot11MeshConnectedToAuthServer; ieee80211_mbss_info_change_notify(sdata, BSS_CHANGED_BEACON); + ieee80211_nss_bss_info_change_notify(sdata, nss_changed); return 0; } --- a/net/mac80211/debug.h +++ b/net/mac80211/debug.h @@ -63,6 +63,12 @@ #define MAC80211_MESH_PS_DEBUG 0 #endif +#ifdef CPTCFG_MAC80211_MESH_OFFLOAD_DEBUG +#define MAC80211_MESH_OFFLOAD_DEBUG 1 +#else +#define MAC80211_MESH_OFFLOAD_DEBUG 0 +#endif + #ifdef CPTCFG_MAC80211_TDLS_DEBUG #define MAC80211_TDLS_DEBUG 1 #else @@ -182,6 +188,10 @@ do { \ _sdata_dbg(MAC80211_MESH_PS_DEBUG, \ sdata, fmt, ##__VA_ARGS__) +#define moffld_dbg(sdata, fmt, ...) \ + _sdata_dbg(MAC80211_MESH_OFFLOAD_DEBUG, \ + sdata, fmt, ##__VA_ARGS__) + #define tdls_dbg(sdata, fmt, ...) \ _sdata_dbg(MAC80211_TDLS_DEBUG, \ sdata, fmt, ##__VA_ARGS__) --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -411,6 +411,7 @@ static const char *hw_flag_names[] = { FLAG(SUPPORTS_CONC_MON_RX_DECAP), FLAG(SUPPORTS_NSS_OFFLOAD), FLAG(SUPPORTS_MULTI_BSSID_AP), + FLAG(SUPPORTS_MESH_NSS_OFFLOAD), #undef FLAG }; --- a/net/mac80211/driver-ops.c +++ b/net/mac80211/driver-ops.c @@ -347,3 +347,23 @@ int drv_ampdu_action(struct ieee80211_lo return ret; } + +#ifdef CPTCFG_MAC80211_MESH +void drv_config_mesh_offload_path(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + enum ieee80211_mesh_path_offld_cmd cmd, + struct ieee80211_mesh_path_offld *path) +{ + if (!check_sdata_in_driver(sdata)) + return; + + if (!ieee80211_hw_check(&local->hw, SUPPORTS_MESH_NSS_OFFLOAD)) + return; + + if (local->ops->config_mesh_offload_path) + local->ops->config_mesh_offload_path(&local->hw, + &sdata->vif, cmd, path); + + /* TODO: trace event */ +} +#endif --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -1450,4 +1450,10 @@ static inline void drv_sta_set_decap_off trace_drv_return_void(local); } +#ifdef CPTCFG_MAC80211_MESH +void drv_config_mesh_offload_path(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + enum ieee80211_mesh_path_offld_cmd cmd, + struct ieee80211_mesh_path_offld *path); +#endif #endif /* __MAC80211_DRIVER_OPS */ --- a/net/mac80211/mesh.h +++ b/net/mac80211/mesh.h @@ -277,6 +277,10 @@ void mesh_rx_path_sel_frame(struct ieee8 struct ieee80211_mgmt *mgmt, size_t len); struct mesh_path * mesh_path_add(struct ieee80211_sub_if_data *sdata, const u8 *dst); +struct mesh_path *__mesh_path_add(struct ieee80211_sub_if_data *sdata, + const u8 *dst); +int __mpp_path_add(struct ieee80211_sub_if_data *sdata, + const u8 *dst, const u8 *mpp); int mesh_path_add_gate(struct mesh_path *mpath); int mesh_path_send_to_gates(struct mesh_path *mpath); @@ -318,6 +322,7 @@ void mesh_path_discard_frame(struct ieee void mesh_path_tx_root_frame(struct ieee80211_sub_if_data *sdata); bool mesh_action_is_path_sel(struct ieee80211_mgmt *mgmt); +void mesh_nss_offld_path_update(struct mesh_path *mpath, bool is_mpath, u8 *old_next_hop_addr); #ifdef CPTCFG_MAC80211_MESH static inline --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -360,6 +360,13 @@ u32 airtime_link_metric_get(struct ieee8 return (u32)result; } +static inline struct sta_info * +next_hop_deref_protected(struct mesh_path *mpath) +{ + return rcu_dereference_protected(mpath->next_hop, + lockdep_is_held(&mpath->state_lock)); +} + /** * hwmp_route_info_get - Update routing info to originator and transmitter * @@ -383,9 +390,10 @@ static u32 hwmp_route_info_get(struct ie { struct ieee80211_local *local = sdata->local; struct mesh_path *mpath; - struct sta_info *sta; + struct sta_info *sta, *next_hop; bool fresh_info; const u8 *orig_addr, *ta; + u8 old_next_hop_addr[ETH_ALEN] = {0}; u32 orig_sn, orig_metric; unsigned long orig_lifetime, exp_time; u32 last_hop_metric, new_metric; @@ -486,7 +494,10 @@ static u32 hwmp_route_info_get(struct ie } if (fresh_info) { - if (rcu_access_pointer(mpath->next_hop) != sta) + next_hop = rcu_dereference(mpath->next_hop); + if (next_hop) + ether_addr_copy(old_next_hop_addr, next_hop->sta.addr); + if (next_hop != sta) mpath->path_change_count++; mesh_path_assign_nexthop(mpath, sta); mpath->flags |= MESH_PATH_SN_VALID; @@ -504,6 +515,8 @@ static u32 hwmp_route_info_get(struct ie /* draft says preq_id should be saved to, but there does * not seem to be any use for it, skipping by now */ + + mesh_nss_offld_path_update(mpath, true, old_next_hop_addr); } else spin_unlock_bh(&mpath->state_lock); } @@ -534,7 +547,10 @@ static u32 hwmp_route_info_get(struct ie } if (fresh_info) { - if (rcu_access_pointer(mpath->next_hop) != sta) + next_hop = rcu_dereference(mpath->next_hop); + if (next_hop) + ether_addr_copy(old_next_hop_addr, next_hop->sta.addr); + if (next_hop != sta) mpath->path_change_count++; mesh_path_assign_nexthop(mpath, sta); mpath->metric = last_hop_metric; @@ -547,6 +563,8 @@ static u32 hwmp_route_info_get(struct ie /* init it at a low value - 0 start is tricky */ ewma_mesh_fail_avg_add(&sta->mesh->fail_avg, 1); mesh_path_tx_pending(mpath); + + mesh_nss_offld_path_update(mpath, true, old_next_hop_addr); } else spin_unlock_bh(&mpath->state_lock); } @@ -683,15 +701,6 @@ static void hwmp_preq_frame_process(stru } } - -static inline struct sta_info * -next_hop_deref_protected(struct mesh_path *mpath) -{ - return rcu_dereference_protected(mpath->next_hop, - lockdep_is_held(&mpath->state_lock)); -} - - static void hwmp_prep_frame_process(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, const u8 *prep_elem, u32 metric) @@ -1326,3 +1335,272 @@ void mesh_path_tx_root_frame(struct ieee return; } } + +static int mesh_path_offld_mpath_refresh(struct ieee80211_sub_if_data *sdata, + u8 *mda) +{ + struct mesh_path *mpath; + + rcu_read_lock(); + + mpath = mesh_path_lookup(sdata, mda); + if (!mpath || !(mpath->flags & MESH_PATH_ACTIVE)) { + moffld_dbg(sdata, + "mpath lookup failed during path refresh for %pM, is_mpath %d\n", + mda, mpath != NULL); + rcu_read_unlock(); + return -ENOENT; + } + + if (!(mpath->flags & MESH_PATH_RESOLVING) && !(mpath->flags & MESH_PATH_FIXED)) + mesh_queue_preq(mpath, PREQ_Q_F_START | PREQ_Q_F_REFRESH); + + rcu_read_unlock(); + + return 0; +} + +static int mesh_path_offld_mpath_del(struct ieee80211_sub_if_data *sdata, u8 *da) +{ + struct mesh_path *mpath, *mppath; + + rcu_read_lock(); + + mpath = mesh_path_lookup(sdata, da); + if (!mpath) { + moffld_dbg(sdata, "mpath lookup failed for %pM during duplicate mpath removal\n", + da); + rcu_read_unlock(); + return -ENOENT; + } + + mppath = mpp_path_lookup(sdata, da); + if (!mppath) { + moffld_dbg(sdata, "proxy path lookup failed for %pM during duplicate mpath removal\n", + da); + rcu_read_unlock(); + return -EINVAL; + } + + mesh_path_del(sdata, mpath->dst); + + rcu_read_unlock(); + + return 0; +} + +static int mesh_path_offld_mpath_exp(struct ieee80211_sub_if_data *sdata, u8 *mda) +{ + struct mesh_path *mpath; + + rcu_read_lock(); + + mpath = mesh_path_lookup(sdata, mda); + if (!mpath) { + mpath = mesh_path_add(sdata, mda); + if (IS_ERR(mpath)) { + rcu_read_unlock(); + moffld_dbg(sdata, + "failed to add mpath for %pM during mpath exp\n", mda); + return PTR_ERR(mpath); + } + } + + spin_lock_bh(&mpath->state_lock); + mpath->flags &= ~MESH_PATH_ACTIVE; + spin_unlock_bh(&mpath->state_lock); + + if (!(mpath->flags & MESH_PATH_RESOLVING) && + mesh_path_sel_is_hwmp(sdata)) + mesh_queue_preq(mpath, PREQ_Q_F_START); + + rcu_read_unlock(); + + return 0; +} + +static int mesh_path_offld_mpp_learn(struct ieee80211_sub_if_data *sdata, + u8 *da, u8 *mda) +{ + struct mesh_path *mppath; + int ret; + + rcu_read_lock(); + mppath = mpp_path_lookup(sdata, da); + if (mppath) { + moffld_dbg(sdata, "proxy path for da %pM mesh_da %pM already exists\n", + da, mda); + rcu_read_unlock(); + return -EEXIST; + } + + ret = mpp_path_add(sdata, da, mda); + if (ret) + moffld_dbg(sdata, "failed to add proxy path entry (%d): da %pM mesh_da %pM\n", + ret, da, mda); + + rcu_read_unlock(); + + return ret; +} + +static int mesh_path_offld_mpp_add(struct ieee80211_sub_if_data *sdata, + u8 *da, u8 *mda) +{ + struct mesh_path *mppath; + int ret; + + rcu_read_lock(); + mppath = mpp_path_lookup(sdata, da); + if (mppath) { + moffld_dbg(sdata, "proxy path for da %pM mesh_da %pM already exists\n", + da, mda); + rcu_read_unlock(); + return -EEXIST; + } + + ret = __mpp_path_add(sdata, da, mda); + if (ret) + moffld_dbg(sdata, "failed to add proxy path entry (%d): da %pM mesh_da %pM\n", + ret, da, mda); + + rcu_read_unlock(); + + return ret; +} + +static int mesh_path_offld_mpp_update(struct ieee80211_sub_if_data *sdata, + u8 *da, u8 *mda) +{ + struct mesh_path *mppath; + + rcu_read_lock(); + mppath = mpp_path_lookup(sdata, da); + if (!mppath) { + moffld_dbg(sdata, + "proxy path lookup for da %pM failed during MPP update with mesh_da %pM\n", + da, mda); + rcu_read_unlock(); + return -ENOENT; + } else { + spin_lock_bh(&mppath->state_lock); + if (!ether_addr_equal(mppath->mpp, mda)) + memcpy(mppath->mpp, mda, ETH_ALEN); + mppath->exp_time = jiffies; + spin_unlock_bh(&mppath->state_lock); + } + rcu_read_unlock(); + + return 0; +} + +static int mesh_path_offld_mpath_not_found(struct ieee80211_sub_if_data *sdata, + u8 *mda, u8 *ta) +{ + struct mesh_path *mpath; + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + + rcu_read_lock(); + + mpath = mesh_path_lookup(sdata, mda); + if (!mpath) { + mpath = mesh_path_add(sdata, mda); + if (IS_ERR(mpath)) { + moffld_dbg(sdata, "mpath add failed for mesh_da %pM (%lu)\n", + mda, PTR_ERR(mpath)); + rcu_read_unlock(); + return PTR_ERR(mpath); + } + } + + if (!(mpath->flags & MESH_PATH_RESOLVING) && + mesh_path_sel_is_hwmp(sdata)) + mesh_queue_preq(mpath, PREQ_Q_F_START); + + rcu_read_unlock(); + + if (!is_zero_ether_addr(ta)) + mesh_path_error_tx(sdata, ifmsh->mshcfg.element_ttl, + mda, 0, WLAN_REASON_MESH_PATH_NOFORWARD, ta); + + return 0; +} + +void ieee80211s_update_metric_ppdu(struct ieee80211_hw *hw, + struct ieee80211_tx_status *st) +{ + struct sta_info *sta; + int i, num_mpdu; + bool failed; + + if (!st->sta) + return; + + if (st->mpdu_succ) { + num_mpdu = st->mpdu_succ; + failed = false; + } else if (st->mpdu_fail) { + num_mpdu = st->mpdu_fail; + failed = true; + } else + return; + + sta = container_of(st->sta, struct sta_info, sta); + if (!ieee80211_vif_is_mesh(&sta->sdata->vif)) + return; + + for (i = 0; i < num_mpdu; i++) { + ewma_mesh_fail_avg_add(&sta->mesh->fail_avg, failed * 100); + if (ewma_mesh_fail_avg_read(&sta->mesh->fail_avg) > + LINK_FAIL_THRESH) + mesh_plink_broken(sta); + + if (!st->rate) + continue; + + ewma_mesh_tx_rate_avg_add(&sta->mesh->tx_rate_avg, + cfg80211_calculate_bitrate(st->rate)); + } +} +EXPORT_SYMBOL(ieee80211s_update_metric_ppdu); + +int ieee80211_mesh_path_offld_change_notify(struct ieee80211_vif *vif, + struct ieee80211_mesh_path_offld *path, + enum ieee80211_mesh_path_offld_action action) +{ + struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + int ret = -ENOTSUPP; + + moffld_dbg(sdata, "received mesh offload event %d\n", action); + + switch (action) { + case IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_REFRESH: + ret = mesh_path_offld_mpath_refresh(sdata, path->mesh_da); + break; + case IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_DEL: + ret = mesh_path_offld_mpath_del(sdata, path->mesh_da); + break; + case IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_EXP: + ret = mesh_path_offld_mpath_exp(sdata, path->mesh_da); + break; + case IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_LEARN: + ret = mesh_path_offld_mpp_learn(sdata, path->da, path->mesh_da); + break; + case IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_ADD: + ret = mesh_path_offld_mpp_add(sdata, path->da, path->mesh_da); + break; + case IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_UPDATE: + ret = mesh_path_offld_mpp_update(sdata, path->da, + path->mesh_da); + break; + case IEEE80211_MESH_PATH_OFFLD_ACTION_PATH_NOT_FOUND: + ret = mesh_path_offld_mpath_not_found(sdata, path->da, + path->ta); + break; + default: + break; + } + + return ret; +} +EXPORT_SYMBOL(ieee80211_mesh_path_offld_change_notify); --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -14,6 +14,7 @@ #include "wme.h" #include "ieee80211_i.h" #include "mesh.h" +#include "driver-ops.h" static void mesh_path_free_rcu(struct mesh_table *tbl, struct mesh_path *mpath); @@ -72,6 +73,63 @@ static void mesh_table_free(struct mesh_ kfree(tbl); } +void mesh_nss_offld_proxy_path_exp_update(struct ieee80211_vif *vif, u8* da, u8* mesh_da, u32 inactive_time) +{ + struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + struct mesh_table *tbl = sdata->u.mesh.mpp_paths; + struct mesh_path *mppath; + struct hlist_node *n; + unsigned long expiry; + + spin_lock_bh(&tbl->walk_lock); + hlist_for_each_entry_safe(mppath, n, &tbl->walk_head, walk_list) { + if(!ether_addr_equal(da, mppath->dst) || !ether_addr_equal(mesh_da, mppath->mpp)) + continue; + if ((!(mppath->flags & MESH_PATH_RESOLVING)) && + (!(mppath->flags & MESH_PATH_FIXED))) { + expiry = jiffies - msecs_to_jiffies(inactive_time); + mppath->exp_time = time_after(mppath->exp_time, expiry) ? + mppath->exp_time : expiry; + } + } + spin_unlock_bh(&tbl->walk_lock); +} +EXPORT_SYMBOL(mesh_nss_offld_proxy_path_exp_update); + +void mesh_nss_offld_path_update(struct mesh_path *mpath, bool is_mpath, u8 *old_next_hop_addr) +{ + struct ieee80211_mesh_path_offld path = {0}; + struct sta_info *next_hop; + struct ieee80211_sub_if_data *sdata = mpath->sdata; + + + path.metric = mpath->metric; + if (time_before(jiffies, mpath->exp_time)) + path.exp_time = jiffies_to_msecs(mpath->exp_time - jiffies); + + path.hop_count = mpath->hop_count; + path.flags = mpath->flags; + path.mesh_gate = mpath->is_gate; + if (is_mpath) { + ether_addr_copy(path.mesh_da, mpath->dst); + } else { + ether_addr_copy(path.mesh_da, mpath->mpp); + ether_addr_copy(path.da, mpath->dst); + } + + next_hop = rcu_dereference(mpath->next_hop); + if (next_hop) + ether_addr_copy(path.next_hop, next_hop->addr); + + if (old_next_hop_addr) + ether_addr_copy(path.old_next_hop, old_next_hop_addr); + + drv_config_mesh_offload_path(sdata->local, sdata, + is_mpath ? IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPATH : + IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPP, + &path); +} + /** * mesh_path_assign_nexthop - update mesh path next hop * @@ -209,16 +267,23 @@ static void mesh_path_move_to_queue(stru static struct mesh_path *mpath_lookup(struct mesh_table *tbl, const u8 *dst, - struct ieee80211_sub_if_data *sdata) + struct ieee80211_sub_if_data *sdata, + bool is_mpath) { struct mesh_path *mpath; + bool update; + struct sta_info *next_hop; mpath = rhashtable_lookup(&tbl->rhead, dst, mesh_rht_params); if (mpath && mpath_expired(mpath)) { spin_lock_bh(&mpath->state_lock); + next_hop = rcu_dereference(mpath->next_hop); + update = !!(mpath->flags & MESH_PATH_ACTIVE); mpath->flags &= ~MESH_PATH_ACTIVE; spin_unlock_bh(&mpath->state_lock); + if (update && is_mpath) + mesh_nss_offld_path_update(mpath, true, next_hop ? next_hop->addr : NULL); } return mpath; } @@ -235,13 +300,13 @@ static struct mesh_path *mpath_lookup(st struct mesh_path * mesh_path_lookup(struct ieee80211_sub_if_data *sdata, const u8 *dst) { - return mpath_lookup(sdata->u.mesh.mesh_paths, dst, sdata); + return mpath_lookup(sdata->u.mesh.mesh_paths, dst, sdata, true); } struct mesh_path * mpp_path_lookup(struct ieee80211_sub_if_data *sdata, const u8 *dst) { - return mpath_lookup(sdata->u.mesh.mpp_paths, dst, sdata); + return mpath_lookup(sdata->u.mesh.mpp_paths, dst, sdata, false); } static struct mesh_path * @@ -303,6 +368,7 @@ mpp_path_lookup_by_idx(struct ieee80211_ int mesh_path_add_gate(struct mesh_path *mpath) { struct mesh_table *tbl; + struct sta_info *next_hop; int err; rcu_read_lock(); @@ -315,6 +381,7 @@ int mesh_path_add_gate(struct mesh_path goto err_rcu; } mpath->is_gate = true; + next_hop = rcu_dereference(mpath->next_hop); mpath->sdata->u.mesh.num_gates++; spin_lock(&tbl->gates_lock); @@ -323,6 +390,8 @@ int mesh_path_add_gate(struct mesh_path spin_unlock_bh(&mpath->state_lock); + mesh_nss_offld_path_update(mpath, true, next_hop ? next_hop->addr : NULL); + mpath_dbg(mpath->sdata, "Mesh path: Recorded new gate: %pM. %d known gates\n", mpath->dst, mpath->sdata->u.mesh.num_gates); @@ -339,16 +408,21 @@ err_rcu: */ static void mesh_gate_del(struct mesh_table *tbl, struct mesh_path *mpath) { + struct sta_info *next_hop; + lockdep_assert_held(&mpath->state_lock); if (!mpath->is_gate) return; + next_hop = rcu_dereference(mpath->next_hop); mpath->is_gate = false; spin_lock_bh(&tbl->gates_lock); hlist_del_rcu(&mpath->gate_list); mpath->sdata->u.mesh.num_gates--; spin_unlock_bh(&tbl->gates_lock); + mesh_nss_offld_path_update(mpath, true, next_hop ? next_hop->addr : NULL); + mpath_dbg(mpath->sdata, "Mesh path: Deleted gate: %pM. %d known gates\n", mpath->dst, mpath->sdata->u.mesh.num_gates); @@ -386,17 +460,8 @@ struct mesh_path *mesh_path_new(struct i return new_mpath; } -/** - * mesh_path_add - allocate and add a new path to the mesh path table - * @dst: destination address of the path (ETH_ALEN length) - * @sdata: local subif - * - * Returns: 0 on success - * - * State: the initial state of the new path is set to 0 - */ -struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata, - const u8 *dst) +struct mesh_path *__mesh_path_add(struct ieee80211_sub_if_data *sdata, + const u8 *dst) { struct mesh_table *tbl; struct mesh_path *mpath, *new_mpath; @@ -437,8 +502,36 @@ struct mesh_path *mesh_path_add(struct i return new_mpath; } -int mpp_path_add(struct ieee80211_sub_if_data *sdata, - const u8 *dst, const u8 *mpp) +/** + * mesh_path_add - allocate and add a new path to the mesh path table + * @dst: destination address of the path (ETH_ALEN length) + * @sdata: local subif + * + * Returns: 0 on success + * + * State: the initial state of the new path is set to 0 + */ +struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata, + const u8 *dst) +{ + struct mesh_path *new_path; + struct ieee80211_mesh_path_offld path = {0}; + + new_path = __mesh_path_add(sdata, dst); + if (IS_ERR(new_path)) + return new_path; + + ether_addr_copy(path.mesh_da, dst); + + drv_config_mesh_offload_path(sdata->local, sdata, + IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPATH, + &path); + + return new_path; +} + +int __mpp_path_add(struct ieee80211_sub_if_data *sdata, + const u8 *dst, const u8 *mpp) { struct mesh_table *tbl; struct mesh_path *new_mpath; @@ -474,6 +567,25 @@ int mpp_path_add(struct ieee80211_sub_if return ret; } +int mpp_path_add(struct ieee80211_sub_if_data *sdata, + const u8 *dst, const u8 *mpp) +{ + struct ieee80211_mesh_path_offld path = {0}; + int ret; + + ret = __mpp_path_add(sdata, dst, mpp); + if (ret) + return ret; + + ether_addr_copy(path.mesh_da, mpp); + ether_addr_copy(path.da, dst); + + drv_config_mesh_offload_path(sdata->local, sdata, + IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPP, + &path); + + return 0; +} /** * mesh_plink_broken - deactivates paths and sends perr when a link breaks @@ -524,11 +636,37 @@ static void mesh_path_free_rcu(struct me kfree_rcu(mpath, rcu); } -static void __mesh_path_del(struct mesh_table *tbl, struct mesh_path *mpath) +static void __mesh_path_del(struct mesh_table *tbl, struct mesh_path *mpath, + bool is_mpath_tbl) { + struct ieee80211_mesh_path_offld path = {0}; + struct sta_info *next_hop; + struct ieee80211_sub_if_data *sdata = mpath->sdata; + + + path.metric = mpath->metric; + path.exp_time = mpath->exp_time; + path.hop_count = mpath->hop_count; + path.flags = mpath->flags; + if (is_mpath_tbl) { + ether_addr_copy(path.mesh_da, mpath->dst); + } else { + ether_addr_copy(path.mesh_da, mpath->mpp); + ether_addr_copy(path.da, mpath->dst); + } + + next_hop = rcu_dereference(mpath->next_hop); + if (next_hop) + ether_addr_copy(path.next_hop, next_hop->addr); + hlist_del_rcu(&mpath->walk_list); rhashtable_remove_fast(&tbl->rhead, &mpath->rhash, mesh_rht_params); mesh_path_free_rcu(tbl, mpath); + + drv_config_mesh_offload_path(sdata->local, sdata, + is_mpath_tbl ? IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPATH : + IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPP, + &path); } /** @@ -552,7 +690,7 @@ void mesh_path_flush_by_nexthop(struct s spin_lock_bh(&tbl->walk_lock); hlist_for_each_entry_safe(mpath, n, &tbl->walk_head, walk_list) { if (rcu_access_pointer(mpath->next_hop) == sta) - __mesh_path_del(tbl, mpath); + __mesh_path_del(tbl, mpath, true); } spin_unlock_bh(&tbl->walk_lock); } @@ -567,19 +705,19 @@ static void mpp_flush_by_proxy(struct ie spin_lock_bh(&tbl->walk_lock); hlist_for_each_entry_safe(mpath, n, &tbl->walk_head, walk_list) { if (ether_addr_equal(mpath->mpp, proxy)) - __mesh_path_del(tbl, mpath); + __mesh_path_del(tbl, mpath, false); } spin_unlock_bh(&tbl->walk_lock); } -static void table_flush_by_iface(struct mesh_table *tbl) +static void table_flush_by_iface(struct mesh_table *tbl, bool is_mpath_tbl) { struct mesh_path *mpath; struct hlist_node *n; spin_lock_bh(&tbl->walk_lock); hlist_for_each_entry_safe(mpath, n, &tbl->walk_head, walk_list) { - __mesh_path_del(tbl, mpath); + __mesh_path_del(tbl, mpath, is_mpath_tbl); } spin_unlock_bh(&tbl->walk_lock); } @@ -594,8 +732,8 @@ static void table_flush_by_iface(struct */ void mesh_path_flush_by_iface(struct ieee80211_sub_if_data *sdata) { - table_flush_by_iface(sdata->u.mesh.mesh_paths); - table_flush_by_iface(sdata->u.mesh.mpp_paths); + table_flush_by_iface(sdata->u.mesh.mesh_paths, true); + table_flush_by_iface(sdata->u.mesh.mpp_paths, false); } /** @@ -609,7 +747,7 @@ void mesh_path_flush_by_iface(struct iee */ static int table_path_del(struct mesh_table *tbl, struct ieee80211_sub_if_data *sdata, - const u8 *addr) + const u8 *addr, bool is_mpath_tbl) { struct mesh_path *mpath; @@ -620,7 +758,7 @@ static int table_path_del(struct mesh_ta return -ENXIO; } - __mesh_path_del(tbl, mpath); + __mesh_path_del(tbl, mpath, is_mpath_tbl); spin_unlock_bh(&tbl->walk_lock); return 0; } @@ -641,7 +779,7 @@ int mesh_path_del(struct ieee80211_sub_i /* flush relevant mpp entries first */ mpp_flush_by_proxy(sdata, addr); - err = table_path_del(sdata->u.mesh.mesh_paths, sdata, addr); + err = table_path_del(sdata->u.mesh.mesh_paths, sdata, addr, true); sdata->u.mesh.mesh_paths_generation++; return err; } @@ -744,7 +882,10 @@ void mesh_path_flush_pending(struct mesh */ void mesh_path_fix_nexthop(struct mesh_path *mpath, struct sta_info *next_hop) { + struct sta_info *old_next_hop; + spin_lock_bh(&mpath->state_lock); + old_next_hop = rcu_dereference(mpath->next_hop); mesh_path_assign_nexthop(mpath, next_hop); mpath->sn = 0xffff; mpath->metric = 0; @@ -757,6 +898,8 @@ void mesh_path_fix_nexthop(struct mesh_p /* init it at a low value - 0 start is tricky */ ewma_mesh_fail_avg_add(&next_hop->mesh->fail_avg, 1); mesh_path_tx_pending(mpath); + + mesh_nss_offld_path_update(mpath, true, old_next_hop ? old_next_hop->addr : NULL); } int mesh_pathtbl_init(struct ieee80211_sub_if_data *sdata) @@ -794,7 +937,7 @@ free_path: static void mesh_path_tbl_expire(struct ieee80211_sub_if_data *sdata, - struct mesh_table *tbl) + struct mesh_table *tbl, bool is_mpath_tbl) { struct mesh_path *mpath; struct hlist_node *n; @@ -804,15 +947,15 @@ void mesh_path_tbl_expire(struct ieee802 if ((!(mpath->flags & MESH_PATH_RESOLVING)) && (!(mpath->flags & MESH_PATH_FIXED)) && time_after(jiffies, mpath->exp_time + MESH_PATH_EXPIRE)) - __mesh_path_del(tbl, mpath); + __mesh_path_del(tbl, mpath, is_mpath_tbl); } spin_unlock_bh(&tbl->walk_lock); } void mesh_path_expire(struct ieee80211_sub_if_data *sdata) { - mesh_path_tbl_expire(sdata, sdata->u.mesh.mesh_paths); - mesh_path_tbl_expire(sdata, sdata->u.mesh.mpp_paths); + mesh_path_tbl_expire(sdata, sdata->u.mesh.mesh_paths, true); + mesh_path_tbl_expire(sdata, sdata->u.mesh.mpp_paths, false); } void mesh_pathtbl_unregister(struct ieee80211_sub_if_data *sdata) --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -2862,6 +2862,7 @@ ieee80211_rx_h_mesh_fwding(struct ieee80 struct mesh_path *mppath; char *proxied_addr; char *mpp_addr; + bool update = false; if (is_multicast_ether_addr(hdr->addr1)) { mpp_addr = hdr->addr3; @@ -2881,10 +2882,15 @@ ieee80211_rx_h_mesh_fwding(struct ieee80 mpp_path_add(sdata, proxied_addr, mpp_addr); } else { spin_lock_bh(&mppath->state_lock); - if (!ether_addr_equal(mppath->mpp, mpp_addr)) + if (!ether_addr_equal(mppath->mpp, mpp_addr)) { memcpy(mppath->mpp, mpp_addr, ETH_ALEN); + update = true; + } mppath->exp_time = jiffies; spin_unlock_bh(&mppath->state_lock); + + if (update) + mesh_nss_offld_path_update(mppath, false, NULL); } rcu_read_unlock(); } --- a/drivers/net/wireless/ath/ath11k/debug.h +++ b/drivers/net/wireless/ath/ath11k/debug.h @@ -9,6 +9,7 @@ #include "trace.h" #include "debugfs.h" +extern struct dentry *debugfs_ath11k; enum ath11k_debug_mask { ATH11K_DBG_AHB = 0x00000001, ATH11K_DBG_WMI = 0x00000002, --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2547,6 +2547,9 @@ static struct sk_buff *ieee80211_build_h info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; #endif + info = IEEE80211_SKB_CB(skb); + memset(info, 0, sizeof(*info)); + /* convert Ethernet header to proper 802.11 header (based on * operation mode) */ ethertype = (skb->data[12] << 8) | skb->data[13]; @@ -2593,6 +2596,13 @@ static struct sk_buff *ieee80211_build_h break; #ifdef CPTCFG_MAC80211_MESH case NL80211_IFTYPE_MESH_POINT: + if (ieee80211_hw_check(&local->hw, SUPPORTS_NSS_OFFLOAD) && + (sdata->vif.driver_flags & IEEE80211_VIF_NSS_OFFLOAD_DEBUG_MODE) && + !(is_multicast_ether_addr(skb->data))) { + info->flags = IEEE80211_TX_CTL_HW_80211_ENCAP; + goto nss_mesh; + } + if (!is_multicast_ether_addr(skb->data)) { struct sta_info *next_hop; bool mpp_lookup = true; @@ -2868,10 +2878,10 @@ static struct sk_buff *ieee80211_build_h skb_reset_mac_header(skb); - info = IEEE80211_SKB_CB(skb); - memset(info, 0, sizeof(*info)); - - info->flags = info_flags; +#ifdef CPTCFG_MAC80211_MESH +nss_mesh: +#endif + info->flags |= info_flags; info->ack_frame_id = info_id; info->band = band; info->control.flags = ctrl_flags; @@ -3944,6 +3954,7 @@ void __ieee80211_subif_start_xmit(struct struct ieee80211_key *key = NULL; struct sta_info *sta; struct sk_buff *next; + struct ieee80211_tx_info *info; struct ieee80211_sub_if_data *ap_sdata; if (unlikely(skb->len < ETH_HLEN)) { @@ -4040,9 +4051,15 @@ void __ieee80211_subif_start_xmit(struct goto out; } - ieee80211_tx_stats(dev, skb->len); - - ieee80211_xmit(sdata, sta, skb); + info = IEEE80211_SKB_CB(skb); + if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) { + if (sta) + key = rcu_dereference(sta->ptk[sta->ptk_idx]); + ieee80211_8023_xmit(sdata, dev, sta, key, skb); + } else { + ieee80211_tx_stats(dev, skb->len); + ieee80211_xmit(sdata, sta, skb); + } } goto out; out_free: