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