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