| // 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; |
| } |