blob: 3b01e26215df2fc587e22f8edbd6003e8fa51e59 [file] [log] [blame]
diff --git a/package/network/services/hostapd/patches/904-2102-zero-wait_dfs.patch b/package/network/services/hostapd/patches/904-2102-zero-wait_dfs.patch
new file mode 100644
index 0000000..417c311
--- /dev/null
+++ b/package/network/services/hostapd/patches/904-2102-zero-wait_dfs.patch
@@ -0,0 +1,848 @@
+diff --git a/hostapd/config_file.c b/hostapd/config_file.c
+index 6b6101f..704afbb 100644
+--- a/hostapd/config_file.c
++++ b/hostapd/config_file.c
+@@ -2524,6 +2524,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
+ conf->ieee80211d = atoi(pos);
+ } else if (os_strcmp(buf, "ieee80211h") == 0) {
+ conf->ieee80211h = atoi(pos);
++ } else if (os_strcmp(buf, "radar_offchan") == 0) {
++ conf->radar_offchan = atoi(pos);
+ } else if (os_strcmp(buf, "ieee8021x") == 0) {
+ bss->ieee802_1x = atoi(pos);
+ } else if (os_strcmp(buf, "eapol_version") == 0) {
+diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
+index c71f2ac..58c7034 100644
+--- a/hostapd/hostapd.conf
++++ b/hostapd/hostapd.conf
+@@ -143,6 +143,13 @@ ssid=test
+ # ieee80211d=1 and local_pwr_constraint configured.
+ #spectrum_mgmt_required=1
+
++# Enable radar/CAC detection through a dedicated offchannel chain available on
++# some hw. The chain can't be used to transmits or receives frames.
++# This feature allows to avoid CAC downtime switching on a different channel
++# during CAC detection on the selected radar channel.
++# (default: 0 = disabled, 1 = enabled)
++#radar_offchan=0
++
+ # Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz),
+ # g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used
+ # with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this
+diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
+index 920dba1..44d3b85 100644
+--- a/src/ap/ap_config.h
++++ b/src/ap/ap_config.h
+@@ -965,6 +965,7 @@ struct hostapd_config {
+ int ieee80211d;
+
+ int ieee80211h; /* DFS */
++ int radar_offchan;
+
+ /*
+ * Local power constraint is an octet encoded as an unsigned integer in
+diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
+index 85728d4..ed16d1f 100644
+--- a/src/ap/ap_drv_ops.c
++++ b/src/ap/ap_drv_ops.c
+@@ -810,7 +810,8 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface,
+ int channel, int ht_enabled, int vht_enabled,
+ int he_enabled,
+ int sec_channel_offset, int oper_chwidth,
+- int center_segment0, int center_segment1)
++ int center_segment0, int center_segment1,
++ int radar_offchan)
+ {
+ struct hostapd_data *hapd = iface->bss[0];
+ struct hostapd_freq_params data;
+@@ -836,10 +837,14 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface,
+ wpa_printf(MSG_ERROR, "Can't set freq params");
+ return -1;
+ }
++ data.radar_offchan = radar_offchan;
+
+ res = hapd->driver->start_dfs_cac(hapd->drv_priv, &data);
+ if (!res) {
+- iface->cac_started = 1;
++ if (radar_offchan)
++ iface->radar_offchan.cac_started = 1;
++ else
++ iface->cac_started = 1;
+ os_get_reltime(&iface->dfs_cac_start);
+ }
+
+diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
+index 5738c1c..12c87b0 100644
+--- a/src/ap/ap_drv_ops.h
++++ b/src/ap/ap_drv_ops.h
+@@ -130,7 +130,8 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface,
+ int channel, int ht_enabled, int vht_enabled,
+ int he_enabled,
+ int sec_channel_offset, int oper_chwidth,
+- int center_segment0, int center_segment1);
++ int center_segment0, int center_segment1,
++ int radar_offchan);
+ int hostapd_drv_do_acs(struct hostapd_data *hapd);
+ int hostapd_drv_update_dh_ie(struct hostapd_data *hapd, const u8 *peer,
+ u16 reason_code, const u8 *ie, size_t ielen);
+diff --git a/src/ap/dfs.c b/src/ap/dfs.c
+index 54a61b0..217c0fc 100644
+--- a/src/ap/dfs.c
++++ b/src/ap/dfs.c
+@@ -51,16 +51,31 @@ static int dfs_get_used_n_chans(struct hostapd_iface *iface, int *seg1)
+ return n_chans;
+ }
+
+-
++/*
++ * flags:
++ * - 0: any channel
++ * - 1: non-radar channel or radar available one
++ * - 2: radar-only channel not yet available
++ */
+ static int dfs_channel_available(struct hostapd_channel_data *chan,
+- int skip_radar)
++ int flags)
+ {
++ if (flags == 2) {
++ /* Select only radar channel where CAC has not been
++ * performed yet
++ */
++ if ((chan->flag & HOSTAPD_CHAN_RADAR) &&
++ (chan->flag & HOSTAPD_CHAN_DFS_MASK) ==
++ HOSTAPD_CHAN_DFS_USABLE)
++ return 1;
++ return 0;
++ }
+ /*
+ * When radar detection happens, CSA is performed. However, there's no
+ * time for CAC, so radar channels must be skipped when finding a new
+ * channel for CSA, unless they are available for immediate use.
+ */
+- if (skip_radar && (chan->flag & HOSTAPD_CHAN_RADAR) &&
++ if (flags && (chan->flag & HOSTAPD_CHAN_RADAR) &&
+ ((chan->flag & HOSTAPD_CHAN_DFS_MASK) !=
+ HOSTAPD_CHAN_DFS_AVAILABLE))
+ return 0;
+@@ -136,10 +151,15 @@ dfs_get_chan_data(struct hostapd_hw_modes *mode, int freq, int first_chan_idx)
+ return NULL;
+ }
+
+-
++/*
++ * flags:
++ * - 0: any channel
++ * - 1: non-radar channel or radar available one
++ * - 2: radar-only channel not yet available
++ */
+ static int dfs_chan_range_available(struct hostapd_hw_modes *mode,
+ int first_chan_idx, int num_chans,
+- int skip_radar)
++ int flags)
+ {
+ struct hostapd_channel_data *first_chan, *chan;
+ int i;
+@@ -178,7 +198,7 @@ static int dfs_chan_range_available(struct hostapd_hw_modes *mode,
+ return 0;
+ }
+
+- if (!dfs_channel_available(chan, skip_radar)) {
++ if (!dfs_channel_available(chan, flags)) {
+ wpa_printf(MSG_DEBUG, "DFS: channel not available %d",
+ first_chan->freq + i * 20);
+ return 0;
+@@ -205,10 +225,15 @@ static int is_in_chanlist(struct hostapd_iface *iface,
+ * - hapd->secondary_channel
+ * - hapd->vht/he_oper_centr_freq_seg0_idx
+ * - hapd->vht/he_oper_centr_freq_seg1_idx
++ *
++ * flags:
++ * - 0: any channel
++ * - 1: non-radar channel or radar available one
++ * - 2: radar-only channel not yet available
+ */
+ static int dfs_find_channel(struct hostapd_iface *iface,
+ struct hostapd_channel_data **ret_chan,
+- int idx, int skip_radar)
++ int idx, int flags)
+ {
+ struct hostapd_hw_modes *mode;
+ struct hostapd_channel_data *chan;
+@@ -233,7 +258,7 @@ static int dfs_find_channel(struct hostapd_iface *iface,
+ }
+
+ /* Skip incompatible chandefs */
+- if (!dfs_chan_range_available(mode, i, n_chans, skip_radar)) {
++ if (!dfs_chan_range_available(mode, i, n_chans, flags)) {
+ wpa_printf(MSG_DEBUG,
+ "DFS: range not available for %d (%d)",
+ chan->freq, chan->chan);
+@@ -467,13 +492,18 @@ static int dfs_check_chans_unavailable(struct hostapd_iface *iface,
+ return res;
+ }
+
+-
++/*
++ * flags:
++ * - 0: any channel
++ * - 1: non-radar channel or radar available one
++ * - 2: radar-only channel not yet available
++ */
+ static struct hostapd_channel_data *
+ dfs_get_valid_channel(struct hostapd_iface *iface,
+ int *secondary_channel,
+ u8 *oper_centr_freq_seg0_idx,
+ u8 *oper_centr_freq_seg1_idx,
+- int skip_radar)
++ int flags)
+ {
+ struct hostapd_hw_modes *mode;
+ struct hostapd_channel_data *chan = NULL;
+@@ -502,7 +532,7 @@ dfs_get_valid_channel(struct hostapd_iface *iface,
+ return NULL;
+
+ /* Get the count first */
+- num_available_chandefs = dfs_find_channel(iface, NULL, 0, skip_radar);
++ num_available_chandefs = dfs_find_channel(iface, NULL, 0, flags);
+ wpa_printf(MSG_DEBUG, "DFS: num_available_chandefs=%d",
+ num_available_chandefs);
+ if (num_available_chandefs == 0)
+@@ -523,7 +553,7 @@ dfs_get_valid_channel(struct hostapd_iface *iface,
+ return NULL;
+
+ chan_idx = _rand % num_available_chandefs;
+- dfs_find_channel(iface, &chan, chan_idx, skip_radar);
++ dfs_find_channel(iface, &chan, chan_idx, flags);
+ if (!chan) {
+ wpa_printf(MSG_DEBUG, "DFS: no random channel found");
+ return NULL;
+@@ -552,7 +582,7 @@ dfs_get_valid_channel(struct hostapd_iface *iface,
+ for (i = 0; i < num_available_chandefs - 1; i++) {
+ /* start from chan_idx + 1, end when chan_idx - 1 */
+ chan_idx2 = (chan_idx + 1 + i) % num_available_chandefs;
+- dfs_find_channel(iface, &chan2, chan_idx2, skip_radar);
++ dfs_find_channel(iface, &chan2, chan_idx2, flags);
+ if (chan2 && abs(chan2->chan - chan->chan) > 12) {
+ /* two channels are not adjacent */
+ sec_chan_idx_80p80 = chan2->chan;
+@@ -582,6 +612,27 @@ dfs_get_valid_channel(struct hostapd_iface *iface,
+ return chan;
+ }
+
++static int dfs_set_valid_channel(struct hostapd_iface *iface, int skip_radar)
++{
++ struct hostapd_channel_data *channel;
++ u8 cf1 = 0, cf2 = 0;
++ int sec = 0;
++
++ channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2,
++ skip_radar);
++ if (!channel) {
++ wpa_printf(MSG_ERROR, "could not get valid channel");
++ return -1;
++ }
++
++ iface->freq = channel->freq;
++ iface->conf->channel = channel->chan;
++ iface->conf->secondary_channel = sec;
++ hostapd_set_oper_centr_freq_seg0_idx(iface->conf, cf1);
++ hostapd_set_oper_centr_freq_seg1_idx(iface->conf, cf2);
++
++ return 0;
++}
+
+ static int set_dfs_state_freq(struct hostapd_iface *iface, int freq, u32 state)
+ {
+@@ -761,6 +812,11 @@ static unsigned int dfs_get_cac_time(struct hostapd_iface *iface,
+ return cac_time_ms;
+ }
+
++static int hostapd_is_radar_offchan_enabled(struct hostapd_iface *iface)
++{
++ return (iface->drv_flags2 & WPA_DRIVER_RADAR_OFFCHAN) &&
++ iface->conf->radar_offchan;
++}
+
+ /*
+ * Main DFS handler
+@@ -770,9 +826,8 @@ static unsigned int dfs_get_cac_time(struct hostapd_iface *iface,
+ */
+ int hostapd_handle_dfs(struct hostapd_iface *iface)
+ {
+- struct hostapd_channel_data *channel;
+ int res, n_chans, n_chans1, start_chan_idx, start_chan_idx1;
+- int skip_radar = 0;
++ int skip_radar = 0, radar_offchan;
+
+ if (is_6ghz_freq(iface->freq))
+ return 1;
+@@ -825,28 +880,18 @@ int hostapd_handle_dfs(struct hostapd_iface *iface)
+ wpa_printf(MSG_DEBUG, "DFS %d chans unavailable - choose other channel: %s",
+ res, res ? "yes": "no");
+ if (res) {
+- int sec = 0;
+- u8 cf1 = 0, cf2 = 0;
+-
+- channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2,
+- skip_radar);
+- if (!channel) {
+- wpa_printf(MSG_ERROR, "could not get valid channel");
++ if (dfs_set_valid_channel(iface, skip_radar) < 0) {
+ hostapd_set_state(iface, HAPD_IFACE_DFS);
+ return 0;
+ }
+-
+- iface->freq = channel->freq;
+- iface->conf->channel = channel->chan;
+- iface->conf->secondary_channel = sec;
+- hostapd_set_oper_centr_freq_seg0_idx(iface->conf, cf1);
+- hostapd_set_oper_centr_freq_seg1_idx(iface->conf, cf2);
+ }
+ } while (res);
+
+ /* Finally start CAC */
+ hostapd_set_state(iface, HAPD_IFACE_DFS);
+- wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz", iface->freq);
++ radar_offchan = hostapd_is_radar_offchan_enabled(iface);
++ wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz offchan %d",
++ iface->freq, radar_offchan);
+ wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START
+ "freq=%d chan=%d sec_chan=%d, width=%d, seg0=%d, seg1=%d, cac_time=%ds",
+ iface->freq,
+@@ -863,13 +908,37 @@ int hostapd_handle_dfs(struct hostapd_iface *iface)
+ iface->conf->secondary_channel,
+ hostapd_get_oper_chwidth(iface->conf),
+ hostapd_get_oper_centr_freq_seg0_idx(iface->conf),
+- hostapd_get_oper_centr_freq_seg1_idx(iface->conf));
++ hostapd_get_oper_centr_freq_seg1_idx(iface->conf),
++ radar_offchan);
+
+ if (res) {
+ wpa_printf(MSG_ERROR, "DFS start_dfs_cac() failed, %d", res);
+ return -1;
+ }
+
++ if (radar_offchan) {
++ /* Cache offchannel radar parameters */
++ iface->radar_offchan.channel = iface->conf->channel;
++ iface->radar_offchan.secondary_channel =
++ iface->conf->secondary_channel;
++ iface->radar_offchan.freq = iface->freq;
++ iface->radar_offchan.centr_freq_seg0_idx =
++ hostapd_get_oper_centr_freq_seg0_idx(iface->conf);
++ iface->radar_offchan.centr_freq_seg1_idx =
++ hostapd_get_oper_centr_freq_seg1_idx(iface->conf);
++
++ /*
++ * Let's select a random channel for the moment
++ * and perform CAC on dedicated radar chain
++ */
++ res = dfs_set_valid_channel(iface, 1);
++ if (res < 0)
++ return res;
++
++ iface->radar_offchan.temp_ch = 1;
++ return 1;
++ }
++
+ return 0;
+ }
+
+@@ -890,6 +959,157 @@ int hostapd_is_dfs_chan_available(struct hostapd_iface *iface)
+ return dfs_check_chans_available(iface, start_chan_idx, n_chans);
+ }
+
++static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface,
++ int channel, int freq,
++ int secondary_channel,
++ u8 oper_centr_freq_seg0_idx,
++ u8 oper_centr_freq_seg1_idx)
++{
++ struct hostapd_hw_modes *cmode = iface->current_mode;
++ int ieee80211_mode = IEEE80211_MODE_AP, err, i;
++ struct csa_settings csa_settings;
++ u8 new_vht_oper_chwidth;
++
++ wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", channel);
++ wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL
++ "freq=%d chan=%d sec_chan=%d", freq, channel,
++ secondary_channel);
++
++ new_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf);
++ hostapd_set_oper_chwidth(iface->conf,
++ hostapd_get_oper_chwidth(iface->conf));
++
++ /* Setup CSA request */
++ os_memset(&csa_settings, 0, sizeof(csa_settings));
++ csa_settings.cs_count = 5;
++ csa_settings.block_tx = 1;
++#ifdef CONFIG_MESH
++ if (iface->mconf)
++ ieee80211_mode = IEEE80211_MODE_MESH;
++#endif /* CONFIG_MESH */
++ err = hostapd_set_freq_params(&csa_settings.freq_params,
++ iface->conf->hw_mode,
++ freq, channel,
++ iface->conf->enable_edmg,
++ iface->conf->edmg_channel,
++ iface->conf->ieee80211n,
++ iface->conf->ieee80211ac,
++ iface->conf->ieee80211ax,
++ secondary_channel,
++ new_vht_oper_chwidth,
++ oper_centr_freq_seg0_idx,
++ oper_centr_freq_seg1_idx,
++ cmode->vht_capab,
++ &cmode->he_capab[ieee80211_mode]);
++
++ if (err) {
++ wpa_printf(MSG_ERROR, "DFS failed to calculate CSA freq params");
++ hostapd_disable_iface(iface);
++ return err;
++ }
++
++ for (i = 0; i < iface->num_bss; i++) {
++ err = hostapd_switch_channel(iface->bss[i], &csa_settings);
++ if (err)
++ break;
++ }
++
++ if (err) {
++ wpa_printf(MSG_WARNING, "DFS failed to schedule CSA (%d) - trying fallback",
++ err);
++ iface->freq = freq;
++ iface->conf->channel = channel;
++ iface->conf->secondary_channel = secondary_channel;
++ hostapd_set_oper_chwidth(iface->conf, new_vht_oper_chwidth);
++ hostapd_set_oper_centr_freq_seg0_idx(iface->conf,
++ oper_centr_freq_seg0_idx);
++ hostapd_set_oper_centr_freq_seg1_idx(iface->conf,
++ oper_centr_freq_seg1_idx);
++
++ hostapd_disable_iface(iface);
++ hostapd_enable_iface(iface);
++
++ return 0;
++ }
++
++ /* Channel configuration will be updated once CSA completes and
++ * ch_switch_notify event is received */
++ wpa_printf(MSG_DEBUG, "DFS waiting channel switch event");
++
++ return 0;
++}
++
++static struct hostapd_channel_data *
++dfs_downgrade_bandwidth(struct hostapd_iface *iface, int *secondary_channel,
++ u8 *oper_centr_freq_seg0_idx,
++ u8 *oper_centr_freq_seg1_idx, int *skip_radar);
++
++static void
++hostpad_dfs_update_offchannel_chain(struct hostapd_iface *iface)
++{
++ struct hostapd_channel_data *channel;
++ int sec = 0, flags = 2;
++ u8 cf1 = 0, cf2 = 0;
++
++ channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2, 2);
++ if (!channel || channel->chan == iface->conf->channel)
++ channel = dfs_downgrade_bandwidth(iface, &sec, &cf1, &cf2,
++ &flags);
++ if (!channel ||
++ hostapd_start_dfs_cac(iface, iface->conf->hw_mode,
++ channel->freq, channel->chan,
++ iface->conf->ieee80211n,
++ iface->conf->ieee80211ac,
++ iface->conf->ieee80211ax,
++ sec, hostapd_get_oper_chwidth(iface->conf),
++ cf1, cf2, 1)) {
++ /*
++ * Toggle interface state to enter DFS state
++ * until NOP is finished.
++ */
++ wpa_printf(MSG_ERROR, "DFS failed start CAC offchannel");
++ return;
++ }
++
++ wpa_printf(MSG_DEBUG, "%s: setting offchannel chain to chan %d (%d MHz)",
++ __func__, channel->chan, channel->freq);
++
++ iface->radar_offchan.channel = channel->chan;
++ iface->radar_offchan.freq = channel->freq;
++ iface->radar_offchan.secondary_channel = sec;
++ iface->radar_offchan.centr_freq_seg0_idx = cf1;
++ iface->radar_offchan.centr_freq_seg1_idx = cf2;
++}
++
++/* FIXME: check if all channel bandwith */
++static int
++hostapd_dfs_is_offchan_event(struct hostapd_iface *iface, int freq)
++{
++ if (iface->radar_offchan.freq != freq)
++ return 0;
++
++ return 1;
++}
++
++static int
++hostapd_dfs_start_channel_switch_offchan(struct hostapd_iface *iface)
++{
++ iface->conf->channel = iface->radar_offchan.channel;
++ iface->freq = iface->radar_offchan.freq;
++ iface->conf->secondary_channel =
++ iface->radar_offchan.secondary_channel;
++ hostapd_set_oper_centr_freq_seg0_idx(iface->conf,
++ iface->radar_offchan.centr_freq_seg0_idx);
++ hostapd_set_oper_centr_freq_seg1_idx(iface->conf,
++ iface->radar_offchan.centr_freq_seg1_idx);
++
++ hostpad_dfs_update_offchannel_chain(iface);
++
++ return hostapd_dfs_request_channel_switch(iface, iface->conf->channel,
++ iface->freq, iface->conf->secondary_channel,
++ hostapd_get_oper_centr_freq_seg0_idx(iface->conf),
++ hostapd_get_oper_centr_freq_seg1_idx(iface->conf));
++}
+
+ int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
+ int ht_enabled, int chan_offset, int chan_width,
+@@ -911,6 +1131,23 @@ int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
+ set_dfs_state(iface, freq, ht_enabled, chan_offset,
+ chan_width, cf1, cf2,
+ HOSTAPD_CHAN_DFS_AVAILABLE);
++
++ /*
++ * radar event from offchannel chain for selected
++ * channel. Perfrom CSA, move main chain to selected
++ * channel and configure offchannel chain to a new DFS
++ * channel
++ */
++ if (hostapd_is_radar_offchan_enabled(iface) &&
++ hostapd_dfs_is_offchan_event(iface, freq)) {
++ iface->radar_offchan.cac_started = 0;
++ if (iface->radar_offchan.temp_ch) {
++ iface->radar_offchan.temp_ch = 0;
++ return hostapd_dfs_start_channel_switch_offchan(iface);
++ }
++ return 0;
++ }
++
+ /*
+ * Just mark the channel available when CAC completion
+ * event is received in enabled state. CAC result could
+@@ -927,6 +1164,10 @@ int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
+ iface->cac_started = 0;
+ }
+ }
++ } else if (hostapd_is_radar_offchan_enabled(iface) &&
++ hostapd_dfs_is_offchan_event(iface, freq)) {
++ iface->radar_offchan.cac_started = 0;
++ hostpad_dfs_update_offchannel_chain(iface);
+ }
+
+ return 0;
+@@ -1035,6 +1276,44 @@ static int hostapd_dfs_start_channel_switch_cac(struct hostapd_iface *iface)
+ return err;
+ }
+
++static int
++hostapd_dfs_offchan_start_channel_switch(struct hostapd_iface *iface, int freq)
++{
++ if (!hostapd_is_radar_offchan_enabled(iface))
++ return -1; /* Offchannel chain not supported */
++
++ wpa_printf(MSG_DEBUG,
++ "%s called (offchannel CAC active: %s, CSA active: %s)",
++ __func__, iface->radar_offchan.cac_started ? "yes" : "no",
++ hostapd_csa_in_progress(iface) ? "yes" : "no");
++
++ /* Check if CSA in progress */
++ if (hostapd_csa_in_progress(iface))
++ return 0;
++
++ /*
++ * If offchannel radar detation is supported and radar channel
++ * monitored by offchain is available switch to it without waiting
++ * for the CAC otherwise let's keep a random channel.
++ * If radar pattern is reported on offchannel chain, just switch to
++ * monitor another radar channel.
++ */
++ if (hostapd_dfs_is_offchan_event(iface, freq)) {
++ hostpad_dfs_update_offchannel_chain(iface);
++ return 0;
++ }
++
++ /* Offchannel not availanle yet. Perform CAC on main chain */
++ if (iface->radar_offchan.cac_started) {
++ /* We want to switch to monitored channel as soon as
++ * CAC is completed.
++ */
++ iface->radar_offchan.temp_ch = 1;
++ return -1;
++ }
++
++ return hostapd_dfs_start_channel_switch_offchan(iface);
++}
+
+ static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface)
+ {
+@@ -1042,13 +1321,7 @@ static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface)
+ int secondary_channel;
+ u8 oper_centr_freq_seg0_idx;
+ u8 oper_centr_freq_seg1_idx;
+- u8 new_vht_oper_chwidth;
+ int skip_radar = 1;
+- struct csa_settings csa_settings;
+- unsigned int i;
+- int err = 1;
+- struct hostapd_hw_modes *cmode = iface->current_mode;
+- u8 current_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf);
+
+ wpa_printf(MSG_DEBUG, "%s called (CAC active: %s, CSA active: %s)",
+ __func__, iface->cac_started ? "yes" : "no",
+@@ -1110,69 +1383,11 @@ static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface)
+ }
+ }
+
+- wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d",
+- channel->chan);
+- wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL
+- "freq=%d chan=%d sec_chan=%d", channel->freq,
+- channel->chan, secondary_channel);
+-
+- new_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf);
+- hostapd_set_oper_chwidth(iface->conf, current_vht_oper_chwidth);
+-
+- /* Setup CSA request */
+- os_memset(&csa_settings, 0, sizeof(csa_settings));
+- csa_settings.cs_count = 5;
+- csa_settings.block_tx = 1;
+- err = hostapd_set_freq_params(&csa_settings.freq_params,
+- iface->conf->hw_mode,
+- channel->freq,
+- channel->chan,
+- iface->conf->enable_edmg,
+- iface->conf->edmg_channel,
+- iface->conf->ieee80211n,
+- iface->conf->ieee80211ac,
+- iface->conf->ieee80211ax,
+- secondary_channel,
+- new_vht_oper_chwidth,
+- oper_centr_freq_seg0_idx,
+- oper_centr_freq_seg1_idx,
+- cmode->vht_capab,
+- &cmode->he_capab[iface->conf->hw_mode]);
+-
+- if (err) {
+- wpa_printf(MSG_ERROR, "DFS failed to calculate CSA freq params");
+- hostapd_disable_iface(iface);
+- return err;
+- }
+-
+- for (i = 0; i < iface->num_bss; i++) {
+- err = hostapd_switch_channel(iface->bss[i], &csa_settings);
+- if (err)
+- break;
+- }
+-
+- if (err) {
+- wpa_printf(MSG_WARNING, "DFS failed to schedule CSA (%d) - trying fallback",
+- err);
+- iface->freq = channel->freq;
+- iface->conf->channel = channel->chan;
+- iface->conf->secondary_channel = secondary_channel;
+- hostapd_set_oper_chwidth(iface->conf, new_vht_oper_chwidth);
+- hostapd_set_oper_centr_freq_seg0_idx(iface->conf,
+- oper_centr_freq_seg0_idx);
+- hostapd_set_oper_centr_freq_seg1_idx(iface->conf,
+- oper_centr_freq_seg1_idx);
+-
+- hostapd_disable_iface(iface);
+- hostapd_enable_iface(iface);
+- return 0;
+- }
+-
+- /* Channel configuration will be updated once CSA completes and
+- * ch_switch_notify event is received */
+-
+- wpa_printf(MSG_DEBUG, "DFS waiting channel switch event");
+- return 0;
++ return hostapd_dfs_request_channel_switch(iface, channel->chan,
++ channel->freq,
++ secondary_channel,
++ oper_centr_freq_seg0_idx,
++ oper_centr_freq_seg1_idx);
+ }
+
+
+@@ -1199,15 +1414,19 @@ int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq,
+ if (!res)
+ return 0;
+
+- /* Skip if reported radar event not overlapped our channels */
+- res = dfs_are_channels_overlapped(iface, freq, chan_width, cf1, cf2);
+- if (!res)
+- return 0;
++ if (!hostapd_dfs_is_offchan_event(iface, freq)) {
++ /* Skip if reported radar event not overlapped our channels */
++ res = dfs_are_channels_overlapped(iface, freq, chan_width,
++ cf1, cf2);
++ if (!res)
++ return 0;
++ }
+
+- /* radar detected while operating, switch the channel. */
+- res = hostapd_dfs_start_channel_switch(iface);
++ if (hostapd_dfs_offchan_start_channel_switch(iface, freq))
++ /* radar detected while operating, switch the channel. */
++ return hostapd_dfs_start_channel_switch(iface);
+
+- return res;
++ return 0;
+ }
+
+
+@@ -1275,7 +1494,11 @@ int hostapd_dfs_start_cac(struct hostapd_iface *iface, int freq,
+ "seg1=%d cac_time=%ds",
+ freq, (freq - 5000) / 5, chan_offset, chan_width, cf1, cf2,
+ iface->dfs_cac_ms / 1000);
+- iface->cac_started = 1;
++
++ if (hostapd_dfs_is_offchan_event(iface, freq))
++ iface->radar_offchan.cac_started = 1;
++ else
++ iface->cac_started = 1;
+ os_get_reltime(&iface->dfs_cac_start);
+ return 0;
+ }
+diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
+index 54b3674..e9b5500 100644
+--- a/src/ap/hostapd.h
++++ b/src/ap/hostapd.h
+@@ -517,6 +517,21 @@ struct hostapd_iface {
+ int *basic_rates;
+ int freq;
+
++ /* Offchanel chain configuration */
++ struct {
++ int channel;
++ int secondary_channel;
++ int freq;
++ int centr_freq_seg0_idx;
++ int centr_freq_seg1_idx;
++ /* Main chain is on temporary channel during
++ * CAC detection on radar offchain
++ */
++ unsigned int temp_ch:1;
++ /* CAC started on radar offchain */
++ unsigned int cac_started:1;
++ } radar_offchan;
++
+ u16 hw_flags;
+
+ /* Number of associated Non-ERP stations (i.e., stations using 802.11b
+diff --git a/src/drivers/driver.h b/src/drivers/driver.h
+index 05ec9c2..0521c20 100644
+--- a/src/drivers/driver.h
++++ b/src/drivers/driver.h
+@@ -776,6 +776,11 @@ struct hostapd_freq_params {
+ * for IEEE 802.11ay EDMG configuration.
+ */
+ struct ieee80211_edmg_config edmg;
++
++ /**
++ * radar_offchan - Whether radar/CAC offchannel is requested
++ */
++ int radar_offchan;
+ };
+
+ /**
+@@ -1938,6 +1943,8 @@ struct wpa_driver_capa {
+
+ /** Driver supports a separate control port RX for EAPOL frames */
+ #define WPA_DRIVER_FLAGS2_CONTROL_PORT_RX 0x0000000000000001ULL
++/** Driver supports offchannel radar/CAC detection */
++#define WPA_DRIVER_RADAR_OFFCHAN 0x0000000000000200ULL
+ u64 flags2;
+
+ #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
+diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
+index 72ae074..09e0bc9 100644
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -4576,6 +4576,7 @@ static int nl80211_put_freq_params(struct nl_msg *msg,
+ wpa_printf(MSG_DEBUG, " * he_enabled=%d", freq->he_enabled);
+ wpa_printf(MSG_DEBUG, " * vht_enabled=%d", freq->vht_enabled);
+ wpa_printf(MSG_DEBUG, " * ht_enabled=%d", freq->ht_enabled);
++ wpa_printf(MSG_DEBUG, " * radar_offchan=%d", freq->radar_offchan);
+
+ hw_mode = ieee80211_freq_to_chan(freq->freq, &channel);
+ is_24ghz = hw_mode == HOSTAPD_MODE_IEEE80211G ||
+@@ -4653,6 +4654,9 @@ static int nl80211_put_freq_params(struct nl_msg *msg,
+ NL80211_CHAN_NO_HT))
+ return -ENOBUFS;
+ }
++ if (freq->radar_offchan)
++ nla_put_flag(msg, NL80211_ATTR_RADAR_OFFCHAN);
++
+ return 0;
+ }
+
+diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
+index 3e8dcef..1dcdbe6 100644
+--- a/src/drivers/driver_nl80211_capa.c
++++ b/src/drivers/driver_nl80211_capa.c
+@@ -639,6 +639,10 @@ static void wiphy_info_ext_feature_flags(struct wiphy_info_data *info,
+ if (ext_feature_isset(ext_features, len,
+ NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS))
+ info->drv->multicast_registrations = 1;
++
++ if (ext_feature_isset(ext_features, len,
++ NL80211_EXT_FEATURE_RADAR_OFFCHAN))
++ capa->flags2 |= WPA_DRIVER_RADAR_OFFCHAN;
+ }
+
+
+diff --git a/src/drivers/nl80211_copy.h b/src/drivers/nl80211_copy.h
+index ab84efc..31a227c 100644
+--- a/src/drivers/nl80211_copy.h
++++ b/src/drivers/nl80211_copy.h
+@@ -2534,6 +2534,10 @@ enum nl80211_commands {
+ * @NL80211_ATTR_WIPHY_ANTENNA_GAIN: Configured antenna gain. Used to reduce
+ * transmit power to stay within regulatory limits. u32, dBi.
+ *
++ * @NL80211_ATTR_RADAR_OFFCHAN: Configure dedicated chain available for radar
++ * detection on some hw. The chain can't be used to transmits or receives
++ * frames. The driver is supposed to implement CAC management in sw or fw.
++ *
+ * @NUM_NL80211_ATTR: total number of nl80211_attrs available
+ * @NL80211_ATTR_MAX: highest attribute number currently defined
+ * @__NL80211_ATTR_AFTER_LAST: internal use
+@@ -3039,6 +3043,8 @@ enum nl80211_attrs {
+
+ NL80211_ATTR_WIPHY_ANTENNA_GAIN,
+
++ NL80211_ATTR_RADAR_OFFCHAN,
++
+ /* add attributes here, update the policy in nl80211.c */
+
+ __NL80211_ATTR_AFTER_LAST,
+@@ -5823,6 +5829,9 @@ enum nl80211_feature_flags {
+ * @NL80211_EXT_FEATURE_BSS_COLOR: The driver supports BSS color collision
+ * detection and change announcemnts.
+ *
++ * @NL80211_EXT_FEATURE_RADAR_OFFCHAN: Device supports offchannel radar/CAC
++ * detection.
++ *
+ * @NUM_NL80211_EXT_FEATURES: number of extended features.
+ * @MAX_NL80211_EXT_FEATURES: highest extended feature index.
+ */
+@@ -5888,6 +5897,7 @@ enum nl80211_ext_feature_index {
+ NL80211_EXT_FEATURE_SECURE_RTT,
+ NL80211_EXT_FEATURE_PROT_RANGE_NEGO_AND_MEASURE,
+ NL80211_EXT_FEATURE_BSS_COLOR,
++ NL80211_EXT_FEATURE_RADAR_OFFCHAN,
+
+ /* add new features before the definition below */
+ NUM_NL80211_EXT_FEATURES,
+