blob: 2357ab1db4a7a45b7555f4b582fa305745a69fc8 [file] [log] [blame]
/*
* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* DOC: defines driver functions interfacing with linux kernel
*/
#include <qdf_list.h>
#include <qdf_status.h>
#include <linux/wireless.h>
#include <linux/netdevice.h>
#include <wlan_cfg80211.h>
#include <wlan_osif_priv.h>
#include <wlan_interop_issues_ap_ucfg_api.h>
#include <wlan_cfg80211_interop_issues_ap.h>
#include <osif_psoc_sync.h>
#include <qdf_mem.h>
#include <wlan_utility.h>
#include "wlan_hdd_main.h"
#include "cfg_ucfg_api.h"
const struct nla_policy
interop_issues_ap_policy[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_MAX + 1] = {
[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_TYPE] = {
.type = NLA_U32,
.len = sizeof(uint32_t) },
[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_LIST] = {
.type = NLA_U32,
.len = sizeof(uint32_t) },
[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_BSSID] = {
.type = NLA_UNSPEC,
.len = QDF_MAC_ADDR_SIZE },
};
/**
* wlan_cfg80211_send_interop_issues_ap_cb() - report information
* @data: interop issues ap mac received from fw
*
* Generate a wlan interop issues ap info package and send it to user
* space daemon through netlink.
*
* Return: none
*/
static void
wlan_cfg80211_send_interop_issues_ap_cb(
struct wlan_interop_issues_ap_event *data)
{
struct wlan_objmgr_pdev *pdev;
struct pdev_osif_priv *os_priv;
struct sk_buff *skb;
uint32_t index, len;
if (!data) {
osif_err("Invalid result.");
return;
}
pdev = data->pdev;
if (!pdev) {
osif_err("pdev is null.");
return;
}
os_priv = wlan_pdev_get_ospriv(pdev);
if (!os_priv) {
osif_err("os_priv is null.");
return;
}
index = QCA_NL80211_VENDOR_SUBCMD_INTEROP_ISSUES_AP_INDEX;
len = nla_total_size(QDF_MAC_ADDR_SIZE + NLMSG_HDRLEN);
skb = cfg80211_vendor_event_alloc(os_priv->wiphy, NULL, len, index,
GFP_KERNEL);
if (!skb) {
osif_err("skb alloc failed");
return;
}
osif_debug("interop issues ap mac:" QDF_MAC_ADDR_FMT,
QDF_MAC_ADDR_REF(data->rap_addr.bytes));
if (nla_put(skb,
QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_BSSID,
QDF_MAC_ADDR_SIZE, data->rap_addr.bytes)) {
osif_err("nla put fail");
kfree_skb(skb);
return;
}
cfg80211_vendor_event(skb, GFP_KERNEL);
}
static void wlan_interop_issues_ap_register_cbk(struct wlan_objmgr_pdev *pdev)
{
struct wlan_interop_issues_ap_callbacks cb;
cb.os_if_interop_issues_ap_event_handler =
wlan_cfg80211_send_interop_issues_ap_cb;
ucfg_register_interop_issues_ap_callback(pdev, &cb);
}
/**
* wlan_parse_interop_issues_ap() - parse the interop issues ap info
* @interop_issues_ap: the pointer of interop issues ap
* @attr: list of attributes
*
* Return: 0 on success; error number on failure
*/
static int
wlan_parse_interop_issues_ap(struct qdf_mac_addr *interop_issues_ap,
struct nlattr *attr)
{
struct nlattr *tb2[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_MAX + 1];
struct nlattr *curr_attr = NULL;
uint32_t rem;
qdf_size_t i = 0;
nla_for_each_nested(curr_attr, attr, rem) {
if (i == MAX_INTEROP_ISSUES_AP_NUM) {
osif_err("Ignoring excess");
break;
}
if (wlan_cfg80211_nla_parse(tb2,
QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_MAX,
nla_data(curr_attr),
nla_len(curr_attr),
interop_issues_ap_policy)) {
osif_err("nla_parse failed");
return -EINVAL;
}
if (!tb2[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_BSSID]) {
osif_err("attr addr failed");
return -EINVAL;
}
nla_memcpy(interop_issues_ap[i].bytes,
tb2[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_BSSID],
QDF_MAC_ADDR_SIZE);
osif_debug(QDF_MAC_ADDR_FMT,
QDF_MAC_ADDR_REF(interop_issues_ap[i].bytes));
i++;
}
return i;
}
/**
* __wlan_cfg80211_set_interop_issues_ap_config() - set config status
* @wiphy: WIPHY structure pointer
* @wdev: Wireless device structure pointer
* @data: Pointer to the data received
* @data_len: Length of the data received
*
* Return: 0 on success and errno on failure
*/
static int
__wlan_cfg80211_set_interop_issues_ap_config(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct net_device *dev = wdev->netdev;
struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_MAX + 1];
struct nlattr *attr;
uint32_t count = 0;
struct wlan_interop_issues_ap_info interop_issues_ap = {0};
struct wlan_objmgr_psoc *psoc;
if (!adapter->vdev) {
osif_err("Invalid vdev");
return -EINVAL;
}
psoc = wlan_vdev_get_psoc(adapter->vdev);
if (!psoc) {
osif_err("Invalid psoc");
return -EINVAL;
}
if (wlan_cfg80211_nla_parse(tb,
QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_MAX,
data, data_len,
interop_issues_ap_policy)) {
osif_err("Invalid ATTR");
return -EINVAL;
}
attr = tb[QCA_WLAN_VENDOR_ATTR_INTEROP_ISSUES_AP_LIST];
if (attr) {
count =
wlan_parse_interop_issues_ap(interop_issues_ap.rap_items,
attr);
if (count < 0)
return -EINVAL;
}
osif_debug("Num of interop issues ap: %d", count);
interop_issues_ap.count = count;
interop_issues_ap.detect_enable = true;
/*
* need to figure out a converged way of obtaining the vdev for
* a given netdev that doesn't involve the legacy mechanism.
*/
ucfg_set_interop_issues_ap_config(psoc, &interop_issues_ap);
return 0;
}
int wlan_cfg80211_set_interop_issues_ap_config(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct osif_psoc_sync *psoc_sync;
int ret;
ret = osif_psoc_sync_op_start(wiphy_dev(wiphy), &psoc_sync);
if (ret)
return ret;
ret = __wlan_cfg80211_set_interop_issues_ap_config(wiphy, wdev,
data, data_len);
osif_psoc_sync_op_stop(psoc_sync);
return ret;
}
void wlan_cfg80211_init_interop_issues_ap(struct wlan_objmgr_pdev *pdev)
{
/*
* the special mac is used to trigger uplayer sets
* interop issues ap list to fw when driver reloads but
* cnss-daemon does not restart.
*/
uint8_t fmac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
struct wlan_interop_issues_ap_info interop_issues_ap = {0};
struct wlan_interop_issues_ap_event data;
struct wlan_objmgr_psoc *psoc;
wlan_interop_issues_ap_register_cbk(pdev);
psoc = wlan_pdev_get_psoc(pdev);
if (!psoc) {
osif_err("Invalid psoc");
return;
}
interop_issues_ap.detect_enable = true;
ucfg_set_interop_issues_ap_config(psoc, &interop_issues_ap);
data.pdev = pdev;
qdf_mem_copy(data.rap_addr.bytes, fmac, QDF_MAC_ADDR_SIZE);
wlan_cfg80211_send_interop_issues_ap_cb(&data);
}