blob: b1795b2079f52dd6bcf30dde10872c2a29b08971 [file] [log] [blame]
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
*/
#include <net/netlink.h>
#include <net/mac80211.h>
#include "core.h"
#include "debug.h"
static const struct nla_policy
ath11k_vendor_set_wifi_config_policy[QCA_WLAN_VENDOR_ATTR_CONFIG_MAX + 1] = {
[QCA_WLAN_VENDOR_ATTR_CONFIG_GTX] = {.type = NLA_FLAG}
};
static const struct nla_policy
ath11k_cfg80211_afc_event_policy[QCA_WLAN_VENDOR_ATTR_AFC_EVENT_MAX] = {
[QCA_WLAN_VENDOR_ATTR_AFC_EVENT_TYPE] = { .type = NLA_U8 },
[QCA_WLAN_VENDOR_ATTR_AFC_EVENT_DATA] =
{ .type = NLA_BINARY,
.len = QCA_NL80211_AFC_REQ_RESP_BUF_MAX_SIZE },
};
static const struct nla_policy
ath11k_cfg80211_afc_response_policy[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_MAX] = {
[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_DATA_TYPE] = { .type = NLA_U8 },
[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_DATA] =
{ .type = NLA_BINARY,
.len = QCA_NL80211_AFC_REQ_RESP_BUF_MAX_SIZE },
};
static int ath11k_vendor_set_wifi_config(struct wiphy *wihpy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_vif *vif;
struct ath11k_vif *arvif;
struct ath11k *ar;
struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_CONFIG_MAX + 1];
int ret = 0;
if (!wdev)
return -EINVAL;
vif = wdev_to_ieee80211_vif(wdev);
if (!vif)
return -EINVAL;
arvif = (struct ath11k_vif*)vif->drv_priv;
if (!arvif)
return -EINVAL;
ar = arvif->ar;
mutex_lock(&ar->conf_mutex);
ret = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_CONFIG_MAX, data, data_len,
ath11k_vendor_set_wifi_config_policy, NULL);
if (ret) {
ath11k_warn(ar->ab, "invalid set wifi config policy attribute\n");
goto exit;
}
ar->ap_ps_enabled = nla_get_flag(tb[QCA_WLAN_VENDOR_ATTR_CONFIG_GTX]);
ret = ath11k_mac_ap_ps_recalc(ar);
if (ret) {
ath11k_warn(ar->ab, "failed to send ap ps ret %d\n", ret);
goto exit;
}
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static void ath11k_afc_resp_ntoh_conv(struct ath11k_base *ab, u32 *data, int data_len)
{
int iter = 0;
if (!data || !data_len) {
ath11k_warn(ab, "Invalid AFC response\n");
return;
}
for (iter = 0; iter < data_len; iter++) {
/* Skip endian conversion for description field
* in the AFC response as it is a char array.
*/
if (iter < QCA_WLAN_AFC_RESP_DESC_FIELD_START_OCTET ||
iter > QCA_WLAN_AFC_RESP_DESC_FIELD_END_OCTET) {
data[iter] = ntohl(data[iter]);
}
}
}
static int ath11k_vendor_receive_afc_response(struct wiphy *wihpy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_vif *vif;
struct ath11k_vif *arvif;
struct ath11k *ar;
struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_MAX + 1];
int afc_resp_len = 0, afc_resp_len_32 = 0;
u8 data_type = 0;
struct ath11k_afc_host_resp *afc_rsp = NULL;
int ret = 0;
if (!wdev)
return -EINVAL;
vif = wdev_to_ieee80211_vif(wdev);
if (!vif)
return -EINVAL;
arvif = (struct ath11k_vif *)vif->drv_priv;
if (!arvif)
return -EINVAL;
ar = arvif->ar;
ath11k_dbg(ar->ab, ATH11K_DBG_AFC, "Received AFC response event\n");
ret = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_MAX, data, data_len,
ath11k_cfg80211_afc_event_policy, NULL);
if (ret) {
ath11k_warn(ar->ab, "invalid set afc config policy attribute\n");
return ret;
}
if (!tb[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_DATA_TYPE])
return -EINVAL;
data_type = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_DATA_TYPE]);
if (!tb[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_DATA]) {
ath11k_warn(ar->ab, "AFC response data not found\n");
return -EINVAL;
}
afc_resp_len = nla_len(tb[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_DATA]);
if (!afc_resp_len) {
ath11k_warn(ar->ab, "AFC response data is not present!\n");
return -EINVAL;
}
afc_rsp = kzalloc(afc_resp_len, GFP_KERNEL);
if (!afc_rsp)
return -ENOMEM;
nla_memcpy((void *)afc_rsp, tb[QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_DATA],
afc_resp_len);
switch (data_type) {
case QCA_WLAN_VENDOR_ATTR_AFC_JSON_RESP:
/* No processing required in Driver for JSON data */
break;
case QCA_WLAN_VENDOR_ATTR_AFC_BIN_RESP:
/* The AFC response received from the user space application
* is expected to be packed in network byte order(Big endian).
* Since q6 is little endian, Host needs to convert the afc
* response to little endian format.
*
* Note: This conversion of data to little endian format is only
* required for Binary type data. For raw JSON data,
* no conversion is required since it is text string.
*
* Since all the members of the AFC response structure are defined
* to be 32-bit words, convert the length appropriately for
* conversion to little endian format.
*/
afc_resp_len_32 = (afc_resp_len / sizeof(uint32_t));
ath11k_afc_resp_ntoh_conv(ar->ab, (u32 *)afc_rsp, afc_resp_len_32);
ath11k_dbg_dump(ar->ab, ATH11K_DBG_AFC, NULL, "", afc_rsp, afc_resp_len);
break;
default:
ath11k_warn(ar->ab, "Invalid response format type %d\n", data_type);
ret = -EINVAL;
goto exit;
}
/* Copy the data buffer to AFC memory location */
ret = ath11k_copy_afc_response(ar, (char *)afc_rsp, afc_resp_len);
if (ret)
goto exit;
ath11k_dbg(ar->ab, ATH11K_DBG_AFC, "AFC response copied to AFC memory\n");
ret = ath11k_wmi_send_afc_resp_rx_ind(ar, data_type);
if (ret) {
ath11k_warn(ar->ab, "AFC Rx indication to FW failed: %d\n", ret);
goto exit;
}
ath11k_dbg(ar->ab, ATH11K_DBG_AFC, "AFC Resp RX indication sent to target\n");
exit:
kfree(afc_rsp);
return ret;
}
static struct wiphy_vendor_command ath11k_vendor_commands[] = {
{
.info.vendor_id = QCA_NL80211_VENDOR_ID,
.info.subcmd = QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION,
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = ath11k_vendor_set_wifi_config,
.policy = ath11k_vendor_set_wifi_config_policy,
.maxattr = QCA_WLAN_VENDOR_ATTR_CONFIG_MAX
},
{
.info.vendor_id = QCA_NL80211_VENDOR_ID,
.info.subcmd = QCA_NL80211_VENDOR_SUBCMD_AFC_RESPONSE,
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = ath11k_vendor_receive_afc_response,
.policy = ath11k_cfg80211_afc_response_policy,
.maxattr = QCA_WLAN_VENDOR_ATTR_AFC_RESPONSE_MAX
},
};
int ath11k_send_power_update_complete(struct ath11k *ar)
{
struct ath11k_base *ab = ar->ab;
struct ath11k_afc_req_fixed_params fixed_param = {0};
struct ath11k_afc_info *afc = &ar->afc;
struct sk_buff *nl_skb;
int ret = 0;
fixed_param.req_id = afc->request_id;
fixed_param.min_des_power = DEFAULT_MIN_POWER;
fixed_param.req_length = sizeof(struct ath11k_afc_req_fixed_params);
fixed_param.status_code = afc->afc_reg_info->fw_status_code;
nl_skb = cfg80211_vendor_event_alloc(ar->hw->wiphy, NULL,
nla_total_size(sizeof(u8) +
sizeof(struct ath11k_afc_req_fixed_params)),
QCA_NL80211_VENDOR_SUBCMD_AFC_EVENT_INDEX,
GFP_ATOMIC);
if (!nl_skb) {
ath11k_warn(ab, "failed to allocate skb for power update complete event\n");
return -ENOMEM;
}
ret = nla_put_u8(nl_skb, QCA_WLAN_VENDOR_ATTR_AFC_EVENT_TYPE,
QCA_WLAN_VENDOR_AFC_POWER_UPDATE_COMPLETE_EVENT);
if (ret) {
ath11k_warn(ar->ab, "failed to put afc event type for power cmd\n");
kfree_skb(nl_skb);
return -EFAULT;
}
ret = nla_put(nl_skb, QCA_WLAN_VENDOR_ATTR_AFC_EVENT_DATA,
sizeof(struct ath11k_afc_req_fixed_params), &fixed_param);
if (ret) {
ath11k_warn(ar->ab, "failed to put afc event data for power cmd\n");
kfree_skb(nl_skb);
return -EFAULT;
}
ath11k_dbg(ab, ATH11K_DBG_AFC,
"Sending power update complete for afc request id %llu status code %d\n",
fixed_param.req_id, fixed_param.status_code);
cfg80211_vendor_event(nl_skb, GFP_ATOMIC);
return 0;
}
int ath11k_send_afc_start(struct ath11k *ar, struct ath11k_afc_req_fixed_params *afc_data)
{
struct sk_buff *nl_skb;
int ret = 0;
nl_skb = cfg80211_vendor_event_alloc(ar->hw->wiphy, NULL,
nla_total_size(sizeof(u8) +
sizeof(struct ath11k_afc_req_fixed_params)),
QCA_NL80211_VENDOR_SUBCMD_AFC_EVENT_INDEX,
GFP_ATOMIC);
if (!nl_skb) {
ath11k_warn(ar->ab, "failed to allocate skb for afc expiry event\n");
goto out;
}
ret = nla_put_u8(nl_skb, QCA_WLAN_VENDOR_ATTR_AFC_EVENT_TYPE,
QCA_WLAN_VENDOR_AFC_EXPIRY_EVENT);
if (ret) {
ath11k_warn(ar->ab, "failed to put afc event type\n");
kfree_skb(nl_skb);
goto out;
}
ret = nla_put(nl_skb, QCA_WLAN_VENDOR_ATTR_AFC_EVENT_DATA,
sizeof(struct ath11k_afc_req_fixed_params), afc_data);
if (ret) {
ath11k_warn(ar->ab, "failed to put afc event data\n");
kfree_skb(nl_skb);
goto out;
}
cfg80211_vendor_event(nl_skb, GFP_ATOMIC);
ath11k_dbg(ar->ab, ATH11K_DBG_AFC,
"Sending expiry event to higher layer of type %d\n",
QCA_WLAN_VENDOR_AFC_EXPIRY_EVENT);
out:
return ret;
}
static const struct nl80211_vendor_cmd_info ath11k_vendor_events[] = {
[QCA_NL80211_VENDOR_SUBCMD_AFC_EVENT_INDEX] = {
.vendor_id = QCA_NL80211_VENDOR_ID,
.subcmd = QCA_NL80211_VENDOR_SUBCMD_AFC_EVENT
},
};
int ath11k_vendor_register(struct ath11k *ar)
{
ar->hw->wiphy->vendor_commands = ath11k_vendor_commands;
ar->hw->wiphy->n_vendor_commands = ARRAY_SIZE(ath11k_vendor_commands);
ar->hw->wiphy->vendor_events = ath11k_vendor_events;
ar->hw->wiphy->n_vendor_events = ARRAY_SIZE(ath11k_vendor_events);
return 0;
}