/* * 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 "debug.h" static u8 *htcoex_get_elem(int elem_id, u8 *start, u16 len) { size_t left = len; u8 *pos = start; u8 *elem_start = NULL; if ((start == NULL) || (len == 0)) return NULL; while (left >= 2) { u8 id, elen; id = *pos++; elen = *pos++; left -= 2; if (elen > left) break; if (id == elem_id) { elem_start = (void *)pos; break; } left -= elen; pos += elen; } return elem_start; } static void htcoex_flush_bss_info(struct htcoex *coex) { struct htcoex_bss_info *coex_bss, *tmp; spin_lock(&coex->bss_info_lock); list_for_each_entry_safe(coex_bss, tmp, &coex->bss_info_list, list) { list_del(&coex_bss->list); kfree(coex_bss->raw_frame); kfree(coex_bss); } spin_unlock(&coex->bss_info_lock); return; } static void htcoex_get_coexinfo(struct htcoex_bss_info *coex_bss, struct htcoex_coex_info *coex_info) { struct ieee80211_ht_cap *htcap; struct ieee80211_mgmt *frame; u16 cap_info; int chan_id = 0; frame = (struct ieee80211_mgmt *)(coex_bss->raw_frame); chan_id = ieee80211_frequency_to_channel( (int)(coex_bss->channel->center_freq)); htcap = (struct ieee80211_ht_cap *)htcoex_get_elem( WLAN_EID_HT_CAPABILITY, frame->u.beacon.variable, coex_bss->frame_len - (24 + 12)); if (htcap) { cap_info = le16_to_cpu(htcap->cap_info); ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex BSSID " "%02x:%02x:%02x:%02x:%02x:%02x htcap %04x\n", frame->bssid[0], frame->bssid[1], frame->bssid[2], frame->bssid[3], frame->bssid[4], frame->bssid[5], cap_info); if (cap_info & IEEE80211_HT_CAP_40MHZ_INTOLERANT) coex_info->intolerant40++; } else if (coex_bss->channel->band == IEEE80211_BAND_2GHZ) { int i; for (i = 0; i < coex_info->num_chans; i++) { if (chan_id == coex_info->chans[i]) break; } if (i == coex_info->num_chans) { /* chans only get size 14 */ BUG_ON(coex_info->num_chans >= 14); coex_info->chans[coex_info->num_chans++] = chan_id; } } return; } static void htcoex_scan_start(unsigned long arg) { struct htcoex *coex = (struct htcoex *) arg; struct ath6kl_vif *vif = coex->vif; struct ath6kl *ar; int ret; BUG_ON(!vif); ar = vif->ar; if ((vif->nw_type != INFRA_NETWORK) || !test_bit(CONNECTED, &vif->flags) || vif->scan_req || test_bit(EAPOL_HANDSHAKE_PROTECT, &ar->flag)) goto resche; ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex scan (vif %p) num_scan %d\n", vif, coex->num_scan); if (!vif->usr_bss_filter) { clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); ret = ath6kl_wmi_bssfilter_cmd( ar->wmi, vif->fw_vif_idx, ALL_BUT_BSS_FILTER, 0); if (ret) { ath6kl_err("couldn't set bss filtering\n"); goto resche; } } /* bypass this time if ROC is ongoing. */ if (test_bit(ROC_PEND, &vif->flags)) goto resche; ret = ath6kl_wmi_startscan_cmd(ar->wmi, vif->fw_vif_idx, WMI_LONG_SCAN, 0, false, 0, 0, coex->num_scan_channels, coex->scan_channels); if (ret) ath6kl_err("wmi_startscan_cmd failed\n"); else { vif->scan_req = &coex->request; coex->num_scan++; } resche: if ((coex->flags & ATH6KL_HTCOEX_FLAGS_START) && (coex->scan_interval)) mod_timer(&coex->scan_timer, jiffies + msecs_to_jiffies(coex->scan_interval)); return; } static void htcoex_send_action(struct ath6kl_vif *vif, struct htcoex_coex_info *coex_info) { struct ath6kl *ar = vif->ar; struct ieee80211_mgmt *action_frame; int len = 0; ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex send action (vif %p) intolerant40 %d num_chans %d bss_ch %d\n", vif, coex_info->intolerant40, coex_info->num_chans, vif->bss_ch); len = 24 + sizeof(struct ieee80211_action_public) + sizeof(struct ieee80211_bss_coex_ie) + sizeof(struct ieee80211_intolerant_chan_report_ie) + coex_info->num_chans; action_frame = kzalloc(len, GFP_KERNEL); if (action_frame) { struct ieee80211_action_public *action_public; struct ieee80211_bss_coex_ie *bss_coex_ie; struct ieee80211_intolerant_chan_report_ie *into_chan_report_ie; int i; /* Build action frame. */ action_frame->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION); memcpy(action_frame->da, vif->bssid, ETH_ALEN); memcpy(action_frame->sa, vif->ndev->dev_addr, ETH_ALEN); memcpy(action_frame->bssid, vif->bssid, ETH_ALEN); action_public = (struct ieee80211_action_public *) (&action_frame->u.action); action_public->category = WLAN_CATEGORY_PUBLIC; action_public->action_code = WLAN_COEX_ACTION_2040COEX_MGMT; bss_coex_ie = (struct ieee80211_bss_coex_ie *) (action_public->variable); bss_coex_ie->element_id = WLAN_EID_BSS_COEX_2040; bss_coex_ie->len = 1; if (coex_info->intolerant40) bss_coex_ie->value |= IEEE80211_COEX_IE_20_WIDTH_REQ; into_chan_report_ie = (struct ieee80211_intolerant_chan_report_ie *) (++bss_coex_ie); into_chan_report_ie->element_id = WLAN_EID_INTOLERANT_CHAN_REPORT; into_chan_report_ie->len = coex_info->num_chans + 1; into_chan_report_ie->reg_class = 0; for (i = 0; i < coex_info->num_chans; i++) into_chan_report_ie->chan_variable[i] = coex_info->chans[i]; ath6kl_dbg_dump(ATH6KL_DBG_RAW_BYTES, __func__, "tx-htcoex ", (u8 *)action_frame, len); BUG_ON(vif->bss_ch == 0); ath6kl_wmi_send_action_cmd(ar->wmi, vif->fw_vif_idx, vif->send_action_id++, vif->bss_ch, 0, (u8 *)action_frame, len); kfree(action_frame); } else { ath6kl_err("failed to alloc memory for action_frame\n"); } return; } static void htcoex_ht40_rateset(struct ath6kl_vif *vif, struct htcoex *coex, bool enabled) { struct ath6kl *ar = vif->ar; u64 ratemask; if (enabled) ratemask = ATH6KL_HTCOEX_RATEMASK_HT40; else ratemask = ATH6KL_HTCOEX_RATEMASK_HT20; ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex rateset (vif %p) HT40 rates %s, %llx -> %llx\n", vif, (enabled ? "Enabled" : "Disabled"), coex->current_ratemask, ratemask); if (coex->current_ratemask != ratemask) ath6kl_wmi_set_fix_rates(ar->wmi, vif->fw_vif_idx, ratemask); coex->current_ratemask = ratemask; return; } static int htcoex_clear_scan_channels(struct ath6kl_vif *vif) { struct htcoex *coex = vif->htcoex_ctx; if (!coex) return -EINVAL; kfree(coex->scan_channels); coex->scan_channels = NULL; coex->num_scan_channels = 0; return 0; } static int htcoex_set_scan_channels(struct ath6kl_vif *vif) { struct htcoex *coex = vif->htcoex_ctx; struct wiphy *wiphy = coex->request.wiphy; int i; if (!wiphy->bands[IEEE80211_BAND_2GHZ]) return -EINVAL; /*If the scan channel is already set, * clear it and apply new channel list. */ if (coex->num_scan_channels != 0 || coex->scan_channels) htcoex_clear_scan_channels(vif); /*scan_channels may be larger than what we actually need because some * of them might be disabled so we will filter them later. */ coex->scan_channels = kzalloc(wiphy->bands[IEEE80211_BAND_2GHZ]->n_channels * sizeof(u16), GFP_KERNEL); if (!coex->scan_channels) return -ENOMEM; /*Copy all enabled 2G channels.*/ for (i = 0; i < wiphy->bands[IEEE80211_BAND_2GHZ]->n_channels; i++) { struct ieee80211_channel *chan; chan = &wiphy->bands[IEEE80211_BAND_2GHZ]->channels[i]; if (chan->flags & IEEE80211_CHAN_DISABLED) continue; coex->scan_channels[coex->num_scan_channels++] = chan->center_freq; } return 0; } struct htcoex *ath6kl_htcoex_init(struct ath6kl_vif *vif) { struct htcoex *coex; coex = kzalloc(sizeof(struct htcoex), GFP_KERNEL); if (!coex) { ath6kl_err("failed to alloc memory for htcoex\n"); return NULL; } coex->vif = vif; /* Disable by default. */ coex->flags &= ~ATH6KL_HTCOEX_FLAGS_ENABLED; coex->scan_interval = ATH6KL_HTCOEX_SCAN_PERIOD; coex->rate_rollback_interval = ATH6KL_HTCOEX_RATE_ROLLBACK; /* Init. periodic scan timer. */ init_timer(&coex->scan_timer); coex->scan_timer.function = htcoex_scan_start; coex->scan_timer.data = (unsigned long) coex; #ifdef CFG80211_NETDEV_REPLACED_BY_WDEV coex->request.wdev = &vif->wdev; #else coex->request.dev = vif->ndev; #endif coex->request.wiphy = vif->ar->wiphy; coex->num_scan_channels = 0; coex->scan_channels = NULL; spin_lock_init(&coex->bss_info_lock); INIT_LIST_HEAD(&coex->bss_info_list); ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex init (vif %p interval %d)\n", vif, coex->scan_interval); return coex; } void ath6kl_htcoex_deinit(struct ath6kl_vif *vif) { struct htcoex *coex = vif->htcoex_ctx; if (coex) { if (coex->flags & ATH6KL_HTCOEX_FLAGS_START) { del_timer_sync(&coex->scan_timer); htcoex_clear_scan_channels(vif); } htcoex_flush_bss_info(coex); kfree(coex); } vif->htcoex_ctx = NULL; ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex deinit (vif %p)\n", vif); return; } void ath6kl_htcoex_bss_info(struct ath6kl_vif *vif, struct ieee80211_mgmt *mgmt, int len, struct ieee80211_channel *channel) { struct htcoex *coex = vif->htcoex_ctx; struct htcoex_bss_info *coex_bss, *tmp; int match = 0; /* Add bss even scan not issue by htcoex. */ if (!(coex->flags & ATH6KL_HTCOEX_FLAGS_START) || (vif->nw_type != INFRA_NETWORK) || (memcmp(mgmt->bssid, vif->bssid, ETH_ALEN) == 0)) return; ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex bssinfo (vif %p) BSSID " "%02x:%02x:%02x:%02x:%02x:%02x\n", vif, mgmt->bssid[0], mgmt->bssid[1], mgmt->bssid[2], mgmt->bssid[3], mgmt->bssid[4], mgmt->bssid[5]); if (!list_empty(&coex->bss_info_list)) { list_for_each_entry_safe(coex_bss, tmp, &coex->bss_info_list, list) { if (memcmp(coex_bss->bssid, mgmt->bssid, ETH_ALEN) == 0) { match = 1; break; } } } if (!match) { coex_bss = kzalloc(sizeof(struct htcoex_bss_info), GFP_KERNEL); if (!coex_bss) { ath6kl_err("failed to alloc memory for coex_bss\n"); return; } coex_bss->raw_frame = kmalloc(len, GFP_KERNEL); if (!coex_bss->raw_frame) { kfree(coex_bss); ath6kl_err("failed to alloc memory for coex_bss->raw_frame\n"); return; } /* Add BSS info. */ coex_bss->frame_len = len; memcpy(coex_bss->raw_frame, mgmt, len); memcpy(coex_bss->bssid, mgmt->bssid, ETH_ALEN); coex_bss->channel = channel; spin_lock(&coex->bss_info_lock); list_add_tail(&coex_bss->list, &coex->bss_info_list); spin_unlock(&coex->bss_info_lock); } return; } int ath6kl_htcoex_scan_complete_event(struct ath6kl_vif *vif, bool aborted) { struct htcoex *coex = vif->htcoex_ctx; struct htcoex_bss_info *coex_bss, *tmp; struct htcoex_coex_info coex_info; int ret = HTCOEX_PASS_SCAN_DONE; if (!coex) { ath6kl_err("%s %lu\n",__func__, vif->ar->flag); return ret; } /* Send Action frame even scan issue by user. */ if (!(coex->flags & ATH6KL_HTCOEX_FLAGS_START) || (vif->nw_type != INFRA_NETWORK) || (!vif->scan_req)) goto done; ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex scan done (vif %p aborted %d) flags %x\n", vif, aborted, coex->flags); memset(&coex_info, 0, sizeof(struct htcoex_coex_info)); /* Parse all Beacon/ProbeResp's IEs to find 20/40 coex. information. */ list_for_each_entry_safe(coex_bss, tmp, &coex->bss_info_list, list) { htcoex_get_coexinfo(coex_bss, &coex_info); } /* Send action frame to AP. */ if (coex_info.intolerant40 || coex_info.num_chans) htcoex_send_action(vif, &coex_info); /* Disable HT40 rates. */ if (coex_info.intolerant40) { htcoex_ht40_rateset(vif, coex, false); coex->tolerant40_cnt = 0; } else { /* Roll-back to HT40 rate if acceptable. */ if (coex->rate_rollback_interval && (++coex->tolerant40_cnt > coex->rate_rollback_interval)) htcoex_ht40_rateset(vif, coex, true); } /* Issue by htcoex. */ if (vif->scan_req == &coex->request) { vif->scan_req = NULL; ret = HTCOEX_PORC_SCAN_DONE; } done: /* Always flush it. */ htcoex_flush_bss_info(coex); return ret; } void ath6kl_htcoex_connect_event(struct ath6kl_vif *vif) { struct htcoex *coex = vif->htcoex_ctx; struct cfg80211_bss *bss; u8 *ies = NULL; u16 ies_len = 0; if (vif->nw_type != INFRA_NETWORK) return; bss = ath6kl_bss_get(vif->ar, NULL, vif->bssid, vif->ssid, vif->ssid_len, WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS); if (!bss) { WARN_ON(1); return; } #ifdef CFG80211_SAFE_BSS_INFO_ACCESS rcu_read_lock(); if (bss->ies) { ies = (u8 *)(bss->ies->data); ies_len = bss->ies->len; } #else if (bss->information_elements) { ies = bss->information_elements; ies_len = bss->len_information_elements; } #endif /* Start if BSS is 11n and 2G channel. */ if ((coex->flags & ATH6KL_HTCOEX_FLAGS_ENABLED) && (bss->channel->band == IEEE80211_BAND_2GHZ) && (htcoex_get_elem(WLAN_EID_HT_CAPABILITY, ies, ies_len) != NULL)){ if (coex->flags & ATH6KL_HTCOEX_FLAGS_START) del_timer(&coex->scan_timer); if (coex->scan_interval < ATH6KL_HTCOEX_SCAN_PERIOD) coex->scan_interval = ATH6KL_HTCOEX_SCAN_PERIOD; /*htcoex set scan channels*/ if (htcoex_set_scan_channels(vif) == 0) { mod_timer(&coex->scan_timer, jiffies + msecs_to_jiffies(coex->scan_interval)); coex->flags |= ATH6KL_HTCOEX_FLAGS_START; } else { ath6kl_err("htcoex set scan channels failed\n"); WARN_ON(1); } } else coex->flags &= ~ATH6KL_HTCOEX_FLAGS_START; #ifdef CFG80211_SAFE_BSS_INFO_ACCESS rcu_read_unlock(); #endif coex->num_scan = 0; coex->tolerant40_cnt = 0; coex->current_ratemask = ATH6KL_HTCOEX_RATEMASK_FULL; htcoex_flush_bss_info(coex); ath6kl_bss_put(vif->ar, bss); ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex connect (vif %p) flags %x interval %d cycle %d\n", vif, coex->flags, coex->scan_interval, coex->rate_rollback_interval); return; } void ath6kl_htcoex_disconnect_event(struct ath6kl_vif *vif) { struct htcoex *coex = vif->htcoex_ctx; if (vif->nw_type != INFRA_NETWORK) return; if (!coex) { ath6kl_err("%s %lu\n",__func__, vif->ar->flag); return; } ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex disconnect (vif %p) flags %x\n", vif, coex->flags); if (coex->flags & ATH6KL_HTCOEX_FLAGS_START) { del_timer(&coex->scan_timer); coex->flags &= ~ATH6KL_HTCOEX_FLAGS_START; /* Back to full rates */ ath6kl_wmi_set_fix_rates(vif->ar->wmi, vif->fw_vif_idx, ATH6KL_HTCOEX_RATEMASK_FULL); /*clear the scan channels*/ htcoex_clear_scan_channels(vif); } htcoex_flush_bss_info(coex); return; } int ath6kl_htcoex_config(struct ath6kl_vif *vif, u32 interval, u8 rate_rollback) { struct htcoex *coex = vif->htcoex_ctx; int restart = 0; coex->scan_interval = interval; if (coex->flags & ATH6KL_HTCOEX_FLAGS_START) { del_timer(&coex->scan_timer); htcoex_clear_scan_channels(vif); coex->flags &= ~ATH6KL_HTCOEX_FLAGS_START; restart = 1; } if (coex->scan_interval == 0) coex->flags &= ~ATH6KL_HTCOEX_FLAGS_ENABLED; else { if (coex->scan_interval < ATH6KL_HTCOEX_SCAN_PERIOD) coex->scan_interval = ATH6KL_HTCOEX_SCAN_PERIOD; coex->flags |= ATH6KL_HTCOEX_FLAGS_ENABLED; if (restart) { if (htcoex_set_scan_channels(vif) == 0) { mod_timer(&coex->scan_timer, jiffies + msecs_to_jiffies(coex->scan_interval)); coex->flags |= ATH6KL_HTCOEX_FLAGS_START; } else { ath6kl_err("htcoex set scan channels failed\n"); WARN_ON(1); } } coex->rate_rollback_interval = rate_rollback; } htcoex_flush_bss_info(coex); ath6kl_dbg(ATH6KL_DBG_HTCOEX, "htcoex config (vif %p interval %d %s rate_rollback %d restart %d)\n", vif, coex->scan_interval, (coex->flags & ATH6KL_HTCOEX_FLAGS_ENABLED) ? "ON" : "OFF", coex->rate_rollback_interval, restart); return 0; }