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