blob: 0bbeb00858ce23851bcbb15b03edee2c889696bd [file] [log] [blame]
/*
* Copyright (c) 2016-2020 The Linux Foundation. All rights reserved.
* Copyright (c) 2007-2008 Sam Leffler, Errno Consulting
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* DOC: This file has the functions related to DFS CAC.
*/
#include "../dfs_channel.h"
#include "../dfs_zero_cac.h"
#include <wlan_objmgr_vdev_obj.h>
#include "wlan_dfs_utils_api.h"
#include "wlan_dfs_mlme_api.h"
#include "../dfs_internal.h"
#include "../dfs_process_radar_found_ind.h"
#define IS_CHANNEL_WEATHER_RADAR(freq) ((freq >= 5600) && (freq <= 5650))
#define ADJACENT_WEATHER_RADAR_CHANNEL 5580
#define CH100_START_FREQ 5490
#define CH100 100
int dfs_override_cac_timeout(struct wlan_dfs *dfs, int cac_timeout)
{
if (!dfs)
return -EIO;
dfs->dfs_cac_timeout_override = cac_timeout;
dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS, "CAC timeout is now %s %d",
(cac_timeout == -1) ? "default" : "overridden",
cac_timeout);
return 0;
}
int dfs_get_override_cac_timeout(struct wlan_dfs *dfs, int *cac_timeout)
{
if (!dfs)
return -EIO;
(*cac_timeout) = dfs->dfs_cac_timeout_override;
return 0;
}
#ifdef CONFIG_CHAN_NUM_API
void dfs_cac_valid_reset(struct wlan_dfs *dfs,
uint8_t prevchan_ieee,
uint32_t prevchan_flags)
{
if (dfs->dfs_cac_valid_time) {
if ((prevchan_ieee != dfs->dfs_curchan->dfs_ch_ieee) ||
(prevchan_flags != dfs->dfs_curchan->dfs_ch_flags)) {
dfs_err(dfs, WLAN_DEBUG_DFS_ALWAYS,
"Cancelling timer & clearing cac_valid"
);
qdf_timer_stop(&dfs->dfs_cac_valid_timer);
dfs->dfs_cac_valid = 0;
}
}
}
#endif
#ifdef CONFIG_CHAN_FREQ_API
void dfs_cac_valid_reset_for_freq(struct wlan_dfs *dfs,
uint16_t prevchan_freq,
uint32_t prevchan_flags)
{
if (dfs->dfs_cac_valid_time) {
if ((prevchan_freq != dfs->dfs_curchan->dfs_ch_freq) ||
(prevchan_flags != dfs->dfs_curchan->dfs_ch_flags)) {
dfs_err(dfs, WLAN_DEBUG_DFS_ALWAYS,
"Cancelling timer & clearing cac_valid");
qdf_timer_stop(&dfs->dfs_cac_valid_timer);
dfs->dfs_cac_valid = 0;
}
}
}
#endif
/**
* dfs_cac_valid_timeout() - Timeout function for dfs_cac_valid_timer
* cac_valid bit will be reset in this function.
*/
static os_timer_func(dfs_cac_valid_timeout)
{
struct wlan_dfs *dfs = NULL;
OS_GET_TIMER_ARG(dfs, struct wlan_dfs *);
dfs->dfs_cac_valid = 0;
dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS, ": Timed out!!");
}
/**
* dfs_clear_cac_started_chan() - Clear dfs cac started channel.
* @dfs: Pointer to wlan_dfs structure.
*/
static void dfs_clear_cac_started_chan(struct wlan_dfs *dfs)
{
qdf_mem_zero(&dfs->dfs_cac_started_chan,
sizeof(dfs->dfs_cac_started_chan));
}
void dfs_process_cac_completion(struct wlan_dfs *dfs)
{
enum phy_ch_width ch_width = CH_WIDTH_INVALID;
uint16_t primary_chan_freq = 0, secondary_chan_freq = 0;
struct dfs_channel *dfs_curchan;
dfs->dfs_cac_timer_running = 0;
dfs_curchan = dfs->dfs_curchan;
dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS, "cac expired, chan %d cur time %d",
dfs->dfs_curchan->dfs_ch_freq,
(qdf_system_ticks_to_msecs(qdf_system_ticks()) / 1000));
/*
* When radar is detected during a CAC we are woken up prematurely to
* switch to a new channel. Check the channel to decide how to act.
*/
if (WLAN_IS_CHAN_RADAR(dfs->dfs_curchan)) {
dfs_mlme_mark_dfs_for_freq(dfs->dfs_pdev_obj,
dfs_curchan->dfs_ch_ieee,
dfs_curchan->dfs_ch_freq,
dfs_curchan->dfs_ch_mhz_freq_seg2,
dfs_curchan->dfs_ch_flags);
dfs_debug(dfs, WLAN_DEBUG_DFS,
"CAC timer on chan %u (%u MHz) stopped due to radar",
dfs_curchan->dfs_ch_ieee,
dfs_curchan->dfs_ch_freq);
} else {
dfs_debug(dfs, WLAN_DEBUG_DFS,
"CAC timer on channel %u (%u MHz) expired;"
"no radar detected",
dfs_curchan->dfs_ch_ieee,
dfs_curchan->dfs_ch_freq);
/* On CAC completion, set the bit 'cac_valid'.
* CAC will not be re-done if this bit is reset.
* The flag will be reset when dfs_cac_valid_timer
* timesout.
*/
if (dfs->dfs_cac_valid_time) {
dfs->dfs_cac_valid = 1;
qdf_timer_mod(&dfs->dfs_cac_valid_timer,
dfs->dfs_cac_valid_time * 1000);
}
dfs_find_chwidth_and_center_chan_for_freq(dfs,
&ch_width,
&primary_chan_freq,
&secondary_chan_freq);
/* Mark the current channel as preCAC done */
dfs_mark_precac_done_for_freq(dfs, primary_chan_freq,
secondary_chan_freq, ch_width);
}
dfs_clear_cac_started_chan(dfs);
/* Iterate over the nodes, processing the CAC completion event. */
dfs_mlme_proc_cac(dfs->dfs_pdev_obj, 0);
/* Send a CAC timeout, VAP up event to user space */
dfs_mlme_deliver_event_up_after_cac(dfs->dfs_pdev_obj);
if (dfs->dfs_defer_precac_channel_change == 1) {
dfs_mlme_channel_change_by_precac(dfs->dfs_pdev_obj);
dfs->dfs_defer_precac_channel_change = 0;
}
}
/**
* dfs_cac_timeout() - DFS cactimeout function.
*
* Sets dfs_cac_timer_running to 0 and dfs_cac_valid_timer.
*/
#ifdef CONFIG_CHAN_FREQ_API
static os_timer_func(dfs_cac_timeout)
{
struct wlan_dfs *dfs = NULL;
OS_GET_TIMER_ARG(dfs, struct wlan_dfs *);
if (dfs_is_hw_mode_switch_in_progress(dfs))
dfs->dfs_defer_params.is_cac_completed = true;
else
dfs_process_cac_completion(dfs);
}
#else
#ifdef CONFIG_CHAN_NUM_API
static os_timer_func(dfs_cac_timeout)
{
struct wlan_dfs *dfs = NULL;
enum phy_ch_width ch_width = CH_WIDTH_INVALID;
uint8_t primary_chan_ieee = 0, secondary_chan_ieee = 0;
OS_GET_TIMER_ARG(dfs, struct wlan_dfs *);
dfs->dfs_cac_timer_running = 0;
dfs_info(dfs, WLAN_DEBUG_DFS_ALWAYS, "cac expired, chan %d curr time %d",
dfs->dfs_curchan->dfs_ch_freq,
(qdf_system_ticks_to_msecs(qdf_system_ticks()) / 1000));
/*
* When radar is detected during a CAC we are woken up prematurely to
* switch to a new channel. Check the channel to decide how to act.
*/
if (WLAN_IS_CHAN_RADAR(dfs->dfs_curchan)) {
dfs_mlme_mark_dfs(dfs->dfs_pdev_obj,
dfs->dfs_curchan->dfs_ch_ieee,
dfs->dfs_curchan->dfs_ch_freq,
dfs->dfs_curchan->dfs_ch_vhtop_ch_freq_seg2,
dfs->dfs_curchan->dfs_ch_flags);
dfs_debug(dfs, WLAN_DEBUG_DFS,
"CAC timer on channel %u (%u MHz) stopped due to radar",
dfs->dfs_curchan->dfs_ch_ieee,
dfs->dfs_curchan->dfs_ch_freq);
} else {
dfs_debug(dfs, WLAN_DEBUG_DFS,
"CAC timer on channel %u (%u MHz) expired; no radar detected",
dfs->dfs_curchan->dfs_ch_ieee,
dfs->dfs_curchan->dfs_ch_freq);
/* On CAC completion, set the bit 'cac_valid'.
* CAC will not be re-done if this bit is reset.
* The flag will be reset when dfs_cac_valid_timer
* timesout.
*/
if (dfs->dfs_cac_valid_time) {
dfs->dfs_cac_valid = 1;
qdf_timer_mod(&dfs->dfs_cac_valid_timer,
dfs->dfs_cac_valid_time * 1000);
}
dfs_find_chwidth_and_center_chan(dfs,
&ch_width,
&primary_chan_ieee,
&secondary_chan_ieee);
/* Mark the current channel as preCAC done */
dfs_mark_precac_done(dfs, primary_chan_ieee,
secondary_chan_ieee, ch_width);
}
dfs_clear_cac_started_chan(dfs);
/* Iterate over the nodes, processing the CAC completion event. */
dfs_mlme_proc_cac(dfs->dfs_pdev_obj, 0);
/* Send a CAC timeout, VAP up event to user space */
dfs_mlme_deliver_event_up_after_cac(dfs->dfs_pdev_obj);
if (dfs->dfs_defer_precac_channel_change == 1) {
dfs_mlme_channel_change_by_precac(dfs->dfs_pdev_obj);
dfs->dfs_defer_precac_channel_change = 0;
}
}
#endif
#endif
void dfs_cac_timer_attach(struct wlan_dfs *dfs)
{
dfs->dfs_cac_timeout_override = -1;
dfs->wlan_dfs_cac_time = WLAN_DFS_WAIT_MS;
qdf_timer_init(NULL,
&(dfs->dfs_cac_timer),
dfs_cac_timeout,
(void *)(dfs),
QDF_TIMER_TYPE_WAKE_APPS);
qdf_timer_init(NULL,
&(dfs->dfs_cac_valid_timer),
dfs_cac_valid_timeout,
(void *)(dfs),
QDF_TIMER_TYPE_WAKE_APPS);
}
void dfs_cac_timer_reset(struct wlan_dfs *dfs)
{
qdf_timer_stop(&dfs->dfs_cac_timer);
dfs_get_override_cac_timeout(dfs,
&(dfs->dfs_cac_timeout_override));
dfs_clear_cac_started_chan(dfs);
}
void dfs_cac_timer_detach(struct wlan_dfs *dfs)
{
qdf_timer_free(&dfs->dfs_cac_timer);
qdf_timer_free(&dfs->dfs_cac_valid_timer);
dfs->dfs_cac_valid = 0;
}
int dfs_is_ap_cac_timer_running(struct wlan_dfs *dfs)
{
return dfs->dfs_cac_timer_running;
}
#ifdef CONFIG_CHAN_FREQ_API
void dfs_start_cac_timer(struct wlan_dfs *dfs)
{
int cac_timeout = 0;
struct dfs_channel *chan = dfs->dfs_curchan;
cac_timeout =
dfs_mlme_get_cac_timeout_for_freq(dfs->dfs_pdev_obj,
chan->dfs_ch_freq,
chan->dfs_ch_mhz_freq_seg2,
chan->dfs_ch_flags);
dfs->dfs_cac_started_chan = *chan;
dfs_debug(dfs, WLAN_DEBUG_DFS,
"chan = %d cfreq2 = %d timeout = %d sec, curr_time = %d sec",
chan->dfs_ch_ieee, chan->dfs_ch_vhtop_ch_freq_seg2,
cac_timeout,
qdf_system_ticks_to_msecs(qdf_system_ticks()) / 1000);
qdf_timer_mod(&dfs->dfs_cac_timer, cac_timeout * 1000);
dfs->dfs_cac_aborted = 0;
}
#else
#ifdef CONFIG_CHAN_NUM_API
void dfs_start_cac_timer(struct wlan_dfs *dfs)
{
int cac_timeout = 0;
struct dfs_channel *chan = dfs->dfs_curchan;
cac_timeout = dfs_mlme_get_cac_timeout(dfs->dfs_pdev_obj,
chan->dfs_ch_freq,
chan->dfs_ch_vhtop_ch_freq_seg2,
chan->dfs_ch_flags);
dfs->dfs_cac_started_chan = *chan;
dfs_debug(dfs, WLAN_DEBUG_DFS,
"chan = %d cfreq2 = %d timeout = %d sec, curr_time = %d sec",
chan->dfs_ch_ieee, chan->dfs_ch_vhtop_ch_freq_seg2,
cac_timeout,
qdf_system_ticks_to_msecs(qdf_system_ticks()) / 1000);
qdf_timer_mod(&dfs->dfs_cac_timer, cac_timeout * 1000);
dfs->dfs_cac_aborted = 0;
}
#endif
#endif
void dfs_cancel_cac_timer(struct wlan_dfs *dfs)
{
qdf_timer_stop(&dfs->dfs_cac_timer);
dfs_clear_cac_started_chan(dfs);
}
void dfs_cac_stop(struct wlan_dfs *dfs)
{
uint32_t phyerr;
dfs_get_debug_info(dfs, (void *)&phyerr);
dfs_debug(dfs, WLAN_DEBUG_DFS,
"Stopping CAC Timer %d procphyerr 0x%08x",
dfs->dfs_curchan->dfs_ch_freq, phyerr);
qdf_timer_stop(&dfs->dfs_cac_timer);
if (dfs->dfs_cac_timer_running)
dfs->dfs_cac_aborted = 1;
dfs_clear_cac_started_chan(dfs);
dfs->dfs_cac_timer_running = 0;
}
void dfs_stacac_stop(struct wlan_dfs *dfs)
{
uint32_t phyerr;
dfs_get_debug_info(dfs, (void *)&phyerr);
dfs_debug(dfs, WLAN_DEBUG_DFS,
"Stopping STA CAC Timer %d procphyerr 0x%08x",
dfs->dfs_curchan->dfs_ch_freq, phyerr);
dfs_clear_cac_started_chan(dfs);
}
/*
* dfs_is_subset_channel_for_freq() - Find out if prev channel and current
* channel are subsets of each other.
* @old_subchans_freq: Pointer to previous sub-channels freq.
* @old_n_chans: Number of previous sub-channels.
* @new_subchans_freq: Pointer to new sub-channels freq.
* @new_n_chans: Number of new sub-channels
*/
#ifdef CONFIG_CHAN_FREQ_API
static bool
dfs_is_subset_channel_for_freq(uint16_t *old_subchans_freq,
uint8_t old_n_chans,
uint16_t *new_subchans_freq,
uint8_t new_n_chans)
{
bool is_found;
int i, j;
if (!new_n_chans)
return true;
if (new_n_chans > old_n_chans)
return false;
for (i = 0; i < new_n_chans; i++) {
is_found = false;
for (j = 0; j < old_n_chans; j++) {
if (new_subchans_freq[i] == old_subchans_freq[j]) {
is_found = true;
break;
}
}
/* If new_subchans[i] is not found in old_subchans, then,
* new_chan is not subset of old_chan.
*/
if (!is_found)
break;
}
return is_found;
}
#endif
#ifdef CONFIG_CHAN_FREQ_API
static uint8_t
dfs_find_dfs_sub_channels_for_freq(struct wlan_dfs *dfs,
struct dfs_channel *chan,
uint16_t *subchan_arr)
{
if (WLAN_IS_CHAN_MODE_160(chan) || WLAN_IS_CHAN_MODE_80_80(chan)) {
if (WLAN_IS_CHAN_DFS(chan) && WLAN_IS_CHAN_DFS_CFREQ2(chan))
return dfs_get_bonding_channel_without_seg_info_for_freq
(chan, subchan_arr);
if (WLAN_IS_CHAN_DFS(chan))
return dfs_get_bonding_channels_for_freq(dfs,
chan,
SEG_ID_PRIMARY,
DETECTOR_ID_0,
subchan_arr);
if (WLAN_IS_CHAN_DFS_CFREQ2(chan))
return dfs_get_bonding_channels_for_freq
(dfs, chan, SEG_ID_SECONDARY,
DETECTOR_ID_0, subchan_arr);
/* All channels in 160/80_80 BW are non DFS, return 0
* as number of subchannels
*/
return 0;
} else if (WLAN_IS_CHAN_DFS(chan)) {
return dfs_get_bonding_channel_without_seg_info_for_freq
(chan, subchan_arr);
}
/* All channels are non DFS, return 0 as number of subchannels*/
return 0;
}
#endif
/* dfs_is_new_chan_subset_of_old_chan() - Find if new channel is subset of
* old channel.
* @dfs: Pointer to wlan_dfs structure.
* @new_chan: Pointer to new channel of dfs_channel structure.
* @old_chan: Pointer to old channel of dfs_channel structure.
*
* Return: True if new channel is subset of old channel, else false.
*/
#ifdef CONFIG_CHAN_FREQ_API
static bool
dfs_is_new_chan_subset_of_old_chan(struct wlan_dfs *dfs,
struct dfs_channel *new_chan,
struct dfs_channel *old_chan)
{
uint16_t new_subchans[NUM_CHANNELS_160MHZ];
uint16_t old_subchans[NUM_CHANNELS_160MHZ];
uint8_t n_new_subchans = 0;
uint8_t n_old_subchans = 0;
/* Given channel is the old channel. i.e. The channel which
* should have the new channel as subset.
*/
n_old_subchans = dfs_find_dfs_sub_channels_for_freq(dfs, old_chan,
old_subchans);
/* cur_chan is the new channel to be check if subset of old channel */
n_new_subchans = dfs_find_dfs_sub_channels_for_freq(dfs, new_chan,
new_subchans);
return dfs_is_subset_channel_for_freq(old_subchans,
n_old_subchans,
new_subchans,
n_new_subchans);
}
#endif
bool dfs_is_cac_required(struct wlan_dfs *dfs,
struct dfs_channel *cur_chan,
struct dfs_channel *prev_chan,
bool *continue_current_cac)
{
struct dfs_channel *cac_started_chan = &dfs->dfs_cac_started_chan;
if (dfs->dfs_ignore_dfs || dfs->dfs_cac_valid || dfs->dfs_ignore_cac) {
dfs_debug(dfs, WLAN_DEBUG_DFS,
"Skip CAC, ignore_dfs = %d cac_valid = %d ignore_cac = %d",
dfs->dfs_ignore_dfs, dfs->dfs_cac_valid,
dfs->dfs_ignore_cac);
return false;
}
/* If the channel has completed PRE-CAC then CAC can be skipped here. */
if (dfs_is_precac_done(dfs, cur_chan)) {
dfs_debug(dfs, WLAN_DEBUG_DFS,
"PRE-CAC alreay done on this channel %d",
cur_chan->dfs_ch_ieee);
return false;
}
if (dfs_is_ap_cac_timer_running(dfs)) {
/* Check if we should continue the existing CAC or
* cancel the existing CAC.
* For example: - if an existing VAP(0) is already in
* DFS wait state (which means the radio(wifi) is
* running the CAC) and it is in channel A and another
* VAP(1) comes up in the same channel then instead of
* cancelling the CAC we can let the CAC continue.
*/
if (dfs_is_new_chan_subset_of_old_chan(dfs,
cur_chan,
cac_started_chan)) {
*continue_current_cac = true;
} else {
/* New CAC is needed, cancel the running CAC
* timer.
* 1) When AP is in DFS_WAIT state and it is in
* channel A and user restarts the AP vap in
* channel B, then cancel the running CAC in
* channel A and start new CAC in channel B.
*
* 2) When AP detects the RADAR during CAC in
* channel A, it cancels the running CAC and
* tries to find channel B with the reduced
* bandwidth with of channel A.
* In this case, since the CAC is aborted by
* the RADAR, AP should start the CAC again.
*/
dfs_cancel_cac_timer(dfs);
}
} else { /* CAC timer is not running. */
if (dfs_is_new_chan_subset_of_old_chan(dfs,
cur_chan,
prev_chan)) {
/* AP bandwidth reduce case:
* When AP detects the RADAR in in-service monitoring
* mode in channel A, it cancels the running CAC and
* tries to find the channel B with the reduced
* bandwidth of channel A.
* If the new channel B is subset of the channel A
* then AP skips the CAC.
*/
if (!dfs->dfs_cac_aborted) {
dfs_debug(dfs, WLAN_DEBUG_DFS, "Skip CAC");
return false;
}
}
}
return true;
}