blob: 14e595f05aa09c759346d814b8f71db61d654e54 [file] [log] [blame]
/**
Copyright (c) 2008 - 2013 Quantenna Communications Inc
All Rights Reserved
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/device.h>
#include <linux/time.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
#include <linux/pm_qos.h>
#else
#include <linux/pm_qos_params.h>
#endif
#include "qdrv_features.h"
#include "qdrv_debug.h"
#include "qdrv_mac.h"
#include "qdrv_soc.h"
#include "qdrv_hal.h"
#include "qdrv_muc.h"
#include "qdrv_dsp.h"
#include "qtn/registers.h"
#include "qtn/muc_phy_stats.h"
#include "qdrv_comm.h"
#include "qdrv_wlan.h"
#include "qdrv_radar.h"
#include "radar/radar.h"
#include "radar/detect.h"
#include <net/iw_handler.h> /* wireless_send_event(..) */
#include "qdrv_debug.h"
#include <net80211/ieee80211_var.h>
#include "qdrv_control.h"
#include <asm/board/pm.h>
/* Will move this to a configuration later. */
#define CONFIG_QHOP 1
#define CAC_PERIOD (70 * HZ)
#define CAC_WEATHER_PERIOD_EU (600 * HZ)
#define CAC_PERIOD_QUICK (30 * HZ)
#define NONOCCUPY_PERIOD_QUICK (60 * HZ)
#define STA_SILENCE_PERIOD (CAC_PERIOD + 10 * HZ)
#define STA_WEATHER_CHAN_SILENCE_PERIOD (CAC_WEATHER_PERIOD_EU + 10 * HZ)
#define DFS_CS_TIMER_VAL (HZ / 10)
#define QDRV_RADAR_SAMPLE_RATE 1 /* sampling rate (seconds) */
#define QDRV_RADAR_SAMPLE_DELAY 10 /* Give MuC time to update stats (jiffies) */
static void qdrv_radar_sample_work(struct work_struct *unused);
static bool qdrv_radar_configured = false;
static bool qdrv_radar_first_call = true;
static bool qdrv_radar_sta_dfs = false;
/*
* Control block for qdrv_radar
*/
struct qdrv_radar_sample {
struct delayed_work sample_work;
struct detect_drv_sample_t *sample;
};
static struct {
bool enabled;
bool xmit_stopped;
struct qdrv_mac *mac;
struct ieee80211com *ic;
struct ieee80211_channel *cac_chan;
struct timer_list cac_timer; /* a timer for CAC */
struct timer_list nonoccupy_timer[IEEE80211_CHAN_MAX+1];
struct ieee80211_channel *dfs_des_chan;
struct timer_list dfs_cs_timer; /* a timer for a channel switch */
struct qdrv_radar_sample muc_sampling;
struct muc_tx_stats *stats_uc_tx_ptr;
struct notifier_block pm_notifier;
struct tasklet_struct ocac_tasklet;
uint32_t region;
} qdrv_radar_cb;
/*
* Utility macros
*/
/*
* True if this mode must behave like a DFS master, ie do Channel
* Check Availability and In Service Monitoring. We need to make sure
* that all modes cannot send data without being authorized. Such
* enforcement is not done in monitor mode however.
*/
static inline int ieee80211_is_dfs_master(struct ieee80211com *ic)
{
KASSERT(ic->ic_opmode != IEEE80211_M_WDS,
(DBGEFMT "Incorrect ic opmode %d\n", DBGARG, ic->ic_opmode));
if (ic->ic_opmode == IEEE80211_M_HOSTAP)
return 1;
if (ieee80211_is_repeater(ic))
return 1;
if (ic->ic_opmode == IEEE80211_M_IBSS)
return 1;
if (ic->ic_opmode == IEEE80211_M_AHDEMO)
return 1;
return 0;
}
static inline int qdrv_is_dfs_master(void)
{
return ieee80211_is_dfs_master(qdrv_radar_cb.ic);
}
static inline int qdrv_is_dfs_slave(void)
{
return !ieee80211_is_dfs_master(qdrv_radar_cb.ic);
}
#define GET_CHANIDX(chan) ((chan) - ic->ic_channels)
static void mark_radar(void);
static void stop_cac(void);
static void stop_dfs_cs(void);
static void qdrv_ocac_irqhandler(void *arg1, void *arg2);
static int qdrv_init_ocac_irqhandler(struct qdrv_wlan *qw);
static bool qdrv_radar_is_dfs_chan(uint8_t wifi_chan);
static bool qdrv_radar_is_dfs_weather_chan(uint8_t wifi_chan);
#ifndef SYSTEM_BUILD
#define ic2dev(ic) ((struct ieee80211vap *)(TAILQ_FIRST(&(ic)->ic_vaps)) ? \
((struct ieee80211vap *)(TAILQ_FIRST(&(ic)->ic_vaps)))->iv_dev : NULL)
#else
#define ic2dev(ic) NULL
#endif
/* used to report RADAR: messages to event server */
#define radar_event_report(...) qdrv_eventf(__VA_ARGS__)
#define DBGPRINTF_N_QEVT(qevtdev, ...) do {\
DBGPRINTF_N(__VA_ARGS__);\
radar_event_report(qevtdev, __VA_ARGS__);\
} while (0)
#ifdef CONFIG_QHOP
/*
* RBS reports channel change detect to MBS over the WDS link.
*/
static void
qdrv_qhop_send_rbs_report_frame(struct ieee80211vap *vap, u_int8_t new_chan)
{
struct ieee80211_node *ni = ieee80211_get_wds_peer_node_ref(vap);
struct sk_buff *skb;
int frm_len = sizeof(struct qdrv_vendor_action_header) + sizeof(struct qdrv_vendor_action_qhop_dfs_data);
u_int8_t *frm;
if (!ni) {
DBGPRINTF_E("WDS peer is NULL!\n");
return;
}
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH,
"%s: Sending action frame with RBS report IE: %u\n", __func__, new_chan);
skb = ieee80211_getmgtframe(&frm, frm_len);
if (skb == NULL) {
IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, "%s: cannot get buf; size %u", __func__, frm_len);
vap->iv_stats.is_tx_nobuf++;
ieee80211_free_node(ni);
return;
}
/* Fill in QHOP action header and data */
*frm++ = IEEE80211_ACTION_CAT_VENDOR;
frm += 3;
*frm++ = QDRV_ACTION_TYPE_QHOP;
*frm++ = QDRV_ACTION_QHOP_DFS_REPORT;
*frm++ = new_chan;
ieee80211_mgmt_output(ni, skb, IEEE80211_FC0_SUBTYPE_ACTION, ni->ni_macaddr);
}
#endif
/*
* Perioodically sample data from the MuC
*/
static void qdrv_radar_sample_work(struct work_struct *unused)
{
struct muc_tx_stats stats_muc_tx;
unsigned long lock_flags;
if (qdrv_radar_cb.stats_uc_tx_ptr == NULL) {
return;
}
memcpy(&stats_muc_tx, qdrv_radar_cb.stats_uc_tx_ptr, sizeof(stats_muc_tx));
/* Update the structure owned by the radar module */
spin_lock_irqsave(&qdrv_radar_cb.muc_sampling.sample->lock, lock_flags);
/* Divide sample values by sample rate to get rate per second */
qdrv_radar_cb.muc_sampling.sample->tx_pkts =
stats_muc_tx.tx_sample_pkts / qdrv_radar_cb.ic->ic_sample_rate;
qdrv_radar_cb.muc_sampling.sample->tx_bytes =
stats_muc_tx.tx_sample_bytes / qdrv_radar_cb.ic->ic_sample_rate;
spin_unlock_irqrestore(&qdrv_radar_cb.muc_sampling.sample->lock, lock_flags);
schedule_delayed_work(&qdrv_radar_cb.muc_sampling.sample_work,
qdrv_radar_cb.ic->ic_sample_rate * HZ);
}
/*
* Status-checking inline functions
*/
inline static bool is_cac_started(void)
{
return (qdrv_radar_cb.cac_chan != NULL);
}
inline static bool is_dfs_cs_started(void)
{
return (qdrv_radar_cb.dfs_des_chan != NULL);
}
/*
* Enable radar detection on channel
*/
inline static void sys_enable_rdetection(void)
{
if (DBG_LOG_FUNC_TEST(QDRV_LF_DFS_DISALLOWRADARDETECT)) {
DBGPRINTF_N("RADAR: test mode - radar not enabled\n");
return;
}
if (qdrv_is_dfs_slave() && !qdrv_radar_sta_dfs)
return;
radar_enable();
}
/*
* Disable radar detection on channel
*/
inline static void sys_disable_rdetection(void)
{
radar_disable();
}
/*
* Start the radar module
*/
inline static bool sys_start_radarmod(const char *region)
{
bool region_enabled;
struct ieee80211com *ic = qdrv_radar_cb.ic;
region_enabled = radar_start(region);
if (region_enabled) {
radar_set_bw(ic->ic_radar_bw);
sys_disable_rdetection();
radar_register(mark_radar);
radar_register_is_dfs_chan(qdrv_radar_is_dfs_chan);
radar_register_is_dfs_weather_chan(qdrv_radar_is_dfs_weather_chan);
}
return region_enabled;
}
/*
* Stop the radar module
*/
inline static void sys_stop_radarmod(void)
{
radar_stop();
}
static inline void sys_raw_enable_xmit(void)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct qdrv_wlan *qw = container_of(ic, struct qdrv_wlan, ic);
qdrv_hostlink_xmitctl(qw, true);
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "transmission enabled\n");
}
static inline void sys_raw_disable_xmit(void)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct qdrv_wlan *qw = container_of(ic, struct qdrv_wlan, ic);
qdrv_hostlink_xmitctl(qw, false);
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "transmission disabled\n");
}
/*
* Instruct MuC to enable transmission
*/
void sys_enable_xmit(void)
{
if (qdrv_radar_cb.xmit_stopped == true) {
sys_raw_enable_xmit();
qdrv_radar_cb.xmit_stopped = false;
} else {
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "Xmit already enabled\n");
}
}
/*
* Instruct MuC to disable transmission
*/
void sys_disable_xmit(void)
{
if (qdrv_radar_cb.xmit_stopped == true) {
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "Xmit already disabled\n");
return;
}
sys_raw_disable_xmit();
qdrv_radar_cb.xmit_stopped = true;
}
/*
* Instruct MuC to enable/disable transmission for STA mode
*/
void qdrv_sta_set_xmit(int enable)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
if (!qdrv_radar_cb.ic)
return;
if (qdrv_is_dfs_master())
return;
if (enable) {
if (ic->sta_dfs_info.sta_dfs_strict_mode &&
((ieee80211_is_chan_radar_detected(ic->ic_curchan)) ||
(ieee80211_is_chan_cac_required(ic->ic_curchan)))) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"\n%s: xmit cannot be enabled on channel %d [%s]\n",
__func__, ic->ic_curchan ? ic->ic_curchan->ic_ieee : 0,
ieee80211_is_chan_radar_detected(ic->ic_curchan) ?
"CAC required" : "in Non-Occupancy list");
return;
}
sys_enable_xmit();
} else if (qdrv_radar_cb.xmit_stopped == false) {
sys_raw_disable_xmit();
qdrv_radar_cb.xmit_stopped = true;
} else {
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "Xmit already disabled\n");
}
}
/*
* Start or restart the non-occupy period
*/
static void start_nonoccupy(unsigned chan_idx)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct timer_list *nonoccupy_timer;
struct ieee80211_channel *chan;
unsigned long expires;
unsigned long sta_dfs_timer_expires;
KASSERT(chan_idx < ic->ic_nchans,
(DBGEFMT "out-of-range channel idx %u\n", DBGARG, chan_idx));
chan = &ic->ic_channels[chan_idx];
if (ic->sta_dfs_info.sta_dfs_strict_mode) {
/* Check IEEE80211_CHAN_RADAR flag to avoid repeated actions */
if (chan->ic_flags & IEEE80211_CHAN_RADAR)
return;
/*
* Mark channel with NOT_AVAILABLE_RADAR_DETECTED flag after a delay
* to allow the transmission of the measurement report to the AP
* by the STA.
*/
ic->sta_dfs_info.sta_radar_timer.data = chan->ic_ieee;
sta_dfs_timer_expires = jiffies + IEEE80211_MS_TO_JIFFIES(ic->sta_dfs_info.sta_dfs_tx_chan_close_time);
ic->sta_dfs_info.sta_dfs_radar_detected_timer = true;
ic->sta_dfs_info.sta_dfs_radar_detected_channel = chan->ic_ieee;
mod_timer(&ic->sta_dfs_info.sta_radar_timer, sta_dfs_timer_expires);
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "%s: Start sta_radar_timer [expiry:CSA/%lums]\n",
__func__, ic->sta_dfs_info.sta_dfs_tx_chan_close_time);
} else if (qdrv_is_dfs_slave()) {
/* DFS slave depends on a master for this period */
return;
}
if (ieee80211_is_repeater_associated(ic) && !(ic->sta_dfs_info.sta_dfs_strict_mode))
return;
chan->ic_flags |= IEEE80211_CHAN_RADAR;
chan->ic_radardetected++;
nonoccupy_timer = &qdrv_radar_cb.nonoccupy_timer[chan_idx];
expires = jiffies + ic->ic_non_occupancy_period;
if (DBG_LOG_FUNC_TEST(QDRV_LF_DFS_QUICKTIMER)) {
DBGPRINTF_N("RADAR: test mode - non-occupancy period will expire quickly\n");
expires = jiffies + NONOCCUPY_PERIOD_QUICK;
}
mod_timer(nonoccupy_timer, expires);
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: non-occupancy period started for channel %3d "
"(%4d MHz)\n", chan->ic_ieee, chan->ic_freq);
if (ic->sta_dfs_info.sta_dfs_strict_mode) {
return;
}
if (ic->ic_mark_channel_availability_status) {
ic->ic_mark_channel_availability_status(ic, chan, IEEE80211_CHANNEL_STATUS_NOT_AVAILABLE_RADAR_DETECTED);
}
}
/*
* Stop active or inactive nonoccupy period
*/
static void raw_stop_nonoccupy(unsigned chan_idx)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct timer_list *nonoccupy_timer;
struct ieee80211_channel *chan = &ic->ic_channels[chan_idx];
KASSERT(chan_idx < ic->ic_nchans,
(DBGFMT "out-of-range channel idx %u\n", DBGARG, chan_idx));
if (!(chan->ic_flags & IEEE80211_CHAN_RADAR)) {
return;
}
chan->ic_flags &= ~IEEE80211_CHAN_RADAR;
nonoccupy_timer = &qdrv_radar_cb.nonoccupy_timer[chan_idx];
del_timer(nonoccupy_timer);
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: non-occupancy period stopped for channel %3d "
"(%4d MHz)\n", chan->ic_ieee, chan->ic_freq);
}
static void qdrv_radar_enable_action(void)
{
struct ieee80211com *ic;
struct qdrv_wlan *qw;
struct qtn_stats_log *iw_stats_log;
if (qdrv_radar_first_call == true || !qdrv_radar_configured) {
DBGPRINTF_E("radar unconfigured\n");
return;
}
if (qdrv_radar_cb.enabled) {
DBGPRINTF_E("radar already enabled\n");
return;
}
ic = qdrv_radar_cb.ic;
qw = container_of(ic, struct qdrv_wlan, ic);
qdrv_mac_enable_irq(qw->mac, RUBY_M2L_IRQ_LO_OCAC);
/* start sampling */
iw_stats_log = qdrv_radar_cb.mac->mac_sys_stats;
if (qdrv_radar_cb.stats_uc_tx_ptr == NULL && iw_stats_log != NULL) {
qdrv_radar_cb.stats_uc_tx_ptr = ioremap_nocache(
muc_to_lhost((u32)iw_stats_log->tx_muc_stats),
sizeof(struct muc_tx_stats));
}
schedule_delayed_work(&qdrv_radar_cb.muc_sampling.sample_work,
(ic->ic_sample_rate * HZ) + QDRV_RADAR_SAMPLE_DELAY);
qdrv_radar_cb.enabled = true;
if (ic->ic_curchan != IEEE80211_CHAN_ANYC) {
qdrv_radar_before_newchan();
qdrv_radar_on_newchan();
}
/* For external stats */
QDRV_SET_SM_FLAG(qw->sm_stats, QDRV_WLAN_SM_STATE_RADAR_EN);
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "Radar enabled\n");
}
/*
* Disable DFS feature
*/
void qdrv_radar_disable(void)
{
struct ieee80211com *ic;
struct qdrv_wlan *qw;
struct qdrv_mac *mac;
unsigned chan_idx;
struct ieee80211_channel *chan;
if (qdrv_radar_first_call == true || !qdrv_radar_configured) {
DBGPRINTF_E("radar unconfigured\n");
return;
}
if (!qdrv_radar_cb.enabled) {
DBGPRINTF_E("radar already disabled\n");
return;
}
sys_disable_rdetection();
mac = qdrv_radar_cb.mac;
qdrv_mac_disable_irq(mac, RUBY_M2L_IRQ_LO_OCAC);
/* stop CAC if any */
stop_cac();
/* stop CS if any */
stop_dfs_cs();
/* stop sampling */
cancel_delayed_work(&qdrv_radar_cb.muc_sampling.sample_work);
if (qdrv_radar_cb.stats_uc_tx_ptr != NULL) {
iounmap(qdrv_radar_cb.stats_uc_tx_ptr);
qdrv_radar_cb.stats_uc_tx_ptr = NULL;
}
ic = qdrv_radar_cb.ic;
/* delete all nonoccupy timers and clear CAC done flag */
for (chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++) {
chan = &ic->ic_channels[chan_idx];
chan->ic_flags &= ~(IEEE80211_CHAN_DFS_CAC_DONE |
IEEE80211_CHAN_DFS_CAC_IN_PROGRESS);
raw_stop_nonoccupy(chan_idx);
if (ic->sta_dfs_info.sta_dfs_strict_mode && (chan->ic_flags & IEEE80211_CHAN_DFS)) {
ic->ic_chan_availability_status[chan->ic_ieee]
= IEEE80211_CHANNEL_STATUS_NON_AVAILABLE;
if (ic->ic_mark_channel_dfs_cac_status) {
ic->ic_mark_channel_dfs_cac_status(ic, chan,
IEEE80211_CHAN_DFS_CAC_DONE, false);
ic->ic_mark_channel_dfs_cac_status(ic, chan,
IEEE80211_CHAN_DFS_CAC_IN_PROGRESS, false);
}
}
}
if (ic->sta_dfs_info.sta_dfs_strict_mode) {
del_timer(&ic->sta_dfs_info.sta_radar_timer);
ic->sta_dfs_info.sta_dfs_radar_detected_timer = false;
ic->sta_dfs_info.sta_dfs_radar_detected_channel = 0;
}
#ifdef CONFIG_QHOP
del_timer(&ic->rbs_mbs_dfs_info.rbs_dfs_radar_timer);
#endif
/* always enable transmission */
sys_enable_xmit();
qdrv_radar_cb.enabled = false;
/* For external stats */
qw = container_of(ic, struct qdrv_wlan, ic);
QDRV_CLEAR_SM_FLAG(qw->sm_stats, QDRV_WLAN_SM_STATE_RADAR_EN);
/* success */
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "radar disabled\n");
}
void qdrv_set_radar(int enable)
{
if (qdrv_radar_first_call == true || !qdrv_radar_configured) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"radar already unconfigured\n");
return;
}
enable = !!enable;
if (enable == qdrv_radar_cb.enabled) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "Radar already %s\n",
enable ? "enabled" : "disabled");
return;
}
if (enable)
qdrv_radar_enable_action();
else
qdrv_radar_disable();
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "Radar configured manually\n");
}
int qdrv_radar_detections_num(uint32_t chan)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
uint32_t chan_idx = 0;
if (!qdrv_radar_cb.enabled)
return -1;
for (chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++) {
if (ic->ic_channels[chan_idx].ic_ieee == chan)
break;
}
if (!(ic->ic_channels[chan_idx].ic_flags & IEEE80211_CHAN_DFS)) {
return -1;
} else {
return (ic->ic_channels[chan_idx].ic_radardetected);
}
}
static void qdrv_ocac_tasklet(unsigned long data)
{
struct qtn_ocac_info *ocac_info = (struct qtn_ocac_info *)data;
struct radar_ocac_info_s *radar_ocac_info = radar_ocac_info_addr_get();
struct ieee80211com *ic = qdrv_radar_cb.ic;
uint8_t array_ps = radar_ocac_info->array_ps;
struct ieee80211_ocac_tsflog *p_tsflog;
bool radar_enabled = radar_get_status();
if (!qdrv_radar_cb.enabled)
return;
if (!ic->ic_ocac.ocac_chan)
return;
/* Only do off channel CAC on non-DFS channel */
if (qdrv_radar_is_rdetection_required(ic->ic_bsschan))
return;
/* enable radar if it is non-DFS channel and radar is disabled */
if (!radar_enabled) {
sys_enable_rdetection();
}
spin_lock(&radar_ocac_info->lock);
radar_ocac_info->ocac_radar_pts[array_ps].ocac_status = ocac_info->chan_status;
DBGPRINTF(DBG_LL_DEBUG, QDRV_LF_RADAR, "status %d\n",
radar_ocac_info->ocac_radar_pts[array_ps].ocac_status);
radar_record_buffer_pt(&radar_ocac_info->ocac_radar_pts[array_ps].fifo_pt);
radar_ocac_info->array_ps++;
radar_ocac_info->ocac_scan_chan = ic->ic_ocac.ocac_chan->ic_ieee;
spin_unlock(&radar_ocac_info->lock);
if (ocac_info->chan_status == QTN_OCAC_ON_DATA_CHAN) {
ic->ic_ocac.ocac_counts.tasklet_data_chan++;
ic->ic_ocac.ocac_accum_cac_time_ms += ocac_info->actual_dwell_time;
p_tsflog = &ic->ic_ocac.ocac_tsflog;
memcpy(p_tsflog->tsf_log[p_tsflog->log_index], ocac_info->tsf_log,
sizeof(p_tsflog->tsf_log[p_tsflog->log_index]));
p_tsflog->log_index = (p_tsflog->log_index + 1) % QTN_OCAC_TSF_LOG_DEPTH;
ic->ic_chan_switch_reason_record(ic, IEEE80211_CSW_REASON_OCAC_RUN);
} else {
ic->ic_ocac.ocac_counts.tasklet_off_chan++;
}
}
/*
* Send CSA frame to MuC
*/
#ifndef CONFIG_QHOP
static void sys_send_csa(struct ieee80211vap *vap, struct ieee80211_channel* new_chan, u_int64_t tsf)
{
struct ieee80211com *ic;
if ((vap == NULL) || (new_chan == NULL)) {
DBGPRINTF_E("vap 0x%p, new_chan 0x%p\n", vap, new_chan);
return;
}
ic = vap->iv_ic;
ic->ic_send_csa_frame(vap, IEEE80211_CSA_MUST_STOP_TX,
new_chan->ic_ieee, IEEE80211_RADAR_11HCOUNT, tsf);
}
#endif
static void send_channel_related_event(struct net_device *dev, char *event_string)
{
if (event_string == NULL || dev == NULL) {
return;
}
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"send event to userspace, dev=%s msg=%s\n", dev->name, event_string);
radar_event_report(dev, "%s", event_string);
}
/* notify the dfs reentry demon of the channel switch info */
void dfs_reentry_chan_switch_notify(struct net_device *dev, struct ieee80211_channel *new_chan)
{
char *dfs_chan_sw = "dfs_csa";
char *nondfs_chan_sw = "non_dfs_csa";
char *no_chan_valid = "csa_fail";
char *notify_string;
if (NULL == new_chan) {
notify_string = no_chan_valid;
} else if (new_chan->ic_flags & IEEE80211_CHAN_DFS){
notify_string = dfs_chan_sw;
} else {
notify_string = nondfs_chan_sw;
}
send_channel_related_event(dev, notify_string);
}
EXPORT_SYMBOL(dfs_reentry_chan_switch_notify);
/*
* Initiate a channel switch
* - 'new_chan' should not be NULL
*/
static void sys_change_chan(struct ieee80211_channel *new_chan)
{
#define IS_UP(_dev) (((_dev)->flags & (IFF_RUNNING|IFF_UP)) == (IFF_RUNNING|IFF_UP))
#define IEEE80211_VAPS_LOCK_BH(_ic) spin_lock_bh(&(_ic)->ic_vapslock);
#define IEEE80211_VAPS_UNLOCK_BH(_ic) spin_unlock_bh(&(_ic)->ic_vapslock);
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
if (!new_chan || !vap) {
DBGPRINTF_E("null channel or vap\n");
return;
}
/* if dfs channel the notify will be send after cac */
if (!(new_chan->ic_flags & IEEE80211_CHAN_DFS))
dfs_reentry_chan_switch_notify(vap->iv_dev, new_chan);
if (IS_UP(vap->iv_dev)) {
ic->ic_prevchan = ic->ic_curchan;
ic->ic_curchan = ic->ic_des_chan = new_chan;
ic->ic_csw_reason = IEEE80211_CSW_REASON_DFS;
IEEE80211_VAPS_LOCK_BH(ic);
vap->iv_newstate(vap, IEEE80211_S_SCAN, 0);
IEEE80211_VAPS_UNLOCK_BH(ic);
ic->ic_flags &= ~IEEE80211_F_CHANSWITCH;
} else if (vap->iv_state == IEEE80211_S_RUN) {
/* Normally, we don't get to here */
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
if ((vap->iv_opmode == IEEE80211_M_WDS) && (vap->iv_state == IEEE80211_S_RUN)) {
IEEE80211_VAPS_LOCK_BH(ic);
vap->iv_newstate(vap, IEEE80211_S_INIT, 0);
IEEE80211_VAPS_UNLOCK_BH(ic);
}
}
ic->ic_prevchan = ic->ic_curchan;
ic->ic_curchan = new_chan;
ic->ic_bsschan = new_chan;
ic->ic_csw_reason = IEEE80211_CSW_REASON_DFS;
ic->ic_set_channel(ic);
ic->ic_flags &= ~IEEE80211_F_CHANSWITCH;
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
if ((vap->iv_opmode == IEEE80211_M_WDS) && (vap->iv_state == IEEE80211_S_INIT)) {
IEEE80211_VAPS_LOCK_BH(ic);
vap->iv_newstate(vap, IEEE80211_S_RUN, 0);
IEEE80211_VAPS_UNLOCK_BH(ic);
}
if (vap->iv_opmode != IEEE80211_M_HOSTAP)
continue;
if ((vap->iv_state != IEEE80211_S_RUN) && (vap->iv_state != IEEE80211_S_SCAN))
continue;
ic->ic_beacon_update(vap);
}
} else {
ic->ic_flags &= ~IEEE80211_F_CHANSWITCH;
DBGPRINTF_E("channel change failed\n");
}
}
/*
* CAC has successfully passed
*/
static void cac_completed_action(unsigned long data)
{
struct ieee80211com *ic;
struct qdrv_wlan *qw;
struct ieee80211_channel *chan;
struct ieee80211vap *vap;
int chan_status = 0;
ic = qdrv_radar_cb.ic;
if (ic == NULL || !is_cac_started()) {
DBGPRINTF_E("CAC not in progress\n");
return;
}
vap = TAILQ_FIRST(&ic->ic_vaps);
if (vap == NULL || vap->iv_dev == NULL) {
return;
}
qw = container_of(ic, struct qdrv_wlan, ic);
chan = qdrv_radar_cb.cac_chan;
/* resume normal operation on channel */
sys_enable_xmit();
chan->ic_flags |= IEEE80211_CHAN_DFS_CAC_DONE;
chan->ic_flags &= ~IEEE80211_CHAN_DFS_CAC_IN_PROGRESS;
if (ic->sta_dfs_info.sta_dfs_strict_mode) {
if (!(vap->iv_bss && IEEE80211_NODE_AID(vap->iv_bss))) {
chan_status = IEEE80211_CHANNEL_STATUS_NON_AVAILABLE;
} else {
chan_status = IEEE80211_CHANNEL_STATUS_AVAILABLE;
}
} else {
chan_status = IEEE80211_CHANNEL_STATUS_AVAILABLE;
}
if (ic->ic_mark_channel_availability_status) {
ic->ic_mark_channel_availability_status(ic, chan, chan_status);
}
if (ic->ic_mark_channel_dfs_cac_status) {
if (ic->sta_dfs_info.sta_dfs_strict_mode && (chan_status == IEEE80211_CHANNEL_STATUS_NON_AVAILABLE)) {
ic->ic_mark_channel_dfs_cac_status(ic, chan, IEEE80211_CHAN_DFS_CAC_DONE, false);
} else {
ic->ic_mark_channel_dfs_cac_status(ic, chan, IEEE80211_CHAN_DFS_CAC_DONE, true);
}
ic->ic_mark_channel_dfs_cac_status(ic, chan, IEEE80211_CHAN_DFS_CAC_IN_PROGRESS, false);
}
DBGPRINTF_N_QEVT(vap->iv_dev, "RADAR: CAC completed for channel %3d (%4d MHz)\n",
chan->ic_ieee, chan->ic_freq);
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "\n%s: chan_status=%d\n", __func__, chan_status);
QDRV_CLEAR_SM_FLAG(qw->sm_stats, QDRV_WLAN_SM_STATE_CAC_ACTIVE);
/* cac has ended, it means can switch to a dfs channel succed*/
dfs_reentry_chan_switch_notify(vap->iv_dev, qdrv_radar_cb.cac_chan);
qdrv_radar_cb.cac_chan = NULL;
ic->ic_pm_reason = IEEE80211_PM_LEVEL_CAC_COMPLETED;
ieee80211_pm_queue_work_custom(ic, BOARD_PM_WLAN_IDLE_TIMEOUT);
if (ic->ic_ap_next_cac) {
(void) ic->ic_ap_next_cac(ic, vap, CAC_PERIOD, &qdrv_radar_cb.cac_chan,
IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY);
}
}
void qdrv_cac_instant_completed(void)
{
struct timer_list *cac_timer;
if (!is_cac_started())
return;
KASSERT((qdrv_radar_cb.cac_chan->ic_flags & IEEE80211_CHAN_DFS) != 0,
(DBGEFMT "CAC started on non-DFS channel: %3d (%4d MHz)\n",
DBGARG, qdrv_radar_cb.cac_chan->ic_ieee,
qdrv_radar_cb.cac_chan->ic_freq));
KASSERT(!(qdrv_radar_cb.cac_chan->ic_flags & IEEE80211_CHAN_DFS_CAC_DONE),
(DBGEFMT "CAC_DONE marked prior to CAC completed\n", DBGARG));
cac_timer = &qdrv_radar_cb.cac_timer;
mod_timer(cac_timer, jiffies);
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "RADAR: CAC period will expire instantly\n");
}
/*
* Start or restart the CAC procedure
* - precondition: transmission is already disabled
*/
static void start_cac(void)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *cur_chan = ic->ic_curchan;
struct qdrv_wlan *qw = container_of(ic, struct qdrv_wlan, ic);
struct timer_list *cac_timer = &qdrv_radar_cb.cac_timer;
unsigned long expires;
/* CAC not required for DFS slave */
if (qdrv_is_dfs_slave() && !(ic->sta_dfs_info.sta_dfs_strict_mode))
return;
/* stop cac if any */
stop_cac();
KASSERT(qdrv_radar_cb.cac_chan == NULL,
(DBGEFMT "CAC channel is not null\n", DBGARG));
if (cur_chan == IEEE80211_CHAN_ANYC) {
DBGPRINTF_E("operational channel not yet selected\n");
return;
}
/* save the operational channel into the control block */
qdrv_radar_cb.cac_chan = cur_chan;
cur_chan->ic_flags |= IEEE80211_CHAN_DFS_CAC_IN_PROGRESS;
if (ic->ic_mark_channel_dfs_cac_status) {
ic->ic_mark_channel_dfs_cac_status(ic, cur_chan, IEEE80211_CHAN_DFS_CAC_IN_PROGRESS, true);
}
if (ieee80211_is_on_weather_channel(ic, cur_chan))
expires = jiffies + CAC_WEATHER_PERIOD_EU;
else
expires = jiffies + CAC_PERIOD;
if (DBG_LOG_FUNC_TEST(QDRV_LF_DFS_QUICKTIMER)) {
DBGPRINTF_N("RADAR: test mode - CAC period will expire quickly\n");
expires = jiffies + CAC_PERIOD_QUICK;
}
mod_timer(cac_timer, expires);
QDRV_SET_SM_FLAG(qw->sm_stats, QDRV_WLAN_SM_STATE_CAC_ACTIVE);
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: CAC started for channel %3d (%4d MHz)\n",
cur_chan->ic_ieee, cur_chan->ic_freq);
}
/*
* Stop cac procedure
*/
static void raw_stop_cac(void)
{
struct ieee80211_channel *chan = qdrv_radar_cb.cac_chan;
struct timer_list *cac_timer = &qdrv_radar_cb.cac_timer;
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct qdrv_wlan *qw = container_of(ic, struct qdrv_wlan, ic);
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
if (!is_cac_started()) { /* no cac to stop */
DBGPRINTF_E("CAC is not started\n");
return;
}
del_timer(cac_timer);
chan->ic_flags &= ~(IEEE80211_CHAN_DFS_CAC_DONE |
IEEE80211_CHAN_DFS_CAC_IN_PROGRESS);
if (ic->ic_mark_channel_dfs_cac_status) {
ic->ic_mark_channel_dfs_cac_status(ic, chan, IEEE80211_CHAN_DFS_CAC_DONE, false);
ic->ic_mark_channel_dfs_cac_status(ic, chan, IEEE80211_CHAN_DFS_CAC_IN_PROGRESS, false);
}
QDRV_CLEAR_SM_FLAG(qw->sm_stats, QDRV_WLAN_SM_STATE_CAC_ACTIVE);
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: CAC stopped for channel %3d (%4d MHz)\n",
chan->ic_ieee, chan->ic_freq);
/* no cac now */
qdrv_radar_cb.cac_chan = NULL;
/* take it as an channel switch failed event
* to satisfy the dfs reentry demon when it's waiting for the dfs reentry result */
if (vap && vap->iv_dev)
dfs_reentry_chan_switch_notify(vap->iv_dev, NULL);
}
static void stop_cac(void)
{
if (is_cac_started())
raw_stop_cac();
}
void qdrv_radar_stop_active_cac(void)
{
if (!qdrv_radar_cb.enabled)
return;
if (is_cac_started()) {
raw_stop_cac();
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "%s: stop CAC\n", __func__);
}
return;
}
void sta_dfs_strict_cac_action(struct ieee80211_channel *chan)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
if (!ic->sta_dfs_info.sta_dfs_strict_mode) {
return;
}
if (!qdrv_radar_cb.enabled)
return;
if (ieee80211_is_chan_cac_required(chan)) {
sys_disable_xmit();
start_cac();
} else if (ieee80211_is_chan_not_available(chan) || ieee80211_is_chan_available(chan)) {
sys_enable_xmit();
}
}
/*
* The non-occupancy period expires
* - the channel is now available for use
*/
static void nonoccupy_expire_action(unsigned long data)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
unsigned chan_idx = data;
struct ieee80211_channel *chan;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
KASSERT(chan_idx < ic->ic_nchans,
(DBGEFMT "out-of-range channel idx %u\n", DBGARG, chan_idx));
chan = &ic->ic_channels[chan_idx];
chan->ic_flags &= ~IEEE80211_CHAN_RADAR;
if (ic->ic_flags_qtn & IEEE80211_QTN_RADAR_SCAN_START) {
if (ic->ic_initiate_scan) {
ic->ic_initiate_scan(vap);
}
}
/* Mark the channel as not_available and ready for cac */
if (ic->ic_mark_channel_availability_status) {
ic->ic_mark_channel_availability_status(ic, chan, IEEE80211_CHANNEL_STATUS_NOT_AVAILABLE_CAC_REQUIRED);
}
if ((ic->sta_dfs_info.sta_dfs_strict_mode)
&& (ic->ic_curchan == chan)) {
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
if ((vap->iv_state != IEEE80211_S_RUN)
&& (vap->iv_state != IEEE80211_S_SCAN)) {
continue;
}
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "%s: Trigger scan\n", __func__);
vap->iv_newstate(vap, IEEE80211_S_SCAN, 0);
}
}
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: non-occupancy period expired for channel %3d "
"(%4d MHz)\n", chan->ic_ieee, chan->ic_freq);
}
#ifdef CONFIG_QHOP
static void rbs_radar_detected_timer_action(unsigned long chan_ic_ieee)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *chan = ieee80211_find_channel_by_ieee(ic, chan_ic_ieee);
if (ic->ic_chan_compare_equality(ic, ic->ic_curchan, chan)) {
sys_disable_xmit();
}
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "%s expired\n", __func__);
}
#endif
static void sta_radar_detected_timer_action(unsigned long chan_ic_ieee)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *chan = ieee80211_find_channel_by_ieee(ic, chan_ic_ieee);
ic->sta_dfs_info.sta_dfs_radar_detected_timer = false;
if (ic->ic_mark_channel_availability_status) {
ic->ic_mark_channel_availability_status(ic, chan,
IEEE80211_CHANNEL_STATUS_NOT_AVAILABLE_RADAR_DETECTED);
}
if (ic->ic_chan_compare_equality(ic, ic->ic_curchan, chan)) {
sys_disable_xmit();
}
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "%s: sta_radar_timer expired\n", __func__);
}
static void sta_silence_timer_action(unsigned long data)
{
struct ieee80211vap *vap = (struct ieee80211vap *)data;
if (vap)
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
sys_enable_xmit();
}
/*
* Time to perform channel switch
*/
static void dfs_cs_timer_expire_action(unsigned long data)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
if (is_dfs_cs_started()) {
struct ieee80211_channel *chan = qdrv_radar_cb.dfs_des_chan;
if (qdrv_radar_cb.dfs_des_chan != IEEE80211_CHAN_ANYC){
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: DFS channel switch to %3d (%4d MHz)\n",
chan->ic_ieee, chan->ic_freq);
sys_change_chan(chan);
} else {
/* disable the transmission before starting the AP scan */
sys_disable_xmit();
/* no channel selected by radar module. Call Scanner */
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: starting AP scan due to radar "
"detection\n");
(void) ieee80211_start_scan(vap, IEEE80211_SCAN_NO_DFS,
IEEE80211_SCAN_FOREVER, 0, NULL);
}
qdrv_radar_cb.dfs_des_chan = NULL;
}
}
/*
* Start a DFS-triggered channel switch
*/
#ifndef CONFIG_QHOP
static void start_dfs_cs(struct ieee80211_channel *new_chan)
{
struct timer_list *dfs_cs_timer = &qdrv_radar_cb.dfs_cs_timer;
if (is_dfs_cs_started())
stop_dfs_cs();
qdrv_radar_cb.dfs_des_chan = new_chan;
mod_timer(dfs_cs_timer, jiffies + DFS_CS_TIMER_VAL);
}
#endif
/*
* Stop the DFS-triggered channel switch
*/
static void stop_dfs_cs()
{
struct timer_list *dfs_cs_timer = &qdrv_radar_cb.dfs_cs_timer;
if (is_dfs_cs_started()) {
del_timer(dfs_cs_timer);
qdrv_radar_cb.dfs_des_chan = NULL;
}
}
static struct ieee80211_channel *qdrv_validate_fs_chan(int fast_switch, u_int8_t new_ieee)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *chan = NULL;
struct ieee80211_channel *new_chan = NULL;
unsigned chan_idx;
if (new_ieee == 0) {
return NULL;
}
chan = ic->ic_channels;
for (chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++, chan++) {
if (chan->ic_ieee == new_ieee) {
new_chan = chan;
break;
}
}
if (new_chan == NULL) {
DBGPRINTF_E("channel %d not found\n", new_ieee);
} else if (!ic->ic_check_channel(ic, chan, fast_switch, 0)) {
DBGPRINTF_E("channel %d is not usable\n", new_ieee);
new_chan = NULL;
}
return new_chan;
}
/*
* Select a new channel to use
* - according to FCC/ETSI rules on uniform spreading, we shall select a
* channel out of the list of usable channels so that the probability
* of selecting a given channel shall be the same for all channels
* (reference: ETSI 301 893 v1.5.1 $4.7.2.6)
* - possible for this function to return NULL
* - a random channel can be returned if the specified channel is neither
* found nor usable
*/
struct ieee80211_channel *qdrv_radar_select_newchan(u_int8_t new_ieee)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *chan;
struct ieee80211_channel *new_chan = NULL;
unsigned chan_idx;
int fast_switch = (ic->ic_flags_ext & IEEE80211_FEXT_DFS_FAST_SWITCH) != 0;
/* check if we can switch to the user configured channel */
new_chan = qdrv_validate_fs_chan(fast_switch, new_ieee);
if ((new_chan == NULL) && (new_ieee != ic->ic_ieee_best_alt_chan)) {
new_chan = qdrv_validate_fs_chan(fast_switch, ic->ic_ieee_best_alt_chan);
}
/* select a random channel */
if (new_chan == NULL) {
unsigned count;
chan = ic->ic_channels;
for (count = 0, chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++, chan++) {
if (ic->ic_check_channel(ic, chan, fast_switch, 0)) {
count++;
}
}
if (count != 0) {
unsigned rand = jiffies % count;
chan = ic->ic_channels;
for (count = 0, chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++, chan++) {
if (ic->ic_check_channel(ic, chan, fast_switch, 0)) {
if (count++ == rand) {
new_chan = &ic->ic_channels[chan_idx];
break;
}
}
}
}
}
if (new_chan) {
chan = new_chan;
new_chan = ieee80211_scs_switch_pri_chan(ic->ic_scan, new_chan);
if (!new_chan) {
DBGPRINTF_W("All subchannels are crowded with BSS, selected anyway\n");
new_chan = chan;
}
}
if (new_chan) {
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: new channel selected %d (%d MHz)\n",
new_chan->ic_ieee, new_chan->ic_freq);
} else {
DBGPRINTF_E("no valid channel found\n");
}
return new_chan;
}
EXPORT_SYMBOL(qdrv_radar_select_newchan);
/*
* Perform the dfs related action after new channel has been selected
*/
static void dfs_action_after_newchan_select(struct ieee80211_channel *new_chan, bool radar_detected_during_cac)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *cur_chan = ic->ic_curchan;
struct ieee80211vap *vap;
bool vap_found = false;
#ifndef CONFIG_QHOP
struct ieee80211_channel *csa_chan;
uint64_t tsf = 0;
#endif
if (new_chan == NULL) {
vap = TAILQ_FIRST(&ic->ic_vaps);
dfs_reentry_chan_switch_notify(vap->iv_dev, new_chan);
/* disable the transmission before starting the AP scan */
sys_disable_xmit();
/* no channel selected by radar module. Call Scanner */
(void) ieee80211_start_scan(vap, IEEE80211_SCAN_NO_DFS,
IEEE80211_SCAN_FOREVER, 0, NULL);
DBGPRINTF_E("new channel not found or usable\n");
return;
}
#ifdef CONFIG_QHOP
/* If the node is MBS send CSA frames */
if (!ieee80211_scs_is_wds_rbs_node(ic)) {
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
if (vap->iv_opmode != IEEE80211_M_HOSTAP)
continue;
if (vap->iv_state != IEEE80211_S_RUN)
continue;
if (radar_detected_during_cac && ic->rbs_mbs_dfs_info.rbs_mbs_allow_tx_frms_in_cac) {
ic->rbs_mbs_dfs_info.mbs_allow_csa = true;
sys_enable_xmit();
}
ieee80211_dfs_send_csa(vap, new_chan->ic_ieee);
}
}
#else
/*
* Just use CSA action frame, so set ic_csa_count to zero and
* avoid CSA ie included in beacon.
*/
ic->ic_flags |= IEEE80211_F_CHANSWITCH;
ic->ic_csa_count = 0;
/* send CSA action frame for each vap */
csa_chan = new_chan;
ic->ic_get_tsf(&tsf);
tsf += IEEE80211_MS_TO_USEC(QDRV_RADAR_DFLT_CHANSW_MS);
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
/* Just skip WDS mode, because not sure if we support DFS on STA later */
if (vap->iv_opmode == IEEE80211_M_WDS)
continue;
if ((vap->iv_state != IEEE80211_S_RUN) && (vap->iv_state != IEEE80211_S_SCAN))
continue;
vap_found = true;
sys_send_csa(vap, csa_chan, tsf);
}
start_dfs_cs(new_chan);
#endif
ic->ic_dfs_cce.cce_previous = cur_chan->ic_ieee;
ic->ic_dfs_cce.cce_current = new_chan->ic_ieee;
if (vap_found != true )
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_RADAR, "no vap running\n");
}
static void
dfs_send_report_frame(struct ieee80211com *ic, struct ieee80211vap *vap) {
struct ieee80211_node *ni;
struct ieee80211_channel *cur_chan;
struct ieee80211_meas_report_ctrl mreport_ctrl;
struct ieee80211_action_data action_data;
/* DFS enabled STA sends Autonomous Measurement Report Action Frame to AP*/
if (vap == NULL)
return;
KASSERT(vap->iv_state == IEEE80211_S_RUN, (DBGEFMT "Radar send reprot "
"frame, vap state incorrect: %d\n", DBGARG, vap->iv_state));
memset(&mreport_ctrl, 0, sizeof(mreport_ctrl));
memset(&action_data, 0, sizeof(action_data));
ni = vap->iv_bss;
cur_chan = ic->ic_curchan;
mreport_ctrl.meas_type = IEEE80211_CCA_MEASTYPE_BASIC;
mreport_ctrl.report_mode = 0;
mreport_ctrl.autonomous = 1;
mreport_ctrl.u.basic.channel = ieee80211_chan2ieee(ic, cur_chan);
mreport_ctrl.u.basic.basic_report |= IEEE80211_MEASURE_BASIC_REPORT_RADAR;
action_data.cat = IEEE80211_ACTION_CAT_SPEC_MGMT;
action_data.action = IEEE80211_ACTION_S_MEASUREMENT_REPORT;
action_data.params = &mreport_ctrl;
ic->ic_send_mgmt(ni, IEEE80211_FC0_SUBTYPE_ACTION, (int)&action_data);
return;
}
static void
dfs_slave_push_state_machine(struct ieee80211com *ic)
{
struct ieee80211vap *vap;
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
if ((vap->iv_state != IEEE80211_S_RUN) &&
(vap->iv_state != IEEE80211_S_SCAN)) {
continue;
}
vap->iv_newstate(vap, IEEE80211_S_SCAN, 0);
}
ic->ic_chan_switch_reason_record(ic, IEEE80211_CSW_REASON_DFS);
return;
}
#ifdef CONFIG_QHOP
static void
dfs_send_qhop_report_frame(struct ieee80211com *ic, u_int8_t new_ieee)
{
struct ieee80211vap *vap;
/*
* If this is an RBS we send the reports to the MBS on the WDS link
* Note: We are assuming hub and spoke topology. For general tree or mesh
* much more sophisticated routing algorithm should be implemented
*/
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
/* Note: We are assuming hub and spoke topology. For general tree or mesh */
/* much more sophisticated routing algorithm should be implemented */
if (IEEE80211_VAP_WDS_IS_RBS(vap)) {
qdrv_qhop_send_rbs_report_frame(vap, new_ieee);
return;
}
}
}
#endif
/*
* Perform the dfs action including channel switch.
*/
static void dfs_action(uint8_t new_ieee, bool radar_detected_during_cac)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *new_chan = NULL;
struct ieee80211vap *vap;
uint32_t repeater_mode;
#ifdef CONFIG_QHOP
if (ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_RBS) {
if (radar_detected_during_cac && ic->rbs_mbs_dfs_info.rbs_mbs_allow_tx_frms_in_cac) {
ic->rbs_mbs_dfs_info.rbs_allow_qhop_report = true;
sys_enable_xmit();
ic->rbs_mbs_dfs_info.rbs_dfs_radar_timer.data = ic->ic_curchan->ic_ieee;
mod_timer(&ic->rbs_mbs_dfs_info.rbs_dfs_radar_timer,
(jiffies + IEEE80211_MS_TO_JIFFIES(ic->rbs_mbs_dfs_info.rbs_dfs_tx_chan_close_time)));
}
dfs_send_qhop_report_frame(ic, new_ieee);
return;
}
#endif
vap = TAILQ_FIRST(&ic->ic_vaps);
repeater_mode = ieee80211_is_repeater(ic);
if (qdrv_is_dfs_slave() || repeater_mode) {
bool send_measurement_in_sta_dfs_strict = ic->sta_dfs_info.sta_dfs_strict_mode &&
ic->sta_dfs_info.sta_dfs_strict_msr_cac &&
radar_detected_during_cac;
/* DFS slave just initiates scan as DFS action */
if (vap && vap->iv_state == IEEE80211_S_RUN) {
if ((qdrv_radar_cb.xmit_stopped == true)
&& (!send_measurement_in_sta_dfs_strict)) {
DBGPRINTF(DBG_LL_WARNING, QDRV_LF_RADAR,
"%s report radar failed\n",
repeater_mode ? "Repeater" : "STA");
} else {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"%s report radar to master\n",
repeater_mode ? "Repeater" : "STA");
if (send_measurement_in_sta_dfs_strict) {
DBGPRINTF_N_QEVT(vap->iv_dev, "STA-DFS: Sending measurement frame during CAC\n");
sys_enable_xmit();
ic->sta_dfs_info.allow_measurement_report = true;
}
dfs_send_report_frame(ic, vap);
if (qdrv_radar_sta_dfs || repeater_mode)
return;
}
}
if (!repeater_mode) {
dfs_slave_push_state_machine(ic);
return;
}
/*
* If STA interface of a repeater do not associated with
* any root-AP,the repeater should act as if it's an AP.
*/
}
/*
* Default behavior for AP:
* If radar detected during ICAC, continue ICAC on next NOT_AVAILABLE_DFS channel
* If DFS fast switch configured, do random channel selection or fixed channel
* based on customer's configuration;
* If DFS fast switch not configured, use channel scan to pick up a best non-DFS channel
*/
if (vap && ic->ic_ap_next_cac
&& (ic->ic_ap_next_cac(ic, vap, CAC_PERIOD, &qdrv_radar_cb.cac_chan,
IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY)) < 0) {
if (ic->ic_flags_ext & IEEE80211_FEXT_DFS_FAST_SWITCH) {
/*
* select one channel at random
*/
new_chan = qdrv_radar_select_newchan(new_ieee);
dfs_action_after_newchan_select(new_chan, radar_detected_during_cac);
} else {
/*
* Using channel scan to pick up a best non-DFS channel to switch
* Channel switch and DFS action will be done after scanning is done
*/
vap = TAILQ_FIRST(&ic->ic_vaps);
ieee80211_start_scan(vap, IEEE80211_SCAN_FLUSH | IEEE80211_SCAN_NO_DFS
| IEEE80211_SCAN_DFS_ACTION, IEEE80211_SCAN_FOREVER,
vap->iv_des_nssid, vap->iv_des_ssid);
}
}
ic->ic_chan_switch_reason_record(ic, IEEE80211_CSW_REASON_DFS);
}
void qdrv_dfs_action_scan_done(void)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *new_chan = NULL;
IEEE80211_LOCK_IRQ(ic);
new_chan = ieee80211_scan_pickchannel(ic, IEEE80211_SCAN_NO_DFS);
IEEE80211_UNLOCK_IRQ(ic);
dfs_action_after_newchan_select(new_chan, false);
}
/*
* Decide whether or not to detect radar on the channel
*/
bool qdrv_radar_is_rdetection_required(const struct ieee80211_channel *chan)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
bool rdetect = false;
bool doth = ic->ic_flags & IEEE80211_F_DOTH;
if (DBG_LOG_FUNC_TEST(QDRV_LF_DFS_DONTCAREDOTH)) {
DBGPRINTF_N("RADAR: test mode - detection enabled\n");
doth = true;
}
if (doth) {
if (chan == IEEE80211_CHAN_ANYC) {
DBGPRINTF_E("channel not yet set\n");
return false;
}
if (chan->ic_flags & IEEE80211_CHAN_DFS)
rdetect = true;
}
return rdetect;
}
static int32_t qdrv_radar_off_chan_cac_action(struct ieee80211com *ic)
{
struct radar_ocac_info_s *qdrv_ocac_info;
uint8_t ocac_scan_chan;
if (qdrv_is_dfs_slave())
return -EINVAL;
if (ieee80211_is_repeater(ic))
return -EINVAL;
qdrv_ocac_info = radar_ocac_info_addr_get();
ocac_scan_chan = qdrv_ocac_info->ocac_scan_chan;
if (qdrv_radar_cb.region == DFS_RQMT_US) {
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: radar found on channel %u during CAC\n",
ocac_scan_chan);
} else {
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: radar found on off channel %u, current "
"chan %u\n", ocac_scan_chan, ic->ic_curchan->ic_ieee);
}
return ocac_scan_chan;
}
/*
* Invoked when a radar is detected.
* Called directly from iwpriv wifi0 doth_radar <new channel>.
* Called when AP receives a radar detection report from an associated STA.
*/
void qdrv_radar_detected(struct ieee80211com *ic, u_int8_t new_ieee)
{
struct qdrv_wlan *qw = container_of(ic, struct qdrv_wlan, ic);
int retval = -1;
uint8_t local_new_ieee = new_ieee;
uint32_t chan_idx;
struct ieee80211_channel *chan = NULL;
struct ieee80211vap *vap;
bool rdetect;
int32_t radar_chan;
bool radar_detected_during_cac = is_cac_started();
if (!qdrv_radar_configured) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "radar not initialized\n");
return;
}
if (!qdrv_radar_cb.enabled) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "radar not enabled\n");
return;
}
if (ic != qdrv_radar_cb.ic) {
DBGPRINTF_E("ic 0x%p not matching the configured ic 0x%p\n",
ic, qdrv_radar_cb.ic);
return;
}
/* stop cac if any */
stop_cac();
if (qdrv_is_dfs_slave() && !qdrv_radar_sta_dfs) {
DBGPRINTF_E("ic mode is %d and sta dfs is %s\n", ic->ic_opmode,
qdrv_radar_sta_dfs ? "enabled" : "disabled");
return;
}
rdetect = qdrv_radar_is_rdetection_required(ic->ic_curchan);
if (!rdetect) {
/* detect radar during off channel CAC */
if (!ic->ic_ocac.ocac_chan) {
DBGPRINTF_E("radar operating channel invalid\n");
return;
}
radar_chan = qdrv_radar_off_chan_cac_action(ic);
} else {
radar_chan = ic->ic_curchan->ic_ieee;
}
if (radar_chan < 0) {
DBGPRINTF_E("radar operating channel invalid\n");
return;
}
/* get an in-service channel */
for (chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++) {
if (ic->ic_channels[chan_idx].ic_ieee == radar_chan) {
chan = &ic->ic_channels[chan_idx];
break;
}
}
if (!chan) {
DBGPRINTF_E("no matching in-service channel for freq=%d\n",
ic->ic_curchan->ic_freq);
return;
}
KASSERT((chan->ic_flags & IEEE80211_CHAN_DFS), (DBGEFMT "Radar"
" detected on non-DFS channel\n", DBGARG));
DBGPRINTF_N_QEVT(ic2dev(ic), "RADAR: radar found on channel %3d (%4d MHz)\n",
chan->ic_ieee, chan->ic_freq);
/*
* To avoid repeated dfs actions when AP and STAs detected
* same radar, test flag here. (only for AP side)
*/
if ((chan->ic_flags & IEEE80211_CHAN_RADAR) && !(ic->sta_dfs_info.sta_dfs_strict_mode)) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"DFS marked on channel %3d (%4d MHz) already\n",
chan->ic_ieee, chan->ic_freq);
return;
}
if (qw->radar_detect_callback) {
retval = qw->radar_detect_callback(chan);
if (retval == 0)
return;
}
/* check if dfs marking is allowed */
if (!(ic->ic_flags_ext & IEEE80211_FEXT_MARKDFS)) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "DFS marking disabled\n");
return;
}
/* return immediately if we are in the dfs test mode */
if (qdrv_radar_is_test_mode()) {
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_RADAR | QDRV_LF_DFS_TESTMODE,
"test mode - no DFS action taken\n");
if (qdrv_radar_test_mode_csa_en() && qdrv_is_dfs_master()) {
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_RADAR | QDRV_LF_DFS_TESTMODE,
"send CSA action\n");
vap = TAILQ_FIRST(&ic->ic_vaps);
ieee80211_dfs_send_csa(vap, ic->ic_curchan->ic_ieee);
}
return;
}
/* set radar_found_flag eariler to avoid function reentry issue */
start_nonoccupy(chan_idx);
/* stop cac if any */
stop_cac();
/* OCAC do not required for DFS actions */
if (!rdetect) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"OCAC DFS: no DFS action taken\n");
return;
}
/* disable radar detection to avoid redundant detection */
sys_disable_rdetection();
if (local_new_ieee == 0 && ic->ic_ieee_alt_chan != 0) {
local_new_ieee = ic->ic_ieee_alt_chan;
}
/* take a dfs action */
dfs_action(local_new_ieee, radar_detected_during_cac);
}
/*
* Invoked when radar is detected
* - a callback function registered to the radar module
*/
static void mark_radar(void)
{
qdrv_radar_detected(qdrv_radar_cb.ic, 0);
}
int qdrv_radar_test_mode_enabled(void)
{
if ((qdrv_radar_cb.enabled == true) && qdrv_radar_is_test_mode())
return 1;
return 0;
}
/*
* Check if safe to perform channel sampling
* Returns 1 if OK, else 0.
*/
int qdrv_radar_can_sample_chan(void)
{
if ((qdrv_radar_cb.enabled != 0) &&
is_cac_started()) {
return 0;
}
if (qdrv_radar_test_mode_enabled()) {
return 0;
}
return 1;
}
/*
* Take appropriate action(s) right before channel switch
*/
void qdrv_radar_before_newchan(void)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct qdrv_wlan *qw = container_of(ic, struct qdrv_wlan, ic);
struct ieee80211_channel *new_chan = NULL;
int silence_period;
bool rdetect;
/* now safe to set 'new_chan' */
new_chan = ic->ic_curchan;
/* check if the new channel requires radar detection */
rdetect = qdrv_radar_is_rdetection_required(new_chan);
if (!qdrv_radar_cb.enabled) {
if (rdetect && qdrv_is_dfs_slave() &&
!IEEE80211_IS_CHAN_CACDONE(new_chan)) {
if (ieee80211_is_on_weather_channel(ic, new_chan))
silence_period = STA_WEATHER_CHAN_SILENCE_PERIOD;
else
silence_period = STA_SILENCE_PERIOD;
ic->sta_dfs_info.sta_silence_timer.data =
(unsigned long)ieee80211_get_sta_vap(ic);
mod_timer(&ic->sta_dfs_info.sta_silence_timer,
jiffies + silence_period);
sys_disable_xmit();
}
return;
}
if (ic->ic_flags & IEEE80211_F_SCAN) {
if (is_cac_started()) {
/* The ongoing CAC is invalid since channel scan is running */
qdrv_radar_cb.cac_chan->ic_flags &=
~IEEE80211_CHAN_DFS_CAC_IN_PROGRESS;
if (ic->ic_mark_channel_dfs_cac_status) {
ic->ic_mark_channel_dfs_cac_status(ic, qdrv_radar_cb.cac_chan,
IEEE80211_CHAN_DFS_CAC_IN_PROGRESS, false);
}
}
if (rdetect && !IEEE80211_IS_CHAN_CACDONE(new_chan))
sys_disable_xmit();
return;
}
/* stop cac if any */
stop_cac();
/* other channel switches override the DFS-triggered one */
if (is_dfs_cs_started() && (qdrv_radar_cb.dfs_des_chan != new_chan)) {
stop_dfs_cs();
}
if (rdetect) {
QDRV_SET_SM_FLAG(qw->sm_stats, QDRV_WLAN_SM_STATE_RADAR_ACT);
if (qdrv_is_dfs_master() || ic->sta_dfs_info.sta_dfs_strict_mode)
sys_disable_xmit();
sys_disable_rdetection();
} else {
QDRV_CLEAR_SM_FLAG(qw->sm_stats, QDRV_WLAN_SM_STATE_RADAR_ACT);
}
}
void qdrv_radar_enable_radar_detection(void)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
if (!qdrv_radar_cb.enabled)
return;
if (ic->ic_flags & IEEE80211_F_SCAN)
return;
if (qdrv_radar_is_rdetection_required(ic->ic_curchan)) {
radar_set_chan(ic->ic_curchan->ic_ieee);
if (!radar_get_status()) {
if (ic->ic_pm_state[QTN_PM_CURRENT_LEVEL] < BOARD_PM_LEVEL_DUTY) {
sys_enable_rdetection();
}
}
}
}
/*
* Decide what to do on the new channel
*/
void qdrv_radar_on_newchan(void)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *new_chan = NULL;
bool rdetect;
int handle_cac = 0;
if (!ic->ic_curchan)
return;
/* now safe to set 'new_chan' */
new_chan = ic->ic_curchan;
/* check if radar detection on the channel is requried */
rdetect = qdrv_radar_is_rdetection_required(new_chan);
if (!qdrv_radar_cb.enabled) {
if (!rdetect)
sys_enable_xmit();
return;
}
if (ic->ic_flags & IEEE80211_F_SCAN) {
if (!rdetect || IEEE80211_IS_CHAN_CACDONE(new_chan))
sys_enable_xmit();
return;
}
handle_cac = !(IEEE80211_IS_CHAN_CACDONE(ic->ic_curchan)) &&
!(IEEE80211_IS_CHAN_CAC_IN_PROGRESS(ic->ic_curchan));
/* report new channel to the radar module */
radar_set_chan(new_chan->ic_ieee);
/* log a new channel info */
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"now on channel %3d (%4d MHz) with DFS %s (F_DOTH %d, CHAN_DFS %d)\n",
new_chan->ic_ieee, new_chan->ic_freq,
(rdetect) ? "enabled" : "disabled",
(ic->ic_flags & IEEE80211_F_DOTH) ? 1 : 0 ,
(new_chan->ic_flags & IEEE80211_CHAN_DFS) ? 1 : 0);
if (rdetect) {
if (new_chan->ic_flags & IEEE80211_CHAN_DFS_OCAC_DONE) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "Seamless CAC completed "
"and no action needed\n");
sys_enable_xmit();
new_chan->ic_flags |= IEEE80211_CHAN_DFS_CAC_DONE;
if (ic->ic_mark_channel_availability_status) {
ic->ic_mark_channel_availability_status(ic, new_chan, IEEE80211_CHANNEL_STATUS_AVAILABLE);
}
if (ic->ic_mark_channel_dfs_cac_status) {
ic->ic_mark_channel_dfs_cac_status(ic, new_chan, IEEE80211_CHAN_DFS_CAC_DONE, true);
}
} else if (qdrv_is_dfs_master()) {
if (handle_cac) {
start_cac();
} else if (ieee80211_is_chan_available(new_chan)) {
sys_enable_xmit();
}
} else if (ic->sta_dfs_info.sta_dfs_strict_mode) {
sta_dfs_strict_cac_action(new_chan);
}
if (!radar_get_status()) {
if (ic->ic_pm_state[QTN_PM_CURRENT_LEVEL] < BOARD_PM_LEVEL_DUTY) {
sys_enable_rdetection();
}
}
} else {
sys_enable_xmit();
sys_disable_rdetection();
}
}
void qdrv_sta_dfs_enable(int sta_dfs_enable)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
bool rdetect;
if (!qdrv_radar_configured)
return;
if (qdrv_radar_first_call)
return;
if (qdrv_is_dfs_master())
return;
if (qdrv_is_dfs_slave() && !qdrv_radar_sta_dfs)
return;
if (sta_dfs_enable) {
rdetect = qdrv_radar_is_rdetection_required(ic->ic_bsschan);
if (rdetect)
radar_set_chan(ic->ic_bsschan->ic_ieee);
qdrv_radar_enable_action();
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_RADAR, "Station DFS enable\n");
} else {
qdrv_radar_disable();
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_RADAR, "Station DFS disable\n");
}
}
/*
* Enable DFS feature
*/
void qdrv_radar_enable(const char *region)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211vap *vap;
if (!qdrv_radar_configured) {
DBGPRINTF_E("radar unconfigured\n");
return;
}
if (qdrv_radar_cb.enabled) {
DBGPRINTF(DBG_LL_INFO, QDRV_LF_RADAR, "radar already enabled\n");
return;
} else if (strcmp(region, "ru") == 0) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"no DFS / radar requirement for regulatory region Russia\n");
return;
}
ieee80211_ocac_update_params(ic, region);
if (qdrv_radar_first_call) {
if (false == sys_start_radarmod(region)) {
DBGPRINTF_E("Fail to start radar module\n");
return;
}
qdrv_radar_first_call = false;
qdrv_radar_sta_dfs = sta_dfs_is_region_required();
qdrv_radar_cb.region = dfs_rqmt_code(region);
/* initialise MuC sampling */
qdrv_radar_cb.muc_sampling.sample = detect_drv_sample_loc_get();
ic->ic_sample_rate = QDRV_RADAR_SAMPLE_RATE;
vap = TAILQ_FIRST(&ic->ic_vaps);
if (vap != NULL && qdrv_radar_cb.muc_sampling.sample != NULL) {
ic->ic_setparam(vap->iv_bss, IEEE80211_PARAM_SAMPLE_RATE,
ic->ic_sample_rate, NULL, 0);
INIT_DELAYED_WORK(&qdrv_radar_cb.muc_sampling.sample_work,
qdrv_radar_sample_work);
} else {
DBGPRINTF_E("failed to start MuC sampling. vap %p sample %p\n",
vap, qdrv_radar_cb.muc_sampling.sample);
return; /* abort further radar initialization if failure */
}
}
if (qdrv_radar_cb.enabled == true) {
DBGPRINTF_E("re-enabling radar is not supported - reboot\n");
/* for future work of re-enabling radar
sys_stop_radarmod();
sys_start_radarmod(region);
*/
} else {
qdrv_radar_enable_action();
}
}
static bool qdrv_radar_is_dfs_chan(uint8_t wifi_chan)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
uint32_t chan_idx;
for (chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++) {
if (ic->ic_channels[chan_idx].ic_ieee == wifi_chan) {
if (ic->ic_channels[chan_idx].ic_flags & IEEE80211_CHAN_DFS) {
return true;
}
}
}
return false;
}
static bool qdrv_radar_is_dfs_weather_chan(uint8_t wifi_chan)
{
struct ieee80211com *ic = qdrv_radar_cb.ic;
uint32_t chan_idx;
for (chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++) {
if (ic->ic_channels[chan_idx].ic_ieee == wifi_chan) {
if (ic->ic_channels[chan_idx].ic_flags & IEEE80211_CHAN_WEATHER) {
return true;
}
}
}
return false;
}
struct ieee80211_channel * qdrv_radar_get_current_cac_chan(void)
{
return qdrv_radar_cb.cac_chan;
}
EXPORT_SYMBOL(qdrv_radar_get_current_cac_chan);
bool qdrv_dfs_is_eu_region()
{
return qdrv_radar_cb.region == DFS_RQMT_EU;
}
int radar_pm_notify(struct notifier_block *b, unsigned long level, void *v)
{
static int pm_prev_level = BOARD_PM_LEVEL_NO;
const int switch_level = BOARD_PM_LEVEL_DUTY;
struct ieee80211com *ic = qdrv_radar_cb.ic;
struct ieee80211_channel *operate_chan;
bool rdetect;
if (!qdrv_radar_cb.enabled)
goto out;
operate_chan = ic->ic_bsschan;
rdetect = qdrv_radar_is_rdetection_required(operate_chan);
if (rdetect) {
if ((pm_prev_level < switch_level) && (level >= switch_level)) {
sys_disable_rdetection();
} else if ((pm_prev_level >= switch_level) && (level < switch_level)) {
radar_set_chan(ic->ic_bsschan->ic_ieee);
sys_enable_rdetection();
}
}
out:
pm_prev_level = level;
return NOTIFY_OK;
}
static void qdrv_ocac_irqhandler(void *arg1, void *arg2)
{
struct qdrv_wlan *qw = arg1;
struct ieee80211com *ic = &qw->ic;
struct shared_params *sp = qtn_mproc_sync_shared_params_get();
struct qtn_ocac_info *ocac_info = sp->ocac_lhost;
if (ocac_info->chan_status == QTN_OCAC_ON_OFF_CHAN) {
ic->ic_ocac.ocac_counts.intr_off_chan++;
} else {
ic->ic_ocac.ocac_counts.intr_data_chan++;
}
tasklet_schedule(&qdrv_radar_cb.ocac_tasklet);
}
static int qdrv_init_ocac_irqhandler(struct qdrv_wlan *qw)
{
struct int_handler int_handler;
int_handler.handler = qdrv_ocac_irqhandler;
int_handler.arg1 = qw;
int_handler.arg2 = NULL;
if (qdrv_mac_set_handler(qw->mac, RUBY_M2L_IRQ_LO_OCAC, &int_handler) != 0) {
DBGPRINTF_E("Could not set ocac irq handler\n");
return -1;
}
return 0;
}
/*
* initialize qdrv_radar.
* - Has to be invoked inside or after qdrv_wlan_init()
*/
int qdrv_radar_init(struct qdrv_mac *mac)
{
struct ieee80211com *ic = &(((struct qdrv_wlan*)mac->data)->ic);
struct qdrv_wlan *qw = container_of(ic, struct qdrv_wlan, ic);
struct shared_params *sp = qtn_mproc_sync_shared_params_get();
struct qtn_ocac_info *ocac_info = sp->ocac_lhost;
unsigned chan_idx;
struct timer_list *cac_timer;
struct timer_list *dfs_cs_timer;
if (mac->unit != 0) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"init radar request for mac%d ignored\n", mac->unit);
return 0; /* yes, it is success by design */
}
if (qdrv_radar_configured) {
DBGPRINTF_E("radar already configured\n");
return -1;
}
/* clear the control block */
memset(&qdrv_radar_cb, 0, sizeof(qdrv_radar_cb));
qdrv_radar_cb.mac = mac;
qdrv_radar_cb.ic = ic;
/* initialize the cac_timer */
cac_timer = &qdrv_radar_cb.cac_timer;
init_timer(cac_timer);
cac_timer->function = cac_completed_action;
cac_timer->data = (unsigned long) NULL; /* not used */
/* initialize all nonoccupy timers */
ic->ic_non_occupancy_period = QDRV_RADAR_DFLT_NONOCCUPY_PERIOD * HZ;
for (chan_idx = 0; chan_idx < ic->ic_nchans; chan_idx++) {
struct timer_list *nonoccupy_timer = &qdrv_radar_cb.nonoccupy_timer[chan_idx];
init_timer(nonoccupy_timer);
nonoccupy_timer->function = nonoccupy_expire_action;
nonoccupy_timer->data = chan_idx;
}
/* initialize the dfs_cs_timer */
dfs_cs_timer = &qdrv_radar_cb.dfs_cs_timer;
dfs_cs_timer->function = dfs_cs_timer_expire_action;
init_timer(dfs_cs_timer);
ic->sta_dfs_info.sta_radar_timer.function = sta_radar_detected_timer_action;
init_timer(&ic->sta_dfs_info.sta_radar_timer);
ic->sta_dfs_info.sta_silence_timer.function = sta_silence_timer_action;
init_timer(&ic->sta_dfs_info.sta_silence_timer);
#ifdef CONFIG_QHOP
ic->rbs_mbs_dfs_info.rbs_dfs_radar_timer.function = rbs_radar_detected_timer_action;
init_timer(&ic->rbs_mbs_dfs_info.rbs_dfs_radar_timer);
#endif
qdrv_radar_cb.pm_notifier.notifier_call = radar_pm_notify;
pm_qos_add_notifier(PM_QOS_POWER_SAVE, &qdrv_radar_cb.pm_notifier);
/* For off channel CAC */
tasklet_init(&qdrv_radar_cb.ocac_tasklet, &qdrv_ocac_tasklet, (unsigned long)ocac_info);
qdrv_init_ocac_irqhandler(qw);
qdrv_radar_configured = true;
/* success */
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "radar initialized\n");
return 0;
}
/*
* deinitialize qdrv_radar
*/
int qdrv_radar_exit(struct qdrv_mac *mac)
{
if (mac->unit != 0) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"exit request for mac%d ignored\n", mac->unit);
return 0; /* yes, it is success by design */
}
if (qdrv_radar_first_call == true || !qdrv_radar_configured) {
DBGPRINTF_E("radar already unconfigured\n");
return -1;
}
qdrv_radar_disable();
del_timer_sync(&(qdrv_radar_cb.ic->sta_dfs_info.sta_silence_timer));
tasklet_kill(&qdrv_radar_cb.ocac_tasklet);
pm_qos_remove_notifier(PM_QOS_POWER_SAVE, &qdrv_radar_cb.pm_notifier);
/* disable radar detection */
sys_stop_radarmod();
/* clear the control block */
memset(&qdrv_radar_cb, 0, sizeof(qdrv_radar_cb));
qdrv_radar_configured = false;
qdrv_radar_sta_dfs = false;
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "radar exited\n");
return 0;
}
int qdrv_radar_unload(struct qdrv_mac *mac)
{
if (mac->unit != 0) {
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR,
"exit request for mac%d ignored\n", mac->unit);
return 0; /* yes, it is success by design */
}
if (qdrv_radar_first_call == true || !qdrv_radar_configured) {
DBGPRINTF_E("radar already unconfigured\n");
return -1;
}
qdrv_radar_disable();
/* success */
DBGPRINTF(DBG_LL_NOTICE, QDRV_LF_RADAR, "radar unloaded\n");
return 0;
}