M7350/external/compat-wireless/drivers/net/wireless/ath/ath6kl-3.5/p2p.c

2675 lines
68 KiB
C
Raw Permalink Normal View History

2024-09-09 08:57:42 +00:00
/*
* Copyright (c) 2004-2012 Atheros Communications Inc.
*
* 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.
*/
#include "core.h"
#include "htc-ops.h"
#include "debug.h"
#include "hif-ops.h"
struct p2p_ps_info *ath6kl_p2p_ps_init(struct ath6kl_vif *vif)
{
struct p2p_ps_info *p2p_ps;
p2p_ps = kzalloc(sizeof(struct p2p_ps_info), GFP_KERNEL);
if (!p2p_ps) {
ath6kl_err("failed to alloc memory for p2p_ps\n");
return NULL;
}
p2p_ps->vif = vif;
spin_lock_init(&p2p_ps->p2p_ps_lock);
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps init %p type %d\n",
vif,
vif->wdev.iftype);
return p2p_ps;
}
void ath6kl_p2p_ps_deinit(struct ath6kl_vif *vif)
{
struct p2p_ps_info *p2p_ps = vif->p2p_ps_info_ctx;
if (p2p_ps) {
spin_lock(&p2p_ps->p2p_ps_lock);
if (p2p_ps->go_last_beacon_app_ie != NULL)
kfree(p2p_ps->go_last_beacon_app_ie);
if (p2p_ps->go_last_noa_ie != NULL)
kfree(p2p_ps->go_last_noa_ie);
if (p2p_ps->go_working_buffer != NULL)
kfree(p2p_ps->go_working_buffer);
spin_unlock(&p2p_ps->p2p_ps_lock);
kfree(p2p_ps);
}
vif->p2p_ps_info_ctx = NULL;
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps deinit %p\n",
vif);
return;
}
int ath6kl_p2p_ps_reset_noa(struct p2p_ps_info *p2p_ps)
{
if (!p2p_ps)
return -1;
/*
* This could happend if NOA_INFO event is later than
* GO's DISCONNECT event.
*/
if (p2p_ps->vif->wdev.iftype != NL80211_IFTYPE_P2P_GO) {
/*
* For P2P-Compat mode, need to clear target's
* NOA if the target not to reset it after
* P2P-GO teardown.
*/
if (!p2p_ps->vif->ar->p2p_compat) {
ath6kl_dbg(ATH6KL_DBG_INFO,
"failed to reset P2P-GO noa, %p\n", p2p_ps);
return -1;
}
}
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps reset NoA %p index %d\n",
p2p_ps->vif,
p2p_ps->go_noa.index);
spin_lock(&p2p_ps->p2p_ps_lock);
p2p_ps->go_flags &= ~ATH6KL_P2P_PS_FLAGS_NOA_ENABLED;
p2p_ps->go_noa.index++;
p2p_ps->go_noa_enable_idx = 0;
memset(p2p_ps->go_noa.noas,
0,
sizeof(struct ieee80211_p2p_noa_descriptor) *
ATH6KL_P2P_PS_MAX_NOA_DESCRIPTORS);
spin_unlock(&p2p_ps->p2p_ps_lock);
return 0;
}
int ath6kl_p2p_ps_setup_noa(struct p2p_ps_info *p2p_ps,
int noa_id,
u8 count_type,
u32 interval,
u32 start_offset,
u32 duration)
{
struct ieee80211_p2p_noa_descriptor *noa_descriptor;
if ((!p2p_ps) ||
(p2p_ps->vif->wdev.iftype != NL80211_IFTYPE_P2P_GO)) {
ath6kl_err("failed to setup P2P-GO noa\n");
return -1;
}
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps setup NoA %p idx %d ct %d intval %x so %x dur %x\n",
p2p_ps->vif,
noa_id,
count_type,
interval,
start_offset,
duration);
spin_lock(&p2p_ps->p2p_ps_lock);
if (noa_id < ATH6KL_P2P_PS_MAX_NOA_DESCRIPTORS) {
noa_descriptor = &p2p_ps->go_noa.noas[noa_id];
noa_descriptor->count_or_type = count_type;
noa_descriptor->interval = interval;
noa_descriptor->start_or_offset = start_offset;
noa_descriptor->duration = duration;
} else {
spin_unlock(&p2p_ps->p2p_ps_lock);
ath6kl_err("wrong NoA index %d\n", noa_id);
return -2;
}
p2p_ps->go_noa_enable_idx |= (1 << noa_id);
p2p_ps->go_flags |= ATH6KL_P2P_PS_FLAGS_NOA_ENABLED;
spin_unlock(&p2p_ps->p2p_ps_lock);
return 0;
}
int ath6kl_p2p_ps_reset_opps(struct p2p_ps_info *p2p_ps)
{
if ((!p2p_ps) ||
(p2p_ps->vif->wdev.iftype != NL80211_IFTYPE_P2P_GO)) {
ath6kl_err("failed to reset P2P-GO OppPS\n");
return -1;
}
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps reset OppPS %p index %d\n",
p2p_ps->vif,
p2p_ps->go_noa.index);
spin_lock(&p2p_ps->p2p_ps_lock);
p2p_ps->go_flags &= ~ATH6KL_P2P_PS_FLAGS_OPPPS_ENABLED;
p2p_ps->go_noa.index++;
p2p_ps->go_noa.ctwindow_opps_param = 0;
spin_unlock(&p2p_ps->p2p_ps_lock);
return 0;
}
int ath6kl_p2p_ps_setup_opps(struct p2p_ps_info *p2p_ps,
u8 enabled,
u8 ctwindows)
{
if ((!p2p_ps) ||
(p2p_ps->vif->wdev.iftype != NL80211_IFTYPE_P2P_GO)) {
ath6kl_err("failed to setup P2P-GO noa\n");
return -1;
}
WARN_ON(enabled && (!(ctwindows & 0x7f)));
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps setup OppPS %p enabled %d ctwin %d\n",
p2p_ps->vif,
enabled,
ctwindows);
spin_lock(&p2p_ps->p2p_ps_lock);
if (enabled)
p2p_ps->go_noa.ctwindow_opps_param = (0x80 |
(ctwindows & 0x7f));
else
p2p_ps->go_noa.ctwindow_opps_param = 0;
p2p_ps->go_flags |= ATH6KL_P2P_PS_FLAGS_OPPPS_ENABLED;
spin_unlock(&p2p_ps->p2p_ps_lock);
return 0;
}
int ath6kl_p2p_ps_update_notif(struct p2p_ps_info *p2p_ps)
{
struct ath6kl_vif *vif;
struct ieee80211_p2p_noa_ie *noa_ie;
struct ieee80211_p2p_noa_descriptor *noa_descriptor;
int i, idx, len, ret = 0;
u8 *buf;
WARN_ON(!p2p_ps);
vif = p2p_ps->vif;
p2p_ps->go_noa_notif_cnt++;
spin_lock(&p2p_ps->p2p_ps_lock);
if ((p2p_ps->go_flags & ATH6KL_P2P_PS_FLAGS_NOA_ENABLED) ||
(p2p_ps->go_flags & ATH6KL_P2P_PS_FLAGS_OPPPS_ENABLED)) {
WARN_ON((p2p_ps->go_flags & ATH6KL_P2P_PS_FLAGS_NOA_ENABLED) &&
(!p2p_ps->go_noa_enable_idx));
len = p2p_ps->go_last_beacon_app_ie_len +
sizeof(struct ieee80211_p2p_noa_ie);
buf = kmalloc(len, GFP_ATOMIC);
if (buf == NULL) {
spin_unlock(&p2p_ps->p2p_ps_lock);
return -ENOMEM;
}
/* Append NoA IE after user's IEs. */
memcpy(buf,
p2p_ps->go_last_beacon_app_ie,
p2p_ps->go_last_beacon_app_ie_len);
noa_ie = (struct ieee80211_p2p_noa_ie *)
(buf + p2p_ps->go_last_beacon_app_ie_len);
noa_ie->element_id = WLAN_EID_VENDOR_SPECIFIC;
noa_ie->oui = cpu_to_be32((WLAN_OUI_WFA << 8) |
(WLAN_OUI_TYPE_WFA_P2P));
noa_ie->attr = IEEE80211_P2P_ATTR_NOTICE_OF_ABSENCE;
noa_ie->noa_info.index = p2p_ps->go_noa.index;
noa_ie->noa_info.ctwindow_opps_param =
p2p_ps->go_noa.ctwindow_opps_param;
idx = 0;
for (i = 0; i < ATH6KL_P2P_PS_MAX_NOA_DESCRIPTORS; i++) {
if (p2p_ps->go_noa_enable_idx & (1 << i)) {
noa_descriptor =
&noa_ie->noa_info.noas[idx++];
noa_descriptor->count_or_type =
p2p_ps->go_noa.noas[i].count_or_type;
noa_descriptor->duration =
cpu_to_le32(
p2p_ps->go_noa.noas[i].duration);
noa_descriptor->interval =
cpu_to_le32(
p2p_ps->go_noa.noas[i].interval);
noa_descriptor->start_or_offset =
cpu_to_le32(
p2p_ps->go_noa.noas[i].start_or_offset);
}
}
/* Update length */
noa_ie->attr_len = cpu_to_le16(2 +
(sizeof(struct ieee80211_p2p_noa_descriptor) * idx));
noa_ie->len = noa_ie->attr_len + 4 + 1 + 2; /* OUI, attr, len */
len = p2p_ps->go_last_beacon_app_ie_len + (noa_ie->len + 2);
/* Backup NoA IE for origional code path if need. */
p2p_ps->go_last_noa_ie_len = 0;
if (p2p_ps->go_last_noa_ie != NULL)
kfree(p2p_ps->go_last_noa_ie);
p2p_ps->go_last_noa_ie = kmalloc(noa_ie->len + 2, GFP_ATOMIC);
if (p2p_ps->go_last_noa_ie) {
p2p_ps->go_last_noa_ie_len = noa_ie->len + 2;
memcpy(p2p_ps->go_last_noa_ie,
noa_ie,
p2p_ps->go_last_noa_ie_len);
}
spin_unlock(&p2p_ps->p2p_ps_lock);
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps update app IE %p f %x idx %d ie %d len %d\n",
vif,
p2p_ps->go_flags,
idx,
noa_ie->len,
len);
} else {
/*
* Ignore if FW disable NoA/OppPS but actually no NoA/OppPS
* currently.
*/
if (p2p_ps->go_last_beacon_app_ie_len == 0) {
spin_unlock(&p2p_ps->p2p_ps_lock);
return ret;
}
/* Remove NoA IE. */
p2p_ps->go_last_noa_ie_len = 0;
if (p2p_ps->go_last_noa_ie != NULL) {
kfree(p2p_ps->go_last_noa_ie);
p2p_ps->go_last_noa_ie = NULL;
}
buf = kmalloc(p2p_ps->go_last_beacon_app_ie_len, GFP_ATOMIC);
if (buf == NULL) {
spin_unlock(&p2p_ps->p2p_ps_lock);
return -ENOMEM;
}
/* Back to origional Beacon IEs. */
len = p2p_ps->go_last_beacon_app_ie_len;
memcpy(buf,
p2p_ps->go_last_beacon_app_ie,
len);
spin_unlock(&p2p_ps->p2p_ps_lock);
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps update app IE %p f %x beacon_ie %p len %d\n",
vif,
p2p_ps->go_flags,
p2p_ps->go_last_beacon_app_ie,
p2p_ps->go_last_beacon_app_ie_len);
}
/*
* Only need to update Beacon's IE. The ProbeResp'q IE is settled
* while sending.
*/
ret = ath6kl_wmi_set_appie_cmd(vif->ar->wmi,
vif->fw_vif_idx,
WMI_FRAME_BEACON,
buf,
len);
kfree(buf);
return ret;
}
void ath6kl_p2p_ps_user_app_ie(struct p2p_ps_info *p2p_ps,
u8 mgmt_frm_type,
u8 **ie,
int *len)
{
if ((!p2p_ps) || (p2p_ps->vif->wdev.iftype != NL80211_IFTYPE_P2P_GO))
return;
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps hook app IE %p f %x mgmt_frm_type %d len %d\n",
p2p_ps->vif,
p2p_ps->go_flags,
mgmt_frm_type,
*len);
if (mgmt_frm_type == WMI_FRAME_BEACON) {
WARN_ON((*len) == 0);
spin_lock(&p2p_ps->p2p_ps_lock);
p2p_ps->go_last_beacon_app_ie_len = 0;
if (p2p_ps->go_last_beacon_app_ie != NULL)
kfree(p2p_ps->go_last_beacon_app_ie);
p2p_ps->go_last_beacon_app_ie = kmalloc(*len, GFP_ATOMIC);
if (p2p_ps->go_last_beacon_app_ie == NULL) {
spin_unlock(&p2p_ps->p2p_ps_lock);
return;
}
/* Update to the latest one. */
p2p_ps->go_last_beacon_app_ie_len = *len;
memcpy(p2p_ps->go_last_beacon_app_ie, *ie, *len);
spin_unlock(&p2p_ps->p2p_ps_lock);
} else if (mgmt_frm_type == WMI_FRAME_PROBE_RESP) {
/* Assume non-zero means P2P node. */
if ((*len) == 0)
return;
}
/* Hack : Change ie/len to let caller use the new one. */
spin_lock(&p2p_ps->p2p_ps_lock);
if ((p2p_ps->go_flags & ATH6KL_P2P_PS_FLAGS_NOA_ENABLED) ||
(p2p_ps->go_flags & ATH6KL_P2P_PS_FLAGS_OPPPS_ENABLED)) {
/*
* Append the last NoA IE to *ie and
* also update *len to let caller
* use the new one.
*/
WARN_ON(!p2p_ps->go_last_noa_ie);
if (p2p_ps->go_working_buffer != NULL)
kfree(p2p_ps->go_working_buffer);
p2p_ps->go_working_buffer =
kmalloc((p2p_ps->go_last_noa_ie_len + *len),
GFP_ATOMIC);
if (p2p_ps->go_working_buffer) {
if (*len)
memcpy(p2p_ps->go_working_buffer, *ie, *len);
memcpy(p2p_ps->go_working_buffer + (*len),
p2p_ps->go_last_noa_ie,
p2p_ps->go_last_noa_ie_len);
if (mgmt_frm_type == WMI_FRAME_PROBE_RESP) {
/* caller will release it. */
kfree(*ie);
*ie = p2p_ps->go_working_buffer;
p2p_ps->go_working_buffer = NULL;
} else
*ie = p2p_ps->go_working_buffer;
*len += p2p_ps->go_last_noa_ie_len;
}
ath6kl_dbg(ATH6KL_DBG_POWERSAVE,
"p2p_ps change app IE len -> %d\n",
*len);
}
spin_unlock(&p2p_ps->p2p_ps_lock);
return;
}
int ath6kl_p2p_utils_trans_porttype(enum nl80211_iftype type,
u8 *opmode,
u8 *subopmode)
{
int ret = 0;
/*
* nl80211.h support NL80211_IFTYPE_P2P_DEVICE from kernel 3.7,
* but not yet see any applications to use it now.
* To avoid conflict with old nl80211.h and change namming
* with postfix _QCA.
*/
if (type == NL80211_IFTYPE_P2P_DEVICE_QCA) {
*opmode = HI_OPTION_FW_MODE_BSS_STA;
*subopmode = HI_OPTION_FW_SUBMODE_P2PDEV;
} else {
switch (type) {
case NL80211_IFTYPE_AP:
*opmode = HI_OPTION_FW_MODE_AP;
*subopmode = HI_OPTION_FW_SUBMODE_NONE;
break;
case NL80211_IFTYPE_STATION:
*opmode = HI_OPTION_FW_MODE_BSS_STA;
*subopmode = HI_OPTION_FW_SUBMODE_NONE;
break;
case NL80211_IFTYPE_P2P_CLIENT:
*opmode = HI_OPTION_FW_MODE_BSS_STA;
*subopmode = HI_OPTION_FW_SUBMODE_P2PCLIENT;
break;
case NL80211_IFTYPE_P2P_GO:
*opmode = HI_OPTION_FW_MODE_BSS_STA;
*subopmode = HI_OPTION_FW_SUBMODE_P2PGO;
break;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_MESH_POINT:
default:
ath6kl_err("error interface type %d\n", type);
ret = -1;
break;
}
}
return ret;
}
static inline void _revert_ht_cap(struct ath6kl_vif *vif)
{
struct ath6kl *ar = vif->ar;
#ifdef CONFIG_ATH6KL_DEBUG
struct ht_cap_param *htCapParam;
htCapParam = &ar->debug.ht_cap_param[IEEE80211_BAND_5GHZ];
if (htCapParam->isConfig)
ath6kl_wmi_set_ht_cap_cmd(ar->wmi,
vif->fw_vif_idx,
htCapParam->band,
htCapParam->chan_width_40M_supported,
htCapParam->short_GI,
htCapParam->intolerance_40MHz);
else
#endif
ath6kl_wmi_set_ht_cap_cmd(ar->wmi,
vif->fw_vif_idx,
A_BAND_5GHZ,
ATH6KL_5GHZ_HT40_DEF_WIDTH,
ATH6KL_5GHZ_HT40_DEF_SGI,
ATH6KL_5GHZ_HT40_DEF_INTOLR40);
return;
}
int ath6kl_p2p_utils_init_port(struct ath6kl_vif *vif,
enum nl80211_iftype type)
{
struct ath6kl *ar = vif->ar;
u8 fw_vif_idx = vif->fw_vif_idx;
u8 opmode, subopmode;
long left;
u8 skip_vif = 1;
if (ar->p2p_compat)
return 0;
#ifdef USB_AUTO_SUSPEND
if (ar->autopm_turn_on) {
ar->autopm_defer_delay_change_cnt =
USB_SUSPEND_DEFER_DELAY_FOR_P2P;
ath6kl_hif_auto_pm_set_delay(ar, USB_SUSPEND_DELAY_MAX);
}
#endif
/*
* Only need to do this if virtual interface used but bypass
* vif_max=2 case.
* This case suppose be used only for special P2P purpose that is
* without dedicated P2P-Device.
*
* 1st interface always be created by driver init phase and WMI
* interface not yet ready. Actually, we don't need to reset it
* because current design
* always STA interface in firmware and host sides.
*/
if (ar->p2p_dedicate)
skip_vif = 2;
if ((ar->vif_max > skip_vif) && fw_vif_idx) {
if (ar->p2p_dedicate && (fw_vif_idx == (ar->vif_max - 1)))
type = NL80211_IFTYPE_P2P_DEVICE_QCA;
if (ath6kl_p2p_utils_trans_porttype(type,
&opmode,
&subopmode) == 0) {
/* Delete it first. */
if (type != NL80211_IFTYPE_P2P_DEVICE_QCA) {
set_bit(PORT_STATUS_PEND, &vif->flags);
if (ath6kl_wmi_del_port_cmd(ar->wmi,
fw_vif_idx,
fw_vif_idx))
return -EIO;
left = wait_event_interruptible_timeout(
ar->event_wq,
!test_bit(PORT_STATUS_PEND,
&vif->flags),
WMI_TIMEOUT/2);
WARN_ON(left <= 0);
}
/* Only support exectly id-to-id mapping. */
set_bit(PORT_STATUS_PEND, &vif->flags);
if (ath6kl_wmi_add_port_cmd(ar->wmi,
vif,
opmode,
subopmode))
return -EIO;
left = wait_event_interruptible_timeout(
ar->event_wq,
!test_bit(PORT_STATUS_PEND,
&vif->flags),
WMI_TIMEOUT);
WARN_ON(left <= 0);
if (type == NL80211_IFTYPE_P2P_CLIENT) {
/*
* P2P-GO will dissolve P2P-Group immediately
* when P2P-Client disconnect in Android.
* A larger bmiss time to avoid this in noisy
* environment.
*/
ath6kl_wmi_set_bmiss_time(ar->wmi,
vif->fw_vif_idx,
ATH6KL_P2P_BMISS_TIME);
/* p2p client shall not do normal roam */
if (vif->sc_params.scan_ctrl_flags &
ROAM_SCAN_CTRL_FLAGS)
ath6kl_wmi_set_roam_ctrl_cmd(
ar->wmi, vif->fw_vif_idx,
0xFFFF, 0, 0, 100);
}
/* WAR: Revert HT CAP, only for AP/P2P-GO cases. */
if ((type == NL80211_IFTYPE_AP) ||
(type == NL80211_IFTYPE_P2P_GO))
_revert_ht_cap(vif);
} else
return -ENOTSUPP;
}
#define ATH6KL_STA_BMISS_TIME (50)
/* For fw_vif_idx == 0 case. */
if ((fw_vif_idx == 0) &&
(vif->nw_type == INFRA_NETWORK)) {
ath6kl_info("Longer BMISS time for fixed device (%d).",
ATH6KL_STA_BMISS_TIME);
ath6kl_wmi_set_bmiss_time(ar->wmi,
vif->fw_vif_idx,
ATH6KL_STA_BMISS_TIME);
}
#undef ATH6KL_STA_BMISS_TIME
return 0;
}
int ath6kl_p2p_utils_check_port(struct ath6kl_vif *vif,
u8 port_id)
{
if (test_bit(PORT_STATUS_PEND, &vif->flags)) {
WARN_ON(vif->fw_vif_idx != port_id);
clear_bit(PORT_STATUS_PEND, &vif->flags);
wake_up(&vif->ar->event_wq);
}
return 0;
}
struct ath6kl_p2p_flowctrl *ath6kl_p2p_flowctrl_conn_list_init(
struct ath6kl *ar)
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl;
struct ath6kl_fw_conn_list *fw_conn;
int i;
p2p_flowctrl = kzalloc(sizeof(struct ath6kl_p2p_flowctrl), GFP_KERNEL);
if (!p2p_flowctrl) {
ath6kl_err("failed to alloc memory for p2p_flowctrl\n");
return NULL;
}
p2p_flowctrl->ar = ar;
spin_lock_init(&p2p_flowctrl->p2p_flowctrl_lock);
p2p_flowctrl->sche_type = P2P_FLOWCTRL_SCHE_TYPE_CONNECTION;
for (i = 0; i < NUM_CONN; i++) {
fw_conn = &p2p_flowctrl->fw_conn_list[i];
INIT_LIST_HEAD(&fw_conn->conn_queue);
INIT_LIST_HEAD(&fw_conn->re_queue);
fw_conn->connect_status = 0;
fw_conn->previous_can_send = true;
fw_conn->connId = ATH6KL_P2P_FLOWCTRL_NULL_CONNID;
fw_conn->parent_connId = ATH6KL_P2P_FLOWCTRL_NULL_CONNID;
memset(fw_conn->mac_addr, 0, ETH_ALEN);
fw_conn->sche_tx = 0;
fw_conn->sche_re_tx = 0;
fw_conn->sche_re_tx_aging = 0;
fw_conn->sche_tx_queued = 0;
}
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl init %p NUM_CONN %d type %d\n",
ar,
NUM_CONN,
p2p_flowctrl->sche_type);
return p2p_flowctrl;
}
void ath6kl_p2p_flowctrl_conn_list_deinit(struct ath6kl *ar)
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
int i;
if (p2p_flowctrl) {
/* check memory leakage */
for (i = 0; i < NUM_CONN; i++) {
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
fw_conn = &p2p_flowctrl->fw_conn_list[i];
if (fw_conn->sche_tx_queued != 0) {
ath6kl_err("memory leakage? %d,%d,%d,%d,%d\n",
i,
fw_conn->sche_tx,
fw_conn->sche_re_tx,
fw_conn->sche_re_tx_aging,
fw_conn->sche_tx_queued);
WARN_ON(1);
}
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
}
kfree(p2p_flowctrl);
}
ar->p2p_flowctrl_ctx = NULL;
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl deinit %p\n",
ar);
return;
}
/* before calling this, p2p_flowctrl_lock shall be acquired,
* pcontainer, preclaim shall be init by caller.
*/
void ath6kl_p2p_flowctrl_conn_collect_by_conn(
struct ath6kl_fw_conn_list *fw_conn,
struct list_head *pcontainer,
int *preclaim)
{
struct htc_packet *packet, *tmp_pkt;
if (!list_empty(&fw_conn->re_queue)) {
list_for_each_entry_safe(packet,
tmp_pkt,
&fw_conn->re_queue,
list) {
list_del(&packet->list);
packet->status = 0;
list_add_tail(&packet->list, pcontainer);
fw_conn->sche_tx_queued--;
*preclaim += 1;
}
}
if (!list_empty(&fw_conn->conn_queue)) {
list_for_each_entry_safe(packet,
tmp_pkt,
&fw_conn->conn_queue,
list) {
list_del(&packet->list);
packet->status = 0;
list_add_tail(&packet->list, pcontainer);
fw_conn->sche_tx_queued--;
*preclaim += 1;
}
}
}
void ath6kl_p2p_flowctrl_conn_list_cleanup(struct ath6kl *ar)
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
struct list_head container;
int i, reclaim = 0;
WARN_ON(!p2p_flowctrl);
INIT_LIST_HEAD(&container);
for (i = 0; i < NUM_CONN; i++) {
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
fw_conn = &p2p_flowctrl->fw_conn_list[i];
ath6kl_p2p_flowctrl_conn_collect_by_conn(fw_conn,
&container,
&reclaim);
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
}
ath6kl_tx_complete(ar->htc_target, &container);
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl cleanup %p reclaim %d\n",
ar,
reclaim);
return;
}
static u8 _find_parent_conn_id(struct ath6kl_p2p_flowctrl *p2p_flowctrl,
struct ath6kl_vif *hint_vif)
{
struct ath6kl_fw_conn_list *fw_conn;
u8 connId = ATH6KL_P2P_FLOWCTRL_NULL_CONNID;
int i;
/* Need protected in p2p_flowctrl_lock by caller. */
fw_conn = &p2p_flowctrl->fw_conn_list[0];
for (i = 0; i < NUM_CONN; i++, fw_conn++) {
if (fw_conn->connId == ATH6KL_P2P_FLOWCTRL_NULL_CONNID)
continue;
if ((fw_conn->vif == hint_vif) &&
(fw_conn->parent_connId == fw_conn->connId)) {
connId = fw_conn->connId;
break;
}
}
return connId;
}
void ath6kl_p2p_flowctrl_conn_list_cleanup_by_if(
struct ath6kl_vif *vif)
{
struct ath6kl *ar = vif->ar;
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
struct list_head container;
int i, reclaim = 0;
u8 vif_conn_id = ATH6KL_P2P_FLOWCTRL_NULL_CONNID;
if (!p2p_flowctrl)
return;
INIT_LIST_HEAD(&container);
vif_conn_id = _find_parent_conn_id(p2p_flowctrl, vif);
if (vif_conn_id == ATH6KL_P2P_FLOWCTRL_NULL_CONNID)
return;
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
for (i = 0; i < NUM_CONN; i++) {
fw_conn = &p2p_flowctrl->fw_conn_list[i];
if (fw_conn->parent_connId != vif_conn_id)
continue;
ath6kl_p2p_flowctrl_conn_collect_by_conn(fw_conn,
&container,
&reclaim);
}
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
ath6kl_tx_complete(ar->htc_target, &container);
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrlif cleanup %p reclaim %d\n",
ar,
reclaim);
return;
}
static inline bool _check_can_send(struct ath6kl *ar,
struct ath6kl_fw_conn_list *fw_conn)
{
bool can_send = false;
do {
if ((fw_conn->ocs) && !test_bit(SKIP_FLOWCTRL_EVENT, &ar->flag))
break;
can_send = true;
} while (false);
return can_send;
}
void ath6kl_p2p_flowctrl_netif_transition(
struct ath6kl *ar, u8 new_state)
{
struct ath6kl_vif *vif;
spin_lock_bh(&ar->list_lock);
list_for_each_entry(vif, &ar->vif_list, list) {
spin_unlock_bh(&ar->list_lock);
if (new_state == ATH6KL_P2P_FLOWCTRL_NETIF_STOP &&
test_bit(CONNECTED, &vif->flags))
netif_stop_queue(vif->ndev);
else if (new_state == ATH6KL_P2P_FLOWCTRL_NETIF_WAKE &&
(test_bit(CONNECTED, &vif->flags) ||
test_bit(TESTMODE_EPPING, &ar->flag)))
netif_wake_queue(vif->ndev);
spin_lock_bh(&ar->list_lock);
}
spin_unlock_bh(&ar->list_lock);
return;
}
void ath6kl_p2p_flowctrl_tx_schedule(struct ath6kl *ar)
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
struct htc_packet *packet, *tmp_pkt;
int i;
WARN_ON(!p2p_flowctrl);
for (i = 0; i < NUM_CONN; i++) {
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
fw_conn = &p2p_flowctrl->fw_conn_list[i];
/* Bypass this fw_conn if it not yet used. */
if (fw_conn->connId == ATH6KL_P2P_FLOWCTRL_NULL_CONNID) {
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
continue;
}
if (_check_can_send(ar, fw_conn)) {
if (!list_empty(&fw_conn->re_queue)) {
list_for_each_entry_safe(packet,
tmp_pkt,
&fw_conn->re_queue,
list) {
if (packet == NULL)
continue;
list_del(&packet->list);
if (packet->endpoint >= ENDPOINT_MAX)
continue;
fw_conn->sche_re_tx--;
fw_conn->sche_tx_queued--;
ath6kl_htc_tx(ar->htc_target, packet);
}
}
if (!list_empty(&fw_conn->conn_queue)) {
list_for_each_entry_safe(packet,
tmp_pkt,
&fw_conn->conn_queue,
list) {
if (packet == NULL)
continue;
list_del(&packet->list);
if (packet->endpoint >= ENDPOINT_MAX)
continue;
fw_conn->sche_tx++;
fw_conn->sche_tx_queued--;
ath6kl_htc_tx(ar->htc_target, packet);
}
}
}
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl schedule %p conId %d tx %d re_tx %d\n",
ar,
i,
fw_conn->sche_tx,
fw_conn->sche_re_tx);
}
return;
}
int ath6kl_p2p_flowctrl_tx_schedule_pkt(struct ath6kl *ar,
void *pkt)
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
struct ath6kl_cookie *cookie = (struct ath6kl_cookie *)pkt;
int connId = cookie->htc_pkt->connid;
int ret = 0;
WARN_ON(!p2p_flowctrl);
if (connId == ATH6KL_P2P_FLOWCTRL_NULL_CONNID) {
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl fail, NULL connId, just send??\n");
return 1; /* Just send it */
/*return -1;*/ /* Drop it */
}
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
fw_conn = &p2p_flowctrl->fw_conn_list[connId];
if (!_check_can_send(ar, fw_conn)) {
if (cookie->htc_pkt->info.tx.tag !=
ATH6KL_PRI_DATA_PKT_TAG) {
list_add_tail(&cookie->htc_pkt->list,
&fw_conn->conn_queue);
} else {
list_add(&cookie->htc_pkt->list,
&fw_conn->re_queue);
fw_conn->sche_re_tx++;
}
fw_conn->sche_tx_queued++;
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
goto result;
} else if (!list_empty(&fw_conn->conn_queue) ||
!list_empty(&fw_conn->re_queue)) {
if (cookie->htc_pkt->info.tx.tag !=
ATH6KL_PRI_DATA_PKT_TAG) {
list_add_tail(&cookie->htc_pkt->list,
&fw_conn->conn_queue);
} else {
list_add(&cookie->htc_pkt->list, &fw_conn->re_queue);
fw_conn->sche_re_tx++;
}
fw_conn->sche_tx_queued++;
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
ath6kl_p2p_flowctrl_tx_schedule(ar);
goto result;
} else
ret = 1;
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
result:
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl schedule pkt %p %s\n",
ar,
((ret == 0) ? "queue" : "send"));
return ret;
}
void ath6kl_p2p_flowctrl_state_change(struct ath6kl *ar)
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
struct htc_packet *packet, *tmp_pkt;
struct htc_endpoint *endpoint;
struct list_head *tx_queue, container;
int i, eid;
bool flowctrl_allowed;
WARN_ON(!p2p_flowctrl);
INIT_LIST_HEAD(&container);
for (i = 0; i < NUM_CONN; i++) {
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
fw_conn = &p2p_flowctrl->fw_conn_list[i];
flowctrl_allowed = _check_can_send(ar, fw_conn);
if (!flowctrl_allowed && fw_conn->previous_can_send) {
spin_lock_bh(&ar->htc_target->tx_lock);
for (eid = ENDPOINT_5; eid >= ENDPOINT_2; eid--) {
endpoint = &ar->htc_target->endpoint[eid];
tx_queue = &endpoint->txq;
if (list_empty(tx_queue))
continue;
list_for_each_entry_safe(packet,
tmp_pkt,
tx_queue,
list) {
if (packet->connid != i)
continue;
list_del(&packet->list);
if (packet->recycle_count >
ATH6KL_P2P_FLOWCTRL_RECYCLE_LIMIT) {
ath6kl_dbg(ATH6KL_DBG_INFO,
"recycle cnt exceed\n");
packet->status = 0;
list_add_tail(
&packet->list,
&container);
fw_conn->sche_re_tx_aging++;
} else {
packet->recycle_count++;
list_add_tail(
&packet->list,
&fw_conn->re_queue);
fw_conn->sche_re_tx++;
fw_conn->sche_tx_queued++;
}
}
}
spin_unlock_bh(&ar->htc_target->tx_lock);
}
fw_conn->previous_can_send = flowctrl_allowed;
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
}
ath6kl_p2p_flowctrl_netif_transition(
ar, ATH6KL_P2P_FLOWCTRL_NETIF_WAKE);
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl state_change %p re_tx %d re_tx_aging %d\n",
ar,
fw_conn->sche_re_tx,
fw_conn->sche_re_tx_aging);
ath6kl_tx_complete(ar->htc_target, &container);
return;
}
void ath6kl_p2p_flowctrl_state_update(struct ath6kl *ar,
u8 numConn,
u8 ac_map[],
u8 ac_queue_depth[])
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
int i;
WARN_ON(!p2p_flowctrl);
WARN_ON(numConn > NUM_CONN);
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
p2p_flowctrl->p2p_flowctrl_event_cnt++;
for (i = 0; i < numConn; i++) {
fw_conn = &p2p_flowctrl->fw_conn_list[i];
fw_conn->connect_status = ac_map[i];
}
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl state_update %p ac_map %02x %02x %02x %02x\n",
ar,
ac_map[0], ac_map[1], ac_map[2], ac_map[3]);
return;
}
void ath6kl_p2p_flowctrl_set_conn_id(struct ath6kl_vif *vif,
u8 mac_addr[],
u8 connId)
{
struct ath6kl *ar = vif->ar;
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
struct list_head container;
int reclaim = 0;
WARN_ON(!p2p_flowctrl);
INIT_LIST_HEAD(&container);
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
fw_conn = &p2p_flowctrl->fw_conn_list[connId];
if (mac_addr) {
if (fw_conn->sche_tx_queued != 0) {
ath6kl_p2p_flowctrl_conn_collect_by_conn(fw_conn,
&container,
&reclaim);
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
ath6kl_tx_complete(ar->htc_target, &container);
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
}
fw_conn->vif = vif;
fw_conn->connId = connId;
memcpy(fw_conn->mac_addr, mac_addr, ETH_ALEN);
/*
* Parent's connId of P2P-GO/P2P-Client/STA is self.
* Parent's connId of P2P-GO's Clients is P2P-GO.
*/
if ((vif->nw_type == AP_NETWORK) &&
(memcmp(vif->ndev->dev_addr, mac_addr, ETH_ALEN))) {
/* P2P-GO's Client connection event. */
fw_conn->parent_connId =
_find_parent_conn_id(p2p_flowctrl, vif);
} else {
/* P2P-GO/P2P-Client/STA connection event. */
fw_conn->parent_connId = fw_conn->connId;
}
} else {
fw_conn->connId = ATH6KL_P2P_FLOWCTRL_NULL_CONNID;
fw_conn->parent_connId = ATH6KL_P2P_FLOWCTRL_NULL_CONNID;
fw_conn->connect_status = 0;
fw_conn->previous_can_send = true;
memset(fw_conn->mac_addr, 0, ETH_ALEN);
}
fw_conn->sche_tx = 0;
fw_conn->sche_re_tx = 0;
fw_conn->sche_re_tx_aging = 0;
fw_conn->sche_tx_queued = 0;
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl set conn_id %p mode %d conId %d p_conId %d\n",
ar,
vif->nw_type,
connId,
fw_conn->parent_connId);
return;
}
u8 ath6kl_p2p_flowctrl_get_conn_id(struct ath6kl_vif *vif,
struct sk_buff *skb)
{
struct ath6kl *ar = vif->ar;
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
struct ethhdr *ethhdr;
u8 *hint;
u8 connId = ATH6KL_P2P_FLOWCTRL_NULL_CONNID;
int i;
if (!p2p_flowctrl)
return connId;
ethhdr = (struct ethhdr *)(skb->data + sizeof(struct wmi_data_hdr));
if (vif->nw_type != AP_NETWORK)
hint = ethhdr->h_source;
else {
if (is_multicast_ether_addr(ethhdr->h_dest))
hint = ethhdr->h_source;
else
hint = ethhdr->h_dest;
}
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
fw_conn = &p2p_flowctrl->fw_conn_list[0];
for (i = 0; i < NUM_CONN; i++, fw_conn++) {
if (fw_conn->connId == ATH6KL_P2P_FLOWCTRL_NULL_CONNID)
continue;
if (memcmp(fw_conn->mac_addr, hint, ETH_ALEN) == 0) {
connId = fw_conn->connId;
break;
}
}
/* Change to parent's. */
if (p2p_flowctrl->sche_type == P2P_FLOWCTRL_SCHE_TYPE_INTERFACE)
connId = fw_conn->parent_connId;
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
ath6kl_dbg(ATH6KL_DBG_FLOWCTRL,
"p2p_flowctrl get con_id %d %02x:%02x:%02x:%02x:%02x:%02x\n",
connId,
hint[0], hint[1], hint[2], hint[3], hint[4], hint[5]);
return connId;
}
int ath6kl_p2p_flowctrl_stat(struct ath6kl *ar,
u8 *buf, int buf_len)
{
struct ath6kl_p2p_flowctrl *p2p_flowctrl = ar->p2p_flowctrl_ctx;
struct ath6kl_fw_conn_list *fw_conn;
int i, len = 0;
if ((!p2p_flowctrl) || (!buf))
return 0;
len += snprintf(buf + len, buf_len - len, "\n NUM_CONN : %d",
NUM_CONN);
len += snprintf(buf + len, buf_len - len, "\n SCHE_TYPE : %s",
(p2p_flowctrl->sche_type == P2P_FLOWCTRL_SCHE_TYPE_CONNECTION ?
"CONNECTION" : "INTERFACE"));
len += snprintf(buf + len, buf_len - len, "\n EVENT_CNT : %d",
p2p_flowctrl->p2p_flowctrl_event_cnt);
len += snprintf(buf + len, buf_len - len, "\n NOA_UPDATE :");
for (i = 0; i < ar->vif_max; i++) {
struct ath6kl_vif *vif;
struct p2p_ps_info *p2p_ps;
vif = ath6kl_get_vif_by_index(ar, i);
if (vif) {
p2p_ps = vif->p2p_ps_info_ctx;
len += snprintf(buf + len, buf_len - len,
" %d", p2p_ps->go_noa_notif_cnt);
}
}
len += snprintf(buf + len, buf_len - len, "\n");
spin_lock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
for (i = 0; i < NUM_CONN; i++) {
fw_conn = &p2p_flowctrl->fw_conn_list[i];
if (fw_conn->connId == ATH6KL_P2P_FLOWCTRL_NULL_CONNID)
continue;
len += snprintf(buf + len, buf_len - len,
"\n[%d]===============================\n", i);
len += snprintf(buf + len, buf_len - len,
" vif : %p\n", fw_conn->vif);
len += snprintf(buf + len, buf_len - len,
" connId : %d\n", fw_conn->connId);
len += snprintf(buf + len, buf_len - len,
" parent_connId: %d\n", fw_conn->parent_connId);
len += snprintf(buf + len, buf_len - len,
" macAddr : %02x:%02x:%02x:%02x:%02x:%02x\n",
fw_conn->mac_addr[0],
fw_conn->mac_addr[1],
fw_conn->mac_addr[2],
fw_conn->mac_addr[3],
fw_conn->mac_addr[4],
fw_conn->mac_addr[5]);
len += snprintf(buf + len, buf_len - len,
" status : %02x\n", fw_conn->connect_status);
len += snprintf(buf + len, buf_len - len,
" preCanSend : %d\n", fw_conn->previous_can_send);
len += snprintf(buf + len, buf_len - len,
" tx_queued : %d\n", fw_conn->sche_tx_queued);
len += snprintf(buf + len, buf_len - len,
" tx : %d\n", fw_conn->sche_tx);
len += snprintf(buf + len, buf_len - len,
" rx_tx : %d\n", fw_conn->sche_re_tx);
len += snprintf(buf + len, buf_len - len,
" re_tx_aging : %d\n", fw_conn->sche_re_tx_aging);
}
spin_unlock_bh(&p2p_flowctrl->p2p_flowctrl_lock);
return len;
}
struct ath6kl_p2p_rc_info *ath6kl_p2p_rc_init(struct ath6kl *ar)
{
struct ath6kl_p2p_rc_info *p2p_rc;
p2p_rc = kzalloc(sizeof(struct ath6kl_p2p_rc_info), GFP_KERNEL);
if (!p2p_rc) {
ath6kl_err("failed to alloc memory for p2p_rc\n");
return NULL;
}
p2p_rc->ar = ar;
p2p_rc->flags = ATH6KL_RC_FLAGS_DONE;
p2p_rc->user_chan_type = P2P_RC_USER_BLACK_CHAN;
spin_lock_init(&p2p_rc->p2p_rc_lock);
p2p_rc->snr_compensation = P2P_RC_DEF_SNR_COMP;
ath6kl_dbg(ATH6KL_DBG_RC, "p2p_rc init, flags %x\n",
p2p_rc->flags);
return p2p_rc;
}
void ath6kl_p2p_rc_deinit(struct ath6kl *ar)
{
struct ath6kl_p2p_rc_info *p2p_rc = ar->p2p_rc_info_ctx;
kfree(p2p_rc);
ar->p2p_rc_info_ctx = NULL;
ath6kl_dbg(ATH6KL_DBG_RC, "p2p_rc deinit\n");
return;
}
static inline int __p2p_rc_get_chan_slot(struct ieee80211_channel *channel)
{
#define _MIN_5G_CHAN_ID 34
int chid = ieee80211_frequency_to_channel(channel->center_freq);
if (channel->band == (u32)NL80211_BAND_2GHZ)
return chid - 1;
else if (channel->band == (u32)NL80211_BAND_5GHZ)
return ATH6KL_RC_MAX_2G_CHAN_RECORD +
((chid - _MIN_5G_CHAN_ID) / 2);
else {
ath6kl_err("p2p_rc slot fail chid %d!\n", chid);
BUG_ON(1);
return -1;
}
#undef _MIN_5G_CHAN_ID
}
void ath6kl_p2p_rc_fetch_chan(struct ath6kl *ar)
{
struct ath6kl_p2p_rc_info *p2p_rc = ar->p2p_rc_info_ctx;
enum ieee80211_band band;
struct wiphy *wiphy = NULL;
int i, slot;
if (!p2p_rc)
return;
wiphy = p2p_rc->ar->wiphy;
spin_lock_bh(&p2p_rc->p2p_rc_lock);
p2p_rc->chan_record_cnt = 0;
for (i = 0; i < ATH6KL_RC_MAX_CHAN_RECORD; i++) {
p2p_rc->chan_record[i].channel = NULL;
p2p_rc->chan_record[i].best_snr = P2P_RC_NULL_SNR;
p2p_rc->chan_record[i].aver_snr = P2P_RC_NULL_SNR;
}
/* Fetch channel record from wiphy */
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
struct ieee80211_supported_band *sband = wiphy->bands[band];
if (!sband)
continue;
for (i = 0; i < sband->n_channels; i++) {
struct ieee80211_channel *chan = &sband->channels[i];
if (chan->flags & IEEE80211_CHAN_DISABLED)
continue;
slot = __p2p_rc_get_chan_slot(chan);
if ((slot >= 0) && (slot < ATH6KL_RC_MAX_CHAN_RECORD)) {
p2p_rc->chan_record[slot].channel = chan;
p2p_rc->chan_record_cnt++;
} else
ath6kl_err("p2p_rc fetch fail, f %d s %d!\n",
chan->center_freq,
slot);
}
}
p2p_rc->flags |= ATH6KL_RC_FLAGS_CHAN_RECORD_FETCHED;
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc fetch chan, chan_record_cnt %d\n",
p2p_rc->chan_record_cnt);
return;
}
static void _p2p_rc_reset_chan_record(struct ath6kl_p2p_rc_info *p2p_rc)
{
int i;
/* Clear all channel record */
spin_lock_bh(&p2p_rc->p2p_rc_lock);
for (i = 0; i < ATH6KL_RC_MAX_CHAN_RECORD; i++) {
p2p_rc->chan_record[i].best_snr = P2P_RC_NULL_SNR;
p2p_rc->chan_record[i].aver_snr = P2P_RC_NULL_SNR;
}
if (p2p_rc->flags & ATH6KL_RC_FLAGS_CHAN_RECORD_FETCHED) {
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc chan already fetched, chan_record_cnt %d\n",
p2p_rc->chan_record_cnt);
return;
}
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
ath6kl_p2p_rc_fetch_chan(p2p_rc->ar);
return;
}
void ath6kl_p2p_rc_scan_start(struct ath6kl_vif *vif, bool local_scan)
{
struct ath6kl_p2p_rc_info *p2p_rc = vif->ar->p2p_rc_info_ctx;
if (!p2p_rc)
return;
/*
* Assume that
* 1.only one scan behavior between all VIFs at the same time.
* 2.trust vif->scanband_type if this is a user scan.
* 3.if this is a driver scan and always learn it.
*/
/* Only full-scan is valid. */
if ((local_scan) ||
(test_bit(SCANNING, &vif->flags) &&
(vif->scanband_type == SCANBAND_TYPE_ALL))) {
/* Reset the channel record. */
_p2p_rc_reset_chan_record(p2p_rc);
p2p_rc->flags |= ATH6KL_RC_FLAGS_NEED_UPDATED;
p2p_rc->last_update = jiffies;
}
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc scan_start %s, chan %d %s type %d cnt %d\n",
(p2p_rc->flags & ATH6KL_RC_FLAGS_NEED_UPDATED ?
"need-update" : ""),
(vif->scan_req ?
vif->scan_req->n_channels : -1),
(local_scan ? "local-scan" : ""),
vif->scanband_type,
p2p_rc->chan_record_cnt);
return;
}
int ath6kl_p2p_rc_scan_complete_event(struct ath6kl_vif *vif, bool aborted)
{
struct ath6kl_p2p_rc_info *p2p_rc = vif->ar->p2p_rc_info_ctx;
int i;
if (!p2p_rc)
return 0;
p2p_rc->flags &= ~ATH6KL_RC_FLAGS_NEED_UPDATED;
/* Only flush the last_p2p_rc if it's a successful scan. */
spin_lock_bh(&p2p_rc->p2p_rc_lock);
if (!aborted) {
p2p_rc->flags &= ~ATH6KL_RC_FLAGS_DONE;
for (i = 0; i < P2P_RC_TYPE_MAX; i++)
p2p_rc->last_p2p_rc[i] = NULL;
}
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc scan_comp %s\n",
(aborted ? "aborted" : ""));
return 0;
}
static inline void _p2p_rc_bss_update(struct ath6kl_p2p_rc_info *p2p_rc,
struct ieee80211_channel *channel,
u8 snr)
{
int slot;
slot = __p2p_rc_get_chan_slot(channel);
if ((slot >= 0) && (slot < ATH6KL_RC_MAX_CHAN_RECORD)) {
struct p2p_rc_chan_record *p2p_rc_chan;
spin_lock_bh(&p2p_rc->p2p_rc_lock);
p2p_rc_chan = &(p2p_rc->chan_record[slot]);
if (channel != p2p_rc_chan->channel) {
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc bssinfo chan unsync! rx %d rc %d\n",
channel->center_freq,
p2p_rc_chan->channel->center_freq);
return;
}
BUG_ON(!p2p_rc_chan->channel);
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc bssinfo %s freq %d snr %d slot %d\n",
((p2p_rc_chan->best_snr != P2P_RC_NULL_SNR) ?
"update" : "found"),
channel->center_freq,
snr,
slot);
if (snr > P2P_RC_MAX_SNR)
snr = P2P_RC_MAX_SNR;
/* Only keep the max. SNR for calculation. */
if (p2p_rc_chan->best_snr != P2P_RC_NULL_SNR) {
if (snr > p2p_rc_chan->best_snr)
p2p_rc_chan->best_snr = snr;
} else
p2p_rc_chan->best_snr = snr;
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
} else
ath6kl_err("p2p_rc bssinfo invalid frequency %d!\n",
channel->center_freq);
return;
}
void ath6kl_p2p_rc_bss_info(struct ath6kl_vif *vif,
u8 snr,
struct ieee80211_channel *channel)
{
struct ath6kl_p2p_rc_info *p2p_rc = vif->ar->p2p_rc_info_ctx;
if (!p2p_rc)
return;
if (p2p_rc->flags & ATH6KL_RC_FLAGS_NEED_UPDATED)
_p2p_rc_bss_update(p2p_rc, channel, snr);
return;
}
static inline u8 __p2p_rc_average_snr(struct p2p_rc_chan_record *chan[],
int count)
{
struct p2p_rc_chan_record *p2p_rc_chan;
int i, aver_snr = 0, compensate = 0;
for (i = 0; i < ATH6KL_RC_AVERAGE_CHAN_CNT; i++) {
p2p_rc_chan = chan[i];
if ((i < count) &&
(p2p_rc_chan->channel))
aver_snr += p2p_rc_chan->best_snr;
else
aver_snr += P2P_RC_NULL_SNR;
}
/* To round off the aver_snr to get more accurate average. */
if ((aver_snr % ATH6KL_RC_AVERAGE_CHAN_CNT) >
(ATH6KL_RC_AVERAGE_CHAN_CNT >> 1))
compensate++;
aver_snr = (aver_snr / ATH6KL_RC_AVERAGE_CHAN_CNT) + compensate;
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc get_aver freq %d aver_snr %d count %d/%d\n",
(chan[0]->channel ? chan[0]->channel->center_freq : 0),
aver_snr,
count,
ATH6KL_RC_AVERAGE_CHAN_CNT);
return aver_snr;
}
static bool __p2p_rc_is_user_channel(struct ath6kl_p2p_rc_info *p2p_rc,
struct ieee80211_channel *chan)
{
enum p2p_rc_user_chan_type user_chan_type = p2p_rc->user_chan_type;
int i;
for (i = 0; i < ATH6KL_RC_MAX_CHAN_RECORD; i++) {
if ((p2p_rc->user_chan_list[i]) &&
(chan->center_freq == p2p_rc->user_chan_list[i])) {
if (user_chan_type == P2P_RC_USER_WHITE_CHAN)
return true;
else
return false;
}
}
if (user_chan_type == P2P_RC_USER_WHITE_CHAN)
return false;
else
return true;
}
static struct p2p_rc_chan_record *_p2p_rc_get_2g_chan(
struct ath6kl_p2p_rc_info *p2p_rc,
bool only_p2p_social,
bool only_p2p,
bool user_chan,
bool no_lte)
{
struct p2p_rc_chan_record *p2p_rc_chan, *best_p2p_rc_chan = NULL;
struct p2p_rc_chan_record *adj_chan[ATH6KL_RC_AVERAGE_CHAN_CNT];
struct ieee80211_channel *chan;
int i, j, count;
u8 average_h;
BUG_ON((only_p2p_social && only_p2p));
/* Start from 2G channel */
p2p_rc_chan = &(p2p_rc->chan_record[0]);
for (i = 0; i < ATH6KL_RC_MAX_2G_CHAN_RECORD; i++, p2p_rc_chan++) {
chan = p2p_rc_chan->channel;
if (chan) {
if ((user_chan) &&
!__p2p_rc_is_user_channel(p2p_rc, chan))
continue;
else if ((only_p2p_social) &&
!ath6kl_p2p_is_social_channel(chan->center_freq))
continue;
else if ((only_p2p) &&
(chan->flags & (IEEE80211_CHAN_RADAR |
IEEE80211_CHAN_PASSIVE_SCAN |
IEEE80211_CHAN_NO_IBSS)) &&
!ath6kl_p2p_is_p2p_channel(chan->center_freq))
continue;
else if ((no_lte) &&
ath6kl_reg_is_lte_channel(p2p_rc->ar,
chan->center_freq))
continue;
/* Get adjacence channels in 20MHz width. */
count = 0;
adj_chan[count++] = p2p_rc_chan;
for (j = 1; j <= ATH6KL_RC_AVERAGE_CHAN_OFFSET; j++) {
if (chan->center_freq - (j * 5) >=
ATH6KL_RC_AVERAGE_CHAN_START)
adj_chan[count++] = p2p_rc_chan - j;
if (chan->center_freq + (j * 5) <=
ATH6KL_RC_AVERAGE_CHAN_END)
adj_chan[count++] = p2p_rc_chan + j;
}
if ((count) &&
(chan->center_freq <= ATH6KL_RC_AVERAGE_CHAN_END))
p2p_rc_chan->aver_snr = __p2p_rc_average_snr(
adj_chan,
count);
else
p2p_rc_chan->aver_snr = p2p_rc_chan->best_snr;
if (best_p2p_rc_chan) {
average_h = p2p_rc_chan->aver_snr;
if (p2p_rc->flags & ATH6KL_RC_FLAGS_HIGH_CHAN)
average_h++;
if (average_h > best_p2p_rc_chan->aver_snr)
best_p2p_rc_chan = p2p_rc_chan;
} else
best_p2p_rc_chan = p2p_rc_chan;
}
}
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc get_2g freq %d snr %d\n",
(best_p2p_rc_chan ?
best_p2p_rc_chan->channel->center_freq : 0),
(best_p2p_rc_chan ?
best_p2p_rc_chan->best_snr : 0));
return best_p2p_rc_chan;
}
static struct p2p_rc_chan_record *_p2p_rc_get_5g_chan(
struct ath6kl_p2p_rc_info *p2p_rc,
bool only_p2p,
bool no_dfs,
bool user_chan)
{
struct p2p_rc_chan_record *p2p_rc_chan, *best_p2p_rc_chan = NULL;
struct ieee80211_channel *chan;
int i, snr_h;
/* Start from 5G channel */
p2p_rc_chan = &(p2p_rc->chan_record[ATH6KL_RC_MAX_2G_CHAN_RECORD]);
for (i = 0; i < ATH6KL_RC_MAX_5G_CHAN_RECORD; i++, p2p_rc_chan++) {
chan = p2p_rc_chan->channel;
if (chan) {
if ((user_chan) &&
!__p2p_rc_is_user_channel(p2p_rc, chan))
continue;
else if ((only_p2p) &&
(chan->flags & (IEEE80211_CHAN_RADAR |
IEEE80211_CHAN_PASSIVE_SCAN |
IEEE80211_CHAN_NO_IBSS)) &&
!ath6kl_p2p_is_p2p_channel(chan->center_freq))
continue;
else if ((no_dfs) &&
(chan->flags & IEEE80211_CHAN_RADAR) &&
(p2p_rc_chan->best_snr == P2P_RC_NULL_SNR))
continue;
/* If DFS channel but any AP exist and still use it. */
if (best_p2p_rc_chan) {
snr_h = (int)p2p_rc_chan->best_snr;
if (p2p_rc->flags & ATH6KL_RC_FLAGS_HIGH_CHAN)
snr_h++;
if (snr_h > (int)best_p2p_rc_chan->best_snr)
best_p2p_rc_chan = p2p_rc_chan;
} else
best_p2p_rc_chan = p2p_rc_chan;
}
}
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc get_5g freq %d snr %d\n",
(best_p2p_rc_chan ?
best_p2p_rc_chan->channel->center_freq : 0),
(best_p2p_rc_chan ?
best_p2p_rc_chan->best_snr : 0));
return best_p2p_rc_chan;
}
int ath6kl_p2p_rc_get(struct ath6kl *ar, struct ath6kl_rc_report *rc_report)
{
struct ath6kl_p2p_rc_info *p2p_rc = ar->p2p_rc_info_ctx;
struct p2p_rc_chan_record **p2p_rc_chan;
struct p2p_rc_chan_record *user_chan_2g, *user_chan_5g;
if (!p2p_rc)
return -ENOTSUPP;
/* Updating */
if (p2p_rc->flags & ATH6KL_RC_FLAGS_NEED_UPDATED)
return -EINPROGRESS;
if ((p2p_rc->flags & ATH6KL_RC_FLAGS_ALWAYS_FRESH) &&
time_after(jiffies, p2p_rc->last_update + ATH6KL_RC_FRESH_TIME))
return -EAGAIN;
spin_lock_bh(&p2p_rc->p2p_rc_lock);
p2p_rc_chan = &(p2p_rc->last_p2p_rc[0]);
/*
* No further channel information need to be parsed and just
* return the last results.
*/
if (p2p_rc->flags & ATH6KL_RC_FLAGS_DONE)
goto done;
/* Get 2G recommand channel */
p2p_rc_chan[P2P_RC_TYPE_2GALL] = _p2p_rc_get_2g_chan(p2p_rc,
false,
false,
false,
false);
/* Get 5G recommand channel */
p2p_rc_chan[P2P_RC_TYPE_5GALL] = _p2p_rc_get_5g_chan(p2p_rc,
false,
false,
false);
/* Get overall recommand channel. */
if (p2p_rc_chan[P2P_RC_TYPE_2GALL]) {
if (p2p_rc_chan[P2P_RC_TYPE_5GALL]) {
if (p2p_rc_chan[P2P_RC_TYPE_2GALL]->aver_snr >
(p2p_rc_chan[P2P_RC_TYPE_5GALL]->best_snr +
p2p_rc->snr_compensation))
p2p_rc_chan[P2P_RC_TYPE_OVERALL] =
p2p_rc_chan[P2P_RC_TYPE_2GALL];
else /* Prefer to use 5G if has the same value. */
p2p_rc_chan[P2P_RC_TYPE_OVERALL] =
p2p_rc_chan[P2P_RC_TYPE_5GALL];
} else
p2p_rc_chan[P2P_RC_TYPE_OVERALL] =
p2p_rc_chan[P2P_RC_TYPE_2GALL];
} else if (p2p_rc_chan[P2P_RC_TYPE_5GALL])
p2p_rc_chan[P2P_RC_TYPE_OVERALL] =
p2p_rc_chan[P2P_RC_TYPE_5GALL];
/* Get P2P-Social recommand channel */
p2p_rc_chan[P2P_RC_TYPE_SOCAIL] = _p2p_rc_get_2g_chan(p2p_rc,
true,
false,
false,
false);
/* Get P2P-2G recommand channel */
p2p_rc_chan[P2P_RC_TYPE_2GP2P] = _p2p_rc_get_2g_chan(p2p_rc,
false,
true,
false,
false);
/* Get P2P-5G recommand channel */
p2p_rc_chan[P2P_RC_TYPE_5GP2P] = _p2p_rc_get_5g_chan(p2p_rc,
true,
true,
false);
/* Get P2P-All recommand channel. */
if (p2p_rc_chan[P2P_RC_TYPE_2GP2P]) {
if (p2p_rc_chan[P2P_RC_TYPE_5GP2P]) {
if (p2p_rc_chan[P2P_RC_TYPE_2GP2P]->aver_snr >
(p2p_rc_chan[P2P_RC_TYPE_5GP2P]->best_snr +
p2p_rc->snr_compensation))
p2p_rc_chan[P2P_RC_TYPE_ALLP2P] =
p2p_rc_chan[P2P_RC_TYPE_2GP2P];
else /* Prefer to use 5G if has the same value. */
p2p_rc_chan[P2P_RC_TYPE_ALLP2P] =
p2p_rc_chan[P2P_RC_TYPE_5GP2P];
} else
p2p_rc_chan[P2P_RC_TYPE_ALLP2P] =
p2p_rc_chan[P2P_RC_TYPE_2GP2P];
} else if (p2p_rc_chan[P2P_RC_TYPE_5GP2P])
p2p_rc_chan[P2P_RC_TYPE_ALLP2P] =
p2p_rc_chan[P2P_RC_TYPE_5GP2P];
/* Get 5G w/o DFS recommand channel */
p2p_rc_chan[P2P_RC_TYPE_5GNODFS] = _p2p_rc_get_5g_chan(p2p_rc,
false,
true,
false);
/* Get overall w/o DFS recommand channel. */
if (p2p_rc_chan[P2P_RC_TYPE_2GALL]) {
if (p2p_rc_chan[P2P_RC_TYPE_5GNODFS]) {
if (p2p_rc_chan[P2P_RC_TYPE_2GALL]->aver_snr >
(p2p_rc_chan[P2P_RC_TYPE_5GNODFS]->best_snr +
p2p_rc->snr_compensation))
p2p_rc_chan[P2P_RC_TYPE_OVERALLNODFS] =
p2p_rc_chan[P2P_RC_TYPE_2GALL];
else /* Prefer to use 5G if has the same value. */
p2p_rc_chan[P2P_RC_TYPE_OVERALLNODFS] =
p2p_rc_chan[P2P_RC_TYPE_5GNODFS];
} else
p2p_rc_chan[P2P_RC_TYPE_OVERALLNODFS] =
p2p_rc_chan[P2P_RC_TYPE_2GALL];
} else if (p2p_rc_chan[P2P_RC_TYPE_5GNODFS])
p2p_rc_chan[P2P_RC_TYPE_OVERALLNODFS] =
p2p_rc_chan[P2P_RC_TYPE_5GNODFS];
/* Get 2G w/o LTE recommand channel */
p2p_rc_chan[P2P_RC_TYPE_2GNOLTE] = _p2p_rc_get_2g_chan(p2p_rc,
false,
false,
false,
true);
/* Get overall w/o LTE recommand channel. */
if (p2p_rc_chan[P2P_RC_TYPE_2GNOLTE]) {
if (p2p_rc_chan[P2P_RC_TYPE_5GALL]) {
if (p2p_rc_chan[P2P_RC_TYPE_2GNOLTE]->aver_snr >
(p2p_rc_chan[P2P_RC_TYPE_5GALL]->best_snr +
p2p_rc->snr_compensation))
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTE] =
p2p_rc_chan[P2P_RC_TYPE_2GNOLTE];
else /* Prefer to use 5G if has the same value. */
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTE] =
p2p_rc_chan[P2P_RC_TYPE_5GALL];
} else
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTE] =
p2p_rc_chan[P2P_RC_TYPE_2GNOLTE];
} else if (p2p_rc_chan[P2P_RC_TYPE_5GALL])
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTE] =
p2p_rc_chan[P2P_RC_TYPE_5GALL];
/* Get overall w/o LTE&DFS recommand channel. */
if (p2p_rc_chan[P2P_RC_TYPE_2GNOLTE]) {
if (p2p_rc_chan[P2P_RC_TYPE_5GNODFS]) {
if (p2p_rc_chan[P2P_RC_TYPE_2GNOLTE]->aver_snr >
(p2p_rc_chan[P2P_RC_TYPE_5GNODFS]->best_snr +
p2p_rc->snr_compensation))
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTEDFS] =
p2p_rc_chan[P2P_RC_TYPE_2GNOLTE];
else /* Prefer to use 5G if has the same value. */
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTEDFS] =
p2p_rc_chan[P2P_RC_TYPE_5GNODFS];
} else
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTEDFS] =
p2p_rc_chan[P2P_RC_TYPE_2GNOLTE];
} else if (p2p_rc_chan[P2P_RC_TYPE_5GNODFS])
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTEDFS] =
p2p_rc_chan[P2P_RC_TYPE_5GNODFS];
/* Get user-defined recommand channel. */
user_chan_2g = _p2p_rc_get_2g_chan(p2p_rc,
false,
false,
true,
false);
user_chan_5g = _p2p_rc_get_5g_chan(p2p_rc,
false,
false,
true);
if (user_chan_2g) {
if (user_chan_5g) {
if (user_chan_2g->aver_snr >
(user_chan_5g->best_snr + p2p_rc->snr_compensation))
p2p_rc_chan[P2P_RC_TYPE_USER_CHAN] =
user_chan_2g;
else /* Prefer to use 5G if has the same value. */
p2p_rc_chan[P2P_RC_TYPE_USER_CHAN] =
user_chan_5g;
} else
p2p_rc_chan[P2P_RC_TYPE_USER_CHAN] = user_chan_2g;
} else if (user_chan_5g)
p2p_rc_chan[P2P_RC_TYPE_USER_CHAN] = user_chan_5g;
p2p_rc->flags |= ATH6KL_RC_FLAGS_DONE;
done:
/* Get all results back to the caller */
if (p2p_rc_chan[P2P_RC_TYPE_2GALL])
rc_report->rc_2g =
p2p_rc_chan[P2P_RC_TYPE_2GALL]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_5GALL])
rc_report->rc_5g =
p2p_rc_chan[P2P_RC_TYPE_5GALL]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_OVERALL])
rc_report->rc_all =
p2p_rc_chan[P2P_RC_TYPE_OVERALL]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_SOCAIL])
rc_report->rc_p2p_so =
p2p_rc_chan[P2P_RC_TYPE_SOCAIL]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_2GP2P])
rc_report->rc_p2p_2g =
p2p_rc_chan[P2P_RC_TYPE_2GP2P]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_5GP2P])
rc_report->rc_p2p_5g =
p2p_rc_chan[P2P_RC_TYPE_5GP2P]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_ALLP2P])
rc_report->rc_p2p_all =
p2p_rc_chan[P2P_RC_TYPE_ALLP2P]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_5GNODFS])
rc_report->rc_5g_nodfs =
p2p_rc_chan[P2P_RC_TYPE_5GNODFS]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_OVERALLNODFS])
rc_report->rc_all_nodfs =
p2p_rc_chan[P2P_RC_TYPE_OVERALLNODFS]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_2GNOLTE])
rc_report->rc_2g_nolte =
p2p_rc_chan[P2P_RC_TYPE_2GNOLTE]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTE])
rc_report->rc_all_nolte =
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTE]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTEDFS])
rc_report->rc_all_noltedfs =
p2p_rc_chan[P2P_RC_TYPE_OVERALLNOLTEDFS]->channel->center_freq;
if (p2p_rc_chan[P2P_RC_TYPE_USER_CHAN])
rc_report->rc_user_chan =
p2p_rc_chan[P2P_RC_TYPE_USER_CHAN]->channel->center_freq;
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc get\n");
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
return 0;
}
int ath6kl_p2p_rc_dump(struct ath6kl *ar, u8 *buf, int buf_len)
{
struct ath6kl_p2p_rc_info *p2p_rc = ar->p2p_rc_info_ctx;
struct p2p_rc_chan_record *p2p_rc_chan;
struct ath6kl_rc_report rc_report;
int i, idx = 0, len = 0;
if ((!p2p_rc) || (!buf))
return 0;
len += snprintf(buf + len, buf_len - len,
"\nflags 0x%x snr_comp %d last_update %lld now %lld\n",
p2p_rc->flags,
p2p_rc->snr_compensation,
(long long int)p2p_rc->last_update,
(long long int)jiffies);
memset(&rc_report, 0, sizeof(struct ath6kl_rc_report));
if (ath6kl_p2p_rc_get(ar, &rc_report) != 0)
return 0;
len += snprintf(buf + len, buf_len - len,
"\n2G/5G/ALL %d %d %d\n",
rc_report.rc_2g,
rc_report.rc_5g,
rc_report.rc_all);
len += snprintf(buf + len, buf_len - len,
"P2P-Social/2G/5G/ALL %d %d %d %d\n",
rc_report.rc_p2p_so,
rc_report.rc_p2p_2g,
rc_report.rc_p2p_5g,
rc_report.rc_p2p_all);
len += snprintf(buf + len, buf_len - len,
"NoDFS-5G/ALL %d %d\n",
rc_report.rc_5g_nodfs,
rc_report.rc_all_nodfs);
len += snprintf(buf + len, buf_len - len,
"NoLTE-2G/ALL %d %d\n",
rc_report.rc_2g_nolte,
rc_report.rc_all_nolte);
len += snprintf(buf + len, buf_len - len,
"NoLTE-NoDFS-ALL %d\n",
rc_report.rc_all_noltedfs);
len += snprintf(buf + len, buf_len - len,
"UserChan %d\n",
rc_report.rc_user_chan);
len += snprintf(buf + len, buf_len - len,
"\nUserChan Type - %s\nUserChan List -",
(p2p_rc->user_chan_type == P2P_RC_USER_BLACK_CHAN) ?
"Black List" : "White List");
for (i = 0; i < ATH6KL_RC_MAX_CHAN_RECORD; i++) {
if (p2p_rc->user_chan_list[i])
len += snprintf(buf + len, buf_len - len,
" %d",
p2p_rc->user_chan_list[i]);
}
spin_lock_bh(&p2p_rc->p2p_rc_lock);
len += snprintf(buf + len, buf_len - len,
"\n\nchan_record %d\n",
p2p_rc->chan_record_cnt);
for (i = 0, p2p_rc_chan = p2p_rc->chan_record;
i < ATH6KL_RC_MAX_CHAN_RECORD;
i++, p2p_rc_chan++) {
if (p2p_rc_chan->channel)
len += snprintf(buf + len, buf_len - len,
"%02d - %4d SNR %3d AVER_SNR %3d\n",
idx,
p2p_rc_chan->channel->center_freq,
(p2p_rc_chan->best_snr ==
P2P_RC_NULL_SNR) ?
-1 : p2p_rc_chan->best_snr,
(p2p_rc_chan->aver_snr ==
P2P_RC_NULL_SNR) ?
-1 : p2p_rc_chan->aver_snr);
idx++;
}
spin_unlock_bh(&p2p_rc->p2p_rc_lock);
return len;
}
void ath6kl_p2p_rc_config(struct ath6kl *ar, u16 freq, int type)
{
struct ath6kl_p2p_rc_info *p2p_rc = ar->p2p_rc_info_ctx;
int i = -1, j;
bool need_update = false;
if (freq) {
for (i = 0; i < ATH6KL_RC_MAX_CHAN_RECORD; i++) {
if (p2p_rc->user_chan_list[i]) {
if (p2p_rc->user_chan_list[i] == freq)
break;
} else {
need_update = true;
p2p_rc->user_chan_list[i] = freq;
break;
}
}
if (i == ATH6KL_RC_MAX_CHAN_RECORD)
ath6kl_err("p2p_rc user chan list full!\n");
} else {
/* change the type */
if (p2p_rc->user_chan_type != type) {
need_update = true;
p2p_rc->user_chan_type = type;
/* reset the channel list */
memset(p2p_rc->user_chan_list,
0,
(sizeof(u16) * ATH6KL_RC_MAX_CHAN_RECORD));
}
}
/* Re-calculate again */
if (need_update) {
p2p_rc->flags &= ~ATH6KL_RC_FLAGS_DONE;
for (j = 0; j < P2P_RC_TYPE_MAX; j++)
p2p_rc->last_p2p_rc[j] = NULL;
}
ath6kl_dbg(ATH6KL_DBG_RC,
"p2p_rc config freq %d type %d, slot %d %s\n",
freq,
type,
i,
(need_update ? "UPDATED" : ""));
return;
}
bool ath6kl_p2p_frame_retry(struct ath6kl *ar, u8 *frm, int len)
{
struct ieee80211_p2p_action_public *action_frame =
(struct ieee80211_p2p_action_public *)frm;
if (!ar->p2p_frame_retry)
return false;
/*
* WAR : Except P2P-Neg-Confirm frame and other P2P action frames
* could be recovery by supplicant's state machine.
*/
if (len < 8)
return false;
return ((action_frame->category == WLAN_CATEGORY_PUBLIC) &&
(action_frame->action_code ==
WLAN_PUB_ACTION_VENDER_SPECIFIC) &&
(action_frame->oui == cpu_to_be32((WLAN_OUI_WFA << 8) |
(WLAN_OUI_TYPE_WFA_P2P))) &&
(action_frame->action_subtype == WLAN_P2P_GO_NEG_CONF));
}
bool ath6kl_p2p_is_p2p_frame(struct ath6kl *ar, const u8 *frm, size_t len)
{
struct ieee80211_mgmt *action = (struct ieee80211_mgmt *)frm;
struct ieee80211_p2p_action_public *action_public;
struct ieee80211_p2p_action_vendor *action_vendor;
u8 *action_start = (u8 *)(&action->u.action);
if (len < sizeof(struct ieee80211_p2p_action_vendor))
return false;
action_public = (struct ieee80211_p2p_action_public *)action_start;
if ((action_public->category == WLAN_CATEGORY_PUBLIC) &&
(action_public->action_code ==
WLAN_PUB_ACTION_VENDER_SPECIFIC) &&
(action_public->oui == cpu_to_be32((WLAN_OUI_WFA << 8) |
(WLAN_OUI_TYPE_WFA_P2P))))
return true;
action_vendor = (struct ieee80211_p2p_action_vendor *)action_start;
if ((action_vendor->category == WLAN_CATEGORY_VENDOR_SPECIFIC) &&
(action_vendor->oui == cpu_to_be32((WLAN_OUI_WFA << 8) |
(WLAN_OUI_TYPE_WFA_P2P))))
return true;
return false;
}
void ath6kl_p2p_connect_event(struct ath6kl_vif *vif,
u8 beacon_ie_len,
u8 assoc_req_len,
u8 assoc_resp_len,
u8 *assoc_info)
{
u8 *pie, *peie;
struct ieee80211_ht_cap *ht_cap_ie = NULL;
bool vendor_spec_ie_intel = false;
if (vif->nw_type != INFRA_NETWORK)
return;
/* Now, only p2p_war_bad_intel_go need to do something here. */
if (!vif->ar->p2p_war_bad_intel_go)
return;
/* AssocResp IEs */
pie = assoc_info + beacon_ie_len + assoc_req_len +
(sizeof(u16) * 3); /* capinfo + status code + associd */
peie = assoc_info + beacon_ie_len + assoc_req_len + assoc_resp_len;
while (pie < peie) {
switch (*pie) {
case WLAN_EID_HT_CAPABILITY:
if (pie[1] >= sizeof(struct ieee80211_ht_cap))
ht_cap_ie =
(struct ieee80211_ht_cap *)(pie + 2);
break;
case WLAN_EID_VENDOR_SPECIFIC:
if (pie[1] > 0) {
if (pie[1] == 0x0b &&
pie[2] == 0x00 &&
pie[3] == 0x17 &&
pie[4] == 0x35 &&
pie[5] == 0x01)
vendor_spec_ie_intel = true;
}
break;
}
pie += pie[1] + 2;
}
/* WAR EV119712 */
if (ht_cap_ie &&
vendor_spec_ie_intel &&
(vif->ar->target_subtype & TARGET_SUBTYPE_2SS)) {
if (((ht_cap_ie->cap_info & IEEE80211_HT_CAP_SM_PS) ==
(WLAN_HT_CAP_SM_PS_DYNAMIC <<
IEEE80211_HT_CAP_SM_PS_SHIFT)) &&
(ht_cap_ie->mcs.rx_mask[1])) {
ath6kl_info("Enable Intel-GO compatibility.\n");
ath6kl_wmi_set_fix_rates(vif->ar->wmi,
vif->fw_vif_idx,
(0x00000000000fffffULL));
set_bit(PS_STICK, &vif->flags);
ath6kl_wmi_powermode_cmd(vif->ar->wmi,
vif->fw_vif_idx,
MAX_PERF_POWER);
}
}
if (vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) {
ath6kl_wmi_set_keepalive_cmd(vif->ar->wmi, vif->fw_vif_idx,
WLAN_CONFIG_KEEP_ALIVE_INTERVAL);
}
return;
}
void ath6kl_p2p_reconfig_ps(struct ath6kl *ar,
bool mcc,
bool call_on_disconnect)
{
struct ath6kl_vif *vif;
u8 pwr_mode = REC_POWER;
int connected = 0;
/* Not support PS in MCC mode currently. */
list_for_each_entry(vif, &ar->vif_list, list) {
if (test_bit(CONNECTED, &vif->flags)) {
if (test_bit(PS_DISABLED_ALWAYS, &ar->flag)) {
/* No support PS always */
set_bit(PS_STICK, &vif->flags);
pwr_mode = MAX_PERF_POWER;
} else if (mcc) {
/* MCC/AnyVIF - Set all VIFs to PS OFF. */
set_bit(PS_STICK, &vif->flags);
pwr_mode = MAX_PERF_POWER;
} else if (vif->wdev.iftype ==
NL80211_IFTYPE_P2P_GO) {
/* SCC/P2P-GO - Set to PS OFF. */
set_bit(PS_STICK, &vif->flags);
pwr_mode = MAX_PERF_POWER;
} else if (vif->wdev.iftype ==
NL80211_IFTYPE_P2P_CLIENT) {
/* SCC/P2P-GC - Set to PS OFF if need. */
if ((ar->p2p_war_p2p_client_awake) &&
connected) {
set_bit(PS_STICK, &vif->flags);
pwr_mode = MAX_PERF_POWER;
} else {
/* TODO: For WAR EV119712 case */
clear_bit(PS_STICK, &vif->flags);
if (vif->wdev.ps == NL80211_PS_ENABLED)
pwr_mode = REC_POWER;
else
pwr_mode = MAX_PERF_POWER;
}
} else {
/* SCC/notP2P-VIF - Back to original PS mode. */
clear_bit(PS_STICK, &vif->flags);
if (vif->nw_type == AP_NETWORK)
pwr_mode = MAX_PERF_POWER;
else { /* Ad-Hoc & STA */
if (vif->wdev.ps == NL80211_PS_ENABLED)
pwr_mode = REC_POWER;
else
pwr_mode = MAX_PERF_POWER;
}
}
connected++;
ath6kl_dbg(ATH6KL_DBG_INFO,
"PS vif %d ps %d-%d conn %d %s %s => %s\n",
vif->fw_vif_idx,
vif->last_pwr_mode,
vif->wdev.ps,
connected,
(mcc ? "MCC" : "SCC"),
(call_on_disconnect ? "DISCONN" : "CONN"),
(pwr_mode == REC_POWER ? "ON" : "OFF"));
ath6kl_wmi_powermode_cmd(ar->wmi,
vif->fw_vif_idx,
pwr_mode);
}
}
return;
}
static void _p2p_pending_connect_work(struct work_struct *work)
{
struct ath6kl_vif *vif = NULL;
struct p2p_pending_connect_info *pending_connect_info = NULL;
const u8 *bssid, *req_ie, *resp_ie;
if (!work)
goto err;
vif = container_of(work, struct ath6kl_vif,
work_pending_connect.work);
if (!vif)
goto err;
pending_connect_info = vif->pending_connect_info;
if (!vif->pending_connect_info)
goto err;
bssid = req_ie = resp_ie = NULL;
if (!is_zero_ether_addr(pending_connect_info->bssid))
bssid = pending_connect_info->bssid;
if (pending_connect_info->req_ie_len)
req_ie = pending_connect_info->req_ie;
if (pending_connect_info->resp_ie_len)
resp_ie = pending_connect_info->resp_ie;
/* Send connect event to cfg80211 */
cfg80211_connect_result(vif->ndev,
bssid,
req_ie,
pending_connect_info->req_ie_len,
resp_ie,
pending_connect_info->resp_ie_len,
pending_connect_info->status,
pending_connect_info->gfp);
kfree(vif->pending_connect_info);
vif->pending_connect_info = NULL;
return;
err:
ath6kl_err("P2P pending connect work fail! vif %p work %p info %p\n",
vif, work, pending_connect_info);
return;
}
static inline bool _p2p_need_pending_connect(struct ath6kl_vif *vif,
const u8 *bssid)
{
bool need_pending = false;
/* WAR CR468120 */
if ((vif->ar->p2p_war_bad_broadcom_go) &&
(vif->sme_state == SME_CONNECTED) &&
(vif->connect_ctrl_flags & CONNECT_WPS_FLAG) &&
(vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)) {
struct cfg80211_bss *bss;
bss = ath6kl_bss_get(vif->ar,
NULL,
vif->bssid,
vif->ssid,
vif->ssid_len,
WLAN_CAPABILITY_ESS,
WLAN_CAPABILITY_ESS);
if (bss) {
u8 *pie, *peie;
pie = peie = NULL;
#ifdef CFG80211_SAFE_BSS_INFO_ACCESS
rcu_read_lock();
if (bss->ies) {
pie = (u8 *)(bss->ies->data);
peie = pie + bss->ies->len;
}
#else
if (bss->information_elements) {
pie = bss->information_elements;
peie = pie + bss->len_information_elements;
}
#endif
while (pie < peie) {
if (*pie == WLAN_EID_VENDOR_SPECIFIC) {
if ((pie[1] > 0) &&
(pie[2] == 0x00 &&
pie[3] == 0x10 &&
pie[4] == 0x18))
need_pending = true;
}
pie += pie[1] + 2;
}
#ifdef CFG80211_SAFE_BSS_INFO_ACCESS
rcu_read_unlock();
#endif
ath6kl_bss_put(vif->ar, bss);
}
}
return need_pending;
}
static inline bool _p2p_flush_pending_connect(struct ath6kl_vif *vif)
{
if ((vif->ar->p2p_war_bad_broadcom_go) &&
(vif->sme_state == SME_CONNECTED) &&
(vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) &&
(vif->pending_connect_info)) {
ath6kl_info("Flush pending connect work first.\n");
ath6kl_flush_pend_skb(vif);
}
return false;
}
bool ath6kl_p2p_pending_connect_event(struct ath6kl_vif *vif,
const u8 *bssid,
const u8 *req_ie,
size_t req_ie_len,
const u8 *resp_ie,
size_t resp_ie_len,
u16 status,
gfp_t gfp)
{
#define _P2P_PENDING_CONNECT_TIME 800 /* in ms. */
struct p2p_pending_connect_info *pending_connect_info;
if (_p2p_need_pending_connect(vif, bssid)) {
if ((req_ie_len > ATH6KL_P2P_MAX_PENDING_INFO_IELEN) ||
(resp_ie_len > ATH6KL_P2P_MAX_PENDING_INFO_IELEN))
return false;
vif->pending_connect_info =
kzalloc(sizeof(struct p2p_pending_connect_info),
GFP_ATOMIC);
if (vif->pending_connect_info == NULL)
return false;
pending_connect_info = vif->pending_connect_info;
if (bssid)
memcpy(pending_connect_info->bssid, bssid, ETH_ALEN);
if (req_ie) {
memcpy(pending_connect_info->req_ie,
req_ie,
req_ie_len);
pending_connect_info->req_ie_len = req_ie_len;
}
if (resp_ie) {
memcpy(pending_connect_info->resp_ie,
resp_ie,
resp_ie_len);
pending_connect_info->resp_ie_len = resp_ie_len;
}
pending_connect_info->status = status;
pending_connect_info->gfp = gfp;
INIT_DELAYED_WORK(&vif->work_pending_connect,
_p2p_pending_connect_work);
schedule_delayed_work(&vif->work_pending_connect,
(msecs_to_jiffies(_P2P_PENDING_CONNECT_TIME)));
ath6kl_info("Enable Broadcom-GO compatibility, delay %d ms.\n",
_P2P_PENDING_CONNECT_TIME);
return true;
} else
return false;
#undef _P2P_PENDING_CONNECT_TIME
}
void ath6kl_p2p_pending_disconnect_event(struct ath6kl_vif *vif,
u16 reason,
u8 *ie,
size_t ie_len,
gfp_t gfp)
{
/* Flush pending work first if already fired. */
_p2p_flush_pending_connect(vif);
return;
}
bool ath6kl_p2p_ie_append(struct ath6kl_vif *vif, u8 mgmt_frame_type)
{
struct ath6kl *ar = vif->ar;
/*
* IOT : Some older APs' implementation may reject connection if
* concuurrent STA interface include P2P IEs even these APs
* don't understand P2P IEs.
*/
if (ar->p2p_concurrent &&
ar->p2p_dedicate &&
(vif->fw_vif_idx == 0) &&
(vif->nw_type == INFRA_NETWORK) &&
(ar->p2p_ie_not_append & mgmt_frame_type))
return false;
return true;
}
/* P802.11REVmb */
static struct p2p_oper_chan ath6kl_p2p_oper_chan[] = {
{ 81, 2412, 2472, 5, P2P_OPER_CHAN_BW_HT20}, /* CH1 - CH13 */
{ 115, 5180, 5240, 20, P2P_OPER_CHAN_BW_HT20}, /* CH36 - CH48 */
{ 116, 5180, 5220, 20, P2P_OPER_CHAN_BW_HT40_PLUS}, /* CH36 - CH44 */
{ 117, 5200, 5240, 20, P2P_OPER_CHAN_BW_HT40_MINUS}, /* CH40 - CH48 */
{ 124, 5745, 5805, 20, P2P_OPER_CHAN_BW_HT20}, /* CH149 - CH161 */
{ 125, 5745, 5805, 20, P2P_OPER_CHAN_BW_HT20}, /* CH149 - CH161 */
{ 126, 5745, 5785, 20, P2P_OPER_CHAN_BW_HT40_PLUS}, /* CH149 - CH157 */
{ 127, 5765, 5805, 20, P2P_OPER_CHAN_BW_HT40_MINUS}, /* CH153 - CH161 */
{ 0, 0, 0, 0, P2P_OPER_CHAN_BW_NULL},
};
bool ath6kl_p2p_is_p2p_channel(u32 freq)
{
struct p2p_oper_chan *p2p_oper_chan_map, *p2p_oper_chan;
u32 op_class, p2p_freq;
p2p_oper_chan_map = ath6kl_p2p_oper_chan;
for (op_class = 0; p2p_oper_chan_map[op_class].oper_class; op_class++) {
p2p_oper_chan = &p2p_oper_chan_map[op_class];
for (p2p_freq = p2p_oper_chan->min_chan_freq;
p2p_freq <= p2p_oper_chan->max_chan_freq;
p2p_freq += p2p_oper_chan->inc_freq) {
if (freq == p2p_freq) {
/* TODO : check bandwidth */
return true;
}
}
}
return false;
}
bool ath6kl_p2p_is_social_channel(u32 freq)
{
if ((freq == 2412) ||
(freq == 2437) ||
(freq == 2462))
return true;
return false;
}
static int _p2p_build_scan_chan(struct ath6kl *ar, u16 *chan_list)
{
struct wiphy *wiphy = ar->wiphy;
struct ieee80211_supported_band *sband;
struct ieee80211_channel *chan;
int i, num_chan = 0;
sband = wiphy->bands[NL80211_BAND_2GHZ];
for (i = 0; i < sband->n_channels; i++) {
chan = &sband->channels[i];
if (!(chan->flags & IEEE80211_CHAN_DISABLED) &&
ath6kl_p2p_is_p2p_channel(chan->center_freq))
chan_list[num_chan++] = chan->center_freq;
}
sband = wiphy->bands[NL80211_BAND_5GHZ];
for (i = 0; i < sband->n_channels; i++) {
chan = &sband->channels[i];
if (!(chan->flags & IEEE80211_CHAN_DISABLED) &&
ath6kl_p2p_is_p2p_channel(chan->center_freq))
chan_list[num_chan++] = chan->center_freq;
}
return num_chan;
}
int ath6kl_p2p_build_scan_chan(struct ath6kl_vif *vif,
u32 req_chan_num,
u16 *chan_list)
{
#define _P2P_WISE_FULL_SCAN_CNT (15)
struct ath6kl *ar = vif->ar;
bool full_chan_scan = false;
/*
* If this's P2P-Device's scan w/ only P2P-Social channel and assume
* the user is in P2P searching state and will insert a full channel
* scan every _P2P_WISE_FULL_SCAN_CNT time.
*/
if (ar->p2p_wise_scan) {
if (ar->p2p_dedicate &&
(vif->fw_vif_idx == (ar->vif_max - 1))) {
if (req_chan_num > 3)
vif->p2p_wise_full_scan = 0;
else if (req_chan_num == 3)
vif->p2p_wise_full_scan++;
if (vif->p2p_wise_full_scan > _P2P_WISE_FULL_SCAN_CNT) {
full_chan_scan = true;
vif->p2p_wise_full_scan = 0;
}
if (full_chan_scan)
return _p2p_build_scan_chan(ar, chan_list);
}
}
return 0;
#undef _P2P_WISE_FULL_SCAN_CNT
}