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

1747 lines
42 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 "debug.h"
#include "hif-ops.h"
static inline enum ap_keepalive_adjust __ap_keepalive_adjust_txrx_time(
struct ath6kl_sta *conn, u16 last_txrx_time,
unsigned long now, u32 rx_pkts)
{
u32 diff_ms;
enum ap_keepalive_adjust adjust_result = AP_KA_ADJ_ERROR;
if (conn->last_txrx_time_tgt || conn->last_rx_pkts) {
if (last_txrx_time >= conn->last_txrx_time_tgt)
diff_ms = (last_txrx_time -
conn->last_txrx_time_tgt) << 10;
else /* wrap */
diff_ms = (0xffff -
(conn->last_txrx_time_tgt - last_txrx_time))
<< 10;
/* Update to last one. */
conn->last_txrx_time_tgt = last_txrx_time;
conn->last_txrx_time += msecs_to_jiffies(diff_ms);
if (conn->last_txrx_time > now)
conn->last_txrx_time = now;
if (conn->last_rx_pkts != rx_pkts) {
conn->last_rx_pkts = rx_pkts;
conn->last_txrx_time = now;
}
adjust_result = AP_KA_ADJ_ADJUST;
} else {
/* First updated, treat as base time. */
conn->last_txrx_time_tgt = last_txrx_time;
conn->last_txrx_time = now;
conn->last_rx_pkts = rx_pkts;
adjust_result = AP_KA_ADJ_BASESET;
}
return adjust_result;
}
static int _ap_keepalive_update_check_txrx_time(struct ath6kl_vif *vif,
struct ath6kl_sta *conn,
u16 last_txrx_time,
u32 rx_pkts)
{
struct ap_keepalive_info *ap_keepalive = vif->ap_keepalive_ctx;
unsigned long now = jiffies;
enum ap_keepalive_adjust adjust_result;
int action = AP_KA_ACTION_NONE;
spin_lock_bh(&conn->lock);
adjust_result = __ap_keepalive_adjust_txrx_time(conn,
last_txrx_time,
now,
rx_pkts);
if (adjust_result == AP_KA_ADJ_ADJUST) {
if ((now - conn->last_txrx_time) >=
msecs_to_jiffies(ap_keepalive->ap_ka_interval))
action = AP_KA_ACTION_POLL;
if ((now - conn->last_txrx_time) >=
msecs_to_jiffies(ap_keepalive->ap_ka_remove_time))
action = AP_KA_ACTION_REMOVE;
}
spin_unlock_bh(&conn->lock);
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive check (aid %d tgt/hst/now/rx_pkt %d %ld %ld %d %s\n",
conn->aid,
conn->last_txrx_time_tgt,
conn->last_txrx_time,
now,
conn->last_rx_pkts,
(action == AP_KA_ACTION_NONE) ? "NONE" :
((action == AP_KA_ACTION_POLL) ? "POLL" : "REMOVE"));
return action;
}
static int ap_keepalive_preload_txrx_time(struct ath6kl_vif *vif)
{
struct ath6kl *ar = vif->ar;
int ret = 0;
/* Get AP stats only at least one station associated. */
if (vif->sta_list_index)
ret = ath6kl_wmi_get_stats_cmd(ar->wmi, vif->fw_vif_idx);
return ret;
}
static int ap_keepalive_update_check_txrx_time(struct ath6kl_vif *vif)
{
#define _KICKOUT_CAUSE (WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY)
struct ath6kl *ar = vif->ar;
struct wmi_ap_mode_stat *ap_stats = &vif->ap_stats;
struct wmi_per_sta_stat *per_sta_stat;
struct ath6kl_sta *conn;
int i, action;
if (test_bit(STATS_UPDATE_PEND, &vif->flags)) {
ath6kl_info("somebody still query now and ignore it.\n");
return -EBUSY;
}
/* Now, tranfer to host time and update to vif->sta_list[]. */
for (i = 0; i < AP_MAX_NUM_STA; i++) {
per_sta_stat = &ap_stats->sta[i];
if (per_sta_stat->aid) {
conn = ath6kl_find_sta_by_aid(vif, per_sta_stat->aid);
if (conn) {
action = _ap_keepalive_update_check_txrx_time(
vif,
conn,
per_sta_stat->last_txrx_time,
per_sta_stat->rx_pkts);
if (action == AP_KA_ACTION_POLL) {
ath6kl_wmi_ap_poll_sta(ar->wmi,
vif->fw_vif_idx,
conn->aid);
} else if (action == AP_KA_ACTION_REMOVE) {
ath6kl_wmi_ap_set_mlme(ar->wmi,
vif->fw_vif_idx,
WMI_AP_DEAUTH,
conn->mac,
_KICKOUT_CAUSE);
}
} else
ath6kl_err("can't find this AID %d\n",
per_sta_stat->aid);
}
}
return 0;
#undef _KICKOUT_CAUSE
}
static void ap_keepalive_start(unsigned long arg)
{
struct ap_keepalive_info *ap_keepalive =
(struct ap_keepalive_info *) arg;
struct ath6kl_vif *vif = ap_keepalive->vif;
int ret;
BUG_ON(!vif);
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive timer (vif idx %d) sta_list_index %x %s\n",
vif->fw_vif_idx,
vif->sta_list_index,
(ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_PRELOAD_STAT) ?
"preload" : "update check");
if ((vif->nw_type == AP_NETWORK) &&
test_bit(CONNECTED, &vif->flags)) {
if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_PRELOAD_STAT) {
ret = ap_keepalive_preload_txrx_time(vif);
if (ret)
ath6kl_err("preload last_txrx_time fail\n");
} else {
/* Update and check last TXRX time each stations. */
ret = ap_keepalive_update_check_txrx_time(vif);
if (ret)
ath6kl_err("updatecheck last_txrx_time fail\n");
}
if ((ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_START) &&
(ap_keepalive->ap_ka_interval)) {
if (ap_keepalive->flags &
ATH6KL_AP_KA_FLAGS_PRELOAD_STAT) {
mod_timer(&ap_keepalive->ap_ka_timer,
jiffies +
msecs_to_jiffies(
ATH6KL_AP_KA_PRELOAD_LEADTIME));
ap_keepalive->flags &=
~ATH6KL_AP_KA_FLAGS_PRELOAD_STAT;
} else {
mod_timer(&ap_keepalive->ap_ka_timer,
jiffies +
msecs_to_jiffies(
ap_keepalive->ap_ka_interval) -
msecs_to_jiffies(
ATH6KL_AP_KA_PRELOAD_LEADTIME));
ap_keepalive->flags |=
ATH6KL_AP_KA_FLAGS_PRELOAD_STAT;
}
}
}
return;
}
struct ap_keepalive_info *ath6kl_ap_keepalive_init(struct ath6kl_vif *vif,
enum ap_keepalive_mode mode)
{
struct ap_keepalive_info *ap_keepalive;
ap_keepalive = kzalloc(sizeof(struct ap_keepalive_info), GFP_KERNEL);
if (!ap_keepalive) {
ath6kl_err("failed to alloc memory for ap_keepalive\n");
return NULL;
}
ap_keepalive->vif = vif;
ap_keepalive->ap_ka_interval = 0;
ap_keepalive->ap_ka_reclaim_cycle = 0;
if ((mode == AP_KA_MODE_ENABLE) ||
(mode == AP_KA_MODE_CONFIG_BYSUPP)) {
ap_keepalive->flags |= ATH6KL_AP_KA_FLAGS_ENABLED;
ap_keepalive->ap_ka_interval = ATH6KL_AP_KA_INTERVAL_DEFAULT;
ap_keepalive->ap_ka_reclaim_cycle = ATH6KL_AP_KA_RECLAIM_CYCLE;
if (mode == AP_KA_MODE_CONFIG_BYSUPP)
ap_keepalive->flags |=
ATH6KL_AP_KA_FLAGS_CONFIG_BY_SUPP;
} else if (mode == AP_KA_MODE_BYSUPP)
ap_keepalive->flags |= ATH6KL_AP_KA_FLAGS_BY_SUPP;
ap_keepalive->ap_ka_remove_time = ap_keepalive->ap_ka_interval *
ap_keepalive->ap_ka_reclaim_cycle;
/* Init. periodic scan timer. */
init_timer(&ap_keepalive->ap_ka_timer);
ap_keepalive->ap_ka_timer.function = ap_keepalive_start;
ap_keepalive->ap_ka_timer.data = (unsigned long) ap_keepalive;
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive init (vif idx %d) interval %d cycle %d %s\n",
vif->fw_vif_idx,
ap_keepalive->ap_ka_interval,
ap_keepalive->ap_ka_reclaim_cycle,
(ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_ENABLED) ?
((ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_CONFIG_BY_SUPP) ?
"ON-SUPP" : "ON") :
((ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_BY_SUPP) ?
"SUPP" : "OFF"));
return ap_keepalive;
}
void ath6kl_ap_keepalive_deinit(struct ath6kl_vif *vif)
{
struct ap_keepalive_info *ap_keepalive = vif->ap_keepalive_ctx;
if (ap_keepalive) {
if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_START)
del_timer(&ap_keepalive->ap_ka_timer);
kfree(ap_keepalive);
}
vif->ap_keepalive_ctx = NULL;
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive deinit (vif idx %d)\n",
vif->fw_vif_idx);
return;
}
int ath6kl_ap_keepalive_start(struct ath6kl_vif *vif)
{
struct ap_keepalive_info *ap_keepalive = vif->ap_keepalive_ctx;
BUG_ON(!ap_keepalive);
BUG_ON(vif->nw_type != AP_NETWORK);
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive start (vif idx %d) flags %x\n",
vif->fw_vif_idx,
ap_keepalive->flags);
if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_ENABLED) {
mod_timer(&ap_keepalive->ap_ka_timer,
jiffies +
msecs_to_jiffies(ap_keepalive->ap_ka_interval) -
msecs_to_jiffies(ATH6KL_AP_KA_PRELOAD_LEADTIME));
ap_keepalive->flags |= (ATH6KL_AP_KA_FLAGS_START |
ATH6KL_AP_KA_FLAGS_PRELOAD_STAT);
ath6kl_wmi_set_inact_cmd(vif->ar->wmi,
DISALBE_AP_INACTIVE_TIMEMER);
}
return 0;
}
void ath6kl_ap_keepalive_stop(struct ath6kl_vif *vif)
{
struct ap_keepalive_info *ap_keepalive = vif->ap_keepalive_ctx;
if (!ap_keepalive)
return;
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive stop (vif idx %d) flags %x\n",
vif->fw_vif_idx,
ap_keepalive->flags);
if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_START) {
del_timer(&ap_keepalive->ap_ka_timer);
ap_keepalive->flags &= ~(ATH6KL_AP_KA_FLAGS_START |
ATH6KL_AP_KA_FLAGS_PRELOAD_STAT);
}
return;
}
int ath6kl_ap_keepalive_config(struct ath6kl_vif *vif,
u32 ap_ka_interval,
u32 ap_ka_reclaim_cycle)
{
struct ap_keepalive_info *ap_keepalive = vif->ap_keepalive_ctx;
int restart = 0;
if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_BY_SUPP) {
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive offlad to supplicant/hostapd.\n");
return 0;
} else if (!(ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_ENABLED)) {
return 0;
} else if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_CONFIG_BY_SUPP) {
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive config offlad to supplicant/hostapd.\n");
return 0;
}
if ((ap_ka_interval != 0) &&
(ap_ka_interval < ATH6KL_AP_KA_INTERVAL_MIN))
ap_ka_interval = ATH6KL_AP_KA_INTERVAL_MIN;
if (ap_ka_reclaim_cycle == 0)
ap_ka_reclaim_cycle = 1;
if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_START) {
del_timer(&ap_keepalive->ap_ka_timer);
ap_keepalive->flags &= ~(ATH6KL_AP_KA_FLAGS_START |
ATH6KL_AP_KA_FLAGS_PRELOAD_STAT);
restart = 1;
}
if (ap_ka_interval == 0) {
ap_keepalive->ap_ka_interval = 0;
ap_keepalive->ap_ka_reclaim_cycle = 0;
ap_keepalive->flags &= ~ATH6KL_AP_KA_FLAGS_ENABLED;
} else {
if (ap_ka_interval * ap_ka_reclaim_cycle <
ATH6KL_AP_KA_RECLAIM_TIME_MAX) {
ap_keepalive->ap_ka_interval = ap_ka_interval;
ap_keepalive->ap_ka_reclaim_cycle = ap_ka_reclaim_cycle;
} else {
ap_keepalive->ap_ka_interval =
ATH6KL_AP_KA_INTERVAL_DEFAULT;
ap_keepalive->ap_ka_reclaim_cycle =
ATH6KL_AP_KA_RECLAIM_CYCLE;
}
ap_keepalive->ap_ka_remove_time =
ap_keepalive->ap_ka_interval *
ap_keepalive->ap_ka_reclaim_cycle;
ap_keepalive->flags |= ATH6KL_AP_KA_FLAGS_ENABLED;
if (restart) {
mod_timer(&ap_keepalive->ap_ka_timer,
jiffies +
msecs_to_jiffies(
ap_keepalive->ap_ka_interval) -
msecs_to_jiffies(
ATH6KL_AP_KA_PRELOAD_LEADTIME * 1000));
ap_keepalive->flags |=
(ATH6KL_AP_KA_FLAGS_START |
ATH6KL_AP_KA_FLAGS_PRELOAD_STAT);
}
}
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive config (%d intvl %d cycle %d %s restart %d)\n",
vif->fw_vif_idx,
ap_keepalive->ap_ka_interval,
ap_keepalive->ap_ka_reclaim_cycle,
(ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_ENABLED) ?
"ON" : "OFF",
restart);
return 0;
}
int ath6kl_ap_keepalive_config_by_supp(struct ath6kl_vif *vif,
u16 inactive_time)
{
struct ap_keepalive_info *ap_keepalive = vif->ap_keepalive_ctx;
u32 ap_ka_interval;
u32 timeout = inactive_time * 1000; /* to ms. */
if (!(ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_ENABLED) ||
!(ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_CONFIG_BY_SUPP))
return 0;
if (timeout == 0) {
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive config wrong inactive_time!\n");
return 0;
}
/* Min. at lease 1 cycle */
if (timeout < (ATH6KL_AP_KA_INTERVAL_MIN * 1))
timeout = (ATH6KL_AP_KA_INTERVAL_MIN * 1);
else if (timeout > ATH6KL_AP_KA_RECLAIM_TIME_MAX)
timeout = ATH6KL_AP_KA_RECLAIM_TIME_MAX;
if (timeout < (ATH6KL_AP_KA_INTERVAL_DEFAULT *
ATH6KL_AP_KA_RECLAIM_CYCLE)) {
if (timeout <= ATH6KL_AP_KA_INTERVAL_DEFAULT)
ap_ka_interval = ATH6KL_AP_KA_INTERVAL_MIN;
else
ap_ka_interval = ATH6KL_AP_KA_INTERVAL_DEFAULT;
} else
ap_ka_interval = ATH6KL_AP_KA_INTERVAL_DEFAULT;
/* Update the config */
ap_keepalive->ap_ka_interval = ap_ka_interval;
ap_keepalive->ap_ka_reclaim_cycle = timeout / ap_ka_interval;
ap_keepalive->ap_ka_remove_time =
ap_ka_interval * ap_keepalive->ap_ka_reclaim_cycle;
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive config_by_supp (%d supp %d intvl %d cycle %d)\n",
vif->fw_vif_idx,
inactive_time,
ap_keepalive->ap_ka_interval,
ap_keepalive->ap_ka_reclaim_cycle);
return 0;
}
/* Offload to supplicant/hostapd */
static u32 ap_keepalive_get_inactive_time(struct ath6kl_vif *vif,
struct ath6kl_sta *conn)
{
struct wmi_ap_mode_stat *ap_stats = &vif->ap_stats;
u32 inact_time = 0;
int i;
for (i = 0; i < AP_MAX_NUM_STA; i++) {
struct wmi_per_sta_stat *per_sta_stat = &ap_stats->sta[i];
if (per_sta_stat->aid == conn->aid) {
unsigned long now = jiffies;
spin_lock_bh(&conn->lock);
__ap_keepalive_adjust_txrx_time(conn,
per_sta_stat->last_txrx_time,
now,
per_sta_stat->rx_pkts);
/* get inactive time. */
inact_time = jiffies_to_msecs(now -
conn->last_txrx_time);
spin_unlock_bh(&conn->lock);
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive inact tgt/hst/now %d %ld %ld\n",
conn->last_txrx_time_tgt,
conn->last_txrx_time,
now);
}
}
return inact_time;
}
u32 ath6kl_ap_keepalive_get_inactive_time(struct ath6kl_vif *vif, u8 *mac)
{
struct ath6kl_sta *conn;
u32 inact_time;
conn = ath6kl_find_sta(vif, mac);
if (conn)
inact_time = ap_keepalive_get_inactive_time(vif, conn);
else {
inact_time = 0; /* return -1 ? */
ath6kl_err("can't find %02x:%02x:%02x:%02x:%02x:%02x vif %d\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
vif->fw_vif_idx);
}
ath6kl_dbg(ATH6KL_DBG_KEEPALIVE,
"ap_keepalive inact aid %d inact_time %d ms\n",
(conn ? (conn->aid) : 0),
inact_time);
return inact_time;
}
bool ath6kl_ap_keepalive_by_supp(struct ath6kl_vif *vif)
{
struct ap_keepalive_info *ap_keepalive = vif->ap_keepalive_ctx;
if (!ap_keepalive)
return false;
if (ap_keepalive->flags & ATH6KL_AP_KA_FLAGS_BY_SUPP)
return true;
else
return false;
}
struct ap_acl_info *ath6kl_ap_acl_init(struct ath6kl_vif *vif)
{
struct ap_acl_info *ap_acl;
ap_acl = kzalloc(sizeof(struct ap_acl_info), GFP_KERNEL);
if (!ap_acl) {
ath6kl_err("failed to alloc memory for ap_acl\n");
return NULL;
}
ap_acl->vif = vif;
/* Default is OFF and configure it from debugfs or others */
ap_acl->acl_mode = AP_ACL_MODE_DISABLE;
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl init (vif idx %d)\n",
vif->fw_vif_idx);
return ap_acl;
}
void ath6kl_ap_acl_deinit(struct ath6kl_vif *vif)
{
struct ap_acl_info *ap_acl = vif->ap_acl_ctx;
if (ap_acl != NULL) {
if (ap_acl->last_acl_config != NULL)
kfree(ap_acl->last_acl_config);
kfree(ap_acl);
}
vif->ap_acl_ctx = NULL;
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl deinit (vif idx %d)\n",
vif->fw_vif_idx);
return;
}
int ath6kl_ap_acl_start(struct ath6kl_vif *vif)
{
struct ap_acl_info *ap_acl = vif->ap_acl_ctx;
if (ap_acl) {
if (ap_acl->last_acl_config != NULL)
kfree(ap_acl->last_acl_config);
ap_acl->last_acl_config = NULL;
}
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl start (vif idx %d)\n",
vif->fw_vif_idx);
return 0;
}
int ath6kl_ap_acl_stop(struct ath6kl_vif *vif)
{
struct ap_acl_info *ap_acl = vif->ap_acl_ctx;
if (ap_acl) {
ath6kl_ap_acl_config_mac_list_reset(vif);
ath6kl_ap_acl_config_policy(vif, AP_ACL_MODE_DISABLE);
if (ap_acl->last_acl_config != NULL)
kfree(ap_acl->last_acl_config);
ap_acl->last_acl_config = NULL;
}
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl stop (vif idx %d)\n",
vif->fw_vif_idx);
return 0;
}
int ath6kl_ap_acl_config_policy(struct ath6kl_vif *vif,
enum ap_acl_mode mode)
{
struct ap_acl_info *ap_acl = vif->ap_acl_ctx;
struct ap_acl_entry *ap_acl_entry;
u8 i, policy;
if (ap_acl == NULL)
return 0;
switch (mode) {
case AP_ACL_MODE_DISABLE:
policy = AP_ACL_DISABLE;
break;
case AP_ACL_MODE_ALLOW:
policy = AP_ACL_ALLOW_MAC;
break;
case AP_ACL_MODE_DENY:
policy = AP_ACL_DENY_MAC;
break;
default:
BUG_ON(1);
}
ap_acl->acl_mode = mode;
ath6kl_wmi_ap_acl_policy(vif->ar->wmi, vif->fw_vif_idx, policy);
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl config (vif idx %d mode %s)\n",
vif->fw_vif_idx,
(ap_acl->acl_mode ?
(ap_acl->acl_mode == AP_ACL_MODE_ALLOW ?
"ALLOW" : "DENY") :
"DISABLED"));
if (ap_acl->acl_mode != AP_ACL_MODE_DISABLE) {
for (i = 0; i < ATH6KL_AP_ACL_MAX_NUM; i++) {
ap_acl_entry = &ap_acl->acl_list[i];
if (ap_acl_entry->flags & ATH6KL_AP_ACL_FLAGS_USED) {
ath6kl_wmi_ap_acl_mac_list(vif->ar->wmi,
vif->fw_vif_idx,
i,
ap_acl_entry->mac_addr,
ADD_MAC_ADDR);
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl config ADD "
"%02x:%02x:%02x:%02x:%02x:%02x\n",
ap_acl_entry->mac_addr[0],
ap_acl_entry->mac_addr[1],
ap_acl_entry->mac_addr[2],
ap_acl_entry->mac_addr[3],
ap_acl_entry->mac_addr[4],
ap_acl_entry->mac_addr[5]);
}
}
}
return 0;
}
int ath6kl_ap_acl_config_mac_list(struct ath6kl_vif *vif,
u8 *mac_addr, bool removed)
{
struct ap_acl_info *ap_acl = vif->ap_acl_ctx;
struct ap_acl_entry *ap_acl_entry;
int i, slot_idx, first_not_used = 0xff;
u8 action;
if (ap_acl == NULL)
return 0;
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl config %02x:%02x:%02x:%02x:%02x:%02x ",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
for (i = 0; i < ATH6KL_AP_ACL_MAX_NUM; i++) {
ap_acl_entry = &ap_acl->acl_list[i];
if (ap_acl_entry->flags & ATH6KL_AP_ACL_FLAGS_USED) {
if (memcmp(ap_acl_entry->mac_addr,
mac_addr,
ETH_ALEN) == 0) {
ath6kl_dbg(ATH6KL_DBG_ACL,
"found in slot %d\n",
i);
break;
}
} else if (first_not_used == 0xff)
first_not_used = i;
}
if (removed) {
action = DEL_MAC_ADDR;
slot_idx = i;
if (i == ATH6KL_AP_ACL_MAX_NUM) {
ath6kl_dbg(ATH6KL_DBG_ACL,
"not found and ignore it.\n");
return 0;
}
ap_acl_entry = &ap_acl->acl_list[slot_idx];
ap_acl_entry->flags &= ~ATH6KL_AP_ACL_FLAGS_USED;
} else {
action = ADD_MAC_ADDR;
slot_idx = first_not_used;
if (i != ATH6KL_AP_ACL_MAX_NUM)
return 0;
if (first_not_used == 0xff) {
ath6kl_dbg(ATH6KL_DBG_ACL,
"no availabe ACL slot!\n");
return 0;
}
ap_acl_entry = &ap_acl->acl_list[slot_idx];
ap_acl_entry->flags |= ATH6KL_AP_ACL_FLAGS_USED;
memcpy(ap_acl_entry->mac_addr, mac_addr, ETH_ALEN);
}
if (ap_acl->acl_mode != AP_ACL_MODE_DISABLE) {
ath6kl_wmi_ap_acl_mac_list(vif->ar->wmi,
vif->fw_vif_idx,
slot_idx,
ap_acl_entry->mac_addr,
action);
}
ath6kl_dbg(ATH6KL_DBG_ACL,
"ap_acl config %s %02x:%02x:%02x:%02x:%02x:%02x, slot %d\n",
((removed) ? "DEL" : "ADD"),
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5],
slot_idx);
return 0;
}
int ath6kl_ap_acl_config_mac_list_reset(struct ath6kl_vif *vif)
{
struct ap_acl_info *ap_acl = vif->ap_acl_ctx;
struct ap_acl_entry *ap_acl_entry;
int i;
if (ap_acl == NULL)
return 0;
ath6kl_dbg(ATH6KL_DBG_ACL, "ap_acl reset\n");
for (i = 0; i < ATH6KL_AP_ACL_MAX_NUM; i++) {
ap_acl_entry = &ap_acl->acl_list[i];
if (ap_acl_entry->flags & ATH6KL_AP_ACL_FLAGS_USED)
ath6kl_ap_acl_config_mac_list(vif,
ap_acl_entry->mac_addr,
true);
}
return 0;
}
int ath6kl_ap_acl_dump(struct ath6kl *ar, u8 *buf, int buf_len)
{
int i, len = 0;
if (!buf)
return 0;
for (i = 0; i < ar->vif_max; i++) {
struct ath6kl_vif *vif;
vif = ath6kl_get_vif_by_index(ar, i);
if ((vif) &&
(vif->nw_type == AP_NETWORK)) {
struct ap_acl_info *ap_acl;
len += snprintf(buf + len, buf_len - len,
"\nVAP%d - ",
vif->fw_vif_idx);
ap_acl = vif->ap_acl_ctx;
if (!ap_acl)
return 0;
len += snprintf(buf + len, buf_len - len, "mode %d\n",
ap_acl->acl_mode);
for (i = 0; i < ATH6KL_AP_ACL_MAX_NUM; i++) {
struct ap_acl_entry *ap_acl_entry;
ap_acl_entry = &ap_acl->acl_list[i];
len += snprintf(buf + len, buf_len - len,
" %02d/%08x - "
"%02x:%02x:%02x:%02x:%02x:%02x\n",
i,
ap_acl_entry->flags,
ap_acl_entry->mac_addr[0],
ap_acl_entry->mac_addr[1],
ap_acl_entry->mac_addr[2],
ap_acl_entry->mac_addr[3],
ap_acl_entry->mac_addr[4],
ap_acl_entry->mac_addr[5]);
}
}
}
return len;
}
static inline void _ap_amdc_assoc_req_free(struct ap_admc_info *ap_admc,
struct ap_admc_assoc_req *admc_assoc_req)
{
if (admc_assoc_req) {
/*
* TODO: Check admc_assoc_req->action then do something.
*/
kfree(admc_assoc_req);
} else {
/*
* This STA reject by Admission-Control check rule
* or firmware's frame parser.
*/
;
}
return;
}
static void _ap_amdc_assoc_req_timeout(unsigned long arg)
{
struct ap_admc_assoc_req *admc_assoc_req;
struct ap_admc_info *ap_admc;
admc_assoc_req = (struct ap_admc_assoc_req *)arg;
ap_admc = admc_assoc_req->ap_admc;
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc timeout assoResp %p len %d depth %d\n",
admc_assoc_req,
admc_assoc_req->frame_len,
ap_admc->assoc_req_cnt);
admc_assoc_req->action = AP_ADMC_ACT_TIMEOUT;
list_del(&admc_assoc_req->list);
ap_admc->assoc_req_cnt--;
_ap_amdc_assoc_req_free(ap_admc, admc_assoc_req);
return;
}
static inline void _ap_amdc_assoc_req_add(struct ap_admc_info *ap_admc,
u8 *frm,
u16 len)
{
struct ieee80211_mgmt *assocReq;
struct ap_admc_assoc_req *admc_assoc_req;
if (len > ATH6KL_AP_ADMC_ASSOC_REQ_MAX_LEN) {
/* Is it possible? */
ath6kl_err("Not support so large assoc_req frame\n");
return;
}
admc_assoc_req = kzalloc(sizeof(struct ap_admc_assoc_req), GFP_KERNEL);
if (!admc_assoc_req) {
ath6kl_err("failed to alloc memory for admc_assoc_req\n");
return;
}
if (ap_admc->assoc_req_cnt)
ath6kl_info("Last assoc_req not yet finish!");
/* Add assocReq info. */
admc_assoc_req->ap_admc = ap_admc;
admc_assoc_req->frame_len = len;
memcpy(admc_assoc_req->raw_frame, frm, len);
assocReq = (struct ieee80211_mgmt *)(admc_assoc_req->raw_frame);
admc_assoc_req->sta_mac = assocReq->sa;
admc_assoc_req->action = AP_ADMC_ACT_ACCPET;
/*
* It still possbile that the host accept the STA but something wrong
* when sending assocResp. In this case, the target will not send
* connection event and need a timer to reclaim this item.
*/
init_timer(&admc_assoc_req->reclaim_timer);
admc_assoc_req->reclaim_timer.function = _ap_amdc_assoc_req_timeout;
admc_assoc_req->reclaim_timer.data = (unsigned long) admc_assoc_req;
mod_timer(&admc_assoc_req->reclaim_timer,
jiffies + msecs_to_jiffies(ap_admc->assoc_req_timeout));
spin_lock_bh(&ap_admc->assoc_req_lock);
list_add_tail(&admc_assoc_req->list, &ap_admc->assoc_req_list);
ap_admc->assoc_req_cnt++;
spin_unlock_bh(&ap_admc->assoc_req_lock);
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc add assoResp len %d depth %d\n",
len,
ap_admc->assoc_req_cnt);
return;
}
static inline struct ap_admc_assoc_req *_ap_amdc_assoc_req_search(
struct ap_admc_info *ap_admc,
u8 *sta_mac)
{
struct ap_admc_assoc_req *admc_assoc_req, *tmp;
/*
* Here assume the first matched address is the STA we want
* to handle if more than one the same addresses in the list.
*/
admc_assoc_req = NULL;
if (!list_empty(&ap_admc->assoc_req_list)) {
list_for_each_entry_safe(admc_assoc_req,
tmp,
&ap_admc->assoc_req_list,
list) {
if (memcmp(admc_assoc_req->sta_mac,
sta_mac,
ETH_ALEN) == 0)
break;
}
}
if (admc_assoc_req) {
spin_lock_bh(&ap_admc->assoc_req_lock);
del_timer(&admc_assoc_req->reclaim_timer);
list_del(&admc_assoc_req->list);
ap_admc->assoc_req_cnt--;
spin_unlock_bh(&ap_admc->assoc_req_lock);
}
return admc_assoc_req;
}
static inline void _ap_amdc_assoc_req_flush(struct ap_admc_info *ap_admc)
{
struct ap_admc_assoc_req *admc_assoc_req, *tmp;
int freed = 0;
spin_lock_bh(&ap_admc->assoc_req_lock);
list_for_each_entry_safe(admc_assoc_req,
tmp,
&ap_admc->assoc_req_list,
list) {
admc_assoc_req->action = AP_ADMC_ACT_FLUSH;
del_timer(&admc_assoc_req->reclaim_timer);
list_del(&admc_assoc_req->list);
ap_admc->assoc_req_cnt--;
_ap_amdc_assoc_req_free(ap_admc, admc_assoc_req);
freed++;
}
spin_unlock_bh(&ap_admc->assoc_req_lock);
WARN_ON(ap_admc->assoc_req_cnt);
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc flush %d\n",
freed);
return;
}
static bool _ap_amdc_check(struct ap_admc_info *ap_admc,
u8 *frm,
u8 req_type,
u8 **sta_mac,
u8 *reason_code)
{
struct ieee80211_mgmt *assocReq = (struct ieee80211_mgmt *)frm;
bool accept = true;
*reason_code = WLAN_STATUS_SUCCESS;
if (ap_admc->admc_mode == AP_ADMC_MODE_ACCEPT_ALWAYS) {
/* Assume the target already check DA & BSSID already. */
*sta_mac = assocReq->sa;
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc check %02x:%02x:%02x:%02x:%02x:%02x\n",
assocReq->sa[0], assocReq->sa[1], assocReq->sa[2],
assocReq->sa[3], assocReq->sa[4], assocReq->sa[5]);
} else {
; /* TODO */
}
return accept;
}
struct ap_admc_info *ath6kl_ap_admc_init(struct ath6kl_vif *vif,
enum ap_admc_mode mode)
{
struct ap_admc_info *ap_admc;
ap_admc = kzalloc(sizeof(struct ap_admc_info), GFP_KERNEL);
if (!ap_admc) {
ath6kl_err("failed to alloc memory for ap_admc\n");
return NULL;
}
ap_admc->vif = vif;
ap_admc->admc_mode = mode;
ap_admc->assoc_req_timeout = ATH6KL_AP_ADMC_ASSOC_REQ_TIMEOUT;
spin_lock_init(&ap_admc->assoc_req_lock);
INIT_LIST_HEAD(&ap_admc->assoc_req_list);
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc init (vif%d) mode %d timeout %d\n",
vif->fw_vif_idx,
ap_admc->admc_mode,
ap_admc->assoc_req_timeout);
return ap_admc;
}
void ath6kl_ap_admc_deinit(struct ath6kl_vif *vif)
{
struct ap_admc_info *ap_admc = vif->ap_admc_ctx;
if (ap_admc != NULL) {
_ap_amdc_assoc_req_flush(ap_admc);
kfree(ap_admc);
}
vif->ap_admc_ctx = NULL;
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc deinit (vif%d)\n",
vif->fw_vif_idx);
return;
}
int ath6kl_ap_admc_start(struct ath6kl_vif *vif)
{
struct ap_admc_info *ap_admc = vif->ap_admc_ctx;
bool enabled = false;
int ret;
if (!ap_admc)
return -ENOENT;
if (ap_admc->admc_mode != AP_ADMC_MODE_DISABLE)
enabled = true;
ret = ath6kl_wmi_set_assoc_req_relay_cmd(vif->ar->wmi,
vif->fw_vif_idx,
enabled);
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc start %s (vif%d) ret %d\n",
(enabled ? "enable" : "disable"),
vif->fw_vif_idx,
ret);
return ret;
}
int ath6kl_ap_admc_stop(struct ath6kl_vif *vif)
{
struct ap_admc_info *ap_admc = vif->ap_admc_ctx;
int ret;
if (!ap_admc)
return -ENOENT;
_ap_amdc_assoc_req_flush(ap_admc);
ret = ath6kl_wmi_set_assoc_req_relay_cmd(vif->ar->wmi,
vif->fw_vif_idx,
false);
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc stop (vif%d) ret %d\n",
vif->fw_vif_idx,
ret);
return ret;
}
void ath6kl_ap_admc_assoc_req(struct ath6kl_vif *vif,
u8 *assocReq,
u16 assocReq_len,
u8 req_type,
u8 fw_status)
{
struct ap_admc_info *ap_admc = vif->ap_admc_ctx;
u8 reason_code, *sta_mac = NULL;
bool accept;
BUG_ON(!ap_admc);
BUG_ON(ap_admc->admc_mode == AP_ADMC_MODE_DISABLE);
if (vif->nw_type == AP_NETWORK) {
accept = _ap_amdc_check(ap_admc,
assocReq,
req_type,
&sta_mac,
&reason_code);
if (ath6kl_wmi_send_assoc_resp_cmd(vif->ar->wmi,
vif->fw_vif_idx,
accept,
reason_code,
fw_status,
sta_mac,
req_type))
ath6kl_err("ap_admc assoResp fail (vif%d)\n",
vif->fw_vif_idx);
/* Only add into list for successful case. */
if ((fw_status == WLAN_STATUS_SUCCESS) &&
accept)
_ap_amdc_assoc_req_add(ap_admc,
assocReq,
assocReq_len);
else
_ap_amdc_assoc_req_free(ap_admc, NULL);
} else
WARN_ON(1);
return;
}
void ath6kl_ap_admc_assoc_req_fetch(struct ath6kl_vif *vif,
struct wmi_connect_event *ev,
u8 **assocReq,
u16 *assocReq_len)
{
struct ap_admc_info *ap_admc = vif->ap_admc_ctx;
struct ap_admc_assoc_req *admc_assoc_req = NULL;
u8 *sta_mac = ev->u.ap_sta.mac_addr;
BUG_ON(!ap_admc);
*assocReq = NULL;
*assocReq_len = 0;
if (ap_admc->admc_mode == AP_ADMC_MODE_DISABLE) {
*assocReq = ev->assoc_info + ev->beacon_ie_len;
*assocReq_len = ev->assoc_req_len;
return;
}
admc_assoc_req = _ap_amdc_assoc_req_search(ap_admc, sta_mac);
if (admc_assoc_req) {
*assocReq = admc_assoc_req->raw_frame;
*assocReq_len = admc_assoc_req->frame_len;
} else
ath6kl_err("No asoc_req found!");
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc assoResp fetch %p %p len %d\n",
*assocReq,
admc_assoc_req,
*assocReq_len);
return;
}
void ath6kl_ap_admc_assoc_req_release(struct ath6kl_vif *vif,
u8 *assocReq)
{
struct ap_admc_info *ap_admc = vif->ap_admc_ctx;
struct ap_admc_assoc_req *admc_assoc_req;
BUG_ON(!ap_admc);
if (ap_admc->admc_mode == AP_ADMC_MODE_DISABLE)
return;
admc_assoc_req = container_of(assocReq,
struct ap_admc_assoc_req,
raw_frame[0]);
ath6kl_dbg(ATH6KL_DBG_ADMC,
"ap_admc assoResp release %p %p\n",
assocReq,
admc_assoc_req);
_ap_amdc_assoc_req_free(ap_admc, admc_assoc_req);
return;
}
int ath6kl_ap_admc_dump(struct ath6kl *ar, u8 *buf, int buf_len)
{
int i, len = 0;
if (!buf)
return 0;
for (i = 0; i < ar->vif_max; i++) {
struct ath6kl_vif *vif;
vif = ath6kl_get_vif_by_index(ar, i);
if ((vif) &&
(vif->nw_type == AP_NETWORK)) {
struct ap_admc_info *ap_admc;
len += snprintf(buf + len, buf_len - len,
"\nVAP%d - ",
vif->fw_vif_idx);
ap_admc = vif->ap_admc_ctx;
if (!ap_admc)
return 0;
len += snprintf(buf + len, buf_len - len,
"mode %d timeout %d depth %d\n",
ap_admc->admc_mode,
ap_admc->assoc_req_timeout,
ap_admc->assoc_req_cnt);
}
}
return len;
}
void ath6kl_ap_rc_init(struct ath6kl_vif *vif)
{
struct ap_rc_info *ap_rc = &vif->ap_rc_info_ctx;
ap_rc->mode = AP_RC_MODE_DISABLE;
ap_rc->chan = 0;
ap_rc->chan_fixed = 0;
ath6kl_dbg(ATH6KL_DBG_AP_RC,
"ap_rc init mode %d\n",
ap_rc->mode);
return;
}
u16 ath6kl_ap_rc_get(struct ath6kl_vif *vif, u16 chan_config)
{
struct ap_rc_info *ap_rc = &vif->ap_rc_info_ctx;
enum ap_rc_mode rc_mode = ap_rc->mode;
u16 chan_rc = chan_config;
/*
* Only support AP recommend channel when
* 1.ATH6KL_MODULE_ENABLE_P2P_CHANMODE not support.
* 2.in pure SoftAP mode.
*/
if ((vif->next_chan_type == ATH6KL_CHAN_TYPE_NONE) &&
(vif->wdev.iftype != NL80211_IFTYPE_P2P_GO)) {
struct ath6kl_rc_report rc_report;
int ret = -1;
memset(&rc_report, 0, sizeof(struct ath6kl_rc_report));
if (rc_mode != AP_RC_MODE_DISABLE)
ret = ath6kl_p2p_rc_get(vif->ar, &rc_report);
if (ret == 0) {
if (rc_mode == AP_RC_MODE_DISABLE)
chan_rc = 0;
else if (rc_mode == AP_RC_MODE_FIXED)
chan_rc = ap_rc->chan_fixed;
else if (rc_mode == AP_RC_MODE_2GALL)
chan_rc = rc_report.rc_2g;
else if (rc_mode == AP_RC_MODE_2GPOP)
chan_rc = rc_report.rc_p2p_so;
else if (rc_mode == AP_RC_MODE_5GALL)
chan_rc = rc_report.rc_5g;
else if (rc_mode == AP_RC_MODE_OVERALL)
chan_rc = rc_report.rc_all;
else if (rc_mode == AP_RC_MODE_2GNOLTE)
chan_rc = rc_report.rc_2g_nolte;
else if (rc_mode == AP_RC_MODE_5GNODFS)
chan_rc = rc_report.rc_5g_nodfs;
else if (rc_mode == AP_RC_MODE_OVERALLNOLTE)
chan_rc = rc_report.rc_all_nolte;
else if (rc_mode == AP_RC_MODE_OVERALLNODFS)
chan_rc = rc_report.rc_all_nodfs;
else if (rc_mode == AP_RC_MODE_OVERALLNOLTEDFS)
chan_rc = rc_report.rc_all_noltedfs;
ap_rc->chan = chan_rc;
} else {
/*
* If disabled, fixed modes or query fails then not to
* overwrite it
*/
chan_rc = chan_config;
ap_rc->chan = 0;
}
} else
chan_rc = chan_config;
ath6kl_dbg(ATH6KL_DBG_AP_RC,
"ap_rc get mode %d chan %d fixed %d config %d use %d\n",
ap_rc->mode,
ap_rc->chan,
ap_rc->chan_fixed,
chan_config,
chan_rc);
return chan_rc;
}
static s8 _ap_rc_build_scan_chan(struct ath6kl *ar, u16 *chan_list)
{
struct wiphy *wiphy = ar->wiphy;
struct ieee80211_supported_band *sband;
struct ieee80211_channel *chan;
s8 chan_num = 0;
int i;
/* TBD : fine tune channel order or needed channels. */
sband = wiphy->bands[NL80211_BAND_2GHZ];
for (i = 0; i < sband->n_channels; i++) {
chan = &sband->channels[i];
if (!(chan->flags & IEEE80211_CHAN_DISABLED))
chan_list[chan_num++] = 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))
chan_list[chan_num++] = chan->center_freq;
}
return chan_num;
}
void ath6kl_ap_rc_update(struct ath6kl_vif *vif)
{
#define _AP_RC_SCAN_TIMEOUT (2 * HZ)
struct ath6kl *ar = vif->ar;
struct ath6kl_vif *vif_tmp;
struct ap_rc_info *ap_rc = &vif->ap_rc_info_ctx;
bool scan_on_going = false;
int i, ret;
if ((ap_rc->mode == AP_RC_MODE_DISABLE) ||
(ap_rc->mode == AP_RC_MODE_FIXED))
return;
if (test_bit(DISABLE_SCAN, &ar->flag)) {
ath6kl_err("ap_rc scan is disabled temporarily\n");
return;
}
if (down_interruptible(&ar->sem)) {
ath6kl_err("busy, couldn't get access\n");
return;
}
ath6kl_dbg(ATH6KL_DBG_AP_RC,
"ap_rc udpate start\n");
/*
* If any kind of scan on-going and no need scan now.
* Otherwise, start a scan and block until the scan finished.
*/
for (i = 0; i < ar->vif_max; i++) {
vif_tmp = ath6kl_get_vif_by_index(ar, i);
if (vif_tmp && test_bit(SCANNING, &vif_tmp->flags))
scan_on_going = true;
}
if (scan_on_going == false) {
u16 *channels;
s8 num_chan = 0;
bool left;
channels = kzalloc(WMI_MAX_CHANNELS * sizeof(u16), GFP_KERNEL);
if (channels) {
set_bit(SCANNING, &vif->flags);
if (!vif->usr_bss_filter) {
clear_bit(CLEAR_BSSFILTER_ON_BEACON,
&vif->flags);
ath6kl_wmi_bssfilter_cmd(ar->wmi,
vif->fw_vif_idx,
ALL_BSS_FILTER,
0);
}
num_chan = _ap_rc_build_scan_chan(ar, channels);
ret = ath6kl_wmi_startscan_cmd(ar->wmi,
vif->fw_vif_idx,
WMI_LONG_SCAN,
true, false, 0, 0,
num_chan,
channels);
if (ret)
ath6kl_err("wmi_startscan_cmd failed\n");
else {
set_bit(SCANNING_WAIT, &vif->flags);
ath6kl_p2p_rc_scan_start(vif, true);
#ifdef USB_AUTO_SUSPEND
ath6kl_hif_auto_pm_disable(ar);
#endif
/* Block until scan finish */
left = wait_event_interruptible_timeout(
ar->event_wq,
!test_bit(SCANNING_WAIT, &vif->flags),
_AP_RC_SCAN_TIMEOUT);
if (left == 0) {
clear_bit(SCANNING_WAIT, &vif->flags);
ath6kl_wmi_abort_scan_cmd(ar->wmi,
vif->fw_vif_idx);
ath6kl_err("ap_rc scan timeout\n");
}
if (signal_pending(current)) {
clear_bit(SCANNING_WAIT, &vif->flags);
ath6kl_err("ap_rc tgt not respond\n");
}
}
/* Local scan need to clear SCANNING by caller. */
clear_bit(SCANNING, &vif->flags);
kfree(channels);
} else
ath6kl_err("ap_rc alloc channel_list memory fail!\n");
}
up(&ar->sem);
ath6kl_dbg(ATH6KL_DBG_AP_RC,
"ap_rc udpate down %s scan\n",
(scan_on_going ? "without" : "with"));
return;
#undef _AP_RC_SCAN_TIMEOUT
}
int ath6kl_ap_rc_config(struct ath6kl_vif *vif, int mode_or_freq)
{
struct ap_rc_info *ap_rc = &vif->ap_rc_info_ctx;
/* Not yet support DFS in AP mode. */
if ((mode_or_freq == AP_RC_MODE_5GALL) ||
(mode_or_freq == AP_RC_MODE_OVERALL) ||
(mode_or_freq == AP_RC_MODE_OVERALLNOLTE)) {
ath6kl_err("set ap_rc error! mode_or_freq %d not yet support\n",
mode_or_freq);
return -EINVAL;
}
if (mode_or_freq >= AP_RC_MODE_DISABLE) {
if (mode_or_freq <= AP_RC_MODE_MAX) {
ap_rc->mode = mode_or_freq;
ap_rc->chan = 0;
ap_rc->chan_fixed = 0;
} else {
struct ieee80211_channel *chan;
chan = ieee80211_get_channel(vif->ar->wiphy,
mode_or_freq);
if ((chan) &&
!(chan->flags & IEEE80211_CHAN_DISABLED)) {
ap_rc->mode = AP_RC_MODE_FIXED;
ap_rc->chan = 0;
ap_rc->chan_fixed = mode_or_freq;
} else {
ath6kl_err("set ap_rc set fixed freq %d fail\n",
mode_or_freq);
return -EINVAL;
}
}
ath6kl_dbg(ATH6KL_DBG_AP_RC,
"ap_rc change to mode %d chan_fixed %d\n",
ap_rc->mode,
ap_rc->chan_fixed);
return 0;
}
ath6kl_err("set ap_rc error! mode_or_freq %d\n",
mode_or_freq);
return -EINVAL;
}
int ath6kl_ap_ht_update_ies(struct ath6kl_vif *vif)
{
int ret = 0;
if (vif->nw_type != AP_NETWORK)
return 0;
/* EV117149 WAR : Only overwrite it for 5G band. */
if (vif->next_chan > 4000) {
u8 opmode = HT_INFO_OPMODE_PROT_PURE;
if (vif->sta_no_ht_num > 0)
opmode = HT_INFO_OPMODE_PROT_MIXED;
/* TODO : RIFS mode & OBSS-nonHT-STA-Present */
ret = ath6kl_wmi_set_ht_op_cmd(vif->ar->wmi,
vif->fw_vif_idx,
0,
opmode);
}
return ret;
}
static inline bool _ap_is_11b_rate(u8 rate)
{
u8 rates[] = { 2, 4, 11, 22 };
u8 i;
for (i = 0; i < ARRAY_SIZE(rates); i++)
if (rate == rates[i])
return true;
return false;
}
void ath6kl_ap_beacon_info(struct ath6kl_vif *vif, u8 *beacon, u8 beacon_len)
{
u8 *pie, *peie;
u8 *rates_ie = NULL;
u8 *ext_rates_ie = NULL;
struct ieee80211_ht_cap *ht_cap_ie = NULL;
struct ieee80211_ht_oper *ht_oper_ie = NULL;
/*
* Get host interesting AP configuration by parsing the beacon content
* from AP CONNECTED event.
*/
if (vif->nw_type != AP_NETWORK)
return;
/* bypass timestamp, beacon interval and capability fields */
pie = beacon + 8 + 2 + 2;
peie = beacon + beacon_len;
while (pie < peie) {
switch (*pie) {
case WLAN_EID_SUPP_RATES:
if (pie[1])
rates_ie = pie;
break;
case WLAN_EID_EXT_SUPP_RATES:
if (pie[1])
ext_rates_ie = pie;
break;
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_HT_OPER:
if (pie[1] >= sizeof(struct ieee80211_ht_oper))
ht_oper_ie =
(struct ieee80211_ht_oper *)(pie + 2);
break;
}
pie += pie[1] + 2;
}
if (ht_cap_ie && ht_oper_ie) { /* 11N */
u16 cap_info = le16_to_cpu(ht_cap_ie->cap_info);
u8 second_chan = (ht_oper_ie->ht_param &
IEEE80211_HT_PARAM_CHA_SEC_OFFSET);
bool ext_chan = false;
vif->chan_type = ATH6KL_CHAN_TYPE_HT20;
if (second_chan &&
(cap_info & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) {
ext_chan = true;
if (second_chan == IEEE80211_HT_PARAM_CHA_SEC_ABOVE)
vif->chan_type = ATH6KL_CHAN_TYPE_HT40PLUS;
else
vif->chan_type = ATH6KL_CHAN_TYPE_HT40MINUS;
}
if (vif->bss_ch > 5000) { /* 11NA */
if (ext_chan)
vif->phymode = ATH6KL_PHY_MODE_11NA_HT40;
else
vif->phymode = ATH6KL_PHY_MODE_11NA_HT20;
} else { /* 11NG */
if (ext_chan)
vif->phymode = ATH6KL_PHY_MODE_11NG_HT40;
else
vif->phymode = ATH6KL_PHY_MODE_11NG_HT20;
}
} else { /* not 11N */
vif->chan_type = ATH6KL_CHAN_TYPE_NONE;
if (vif->bss_ch > 5000) /* 11A */
vif->phymode = ATH6KL_PHY_MODE_11A;
else { /* 11B/G/GONLY */
u8 i, rate;
bool b_rates, g_rates;
b_rates = g_rates = false;
if (rates_ie) {
for (i = 0; i < rates_ie[1]; i++) {
rate = rates_ie[2 + i] & 0x7f;
if (_ap_is_11b_rate(rate))
b_rates = true;
else
g_rates = true;
}
}
if (ext_rates_ie) {
for (i = 0; i < ext_rates_ie[1]; i++) {
rate = ext_rates_ie[2 + i] & 0x7f;
if (_ap_is_11b_rate(rate))
b_rates = true;
else
g_rates = true;
}
}
if (g_rates)
if (b_rates)
vif->phymode = ATH6KL_PHY_MODE_11G;
else
vif->phymode = ATH6KL_PHY_MODE_11GONLY;
else
if (b_rates)
vif->phymode = ATH6KL_PHY_MODE_11B;
else
ath6kl_err("Unknown AP phymode\n");
}
}
return;
}
void ath6kl_ap_ch_switch(struct ath6kl_vif *vif)
{
if (vif->nw_type != AP_NETWORK)
return;
#ifdef CE_SUPPORT
{
u32 freq;
enum nl80211_channel_type channel_type;
freq = (u32)le16_to_cpu(vif->bss_ch);
if (vif->chan_type == ATH6KL_CHAN_TYPE_NONE)
channel_type = NL80211_CHAN_NO_HT;
else if (vif->chan_type == ATH6KL_CHAN_TYPE_HT20)
channel_type = NL80211_CHAN_HT20;
else if (vif->chan_type == ATH6KL_CHAN_TYPE_HT40MINUS)
channel_type = NL80211_CHAN_HT40MINUS;
else if (vif->chan_type == ATH6KL_CHAN_TYPE_HT40PLUS)
channel_type = NL80211_CHAN_HT40PLUS;
cfg80211_csa_done_info(vif->ndev, freq, channel_type, 1, GFP_ATOMIC);
}
#endif
/*
* If target use the different channel setting as the host and use this
* helper API to update the working channel information to the user.
*
* If target support WMI_CHANNEL_CHANGE_EVENTID (DFS?) and this helper
* API may also useful.
*/
#ifdef NL80211_CMD_OPER_CH_SWITCH_NOTIFY
/*
* The next_chan_type is only valid when
* ATH6KL_MODULE_ENABLE_P2P_CHANMODE is on.
*/
if ((vif->next_chan != vif->bss_ch) ||
(vif->next_chan_type != vif->chan_type)) {
if (ath6kl_mod_debug_quirks(vif->ar,
ATH6KL_MODULE_ENABLE_P2P_CHANMODE)) {
enum nl80211_channel_type type = NL80211_CHAN_NO_HT;
#ifdef CFG80211_NEW_CHAN_DEFINITION
struct ieee80211_channel *chan;
struct cfg80211_chan_def chandef;
#endif
ath6kl_info("AP Channel switch from %d/%d to %d/%d\n",
vif->next_chan, vif->next_chan_type,
vif->bss_ch, vif->chan_type);
if (vif->chan_type == ATH6KL_CHAN_TYPE_NONE)
type = NL80211_CHAN_NO_HT;
else if (vif->chan_type == ATH6KL_CHAN_TYPE_HT40PLUS)
type = NL80211_CHAN_HT40PLUS;
else if (vif->chan_type == ATH6KL_CHAN_TYPE_HT40MINUS)
type = NL80211_CHAN_HT40MINUS;
else if (vif->chan_type == ATH6KL_CHAN_TYPE_HT20)
type = NL80211_CHAN_HT20;
else
WARN_ON(1);
/*
* TODO: Better to check channel information is valid
* or not for P2P-GO mode before report to the user.
*/
#ifdef CFG80211_NEW_CHAN_DEFINITION
chan = ieee80211_get_channel(vif->ar->wiphy,
vif->bss_ch);
if (chan == NULL) {
WARN_ON(1);
return;
}
cfg80211_chandef_create(&chandef, chan, type);
cfg80211_ch_switch_notify(vif->ndev,
&chandef);
#else
cfg80211_ch_switch_notify(vif->ndev,
vif->bss_ch,
type);
#endif
}
}
#endif
return;
}