xref: /freebsd/contrib/wpa/src/ap/dfs.c (revision a90b9d0159070121c221b966469c3e36d912bf82)
15b9c547cSRui Paulo /*
25b9c547cSRui Paulo  * DFS - Dynamic Frequency Selection
35b9c547cSRui Paulo  * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
485732ac8SCy Schubert  * Copyright (c) 2013-2017, Qualcomm Atheros, Inc.
55b9c547cSRui Paulo  *
65b9c547cSRui Paulo  * This software may be distributed under the terms of the BSD license.
75b9c547cSRui Paulo  * See README for more details.
85b9c547cSRui Paulo  */
95b9c547cSRui Paulo 
105b9c547cSRui Paulo #include "utils/includes.h"
115b9c547cSRui Paulo 
125b9c547cSRui Paulo #include "utils/common.h"
135b9c547cSRui Paulo #include "common/ieee802_11_defs.h"
145b9c547cSRui Paulo #include "common/hw_features_common.h"
155b9c547cSRui Paulo #include "common/wpa_ctrl.h"
165b9c547cSRui Paulo #include "hostapd.h"
17*a90b9d01SCy Schubert #include "beacon.h"
185b9c547cSRui Paulo #include "ap_drv_ops.h"
195b9c547cSRui Paulo #include "drivers/driver.h"
205b9c547cSRui Paulo #include "dfs.h"
215b9c547cSRui Paulo 
225b9c547cSRui Paulo 
23*a90b9d01SCy Schubert enum dfs_channel_type {
24*a90b9d01SCy Schubert 	DFS_ANY_CHANNEL,
25*a90b9d01SCy Schubert 	DFS_AVAILABLE, /* non-radar or radar-available */
26*a90b9d01SCy Schubert 	DFS_NO_CAC_YET, /* radar-not-yet-available */
27*a90b9d01SCy Schubert };
28*a90b9d01SCy Schubert 
29*a90b9d01SCy Schubert static struct hostapd_channel_data *
30*a90b9d01SCy Schubert dfs_downgrade_bandwidth(struct hostapd_iface *iface, int *secondary_channel,
31*a90b9d01SCy Schubert 			u8 *oper_centr_freq_seg0_idx,
32*a90b9d01SCy Schubert 			u8 *oper_centr_freq_seg1_idx,
33*a90b9d01SCy Schubert 			enum dfs_channel_type *channel_type);
34*a90b9d01SCy Schubert 
35*a90b9d01SCy Schubert 
dfs_use_radar_background(struct hostapd_iface * iface)36*a90b9d01SCy Schubert static bool dfs_use_radar_background(struct hostapd_iface *iface)
37*a90b9d01SCy Schubert {
38*a90b9d01SCy Schubert 	return (iface->drv_flags2 & WPA_DRIVER_FLAGS2_RADAR_BACKGROUND) &&
39*a90b9d01SCy Schubert 		iface->conf->enable_background_radar;
40*a90b9d01SCy Schubert }
41*a90b9d01SCy Schubert 
42*a90b9d01SCy Schubert 
dfs_get_used_n_chans(struct hostapd_iface * iface,int * seg1)435b9c547cSRui Paulo static int dfs_get_used_n_chans(struct hostapd_iface *iface, int *seg1)
445b9c547cSRui Paulo {
455b9c547cSRui Paulo 	int n_chans = 1;
465b9c547cSRui Paulo 
475b9c547cSRui Paulo 	*seg1 = 0;
485b9c547cSRui Paulo 
495b9c547cSRui Paulo 	if (iface->conf->ieee80211n && iface->conf->secondary_channel)
505b9c547cSRui Paulo 		n_chans = 2;
515b9c547cSRui Paulo 
52206b73d0SCy Schubert 	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
53206b73d0SCy Schubert 		switch (hostapd_get_oper_chwidth(iface->conf)) {
54*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_USE_HT:
555b9c547cSRui Paulo 			break;
56*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_80MHZ:
575b9c547cSRui Paulo 			n_chans = 4;
585b9c547cSRui Paulo 			break;
59*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_160MHZ:
605b9c547cSRui Paulo 			n_chans = 8;
615b9c547cSRui Paulo 			break;
62*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_80P80MHZ:
635b9c547cSRui Paulo 			n_chans = 4;
645b9c547cSRui Paulo 			*seg1 = 4;
655b9c547cSRui Paulo 			break;
665b9c547cSRui Paulo 		default:
675b9c547cSRui Paulo 			break;
685b9c547cSRui Paulo 		}
695b9c547cSRui Paulo 	}
705b9c547cSRui Paulo 
715b9c547cSRui Paulo 	return n_chans;
725b9c547cSRui Paulo }
735b9c547cSRui Paulo 
745b9c547cSRui Paulo 
75*a90b9d01SCy Schubert /* dfs_channel_available: select new channel according to type parameter */
dfs_channel_available(struct hostapd_channel_data * chan,enum dfs_channel_type type)765b9c547cSRui Paulo static int dfs_channel_available(struct hostapd_channel_data *chan,
77*a90b9d01SCy Schubert 				 enum dfs_channel_type type)
785b9c547cSRui Paulo {
79*a90b9d01SCy Schubert 	if (type == DFS_NO_CAC_YET) {
80*a90b9d01SCy Schubert 		/* Select only radar channel where CAC has not been
81*a90b9d01SCy Schubert 		 * performed yet
82*a90b9d01SCy Schubert 		 */
83*a90b9d01SCy Schubert 		if ((chan->flag & HOSTAPD_CHAN_RADAR) &&
84*a90b9d01SCy Schubert 		    (chan->flag & HOSTAPD_CHAN_DFS_MASK) ==
85*a90b9d01SCy Schubert 		     HOSTAPD_CHAN_DFS_USABLE)
86*a90b9d01SCy Schubert 			return 1;
87*a90b9d01SCy Schubert 		return 0;
88*a90b9d01SCy Schubert 	}
89*a90b9d01SCy Schubert 
905b9c547cSRui Paulo 	/*
915b9c547cSRui Paulo 	 * When radar detection happens, CSA is performed. However, there's no
925b9c547cSRui Paulo 	 * time for CAC, so radar channels must be skipped when finding a new
935b9c547cSRui Paulo 	 * channel for CSA, unless they are available for immediate use.
945b9c547cSRui Paulo 	 */
95*a90b9d01SCy Schubert 	if (type == DFS_AVAILABLE && (chan->flag & HOSTAPD_CHAN_RADAR) &&
965b9c547cSRui Paulo 	    ((chan->flag & HOSTAPD_CHAN_DFS_MASK) !=
975b9c547cSRui Paulo 	     HOSTAPD_CHAN_DFS_AVAILABLE))
985b9c547cSRui Paulo 		return 0;
995b9c547cSRui Paulo 
1005b9c547cSRui Paulo 	if (chan->flag & HOSTAPD_CHAN_DISABLED)
1015b9c547cSRui Paulo 		return 0;
1025b9c547cSRui Paulo 	if ((chan->flag & HOSTAPD_CHAN_RADAR) &&
1035b9c547cSRui Paulo 	    ((chan->flag & HOSTAPD_CHAN_DFS_MASK) ==
1045b9c547cSRui Paulo 	     HOSTAPD_CHAN_DFS_UNAVAILABLE))
1055b9c547cSRui Paulo 		return 0;
1065b9c547cSRui Paulo 	return 1;
1075b9c547cSRui Paulo }
1085b9c547cSRui Paulo 
1095b9c547cSRui Paulo 
dfs_is_chan_allowed(struct hostapd_channel_data * chan,int n_chans)1105b9c547cSRui Paulo static int dfs_is_chan_allowed(struct hostapd_channel_data *chan, int n_chans)
1115b9c547cSRui Paulo {
1125b9c547cSRui Paulo 	/*
1135b9c547cSRui Paulo 	 * The tables contain first valid channel number based on channel width.
1145b9c547cSRui Paulo 	 * We will also choose this first channel as the control one.
1155b9c547cSRui Paulo 	 */
1165b9c547cSRui Paulo 	int allowed_40[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157,
117c1d255d3SCy Schubert 			     165, 173, 184, 192 };
1185b9c547cSRui Paulo 	/*
1195b9c547cSRui Paulo 	 * VHT80, valid channels based on center frequency:
120c1d255d3SCy Schubert 	 * 42, 58, 106, 122, 138, 155, 171
1215b9c547cSRui Paulo 	 */
122c1d255d3SCy Schubert 	int allowed_80[] = { 36, 52, 100, 116, 132, 149, 165 };
1235b9c547cSRui Paulo 	/*
1245b9c547cSRui Paulo 	 * VHT160 valid channels based on center frequency:
125c1d255d3SCy Schubert 	 * 50, 114, 163
1265b9c547cSRui Paulo 	 */
127c1d255d3SCy Schubert 	int allowed_160[] = { 36, 100, 149 };
1285b9c547cSRui Paulo 	int *allowed = allowed_40;
1295b9c547cSRui Paulo 	unsigned int i, allowed_no = 0;
1305b9c547cSRui Paulo 
1315b9c547cSRui Paulo 	switch (n_chans) {
1325b9c547cSRui Paulo 	case 2:
1335b9c547cSRui Paulo 		allowed = allowed_40;
1345b9c547cSRui Paulo 		allowed_no = ARRAY_SIZE(allowed_40);
1355b9c547cSRui Paulo 		break;
1365b9c547cSRui Paulo 	case 4:
1375b9c547cSRui Paulo 		allowed = allowed_80;
1385b9c547cSRui Paulo 		allowed_no = ARRAY_SIZE(allowed_80);
1395b9c547cSRui Paulo 		break;
1405b9c547cSRui Paulo 	case 8:
1415b9c547cSRui Paulo 		allowed = allowed_160;
1425b9c547cSRui Paulo 		allowed_no = ARRAY_SIZE(allowed_160);
1435b9c547cSRui Paulo 		break;
1445b9c547cSRui Paulo 	default:
1455b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "Unknown width for %d channels", n_chans);
1465b9c547cSRui Paulo 		break;
1475b9c547cSRui Paulo 	}
1485b9c547cSRui Paulo 
1495b9c547cSRui Paulo 	for (i = 0; i < allowed_no; i++) {
1505b9c547cSRui Paulo 		if (chan->chan == allowed[i])
1515b9c547cSRui Paulo 			return 1;
1525b9c547cSRui Paulo 	}
1535b9c547cSRui Paulo 
1545b9c547cSRui Paulo 	return 0;
1555b9c547cSRui Paulo }
1565b9c547cSRui Paulo 
1575b9c547cSRui Paulo 
158325151a3SRui Paulo static struct hostapd_channel_data *
dfs_get_chan_data(struct hostapd_hw_modes * mode,int freq,int first_chan_idx)159325151a3SRui Paulo dfs_get_chan_data(struct hostapd_hw_modes *mode, int freq, int first_chan_idx)
160325151a3SRui Paulo {
161325151a3SRui Paulo 	int i;
162325151a3SRui Paulo 
163325151a3SRui Paulo 	for (i = first_chan_idx; i < mode->num_channels; i++) {
164325151a3SRui Paulo 		if (mode->channels[i].freq == freq)
165325151a3SRui Paulo 			return &mode->channels[i];
166325151a3SRui Paulo 	}
167325151a3SRui Paulo 
168325151a3SRui Paulo 	return NULL;
169325151a3SRui Paulo }
170325151a3SRui Paulo 
171325151a3SRui Paulo 
dfs_chan_range_available(struct hostapd_hw_modes * mode,int first_chan_idx,int num_chans,enum dfs_channel_type type)1725b9c547cSRui Paulo static int dfs_chan_range_available(struct hostapd_hw_modes *mode,
1735b9c547cSRui Paulo 				    int first_chan_idx, int num_chans,
174*a90b9d01SCy Schubert 				    enum dfs_channel_type type)
1755b9c547cSRui Paulo {
1765b9c547cSRui Paulo 	struct hostapd_channel_data *first_chan, *chan;
1775b9c547cSRui Paulo 	int i;
1784bc52338SCy Schubert 	u32 bw = num_chan_to_bw(num_chans);
1795b9c547cSRui Paulo 
180c1d255d3SCy Schubert 	if (first_chan_idx + num_chans > mode->num_channels) {
181c1d255d3SCy Schubert 		wpa_printf(MSG_DEBUG,
182c1d255d3SCy Schubert 			   "DFS: some channels in range not defined");
1835b9c547cSRui Paulo 		return 0;
184c1d255d3SCy Schubert 	}
1855b9c547cSRui Paulo 
1865b9c547cSRui Paulo 	first_chan = &mode->channels[first_chan_idx];
1875b9c547cSRui Paulo 
1884bc52338SCy Schubert 	/* hostapd DFS implementation assumes the first channel as primary.
1894bc52338SCy Schubert 	 * If it's not allowed to use the first channel as primary, decline the
1904bc52338SCy Schubert 	 * whole channel range. */
191c1d255d3SCy Schubert 	if (!chan_pri_allowed(first_chan)) {
192*a90b9d01SCy Schubert 		wpa_printf(MSG_DEBUG, "DFS: primary channel not allowed");
1934bc52338SCy Schubert 		return 0;
194c1d255d3SCy Schubert 	}
1954bc52338SCy Schubert 
1965b9c547cSRui Paulo 	for (i = 0; i < num_chans; i++) {
197325151a3SRui Paulo 		chan = dfs_get_chan_data(mode, first_chan->freq + i * 20,
198325151a3SRui Paulo 					 first_chan_idx);
199c1d255d3SCy Schubert 		if (!chan) {
200c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG, "DFS: no channel data for %d",
201c1d255d3SCy Schubert 				   first_chan->freq + i * 20);
2025b9c547cSRui Paulo 			return 0;
203c1d255d3SCy Schubert 		}
2045b9c547cSRui Paulo 
2054bc52338SCy Schubert 		/* HT 40 MHz secondary channel availability checked only for
2064bc52338SCy Schubert 		 * primary channel */
207c1d255d3SCy Schubert 		if (!chan_bw_allowed(chan, bw, 1, !i)) {
208c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG, "DFS: bw now allowed for %d",
209c1d255d3SCy Schubert 				   first_chan->freq + i * 20);
2104bc52338SCy Schubert 			return 0;
211c1d255d3SCy Schubert 		}
2124bc52338SCy Schubert 
213*a90b9d01SCy Schubert 		if (!dfs_channel_available(chan, type)) {
214c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG, "DFS: channel not available %d",
215c1d255d3SCy Schubert 				   first_chan->freq + i * 20);
2165b9c547cSRui Paulo 			return 0;
2175b9c547cSRui Paulo 		}
218c1d255d3SCy Schubert 	}
2195b9c547cSRui Paulo 
2205b9c547cSRui Paulo 	return 1;
2215b9c547cSRui Paulo }
2225b9c547cSRui Paulo 
2235b9c547cSRui Paulo 
is_in_chanlist(struct hostapd_iface * iface,struct hostapd_channel_data * chan)2245b9c547cSRui Paulo static int is_in_chanlist(struct hostapd_iface *iface,
2255b9c547cSRui Paulo 			  struct hostapd_channel_data *chan)
2265b9c547cSRui Paulo {
227325151a3SRui Paulo 	if (!iface->conf->acs_ch_list.num)
2285b9c547cSRui Paulo 		return 1;
2295b9c547cSRui Paulo 
230325151a3SRui Paulo 	return freq_range_list_includes(&iface->conf->acs_ch_list, chan->chan);
2315b9c547cSRui Paulo }
2325b9c547cSRui Paulo 
2335b9c547cSRui Paulo 
2345b9c547cSRui Paulo /*
2355b9c547cSRui Paulo  * The function assumes HT40+ operation.
2365b9c547cSRui Paulo  * Make sure to adjust the following variables after calling this:
2375b9c547cSRui Paulo  *  - hapd->secondary_channel
238206b73d0SCy Schubert  *  - hapd->vht/he_oper_centr_freq_seg0_idx
239206b73d0SCy Schubert  *  - hapd->vht/he_oper_centr_freq_seg1_idx
2405b9c547cSRui Paulo  */
dfs_find_channel(struct hostapd_iface * iface,struct hostapd_channel_data ** ret_chan,int idx,enum dfs_channel_type type)2415b9c547cSRui Paulo static int dfs_find_channel(struct hostapd_iface *iface,
2425b9c547cSRui Paulo 			    struct hostapd_channel_data **ret_chan,
243*a90b9d01SCy Schubert 			    int idx, enum dfs_channel_type type)
2445b9c547cSRui Paulo {
2455b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
2465b9c547cSRui Paulo 	struct hostapd_channel_data *chan;
2475b9c547cSRui Paulo 	int i, channel_idx = 0, n_chans, n_chans1;
2485b9c547cSRui Paulo 
2495b9c547cSRui Paulo 	mode = iface->current_mode;
2505b9c547cSRui Paulo 	n_chans = dfs_get_used_n_chans(iface, &n_chans1);
2515b9c547cSRui Paulo 
2525b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "DFS new chan checking %d channels", n_chans);
2535b9c547cSRui Paulo 	for (i = 0; i < mode->num_channels; i++) {
2545b9c547cSRui Paulo 		chan = &mode->channels[i];
2555b9c547cSRui Paulo 
2565b9c547cSRui Paulo 		/* Skip HT40/VHT incompatible channels */
2575b9c547cSRui Paulo 		if (iface->conf->ieee80211n &&
2585b9c547cSRui Paulo 		    iface->conf->secondary_channel &&
2594bc52338SCy Schubert 		    (!dfs_is_chan_allowed(chan, n_chans) ||
260c1d255d3SCy Schubert 		     !(chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P))) {
261c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG,
262c1d255d3SCy Schubert 				   "DFS: channel %d (%d) is incompatible",
263c1d255d3SCy Schubert 				   chan->freq, chan->chan);
2645b9c547cSRui Paulo 			continue;
265c1d255d3SCy Schubert 		}
2665b9c547cSRui Paulo 
2675b9c547cSRui Paulo 		/* Skip incompatible chandefs */
268*a90b9d01SCy Schubert 		if (!dfs_chan_range_available(mode, i, n_chans, type)) {
269c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG,
270c1d255d3SCy Schubert 				   "DFS: range not available for %d (%d)",
271c1d255d3SCy Schubert 				   chan->freq, chan->chan);
2725b9c547cSRui Paulo 			continue;
273c1d255d3SCy Schubert 		}
2745b9c547cSRui Paulo 
275c1d255d3SCy Schubert 		if (!is_in_chanlist(iface, chan)) {
276c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG,
277c1d255d3SCy Schubert 				   "DFS: channel %d (%d) not in chanlist",
278c1d255d3SCy Schubert 				   chan->freq, chan->chan);
2795b9c547cSRui Paulo 			continue;
280c1d255d3SCy Schubert 		}
2815b9c547cSRui Paulo 
28232a95656SCy Schubert 		if (chan->max_tx_power < iface->conf->min_tx_power)
28332a95656SCy Schubert 			continue;
28432a95656SCy Schubert 
285*a90b9d01SCy Schubert 		if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) &&
286*a90b9d01SCy Schubert 		    iface->conf->country[2] == 0x4f)
287*a90b9d01SCy Schubert 			continue;
288*a90b9d01SCy Schubert 
2895b9c547cSRui Paulo 		if (ret_chan && idx == channel_idx) {
290c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG, "Selected channel %d (%d)",
291c1d255d3SCy Schubert 				   chan->freq, chan->chan);
2925b9c547cSRui Paulo 			*ret_chan = chan;
2935b9c547cSRui Paulo 			return idx;
2945b9c547cSRui Paulo 		}
295c1d255d3SCy Schubert 		wpa_printf(MSG_DEBUG, "Adding channel %d (%d)",
296c1d255d3SCy Schubert 			   chan->freq, chan->chan);
2975b9c547cSRui Paulo 		channel_idx++;
2985b9c547cSRui Paulo 	}
2995b9c547cSRui Paulo 	return channel_idx;
3005b9c547cSRui Paulo }
3015b9c547cSRui Paulo 
3025b9c547cSRui Paulo 
dfs_adjust_center_freq(struct hostapd_iface * iface,struct hostapd_channel_data * chan,int secondary_channel,int sec_chan_idx_80p80,u8 * oper_centr_freq_seg0_idx,u8 * oper_centr_freq_seg1_idx)303206b73d0SCy Schubert static void dfs_adjust_center_freq(struct hostapd_iface *iface,
3045b9c547cSRui Paulo 				   struct hostapd_channel_data *chan,
3055b9c547cSRui Paulo 				   int secondary_channel,
306c1d255d3SCy Schubert 				   int sec_chan_idx_80p80,
307206b73d0SCy Schubert 				   u8 *oper_centr_freq_seg0_idx,
308206b73d0SCy Schubert 				   u8 *oper_centr_freq_seg1_idx)
3095b9c547cSRui Paulo {
310206b73d0SCy Schubert 	if (!iface->conf->ieee80211ac && !iface->conf->ieee80211ax)
3115b9c547cSRui Paulo 		return;
3125b9c547cSRui Paulo 
3135b9c547cSRui Paulo 	if (!chan)
3145b9c547cSRui Paulo 		return;
3155b9c547cSRui Paulo 
316206b73d0SCy Schubert 	*oper_centr_freq_seg1_idx = 0;
3175b9c547cSRui Paulo 
318206b73d0SCy Schubert 	switch (hostapd_get_oper_chwidth(iface->conf)) {
319*a90b9d01SCy Schubert 	case CONF_OPER_CHWIDTH_USE_HT:
3205b9c547cSRui Paulo 		if (secondary_channel == 1)
321206b73d0SCy Schubert 			*oper_centr_freq_seg0_idx = chan->chan + 2;
3225b9c547cSRui Paulo 		else if (secondary_channel == -1)
323206b73d0SCy Schubert 			*oper_centr_freq_seg0_idx = chan->chan - 2;
3245b9c547cSRui Paulo 		else
325206b73d0SCy Schubert 			*oper_centr_freq_seg0_idx = chan->chan;
3265b9c547cSRui Paulo 		break;
327*a90b9d01SCy Schubert 	case CONF_OPER_CHWIDTH_80MHZ:
328206b73d0SCy Schubert 		*oper_centr_freq_seg0_idx = chan->chan + 6;
3295b9c547cSRui Paulo 		break;
330*a90b9d01SCy Schubert 	case CONF_OPER_CHWIDTH_160MHZ:
331206b73d0SCy Schubert 		*oper_centr_freq_seg0_idx = chan->chan + 14;
3325b9c547cSRui Paulo 		break;
333*a90b9d01SCy Schubert 	case CONF_OPER_CHWIDTH_80P80MHZ:
334c1d255d3SCy Schubert 		*oper_centr_freq_seg0_idx = chan->chan + 6;
335c1d255d3SCy Schubert 		*oper_centr_freq_seg1_idx = sec_chan_idx_80p80 + 6;
336c1d255d3SCy Schubert 		break;
337c1d255d3SCy Schubert 
3385b9c547cSRui Paulo 	default:
339c1d255d3SCy Schubert 		wpa_printf(MSG_INFO,
340c1d255d3SCy Schubert 			   "DFS: Unsupported channel width configuration");
341206b73d0SCy Schubert 		*oper_centr_freq_seg0_idx = 0;
3425b9c547cSRui Paulo 		break;
3435b9c547cSRui Paulo 	}
3445b9c547cSRui Paulo 
3455b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "DFS adjusting VHT center frequency: %d, %d",
346206b73d0SCy Schubert 		   *oper_centr_freq_seg0_idx,
347206b73d0SCy Schubert 		   *oper_centr_freq_seg1_idx);
3485b9c547cSRui Paulo }
3495b9c547cSRui Paulo 
3505b9c547cSRui Paulo 
3515b9c547cSRui Paulo /* Return start channel idx we will use for mode->channels[idx] */
dfs_get_start_chan_idx(struct hostapd_iface * iface,int * seg1_start)3525b9c547cSRui Paulo static int dfs_get_start_chan_idx(struct hostapd_iface *iface, int *seg1_start)
3535b9c547cSRui Paulo {
3545b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
3555b9c547cSRui Paulo 	struct hostapd_channel_data *chan;
3565b9c547cSRui Paulo 	int channel_no = iface->conf->channel;
3575b9c547cSRui Paulo 	int res = -1, i;
3585b9c547cSRui Paulo 	int chan_seg1 = -1;
3595b9c547cSRui Paulo 
3605b9c547cSRui Paulo 	*seg1_start = -1;
3615b9c547cSRui Paulo 
3625b9c547cSRui Paulo 	/* HT40- */
3635b9c547cSRui Paulo 	if (iface->conf->ieee80211n && iface->conf->secondary_channel == -1)
3645b9c547cSRui Paulo 		channel_no -= 4;
3655b9c547cSRui Paulo 
366*a90b9d01SCy Schubert 	/* VHT/HE/EHT */
367*a90b9d01SCy Schubert 	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
368*a90b9d01SCy Schubert 	    iface->conf->ieee80211be) {
369206b73d0SCy Schubert 		switch (hostapd_get_oper_chwidth(iface->conf)) {
370*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_USE_HT:
3715b9c547cSRui Paulo 			break;
372*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_80MHZ:
373206b73d0SCy Schubert 			channel_no = hostapd_get_oper_centr_freq_seg0_idx(
374206b73d0SCy Schubert 				iface->conf) - 6;
3755b9c547cSRui Paulo 			break;
376*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_160MHZ:
377206b73d0SCy Schubert 			channel_no = hostapd_get_oper_centr_freq_seg0_idx(
378206b73d0SCy Schubert 				iface->conf) - 14;
3795b9c547cSRui Paulo 			break;
380*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_80P80MHZ:
381206b73d0SCy Schubert 			channel_no = hostapd_get_oper_centr_freq_seg0_idx(
382206b73d0SCy Schubert 				iface->conf) - 6;
383206b73d0SCy Schubert 			chan_seg1 = hostapd_get_oper_centr_freq_seg1_idx(
384206b73d0SCy Schubert 				iface->conf) - 6;
3855b9c547cSRui Paulo 			break;
386*a90b9d01SCy Schubert 		case CONF_OPER_CHWIDTH_320MHZ:
387*a90b9d01SCy Schubert 			channel_no = hostapd_get_oper_centr_freq_seg0_idx(
388*a90b9d01SCy Schubert 				iface->conf) - 30;
389*a90b9d01SCy Schubert 			break;
3905b9c547cSRui Paulo 		default:
3915b9c547cSRui Paulo 			wpa_printf(MSG_INFO,
392*a90b9d01SCy Schubert 				   "DFS only EHT20/40/80/160/80+80/320 is supported now");
3935b9c547cSRui Paulo 			channel_no = -1;
3945b9c547cSRui Paulo 			break;
3955b9c547cSRui Paulo 		}
3965b9c547cSRui Paulo 	}
3975b9c547cSRui Paulo 
3985b9c547cSRui Paulo 	/* Get idx */
3995b9c547cSRui Paulo 	mode = iface->current_mode;
4005b9c547cSRui Paulo 	for (i = 0; i < mode->num_channels; i++) {
4015b9c547cSRui Paulo 		chan = &mode->channels[i];
4025b9c547cSRui Paulo 		if (chan->chan == channel_no) {
4035b9c547cSRui Paulo 			res = i;
4045b9c547cSRui Paulo 			break;
4055b9c547cSRui Paulo 		}
4065b9c547cSRui Paulo 	}
4075b9c547cSRui Paulo 
4085b9c547cSRui Paulo 	if (res != -1 && chan_seg1 > -1) {
4095b9c547cSRui Paulo 		int found = 0;
4105b9c547cSRui Paulo 
4115b9c547cSRui Paulo 		/* Get idx for seg1 */
4125b9c547cSRui Paulo 		mode = iface->current_mode;
4135b9c547cSRui Paulo 		for (i = 0; i < mode->num_channels; i++) {
4145b9c547cSRui Paulo 			chan = &mode->channels[i];
4155b9c547cSRui Paulo 			if (chan->chan == chan_seg1) {
4165b9c547cSRui Paulo 				*seg1_start = i;
4175b9c547cSRui Paulo 				found = 1;
4185b9c547cSRui Paulo 				break;
4195b9c547cSRui Paulo 			}
4205b9c547cSRui Paulo 		}
4215b9c547cSRui Paulo 		if (!found)
4225b9c547cSRui Paulo 			res = -1;
4235b9c547cSRui Paulo 	}
4245b9c547cSRui Paulo 
4255b9c547cSRui Paulo 	if (res == -1) {
4265b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG,
4275b9c547cSRui Paulo 			   "DFS chan_idx seems wrong; num-ch: %d ch-no: %d conf-ch-no: %d 11n: %d sec-ch: %d vht-oper-width: %d",
4285b9c547cSRui Paulo 			   mode->num_channels, channel_no, iface->conf->channel,
4295b9c547cSRui Paulo 			   iface->conf->ieee80211n,
4305b9c547cSRui Paulo 			   iface->conf->secondary_channel,
431206b73d0SCy Schubert 			   hostapd_get_oper_chwidth(iface->conf));
4325b9c547cSRui Paulo 
4335b9c547cSRui Paulo 		for (i = 0; i < mode->num_channels; i++) {
4345b9c547cSRui Paulo 			wpa_printf(MSG_DEBUG, "Available channel: %d",
4355b9c547cSRui Paulo 				   mode->channels[i].chan);
4365b9c547cSRui Paulo 		}
4375b9c547cSRui Paulo 	}
4385b9c547cSRui Paulo 
4395b9c547cSRui Paulo 	return res;
4405b9c547cSRui Paulo }
4415b9c547cSRui Paulo 
4425b9c547cSRui Paulo 
4435b9c547cSRui Paulo /* At least one channel have radar flag */
dfs_check_chans_radar(struct hostapd_iface * iface,int start_chan_idx,int n_chans)4445b9c547cSRui Paulo static int dfs_check_chans_radar(struct hostapd_iface *iface,
4455b9c547cSRui Paulo 				 int start_chan_idx, int n_chans)
4465b9c547cSRui Paulo {
4475b9c547cSRui Paulo 	struct hostapd_channel_data *channel;
4485b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
4495b9c547cSRui Paulo 	int i, res = 0;
4505b9c547cSRui Paulo 
4515b9c547cSRui Paulo 	mode = iface->current_mode;
4525b9c547cSRui Paulo 
4535b9c547cSRui Paulo 	for (i = 0; i < n_chans; i++) {
454*a90b9d01SCy Schubert 		if (start_chan_idx + i >= mode->num_channels)
455*a90b9d01SCy Schubert 			break;
4565b9c547cSRui Paulo 		channel = &mode->channels[start_chan_idx + i];
4575b9c547cSRui Paulo 		if (channel->flag & HOSTAPD_CHAN_RADAR)
4585b9c547cSRui Paulo 			res++;
4595b9c547cSRui Paulo 	}
4605b9c547cSRui Paulo 
4615b9c547cSRui Paulo 	return res;
4625b9c547cSRui Paulo }
4635b9c547cSRui Paulo 
4645b9c547cSRui Paulo 
4655b9c547cSRui Paulo /* All channels available */
dfs_check_chans_available(struct hostapd_iface * iface,int start_chan_idx,int n_chans)4665b9c547cSRui Paulo static int dfs_check_chans_available(struct hostapd_iface *iface,
4675b9c547cSRui Paulo 				     int start_chan_idx, int n_chans)
4685b9c547cSRui Paulo {
4695b9c547cSRui Paulo 	struct hostapd_channel_data *channel;
4705b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
4715b9c547cSRui Paulo 	int i;
4725b9c547cSRui Paulo 
4735b9c547cSRui Paulo 	mode = iface->current_mode;
4745b9c547cSRui Paulo 
4755b9c547cSRui Paulo 	for (i = 0; i < n_chans; i++) {
4765b9c547cSRui Paulo 		channel = &mode->channels[start_chan_idx + i];
4775b9c547cSRui Paulo 
4785b9c547cSRui Paulo 		if (channel->flag & HOSTAPD_CHAN_DISABLED)
4795b9c547cSRui Paulo 			break;
4805b9c547cSRui Paulo 
4815b9c547cSRui Paulo 		if (!(channel->flag & HOSTAPD_CHAN_RADAR))
4825b9c547cSRui Paulo 			continue;
4835b9c547cSRui Paulo 
4845b9c547cSRui Paulo 		if ((channel->flag & HOSTAPD_CHAN_DFS_MASK) !=
4855b9c547cSRui Paulo 		    HOSTAPD_CHAN_DFS_AVAILABLE)
4865b9c547cSRui Paulo 			break;
4875b9c547cSRui Paulo 	}
4885b9c547cSRui Paulo 
4895b9c547cSRui Paulo 	return i == n_chans;
4905b9c547cSRui Paulo }
4915b9c547cSRui Paulo 
4925b9c547cSRui Paulo 
4935b9c547cSRui Paulo /* At least one channel unavailable */
dfs_check_chans_unavailable(struct hostapd_iface * iface,int start_chan_idx,int n_chans)4945b9c547cSRui Paulo static int dfs_check_chans_unavailable(struct hostapd_iface *iface,
4955b9c547cSRui Paulo 				       int start_chan_idx,
4965b9c547cSRui Paulo 				       int n_chans)
4975b9c547cSRui Paulo {
4985b9c547cSRui Paulo 	struct hostapd_channel_data *channel;
4995b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
5005b9c547cSRui Paulo 	int i, res = 0;
5015b9c547cSRui Paulo 
5025b9c547cSRui Paulo 	mode = iface->current_mode;
5035b9c547cSRui Paulo 
5045b9c547cSRui Paulo 	for (i = 0; i < n_chans; i++) {
5055b9c547cSRui Paulo 		channel = &mode->channels[start_chan_idx + i];
5065b9c547cSRui Paulo 		if (channel->flag & HOSTAPD_CHAN_DISABLED)
5075b9c547cSRui Paulo 			res++;
5085b9c547cSRui Paulo 		if ((channel->flag & HOSTAPD_CHAN_DFS_MASK) ==
5095b9c547cSRui Paulo 		    HOSTAPD_CHAN_DFS_UNAVAILABLE)
5105b9c547cSRui Paulo 			res++;
5115b9c547cSRui Paulo 	}
5125b9c547cSRui Paulo 
5135b9c547cSRui Paulo 	return res;
5145b9c547cSRui Paulo }
5155b9c547cSRui Paulo 
5165b9c547cSRui Paulo 
5175b9c547cSRui Paulo 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,enum dfs_channel_type type)5185b9c547cSRui Paulo dfs_get_valid_channel(struct hostapd_iface *iface,
5195b9c547cSRui Paulo 		      int *secondary_channel,
520206b73d0SCy Schubert 		      u8 *oper_centr_freq_seg0_idx,
521206b73d0SCy Schubert 		      u8 *oper_centr_freq_seg1_idx,
522*a90b9d01SCy Schubert 		      enum dfs_channel_type type)
5235b9c547cSRui Paulo {
5245b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
5255b9c547cSRui Paulo 	struct hostapd_channel_data *chan = NULL;
526c1d255d3SCy Schubert 	struct hostapd_channel_data *chan2 = NULL;
5275b9c547cSRui Paulo 	int num_available_chandefs;
528c1d255d3SCy Schubert 	int chan_idx, chan_idx2;
529c1d255d3SCy Schubert 	int sec_chan_idx_80p80 = -1;
530c1d255d3SCy Schubert 	int i;
5315b9c547cSRui Paulo 	u32 _rand;
5325b9c547cSRui Paulo 
5335b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "DFS: Selecting random channel");
5345b9c547cSRui Paulo 	*secondary_channel = 0;
535206b73d0SCy Schubert 	*oper_centr_freq_seg0_idx = 0;
536206b73d0SCy Schubert 	*oper_centr_freq_seg1_idx = 0;
5375b9c547cSRui Paulo 
5385b9c547cSRui Paulo 	if (iface->current_mode == NULL)
5395b9c547cSRui Paulo 		return NULL;
5405b9c547cSRui Paulo 
5415b9c547cSRui Paulo 	mode = iface->current_mode;
5425b9c547cSRui Paulo 	if (mode->mode != HOSTAPD_MODE_IEEE80211A)
5435b9c547cSRui Paulo 		return NULL;
5445b9c547cSRui Paulo 
5455b9c547cSRui Paulo 	/* Get the count first */
546*a90b9d01SCy Schubert 	num_available_chandefs = dfs_find_channel(iface, NULL, 0, type);
547c1d255d3SCy Schubert 	wpa_printf(MSG_DEBUG, "DFS: num_available_chandefs=%d",
548c1d255d3SCy Schubert 		   num_available_chandefs);
5495b9c547cSRui Paulo 	if (num_available_chandefs == 0)
5505b9c547cSRui Paulo 		return NULL;
5515b9c547cSRui Paulo 
5525b9c547cSRui Paulo 	if (os_get_random((u8 *) &_rand, sizeof(_rand)) < 0)
553780fb4a2SCy Schubert 		return NULL;
5545b9c547cSRui Paulo 	chan_idx = _rand % num_available_chandefs;
555*a90b9d01SCy Schubert 	wpa_printf(MSG_DEBUG, "DFS: Picked random entry from the list: %d/%d",
556*a90b9d01SCy Schubert 		   chan_idx, num_available_chandefs);
557*a90b9d01SCy Schubert 	dfs_find_channel(iface, &chan, chan_idx, type);
558c1d255d3SCy Schubert 	if (!chan) {
559c1d255d3SCy Schubert 		wpa_printf(MSG_DEBUG, "DFS: no random channel found");
560c1d255d3SCy Schubert 		return NULL;
561c1d255d3SCy Schubert 	}
562c1d255d3SCy Schubert 	wpa_printf(MSG_DEBUG, "DFS: got random channel %d (%d)",
563c1d255d3SCy Schubert 		   chan->freq, chan->chan);
5645b9c547cSRui Paulo 
5655b9c547cSRui Paulo 	/* dfs_find_channel() calculations assume HT40+ */
5665b9c547cSRui Paulo 	if (iface->conf->secondary_channel)
5675b9c547cSRui Paulo 		*secondary_channel = 1;
5685b9c547cSRui Paulo 	else
5695b9c547cSRui Paulo 		*secondary_channel = 0;
5705b9c547cSRui Paulo 
571c1d255d3SCy Schubert 	/* Get secondary channel for HT80P80 */
572*a90b9d01SCy Schubert 	if (hostapd_get_oper_chwidth(iface->conf) ==
573*a90b9d01SCy Schubert 	    CONF_OPER_CHWIDTH_80P80MHZ) {
574c1d255d3SCy Schubert 		if (num_available_chandefs <= 1) {
575c1d255d3SCy Schubert 			wpa_printf(MSG_ERROR,
576c1d255d3SCy Schubert 				   "only 1 valid chan, can't support 80+80");
577c1d255d3SCy Schubert 			return NULL;
578c1d255d3SCy Schubert 		}
579c1d255d3SCy Schubert 
580c1d255d3SCy Schubert 		/*
581c1d255d3SCy Schubert 		 * Loop all channels except channel1 to find a valid channel2
582c1d255d3SCy Schubert 		 * that is not adjacent to channel1.
583c1d255d3SCy Schubert 		 */
584c1d255d3SCy Schubert 		for (i = 0; i < num_available_chandefs - 1; i++) {
585c1d255d3SCy Schubert 			/* start from chan_idx + 1, end when chan_idx - 1 */
586c1d255d3SCy Schubert 			chan_idx2 = (chan_idx + 1 + i) % num_available_chandefs;
587*a90b9d01SCy Schubert 			dfs_find_channel(iface, &chan2, chan_idx2, type);
588c1d255d3SCy Schubert 			if (chan2 && abs(chan2->chan - chan->chan) > 12) {
589c1d255d3SCy Schubert 				/* two channels are not adjacent */
590c1d255d3SCy Schubert 				sec_chan_idx_80p80 = chan2->chan;
591c1d255d3SCy Schubert 				wpa_printf(MSG_DEBUG,
592c1d255d3SCy Schubert 					   "DFS: got second chan: %d (%d)",
593c1d255d3SCy Schubert 					   chan2->freq, chan2->chan);
594c1d255d3SCy Schubert 				break;
595c1d255d3SCy Schubert 			}
596c1d255d3SCy Schubert 		}
597c1d255d3SCy Schubert 
598c1d255d3SCy Schubert 		/* Check if we got a valid secondary channel which is not
599c1d255d3SCy Schubert 		 * adjacent to the first channel.
600c1d255d3SCy Schubert 		 */
601c1d255d3SCy Schubert 		if (sec_chan_idx_80p80 == -1) {
602c1d255d3SCy Schubert 			wpa_printf(MSG_INFO,
603c1d255d3SCy Schubert 				   "DFS: failed to get chan2 for 80+80");
604c1d255d3SCy Schubert 			return NULL;
605c1d255d3SCy Schubert 		}
606c1d255d3SCy Schubert 	}
607c1d255d3SCy Schubert 
608206b73d0SCy Schubert 	dfs_adjust_center_freq(iface, chan,
6095b9c547cSRui Paulo 			       *secondary_channel,
610c1d255d3SCy Schubert 			       sec_chan_idx_80p80,
611206b73d0SCy Schubert 			       oper_centr_freq_seg0_idx,
612206b73d0SCy Schubert 			       oper_centr_freq_seg1_idx);
6135b9c547cSRui Paulo 
6145b9c547cSRui Paulo 	return chan;
6155b9c547cSRui Paulo }
6165b9c547cSRui Paulo 
6175b9c547cSRui Paulo 
dfs_set_valid_channel(struct hostapd_iface * iface,int skip_radar)618*a90b9d01SCy Schubert static int dfs_set_valid_channel(struct hostapd_iface *iface, int skip_radar)
619*a90b9d01SCy Schubert {
620*a90b9d01SCy Schubert 	struct hostapd_channel_data *channel;
621*a90b9d01SCy Schubert 	u8 cf1 = 0, cf2 = 0;
622*a90b9d01SCy Schubert 	int sec = 0;
623*a90b9d01SCy Schubert 
624*a90b9d01SCy Schubert 	channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2,
625*a90b9d01SCy Schubert 					skip_radar ? DFS_AVAILABLE :
626*a90b9d01SCy Schubert 					DFS_ANY_CHANNEL);
627*a90b9d01SCy Schubert 	if (!channel) {
628*a90b9d01SCy Schubert 		wpa_printf(MSG_ERROR, "could not get valid channel");
629*a90b9d01SCy Schubert 		return -1;
630*a90b9d01SCy Schubert 	}
631*a90b9d01SCy Schubert 
632*a90b9d01SCy Schubert 	iface->freq = channel->freq;
633*a90b9d01SCy Schubert 	iface->conf->channel = channel->chan;
634*a90b9d01SCy Schubert 	iface->conf->secondary_channel = sec;
635*a90b9d01SCy Schubert 	hostapd_set_oper_centr_freq_seg0_idx(iface->conf, cf1);
636*a90b9d01SCy Schubert 	hostapd_set_oper_centr_freq_seg1_idx(iface->conf, cf2);
637*a90b9d01SCy Schubert 
638*a90b9d01SCy Schubert 	return 0;
639*a90b9d01SCy Schubert }
640*a90b9d01SCy Schubert 
641*a90b9d01SCy Schubert 
set_dfs_state_freq(struct hostapd_iface * iface,int freq,u32 state)6425b9c547cSRui Paulo static int set_dfs_state_freq(struct hostapd_iface *iface, int freq, u32 state)
6435b9c547cSRui Paulo {
6445b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
6455b9c547cSRui Paulo 	struct hostapd_channel_data *chan = NULL;
6465b9c547cSRui Paulo 	int i;
6475b9c547cSRui Paulo 
6485b9c547cSRui Paulo 	mode = iface->current_mode;
6495b9c547cSRui Paulo 	if (mode == NULL)
6505b9c547cSRui Paulo 		return 0;
6515b9c547cSRui Paulo 
6525b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "set_dfs_state 0x%X for %d MHz", state, freq);
6535b9c547cSRui Paulo 	for (i = 0; i < iface->current_mode->num_channels; i++) {
6545b9c547cSRui Paulo 		chan = &iface->current_mode->channels[i];
6555b9c547cSRui Paulo 		if (chan->freq == freq) {
6565b9c547cSRui Paulo 			if (chan->flag & HOSTAPD_CHAN_RADAR) {
6575b9c547cSRui Paulo 				chan->flag &= ~HOSTAPD_CHAN_DFS_MASK;
6585b9c547cSRui Paulo 				chan->flag |= state;
6595b9c547cSRui Paulo 				return 1; /* Channel found */
6605b9c547cSRui Paulo 			}
6615b9c547cSRui Paulo 		}
6625b9c547cSRui Paulo 	}
6635b9c547cSRui Paulo 	wpa_printf(MSG_WARNING, "Can't set DFS state for freq %d MHz", freq);
6645b9c547cSRui Paulo 	return 0;
6655b9c547cSRui Paulo }
6665b9c547cSRui Paulo 
6675b9c547cSRui Paulo 
set_dfs_state(struct hostapd_iface * iface,int freq,int ht_enabled,int chan_offset,int chan_width,int cf1,int cf2,u32 state)6685b9c547cSRui Paulo static int set_dfs_state(struct hostapd_iface *iface, int freq, int ht_enabled,
6695b9c547cSRui Paulo 			 int chan_offset, int chan_width, int cf1,
6705b9c547cSRui Paulo 			 int cf2, u32 state)
6715b9c547cSRui Paulo {
6725b9c547cSRui Paulo 	int n_chans = 1, i;
6735b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
6745b9c547cSRui Paulo 	int frequency = freq;
675c1d255d3SCy Schubert 	int frequency2 = 0;
6765b9c547cSRui Paulo 	int ret = 0;
6775b9c547cSRui Paulo 
6785b9c547cSRui Paulo 	mode = iface->current_mode;
6795b9c547cSRui Paulo 	if (mode == NULL)
6805b9c547cSRui Paulo 		return 0;
6815b9c547cSRui Paulo 
6825b9c547cSRui Paulo 	if (mode->mode != HOSTAPD_MODE_IEEE80211A) {
6835b9c547cSRui Paulo 		wpa_printf(MSG_WARNING, "current_mode != IEEE80211A");
6845b9c547cSRui Paulo 		return 0;
6855b9c547cSRui Paulo 	}
6865b9c547cSRui Paulo 
6875b9c547cSRui Paulo 	/* Seems cf1 and chan_width is enough here */
6885b9c547cSRui Paulo 	switch (chan_width) {
6895b9c547cSRui Paulo 	case CHAN_WIDTH_20_NOHT:
6905b9c547cSRui Paulo 	case CHAN_WIDTH_20:
6915b9c547cSRui Paulo 		n_chans = 1;
6925b9c547cSRui Paulo 		if (frequency == 0)
6935b9c547cSRui Paulo 			frequency = cf1;
6945b9c547cSRui Paulo 		break;
6955b9c547cSRui Paulo 	case CHAN_WIDTH_40:
6965b9c547cSRui Paulo 		n_chans = 2;
6975b9c547cSRui Paulo 		frequency = cf1 - 10;
6985b9c547cSRui Paulo 		break;
6995b9c547cSRui Paulo 	case CHAN_WIDTH_80:
7005b9c547cSRui Paulo 		n_chans = 4;
7015b9c547cSRui Paulo 		frequency = cf1 - 30;
7025b9c547cSRui Paulo 		break;
703c1d255d3SCy Schubert 	case CHAN_WIDTH_80P80:
704c1d255d3SCy Schubert 		n_chans = 4;
705c1d255d3SCy Schubert 		frequency = cf1 - 30;
706c1d255d3SCy Schubert 		frequency2 = cf2 - 30;
707c1d255d3SCy Schubert 		break;
7085b9c547cSRui Paulo 	case CHAN_WIDTH_160:
7095b9c547cSRui Paulo 		n_chans = 8;
7105b9c547cSRui Paulo 		frequency = cf1 - 70;
7115b9c547cSRui Paulo 		break;
7125b9c547cSRui Paulo 	default:
7135b9c547cSRui Paulo 		wpa_printf(MSG_INFO, "DFS chan_width %d not supported",
7145b9c547cSRui Paulo 			   chan_width);
7155b9c547cSRui Paulo 		break;
7165b9c547cSRui Paulo 	}
7175b9c547cSRui Paulo 
7185b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "DFS freq: %dMHz, n_chans: %d", frequency,
7195b9c547cSRui Paulo 		   n_chans);
7205b9c547cSRui Paulo 	for (i = 0; i < n_chans; i++) {
7215b9c547cSRui Paulo 		ret += set_dfs_state_freq(iface, frequency, state);
7225b9c547cSRui Paulo 		frequency = frequency + 20;
723c1d255d3SCy Schubert 
724c1d255d3SCy Schubert 		if (chan_width == CHAN_WIDTH_80P80) {
725c1d255d3SCy Schubert 			ret += set_dfs_state_freq(iface, frequency2, state);
726c1d255d3SCy Schubert 			frequency2 = frequency2 + 20;
727c1d255d3SCy Schubert 		}
7285b9c547cSRui Paulo 	}
7295b9c547cSRui Paulo 
7305b9c547cSRui Paulo 	return ret;
7315b9c547cSRui Paulo }
7325b9c547cSRui Paulo 
7335b9c547cSRui Paulo 
dfs_are_channels_overlapped(struct hostapd_iface * iface,int freq,int chan_width,int cf1,int cf2)7345b9c547cSRui Paulo static int dfs_are_channels_overlapped(struct hostapd_iface *iface, int freq,
7355b9c547cSRui Paulo 				       int chan_width, int cf1, int cf2)
7365b9c547cSRui Paulo {
7375b9c547cSRui Paulo 	int start_chan_idx, start_chan_idx1;
7385b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
7395b9c547cSRui Paulo 	struct hostapd_channel_data *chan;
7405b9c547cSRui Paulo 	int n_chans, n_chans1, i, j, frequency = freq, radar_n_chans = 1;
7415b9c547cSRui Paulo 	u8 radar_chan;
7425b9c547cSRui Paulo 	int res = 0;
7435b9c547cSRui Paulo 
7445b9c547cSRui Paulo 	/* Our configuration */
7455b9c547cSRui Paulo 	mode = iface->current_mode;
7465b9c547cSRui Paulo 	start_chan_idx = dfs_get_start_chan_idx(iface, &start_chan_idx1);
7475b9c547cSRui Paulo 	n_chans = dfs_get_used_n_chans(iface, &n_chans1);
7485b9c547cSRui Paulo 
7495b9c547cSRui Paulo 	/* Check we are on DFS channel(s) */
7505b9c547cSRui Paulo 	if (!dfs_check_chans_radar(iface, start_chan_idx, n_chans))
7515b9c547cSRui Paulo 		return 0;
7525b9c547cSRui Paulo 
7535b9c547cSRui Paulo 	/* Reported via radar event */
7545b9c547cSRui Paulo 	switch (chan_width) {
7555b9c547cSRui Paulo 	case CHAN_WIDTH_20_NOHT:
7565b9c547cSRui Paulo 	case CHAN_WIDTH_20:
7575b9c547cSRui Paulo 		radar_n_chans = 1;
7585b9c547cSRui Paulo 		if (frequency == 0)
7595b9c547cSRui Paulo 			frequency = cf1;
7605b9c547cSRui Paulo 		break;
7615b9c547cSRui Paulo 	case CHAN_WIDTH_40:
7625b9c547cSRui Paulo 		radar_n_chans = 2;
7635b9c547cSRui Paulo 		frequency = cf1 - 10;
7645b9c547cSRui Paulo 		break;
7655b9c547cSRui Paulo 	case CHAN_WIDTH_80:
7665b9c547cSRui Paulo 		radar_n_chans = 4;
7675b9c547cSRui Paulo 		frequency = cf1 - 30;
7685b9c547cSRui Paulo 		break;
7695b9c547cSRui Paulo 	case CHAN_WIDTH_160:
7705b9c547cSRui Paulo 		radar_n_chans = 8;
7715b9c547cSRui Paulo 		frequency = cf1 - 70;
7725b9c547cSRui Paulo 		break;
7735b9c547cSRui Paulo 	default:
7745b9c547cSRui Paulo 		wpa_printf(MSG_INFO, "DFS chan_width %d not supported",
7755b9c547cSRui Paulo 			   chan_width);
7765b9c547cSRui Paulo 		break;
7775b9c547cSRui Paulo 	}
7785b9c547cSRui Paulo 
7795b9c547cSRui Paulo 	ieee80211_freq_to_chan(frequency, &radar_chan);
7805b9c547cSRui Paulo 
7815b9c547cSRui Paulo 	for (i = 0; i < n_chans; i++) {
7825b9c547cSRui Paulo 		chan = &mode->channels[start_chan_idx + i];
7835b9c547cSRui Paulo 		if (!(chan->flag & HOSTAPD_CHAN_RADAR))
7845b9c547cSRui Paulo 			continue;
7855b9c547cSRui Paulo 		for (j = 0; j < radar_n_chans; j++) {
7865b9c547cSRui Paulo 			wpa_printf(MSG_DEBUG, "checking our: %d, radar: %d",
7875b9c547cSRui Paulo 				   chan->chan, radar_chan + j * 4);
7885b9c547cSRui Paulo 			if (chan->chan == radar_chan + j * 4)
7895b9c547cSRui Paulo 				res++;
7905b9c547cSRui Paulo 		}
7915b9c547cSRui Paulo 	}
7925b9c547cSRui Paulo 
7935b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "overlapped: %d", res);
7945b9c547cSRui Paulo 
7955b9c547cSRui Paulo 	return res;
7965b9c547cSRui Paulo }
7975b9c547cSRui Paulo 
7985b9c547cSRui Paulo 
dfs_get_cac_time(struct hostapd_iface * iface,int start_chan_idx,int n_chans)7995b9c547cSRui Paulo static unsigned int dfs_get_cac_time(struct hostapd_iface *iface,
8005b9c547cSRui Paulo 				     int start_chan_idx, int n_chans)
8015b9c547cSRui Paulo {
8025b9c547cSRui Paulo 	struct hostapd_channel_data *channel;
8035b9c547cSRui Paulo 	struct hostapd_hw_modes *mode;
8045b9c547cSRui Paulo 	int i;
8055b9c547cSRui Paulo 	unsigned int cac_time_ms = 0;
8065b9c547cSRui Paulo 
8075b9c547cSRui Paulo 	mode = iface->current_mode;
8085b9c547cSRui Paulo 
8095b9c547cSRui Paulo 	for (i = 0; i < n_chans; i++) {
810*a90b9d01SCy Schubert 		if (start_chan_idx + i >= mode->num_channels)
811*a90b9d01SCy Schubert 			break;
8125b9c547cSRui Paulo 		channel = &mode->channels[start_chan_idx + i];
8135b9c547cSRui Paulo 		if (!(channel->flag & HOSTAPD_CHAN_RADAR))
8145b9c547cSRui Paulo 			continue;
8155b9c547cSRui Paulo 		if (channel->dfs_cac_ms > cac_time_ms)
8165b9c547cSRui Paulo 			cac_time_ms = channel->dfs_cac_ms;
8175b9c547cSRui Paulo 	}
8185b9c547cSRui Paulo 
8195b9c547cSRui Paulo 	return cac_time_ms;
8205b9c547cSRui Paulo }
8215b9c547cSRui Paulo 
8225b9c547cSRui Paulo 
8235b9c547cSRui Paulo /*
8245b9c547cSRui Paulo  * Main DFS handler
8255b9c547cSRui Paulo  * 1 - continue channel/ap setup
8265b9c547cSRui Paulo  * 0 - channel/ap setup will be continued after CAC
8275b9c547cSRui Paulo  * -1 - hit critical error
8285b9c547cSRui Paulo  */
hostapd_handle_dfs(struct hostapd_iface * iface)8295b9c547cSRui Paulo int hostapd_handle_dfs(struct hostapd_iface *iface)
8305b9c547cSRui Paulo {
8315b9c547cSRui Paulo 	int res, n_chans, n_chans1, start_chan_idx, start_chan_idx1;
8325b9c547cSRui Paulo 	int skip_radar = 0;
8335b9c547cSRui Paulo 
834c1d255d3SCy Schubert 	if (is_6ghz_freq(iface->freq))
835c1d255d3SCy Schubert 		return 1;
836c1d255d3SCy Schubert 
8375b9c547cSRui Paulo 	if (!iface->current_mode) {
8385b9c547cSRui Paulo 		/*
8395b9c547cSRui Paulo 		 * This can happen with drivers that do not provide mode
8405b9c547cSRui Paulo 		 * information and as such, cannot really use hostapd for DFS.
8415b9c547cSRui Paulo 		 */
8425b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG,
8435b9c547cSRui Paulo 			   "DFS: No current_mode information - assume no need to perform DFS operations by hostapd");
8445b9c547cSRui Paulo 		return 1;
8455b9c547cSRui Paulo 	}
8465b9c547cSRui Paulo 
8475b9c547cSRui Paulo 	iface->cac_started = 0;
8485b9c547cSRui Paulo 
8495b9c547cSRui Paulo 	do {
8505b9c547cSRui Paulo 		/* Get start (first) channel for current configuration */
8515b9c547cSRui Paulo 		start_chan_idx = dfs_get_start_chan_idx(iface,
8525b9c547cSRui Paulo 							&start_chan_idx1);
8535b9c547cSRui Paulo 		if (start_chan_idx == -1)
8545b9c547cSRui Paulo 			return -1;
8555b9c547cSRui Paulo 
8565b9c547cSRui Paulo 		/* Get number of used channels, depend on width */
8575b9c547cSRui Paulo 		n_chans = dfs_get_used_n_chans(iface, &n_chans1);
8585b9c547cSRui Paulo 
8595b9c547cSRui Paulo 		/* Setup CAC time */
8605b9c547cSRui Paulo 		iface->dfs_cac_ms = dfs_get_cac_time(iface, start_chan_idx,
8615b9c547cSRui Paulo 						     n_chans);
8625b9c547cSRui Paulo 
8635b9c547cSRui Paulo 		/* Check if any of configured channels require DFS */
8645b9c547cSRui Paulo 		res = dfs_check_chans_radar(iface, start_chan_idx, n_chans);
8655b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG,
8665b9c547cSRui Paulo 			   "DFS %d channels required radar detection",
8675b9c547cSRui Paulo 			   res);
8685b9c547cSRui Paulo 		if (!res)
8695b9c547cSRui Paulo 			return 1;
8705b9c547cSRui Paulo 
8715b9c547cSRui Paulo 		/* Check if all channels are DFS available */
8725b9c547cSRui Paulo 		res = dfs_check_chans_available(iface, start_chan_idx, n_chans);
8735b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG,
8745b9c547cSRui Paulo 			   "DFS all channels available, (SKIP CAC): %s",
8755b9c547cSRui Paulo 			   res ? "yes" : "no");
8765b9c547cSRui Paulo 		if (res)
8775b9c547cSRui Paulo 			return 1;
8785b9c547cSRui Paulo 
8795b9c547cSRui Paulo 		/* Check if any of configured channels is unavailable */
8805b9c547cSRui Paulo 		res = dfs_check_chans_unavailable(iface, start_chan_idx,
8815b9c547cSRui Paulo 						  n_chans);
8825b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "DFS %d chans unavailable - choose other channel: %s",
8835b9c547cSRui Paulo 			   res, res ? "yes": "no");
8845b9c547cSRui Paulo 		if (res) {
885*a90b9d01SCy Schubert 			if (dfs_set_valid_channel(iface, skip_radar) < 0) {
886780fb4a2SCy Schubert 				hostapd_set_state(iface, HAPD_IFACE_DFS);
887780fb4a2SCy Schubert 				return 0;
8885b9c547cSRui Paulo 			}
8895b9c547cSRui Paulo 		}
8905b9c547cSRui Paulo 	} while (res);
8915b9c547cSRui Paulo 
8925b9c547cSRui Paulo 	/* Finally start CAC */
8935b9c547cSRui Paulo 	hostapd_set_state(iface, HAPD_IFACE_DFS);
894*a90b9d01SCy Schubert 	wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz%s", iface->freq,
895*a90b9d01SCy Schubert 		   dfs_use_radar_background(iface) ? " (background)" : "");
8965b9c547cSRui Paulo 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START
8975b9c547cSRui Paulo 		"freq=%d chan=%d sec_chan=%d, width=%d, seg0=%d, seg1=%d, cac_time=%ds",
8985b9c547cSRui Paulo 		iface->freq,
8995b9c547cSRui Paulo 		iface->conf->channel, iface->conf->secondary_channel,
900206b73d0SCy Schubert 		hostapd_get_oper_chwidth(iface->conf),
901206b73d0SCy Schubert 		hostapd_get_oper_centr_freq_seg0_idx(iface->conf),
902206b73d0SCy Schubert 		hostapd_get_oper_centr_freq_seg1_idx(iface->conf),
9035b9c547cSRui Paulo 		iface->dfs_cac_ms / 1000);
9045b9c547cSRui Paulo 
905206b73d0SCy Schubert 	res = hostapd_start_dfs_cac(
906206b73d0SCy Schubert 		iface, iface->conf->hw_mode, iface->freq, iface->conf->channel,
907206b73d0SCy Schubert 		iface->conf->ieee80211n, iface->conf->ieee80211ac,
908*a90b9d01SCy Schubert 		iface->conf->ieee80211ax, iface->conf->ieee80211be,
9095b9c547cSRui Paulo 		iface->conf->secondary_channel,
910206b73d0SCy Schubert 		hostapd_get_oper_chwidth(iface->conf),
911206b73d0SCy Schubert 		hostapd_get_oper_centr_freq_seg0_idx(iface->conf),
912*a90b9d01SCy Schubert 		hostapd_get_oper_centr_freq_seg1_idx(iface->conf),
913*a90b9d01SCy Schubert 		dfs_use_radar_background(iface));
9145b9c547cSRui Paulo 
9155b9c547cSRui Paulo 	if (res) {
9165b9c547cSRui Paulo 		wpa_printf(MSG_ERROR, "DFS start_dfs_cac() failed, %d", res);
9175b9c547cSRui Paulo 		return -1;
9185b9c547cSRui Paulo 	}
9195b9c547cSRui Paulo 
920*a90b9d01SCy Schubert 	if (dfs_use_radar_background(iface)) {
921*a90b9d01SCy Schubert 		/* Cache background radar parameters. */
922*a90b9d01SCy Schubert 		iface->radar_background.channel = iface->conf->channel;
923*a90b9d01SCy Schubert 		iface->radar_background.secondary_channel =
924*a90b9d01SCy Schubert 			iface->conf->secondary_channel;
925*a90b9d01SCy Schubert 		iface->radar_background.freq = iface->freq;
926*a90b9d01SCy Schubert 		iface->radar_background.centr_freq_seg0_idx =
927*a90b9d01SCy Schubert 			hostapd_get_oper_centr_freq_seg0_idx(iface->conf);
928*a90b9d01SCy Schubert 		iface->radar_background.centr_freq_seg1_idx =
929*a90b9d01SCy Schubert 			hostapd_get_oper_centr_freq_seg1_idx(iface->conf);
930*a90b9d01SCy Schubert 
931*a90b9d01SCy Schubert 		/*
932*a90b9d01SCy Schubert 		 * Let's select a random channel according to the
933*a90b9d01SCy Schubert 		 * regulations and perform CAC on dedicated radar chain.
934*a90b9d01SCy Schubert 		 */
935*a90b9d01SCy Schubert 		res = dfs_set_valid_channel(iface, 1);
936*a90b9d01SCy Schubert 		if (res < 0)
937*a90b9d01SCy Schubert 			return res;
938*a90b9d01SCy Schubert 
939*a90b9d01SCy Schubert 		iface->radar_background.temp_ch = 1;
940*a90b9d01SCy Schubert 		return 1;
941*a90b9d01SCy Schubert 	}
942*a90b9d01SCy Schubert 
9435b9c547cSRui Paulo 	return 0;
9445b9c547cSRui Paulo }
9455b9c547cSRui Paulo 
9465b9c547cSRui Paulo 
hostapd_is_dfs_chan_available(struct hostapd_iface * iface)947c1d255d3SCy Schubert int hostapd_is_dfs_chan_available(struct hostapd_iface *iface)
94885732ac8SCy Schubert {
94985732ac8SCy Schubert 	int n_chans, n_chans1, start_chan_idx, start_chan_idx1;
95085732ac8SCy Schubert 
95185732ac8SCy Schubert 	/* Get the start (first) channel for current configuration */
95285732ac8SCy Schubert 	start_chan_idx = dfs_get_start_chan_idx(iface, &start_chan_idx1);
95385732ac8SCy Schubert 	if (start_chan_idx < 0)
95485732ac8SCy Schubert 		return 0;
95585732ac8SCy Schubert 
95685732ac8SCy Schubert 	/* Get the number of used channels, depending on width */
95785732ac8SCy Schubert 	n_chans = dfs_get_used_n_chans(iface, &n_chans1);
95885732ac8SCy Schubert 
95985732ac8SCy Schubert 	/* Check if all channels are DFS available */
96085732ac8SCy Schubert 	return dfs_check_chans_available(iface, start_chan_idx, n_chans);
96185732ac8SCy Schubert }
96285732ac8SCy Schubert 
96385732ac8SCy Schubert 
hostapd_dfs_request_channel_switch(struct hostapd_iface * iface,int channel,int freq,int secondary_channel,u8 current_vht_oper_chwidth,u8 oper_centr_freq_seg0_idx,u8 oper_centr_freq_seg1_idx)964*a90b9d01SCy Schubert static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface,
965*a90b9d01SCy Schubert 					      int channel, int freq,
966*a90b9d01SCy Schubert 					      int secondary_channel,
967*a90b9d01SCy Schubert 					      u8 current_vht_oper_chwidth,
968*a90b9d01SCy Schubert 					      u8 oper_centr_freq_seg0_idx,
969*a90b9d01SCy Schubert 					      u8 oper_centr_freq_seg1_idx)
970*a90b9d01SCy Schubert {
971*a90b9d01SCy Schubert 	struct hostapd_hw_modes *cmode = iface->current_mode;
972*a90b9d01SCy Schubert 	int ieee80211_mode = IEEE80211_MODE_AP, err;
973*a90b9d01SCy Schubert 	struct csa_settings csa_settings;
974*a90b9d01SCy Schubert 	u8 new_vht_oper_chwidth;
975*a90b9d01SCy Schubert 	unsigned int i;
976*a90b9d01SCy Schubert 	unsigned int num_err = 0;
977*a90b9d01SCy Schubert 
978*a90b9d01SCy Schubert 	wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", channel);
979*a90b9d01SCy Schubert 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL
980*a90b9d01SCy Schubert 		"freq=%d chan=%d sec_chan=%d", freq, channel,
981*a90b9d01SCy Schubert 		secondary_channel);
982*a90b9d01SCy Schubert 
983*a90b9d01SCy Schubert 	new_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf);
984*a90b9d01SCy Schubert 	hostapd_set_oper_chwidth(iface->conf, current_vht_oper_chwidth);
985*a90b9d01SCy Schubert 
986*a90b9d01SCy Schubert 	/* Setup CSA request */
987*a90b9d01SCy Schubert 	os_memset(&csa_settings, 0, sizeof(csa_settings));
988*a90b9d01SCy Schubert 	csa_settings.cs_count = 5;
989*a90b9d01SCy Schubert 	csa_settings.block_tx = 1;
990*a90b9d01SCy Schubert 	csa_settings.link_id = -1;
991*a90b9d01SCy Schubert #ifdef CONFIG_IEEE80211BE
992*a90b9d01SCy Schubert 	if (iface->bss[0]->conf->mld_ap)
993*a90b9d01SCy Schubert 		csa_settings.link_id = iface->bss[0]->mld_link_id;
994*a90b9d01SCy Schubert #endif /* CONFIG_IEEE80211BE */
995*a90b9d01SCy Schubert #ifdef CONFIG_MESH
996*a90b9d01SCy Schubert 	if (iface->mconf)
997*a90b9d01SCy Schubert 		ieee80211_mode = IEEE80211_MODE_MESH;
998*a90b9d01SCy Schubert #endif /* CONFIG_MESH */
999*a90b9d01SCy Schubert 	err = hostapd_set_freq_params(&csa_settings.freq_params,
1000*a90b9d01SCy Schubert 				      iface->conf->hw_mode,
1001*a90b9d01SCy Schubert 				      freq, channel,
1002*a90b9d01SCy Schubert 				      iface->conf->enable_edmg,
1003*a90b9d01SCy Schubert 				      iface->conf->edmg_channel,
1004*a90b9d01SCy Schubert 				      iface->conf->ieee80211n,
1005*a90b9d01SCy Schubert 				      iface->conf->ieee80211ac,
1006*a90b9d01SCy Schubert 				      iface->conf->ieee80211ax,
1007*a90b9d01SCy Schubert 				      iface->conf->ieee80211be,
1008*a90b9d01SCy Schubert 				      secondary_channel,
1009*a90b9d01SCy Schubert 				      new_vht_oper_chwidth,
1010*a90b9d01SCy Schubert 				      oper_centr_freq_seg0_idx,
1011*a90b9d01SCy Schubert 				      oper_centr_freq_seg1_idx,
1012*a90b9d01SCy Schubert 				      cmode->vht_capab,
1013*a90b9d01SCy Schubert 				      &cmode->he_capab[ieee80211_mode],
1014*a90b9d01SCy Schubert 				      &cmode->eht_capab[ieee80211_mode],
1015*a90b9d01SCy Schubert 				      hostapd_get_punct_bitmap(iface->bss[0]));
1016*a90b9d01SCy Schubert 
1017*a90b9d01SCy Schubert 	if (err) {
1018*a90b9d01SCy Schubert 		wpa_printf(MSG_ERROR,
1019*a90b9d01SCy Schubert 			   "DFS failed to calculate CSA freq params");
1020*a90b9d01SCy Schubert 		hostapd_disable_iface(iface);
1021*a90b9d01SCy Schubert 		return err;
1022*a90b9d01SCy Schubert 	}
1023*a90b9d01SCy Schubert 
1024*a90b9d01SCy Schubert 	for (i = 0; i < iface->num_bss; i++) {
1025*a90b9d01SCy Schubert 		err = hostapd_switch_channel(iface->bss[i], &csa_settings);
1026*a90b9d01SCy Schubert 		if (err)
1027*a90b9d01SCy Schubert 			num_err++;
1028*a90b9d01SCy Schubert 	}
1029*a90b9d01SCy Schubert 
1030*a90b9d01SCy Schubert 	if (num_err == iface->num_bss) {
1031*a90b9d01SCy Schubert 		wpa_printf(MSG_WARNING,
1032*a90b9d01SCy Schubert 			   "DFS failed to schedule CSA (%d) - trying fallback",
1033*a90b9d01SCy Schubert 			   err);
1034*a90b9d01SCy Schubert 		iface->freq = freq;
1035*a90b9d01SCy Schubert 		iface->conf->channel = channel;
1036*a90b9d01SCy Schubert 		iface->conf->secondary_channel = secondary_channel;
1037*a90b9d01SCy Schubert 		hostapd_set_oper_chwidth(iface->conf, new_vht_oper_chwidth);
1038*a90b9d01SCy Schubert 		hostapd_set_oper_centr_freq_seg0_idx(iface->conf,
1039*a90b9d01SCy Schubert 						     oper_centr_freq_seg0_idx);
1040*a90b9d01SCy Schubert 		hostapd_set_oper_centr_freq_seg1_idx(iface->conf,
1041*a90b9d01SCy Schubert 						     oper_centr_freq_seg1_idx);
1042*a90b9d01SCy Schubert 
1043*a90b9d01SCy Schubert 		hostapd_disable_iface(iface);
1044*a90b9d01SCy Schubert 		hostapd_enable_iface(iface);
1045*a90b9d01SCy Schubert 
1046*a90b9d01SCy Schubert 		return 0;
1047*a90b9d01SCy Schubert 	}
1048*a90b9d01SCy Schubert 
1049*a90b9d01SCy Schubert 	/* Channel configuration will be updated once CSA completes and
1050*a90b9d01SCy Schubert 	 * ch_switch_notify event is received */
1051*a90b9d01SCy Schubert 	wpa_printf(MSG_DEBUG, "DFS waiting channel switch event");
1052*a90b9d01SCy Schubert 
1053*a90b9d01SCy Schubert 	return 0;
1054*a90b9d01SCy Schubert }
1055*a90b9d01SCy Schubert 
1056*a90b9d01SCy Schubert 
hostapd_dfs_update_background_chain(struct hostapd_iface * iface)1057*a90b9d01SCy Schubert static void hostapd_dfs_update_background_chain(struct hostapd_iface *iface)
1058*a90b9d01SCy Schubert {
1059*a90b9d01SCy Schubert 	int sec = 0;
1060*a90b9d01SCy Schubert 	enum dfs_channel_type channel_type = DFS_NO_CAC_YET;
1061*a90b9d01SCy Schubert 	struct hostapd_channel_data *channel;
1062*a90b9d01SCy Schubert 	u8 oper_centr_freq_seg0_idx = 0;
1063*a90b9d01SCy Schubert 	u8 oper_centr_freq_seg1_idx = 0;
1064*a90b9d01SCy Schubert 
1065*a90b9d01SCy Schubert 	/*
1066*a90b9d01SCy Schubert 	 * Allow selection of DFS channel in ETSI to comply with
1067*a90b9d01SCy Schubert 	 * uniform spreading.
1068*a90b9d01SCy Schubert 	 */
1069*a90b9d01SCy Schubert 	if (iface->dfs_domain == HOSTAPD_DFS_REGION_ETSI)
1070*a90b9d01SCy Schubert 		channel_type = DFS_ANY_CHANNEL;
1071*a90b9d01SCy Schubert 
1072*a90b9d01SCy Schubert 	channel = dfs_get_valid_channel(iface, &sec, &oper_centr_freq_seg0_idx,
1073*a90b9d01SCy Schubert 					&oper_centr_freq_seg1_idx,
1074*a90b9d01SCy Schubert 					channel_type);
1075*a90b9d01SCy Schubert 	if (!channel ||
1076*a90b9d01SCy Schubert 	    channel->chan == iface->conf->channel ||
1077*a90b9d01SCy Schubert 	    channel->chan == iface->radar_background.channel)
1078*a90b9d01SCy Schubert 		channel = dfs_downgrade_bandwidth(iface, &sec,
1079*a90b9d01SCy Schubert 						  &oper_centr_freq_seg0_idx,
1080*a90b9d01SCy Schubert 						  &oper_centr_freq_seg1_idx,
1081*a90b9d01SCy Schubert 						  &channel_type);
1082*a90b9d01SCy Schubert 	if (!channel ||
1083*a90b9d01SCy Schubert 	    hostapd_start_dfs_cac(iface, iface->conf->hw_mode,
1084*a90b9d01SCy Schubert 				  channel->freq, channel->chan,
1085*a90b9d01SCy Schubert 				  iface->conf->ieee80211n,
1086*a90b9d01SCy Schubert 				  iface->conf->ieee80211ac,
1087*a90b9d01SCy Schubert 				  iface->conf->ieee80211ax,
1088*a90b9d01SCy Schubert 				  iface->conf->ieee80211be,
1089*a90b9d01SCy Schubert 				  sec, hostapd_get_oper_chwidth(iface->conf),
1090*a90b9d01SCy Schubert 				  oper_centr_freq_seg0_idx,
1091*a90b9d01SCy Schubert 				  oper_centr_freq_seg1_idx, true)) {
1092*a90b9d01SCy Schubert 		wpa_printf(MSG_ERROR, "DFS failed to start CAC offchannel");
1093*a90b9d01SCy Schubert 		iface->radar_background.channel = -1;
1094*a90b9d01SCy Schubert 		return;
1095*a90b9d01SCy Schubert 	}
1096*a90b9d01SCy Schubert 
1097*a90b9d01SCy Schubert 	iface->radar_background.channel = channel->chan;
1098*a90b9d01SCy Schubert 	iface->radar_background.freq = channel->freq;
1099*a90b9d01SCy Schubert 	iface->radar_background.secondary_channel = sec;
1100*a90b9d01SCy Schubert 	iface->radar_background.centr_freq_seg0_idx = oper_centr_freq_seg0_idx;
1101*a90b9d01SCy Schubert 	iface->radar_background.centr_freq_seg1_idx = oper_centr_freq_seg1_idx;
1102*a90b9d01SCy Schubert 
1103*a90b9d01SCy Schubert 	wpa_printf(MSG_DEBUG,
1104*a90b9d01SCy Schubert 		   "%s: setting background chain to chan %d (%d MHz)",
1105*a90b9d01SCy Schubert 		   __func__, channel->chan, channel->freq);
1106*a90b9d01SCy Schubert }
1107*a90b9d01SCy Schubert 
1108*a90b9d01SCy Schubert 
1109*a90b9d01SCy Schubert static bool
hostapd_dfs_is_background_event(struct hostapd_iface * iface,int freq)1110*a90b9d01SCy Schubert hostapd_dfs_is_background_event(struct hostapd_iface *iface, int freq)
1111*a90b9d01SCy Schubert {
1112*a90b9d01SCy Schubert 	return dfs_use_radar_background(iface) &&
1113*a90b9d01SCy Schubert 		iface->radar_background.channel != -1 &&
1114*a90b9d01SCy Schubert 		iface->radar_background.freq == freq;
1115*a90b9d01SCy Schubert }
1116*a90b9d01SCy Schubert 
1117*a90b9d01SCy Schubert 
1118*a90b9d01SCy Schubert static int
hostapd_dfs_start_channel_switch_background(struct hostapd_iface * iface)1119*a90b9d01SCy Schubert hostapd_dfs_start_channel_switch_background(struct hostapd_iface *iface)
1120*a90b9d01SCy Schubert {
1121*a90b9d01SCy Schubert 	u8 current_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf);
1122*a90b9d01SCy Schubert 
1123*a90b9d01SCy Schubert 	iface->conf->channel = iface->radar_background.channel;
1124*a90b9d01SCy Schubert 	iface->freq = iface->radar_background.freq;
1125*a90b9d01SCy Schubert 	iface->conf->secondary_channel =
1126*a90b9d01SCy Schubert 		iface->radar_background.secondary_channel;
1127*a90b9d01SCy Schubert 	hostapd_set_oper_centr_freq_seg0_idx(
1128*a90b9d01SCy Schubert 		iface->conf, iface->radar_background.centr_freq_seg0_idx);
1129*a90b9d01SCy Schubert 	hostapd_set_oper_centr_freq_seg1_idx(
1130*a90b9d01SCy Schubert 		iface->conf, iface->radar_background.centr_freq_seg1_idx);
1131*a90b9d01SCy Schubert 
1132*a90b9d01SCy Schubert 	hostapd_dfs_update_background_chain(iface);
1133*a90b9d01SCy Schubert 
1134*a90b9d01SCy Schubert 	return hostapd_dfs_request_channel_switch(
1135*a90b9d01SCy Schubert 		iface, iface->conf->channel, iface->freq,
1136*a90b9d01SCy Schubert 		iface->conf->secondary_channel, current_vht_oper_chwidth,
1137*a90b9d01SCy Schubert 		hostapd_get_oper_centr_freq_seg0_idx(iface->conf),
1138*a90b9d01SCy Schubert 		hostapd_get_oper_centr_freq_seg1_idx(iface->conf));
1139*a90b9d01SCy Schubert }
1140*a90b9d01SCy Schubert 
1141*a90b9d01SCy Schubert 
hostapd_dfs_complete_cac(struct hostapd_iface * iface,int success,int freq,int ht_enabled,int chan_offset,int chan_width,int cf1,int cf2)11425b9c547cSRui Paulo int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
11435b9c547cSRui Paulo 			     int ht_enabled, int chan_offset, int chan_width,
11445b9c547cSRui Paulo 			     int cf1, int cf2)
11455b9c547cSRui Paulo {
11465b9c547cSRui Paulo 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_COMPLETED
1147*a90b9d01SCy Schubert 		"success=%d freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d radar_detected=%d",
1148*a90b9d01SCy Schubert 		success, freq, ht_enabled, chan_offset, chan_width, cf1, cf2,
1149*a90b9d01SCy Schubert 		iface->radar_detected);
11505b9c547cSRui Paulo 
11515b9c547cSRui Paulo 	if (success) {
11525b9c547cSRui Paulo 		/* Complete iface/ap configuration */
11535b9c547cSRui Paulo 		if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) {
1154*a90b9d01SCy Schubert 			/* Complete AP configuration for the first bring up. If
1155*a90b9d01SCy Schubert 			 * a radar was detected in this channel, interface setup
1156*a90b9d01SCy Schubert 			 * will be handled in
1157*a90b9d01SCy Schubert 			 * 1. hostapd_event_ch_switch() if switching to a
1158*a90b9d01SCy Schubert 			 *    non-DFS channel
1159*a90b9d01SCy Schubert 			 * 2. on next CAC complete event if switching to another
1160*a90b9d01SCy Schubert 			 *    DFS channel.
1161*a90b9d01SCy Schubert 			 */
1162*a90b9d01SCy Schubert 			if (iface->state != HAPD_IFACE_ENABLED &&
1163*a90b9d01SCy Schubert 			    !iface->radar_detected)
11645b9c547cSRui Paulo 				hostapd_setup_interface_complete(iface, 0);
11655b9c547cSRui Paulo 			else
11665b9c547cSRui Paulo 				iface->cac_started = 0;
11675b9c547cSRui Paulo 		} else {
11685b9c547cSRui Paulo 			set_dfs_state(iface, freq, ht_enabled, chan_offset,
11695b9c547cSRui Paulo 				      chan_width, cf1, cf2,
11705b9c547cSRui Paulo 				      HOSTAPD_CHAN_DFS_AVAILABLE);
1171*a90b9d01SCy Schubert 
1172*a90b9d01SCy Schubert 			/*
1173*a90b9d01SCy Schubert 			 * Radar event from background chain for the selected
1174*a90b9d01SCy Schubert 			 * channel. Perform CSA, move the main chain to the
1175*a90b9d01SCy Schubert 			 * selected channel and configure the background chain
1176*a90b9d01SCy Schubert 			 * to a new DFS channel.
1177*a90b9d01SCy Schubert 			 */
1178*a90b9d01SCy Schubert 			if (hostapd_dfs_is_background_event(iface, freq)) {
1179*a90b9d01SCy Schubert 				iface->radar_background.cac_started = 0;
1180*a90b9d01SCy Schubert 				if (!iface->radar_background.temp_ch)
1181*a90b9d01SCy Schubert 					return 0;
1182*a90b9d01SCy Schubert 
1183*a90b9d01SCy Schubert 				iface->radar_background.temp_ch = 0;
1184*a90b9d01SCy Schubert 				return hostapd_dfs_start_channel_switch_background(iface);
1185*a90b9d01SCy Schubert 			}
1186*a90b9d01SCy Schubert 
118785732ac8SCy Schubert 			/*
118885732ac8SCy Schubert 			 * Just mark the channel available when CAC completion
118985732ac8SCy Schubert 			 * event is received in enabled state. CAC result could
119085732ac8SCy Schubert 			 * have been propagated from another radio having the
119185732ac8SCy Schubert 			 * same regulatory configuration. When CAC completion is
119285732ac8SCy Schubert 			 * received during non-HAPD_IFACE_ENABLED state, make
119385732ac8SCy Schubert 			 * sure the configured channel is available because this
119485732ac8SCy Schubert 			 * CAC completion event could have been propagated from
119585732ac8SCy Schubert 			 * another radio.
119685732ac8SCy Schubert 			 */
119785732ac8SCy Schubert 			if (iface->state != HAPD_IFACE_ENABLED &&
1198c1d255d3SCy Schubert 			    hostapd_is_dfs_chan_available(iface)) {
11995b9c547cSRui Paulo 				hostapd_setup_interface_complete(iface, 0);
120085732ac8SCy Schubert 				iface->cac_started = 0;
12015b9c547cSRui Paulo 			}
12025b9c547cSRui Paulo 		}
1203*a90b9d01SCy Schubert 	} else if (hostapd_dfs_is_background_event(iface, freq)) {
1204*a90b9d01SCy Schubert 		iface->radar_background.cac_started = 0;
1205*a90b9d01SCy Schubert 		hostapd_dfs_update_background_chain(iface);
120685732ac8SCy Schubert 	}
120785732ac8SCy Schubert 
1208*a90b9d01SCy Schubert 	iface->radar_detected = false;
120985732ac8SCy Schubert 	return 0;
121085732ac8SCy Schubert }
121185732ac8SCy Schubert 
121285732ac8SCy Schubert 
hostapd_dfs_pre_cac_expired(struct hostapd_iface * iface,int freq,int ht_enabled,int chan_offset,int chan_width,int cf1,int cf2)121385732ac8SCy Schubert int hostapd_dfs_pre_cac_expired(struct hostapd_iface *iface, int freq,
121485732ac8SCy Schubert 				int ht_enabled, int chan_offset, int chan_width,
121585732ac8SCy Schubert 				int cf1, int cf2)
121685732ac8SCy Schubert {
121785732ac8SCy Schubert 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_PRE_CAC_EXPIRED
121885732ac8SCy Schubert 		"freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d",
121985732ac8SCy Schubert 		freq, ht_enabled, chan_offset, chan_width, cf1, cf2);
122085732ac8SCy Schubert 
122185732ac8SCy Schubert 	/* Proceed only if DFS is not offloaded to the driver */
122285732ac8SCy Schubert 	if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD)
122385732ac8SCy Schubert 		return 0;
122485732ac8SCy Schubert 
122585732ac8SCy Schubert 	set_dfs_state(iface, freq, ht_enabled, chan_offset, chan_width,
122685732ac8SCy Schubert 		      cf1, cf2, HOSTAPD_CHAN_DFS_USABLE);
12275b9c547cSRui Paulo 
12285b9c547cSRui Paulo 	return 0;
12295b9c547cSRui Paulo }
12305b9c547cSRui Paulo 
12315b9c547cSRui Paulo 
1232c1d255d3SCy Schubert 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,enum dfs_channel_type * channel_type)1233c1d255d3SCy Schubert dfs_downgrade_bandwidth(struct hostapd_iface *iface, int *secondary_channel,
1234c1d255d3SCy Schubert 			u8 *oper_centr_freq_seg0_idx,
1235*a90b9d01SCy Schubert 			u8 *oper_centr_freq_seg1_idx,
1236*a90b9d01SCy Schubert 			enum dfs_channel_type *channel_type)
1237c1d255d3SCy Schubert {
1238c1d255d3SCy Schubert 	struct hostapd_channel_data *channel;
1239c1d255d3SCy Schubert 
1240c1d255d3SCy Schubert 	for (;;) {
1241c1d255d3SCy Schubert 		channel = dfs_get_valid_channel(iface, secondary_channel,
1242c1d255d3SCy Schubert 						oper_centr_freq_seg0_idx,
1243c1d255d3SCy Schubert 						oper_centr_freq_seg1_idx,
1244*a90b9d01SCy Schubert 						*channel_type);
1245c1d255d3SCy Schubert 		if (channel) {
1246c1d255d3SCy Schubert 			wpa_printf(MSG_DEBUG, "DFS: Selected channel: %d",
1247c1d255d3SCy Schubert 				   channel->chan);
1248c1d255d3SCy Schubert 			return channel;
1249c1d255d3SCy Schubert 		}
1250c1d255d3SCy Schubert 
1251*a90b9d01SCy Schubert 		if (*channel_type != DFS_ANY_CHANNEL) {
1252*a90b9d01SCy Schubert 			*channel_type = DFS_ANY_CHANNEL;
1253c1d255d3SCy Schubert 		} else {
1254c1d255d3SCy Schubert 			int oper_chwidth;
1255c1d255d3SCy Schubert 
1256c1d255d3SCy Schubert 			oper_chwidth = hostapd_get_oper_chwidth(iface->conf);
1257*a90b9d01SCy Schubert 			if (oper_chwidth == CONF_OPER_CHWIDTH_USE_HT)
1258c1d255d3SCy Schubert 				break;
1259*a90b9d01SCy Schubert 			*channel_type = DFS_AVAILABLE;
1260c1d255d3SCy Schubert 			hostapd_set_oper_chwidth(iface->conf, oper_chwidth - 1);
1261c1d255d3SCy Schubert 		}
1262c1d255d3SCy Schubert 	}
1263c1d255d3SCy Schubert 
1264c1d255d3SCy Schubert 	wpa_printf(MSG_INFO,
1265c1d255d3SCy Schubert 		   "%s: no DFS channels left, waiting for NOP to finish",
1266c1d255d3SCy Schubert 		   __func__);
1267c1d255d3SCy Schubert 	return NULL;
1268c1d255d3SCy Schubert }
1269c1d255d3SCy Schubert 
1270c1d255d3SCy Schubert 
hostapd_dfs_start_channel_switch_cac(struct hostapd_iface * iface)12715b9c547cSRui Paulo static int hostapd_dfs_start_channel_switch_cac(struct hostapd_iface *iface)
12725b9c547cSRui Paulo {
12735b9c547cSRui Paulo 	struct hostapd_channel_data *channel;
12745b9c547cSRui Paulo 	int secondary_channel;
1275206b73d0SCy Schubert 	u8 oper_centr_freq_seg0_idx = 0;
1276206b73d0SCy Schubert 	u8 oper_centr_freq_seg1_idx = 0;
1277*a90b9d01SCy Schubert 	enum dfs_channel_type channel_type = DFS_ANY_CHANNEL;
12785b9c547cSRui Paulo 	int err = 1;
12795b9c547cSRui Paulo 
12805b9c547cSRui Paulo 	/* Radar detected during active CAC */
12815b9c547cSRui Paulo 	iface->cac_started = 0;
12825b9c547cSRui Paulo 	channel = dfs_get_valid_channel(iface, &secondary_channel,
1283206b73d0SCy Schubert 					&oper_centr_freq_seg0_idx,
1284206b73d0SCy Schubert 					&oper_centr_freq_seg1_idx,
1285*a90b9d01SCy Schubert 					channel_type);
12865b9c547cSRui Paulo 
12875b9c547cSRui Paulo 	if (!channel) {
1288c1d255d3SCy Schubert 		channel = dfs_downgrade_bandwidth(iface, &secondary_channel,
1289c1d255d3SCy Schubert 						  &oper_centr_freq_seg0_idx,
1290c1d255d3SCy Schubert 						  &oper_centr_freq_seg1_idx,
1291*a90b9d01SCy Schubert 						  &channel_type);
1292c1d255d3SCy Schubert 		if (!channel) {
12935b9c547cSRui Paulo 			wpa_printf(MSG_ERROR, "No valid channel available");
12945b9c547cSRui Paulo 			return err;
12955b9c547cSRui Paulo 		}
1296c1d255d3SCy Schubert 	}
12975b9c547cSRui Paulo 
12985b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d",
12995b9c547cSRui Paulo 		   channel->chan);
13005b9c547cSRui Paulo 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL
13015b9c547cSRui Paulo 		"freq=%d chan=%d sec_chan=%d", channel->freq,
13025b9c547cSRui Paulo 		channel->chan, secondary_channel);
13035b9c547cSRui Paulo 
13045b9c547cSRui Paulo 	iface->freq = channel->freq;
13055b9c547cSRui Paulo 	iface->conf->channel = channel->chan;
13065b9c547cSRui Paulo 	iface->conf->secondary_channel = secondary_channel;
1307206b73d0SCy Schubert 	hostapd_set_oper_centr_freq_seg0_idx(iface->conf,
1308206b73d0SCy Schubert 					     oper_centr_freq_seg0_idx);
1309206b73d0SCy Schubert 	hostapd_set_oper_centr_freq_seg1_idx(iface->conf,
1310206b73d0SCy Schubert 					     oper_centr_freq_seg1_idx);
13115b9c547cSRui Paulo 	err = 0;
13125b9c547cSRui Paulo 
13135b9c547cSRui Paulo 	hostapd_setup_interface_complete(iface, err);
13145b9c547cSRui Paulo 	return err;
13155b9c547cSRui Paulo }
13165b9c547cSRui Paulo 
13175b9c547cSRui Paulo 
1318*a90b9d01SCy Schubert static int
hostapd_dfs_background_start_channel_switch(struct hostapd_iface * iface,int freq)1319*a90b9d01SCy Schubert hostapd_dfs_background_start_channel_switch(struct hostapd_iface *iface,
1320*a90b9d01SCy Schubert 					    int freq)
1321*a90b9d01SCy Schubert {
1322*a90b9d01SCy Schubert 	if (!dfs_use_radar_background(iface))
1323*a90b9d01SCy Schubert 		return -1; /* Background radar chain not supported. */
1324*a90b9d01SCy Schubert 
1325*a90b9d01SCy Schubert 	wpa_printf(MSG_DEBUG,
1326*a90b9d01SCy Schubert 		   "%s called (background CAC active: %s, CSA active: %s)",
1327*a90b9d01SCy Schubert 		   __func__, iface->radar_background.cac_started ? "yes" : "no",
1328*a90b9d01SCy Schubert 		   hostapd_csa_in_progress(iface) ? "yes" : "no");
1329*a90b9d01SCy Schubert 
1330*a90b9d01SCy Schubert 	/* Check if CSA in progress */
1331*a90b9d01SCy Schubert 	if (hostapd_csa_in_progress(iface))
1332*a90b9d01SCy Schubert 		return 0;
1333*a90b9d01SCy Schubert 
1334*a90b9d01SCy Schubert 	if (hostapd_dfs_is_background_event(iface, freq)) {
1335*a90b9d01SCy Schubert 		/*
1336*a90b9d01SCy Schubert 		 * Radar pattern is reported on the background chain.
1337*a90b9d01SCy Schubert 		 * Just select a new random channel according to the
1338*a90b9d01SCy Schubert 		 * regulations for monitoring.
1339*a90b9d01SCy Schubert 		 */
1340*a90b9d01SCy Schubert 		hostapd_dfs_update_background_chain(iface);
1341*a90b9d01SCy Schubert 		return 0;
1342*a90b9d01SCy Schubert 	}
1343*a90b9d01SCy Schubert 
1344*a90b9d01SCy Schubert 	/*
1345*a90b9d01SCy Schubert 	 * If background radar detection is supported and the radar channel
1346*a90b9d01SCy Schubert 	 * monitored by the background chain is available switch to it without
1347*a90b9d01SCy Schubert 	 * waiting for the CAC.
1348*a90b9d01SCy Schubert 	 */
1349*a90b9d01SCy Schubert 	if (iface->radar_background.channel == -1)
1350*a90b9d01SCy Schubert 		return -1; /* Background radar chain not available. */
1351*a90b9d01SCy Schubert 
1352*a90b9d01SCy Schubert 	if (iface->radar_background.cac_started) {
1353*a90b9d01SCy Schubert 		/*
1354*a90b9d01SCy Schubert 		 * Background channel not available yet. Perform CAC on the
1355*a90b9d01SCy Schubert 		 * main chain.
1356*a90b9d01SCy Schubert 		 */
1357*a90b9d01SCy Schubert 		iface->radar_background.temp_ch = 1;
1358*a90b9d01SCy Schubert 		return -1;
1359*a90b9d01SCy Schubert 	}
1360*a90b9d01SCy Schubert 
1361*a90b9d01SCy Schubert 	return hostapd_dfs_start_channel_switch_background(iface);
1362*a90b9d01SCy Schubert }
1363*a90b9d01SCy Schubert 
1364*a90b9d01SCy Schubert 
hostapd_dfs_start_channel_switch(struct hostapd_iface * iface)13655b9c547cSRui Paulo static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface)
13665b9c547cSRui Paulo {
13675b9c547cSRui Paulo 	struct hostapd_channel_data *channel;
13685b9c547cSRui Paulo 	int secondary_channel;
1369206b73d0SCy Schubert 	u8 oper_centr_freq_seg0_idx;
1370206b73d0SCy Schubert 	u8 oper_centr_freq_seg1_idx;
1371*a90b9d01SCy Schubert 	enum dfs_channel_type channel_type = DFS_AVAILABLE;
1372c1d255d3SCy Schubert 	u8 current_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf);
13735b9c547cSRui Paulo 
13745b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "%s called (CAC active: %s, CSA active: %s)",
13755b9c547cSRui Paulo 		   __func__, iface->cac_started ? "yes" : "no",
13765b9c547cSRui Paulo 		   hostapd_csa_in_progress(iface) ? "yes" : "no");
13775b9c547cSRui Paulo 
13785b9c547cSRui Paulo 	/* Check if CSA in progress */
13795b9c547cSRui Paulo 	if (hostapd_csa_in_progress(iface))
13805b9c547cSRui Paulo 		return 0;
13815b9c547cSRui Paulo 
13825b9c547cSRui Paulo 	/* Check if active CAC */
13835b9c547cSRui Paulo 	if (iface->cac_started)
13845b9c547cSRui Paulo 		return hostapd_dfs_start_channel_switch_cac(iface);
13855b9c547cSRui Paulo 
138685732ac8SCy Schubert 	/*
138785732ac8SCy Schubert 	 * Allow selection of DFS channel in ETSI to comply with
138885732ac8SCy Schubert 	 * uniform spreading.
138985732ac8SCy Schubert 	 */
139085732ac8SCy Schubert 	if (iface->dfs_domain == HOSTAPD_DFS_REGION_ETSI)
1391*a90b9d01SCy Schubert 		channel_type = DFS_ANY_CHANNEL;
139285732ac8SCy Schubert 
13935b9c547cSRui Paulo 	/* Perform channel switch/CSA */
13945b9c547cSRui Paulo 	channel = dfs_get_valid_channel(iface, &secondary_channel,
1395206b73d0SCy Schubert 					&oper_centr_freq_seg0_idx,
1396206b73d0SCy Schubert 					&oper_centr_freq_seg1_idx,
1397*a90b9d01SCy Schubert 					channel_type);
13985b9c547cSRui Paulo 
13995b9c547cSRui Paulo 	if (!channel) {
14005b9c547cSRui Paulo 		/*
14015b9c547cSRui Paulo 		 * If there is no channel to switch immediately to, check if
14025b9c547cSRui Paulo 		 * there is another channel where we can switch even if it
14035b9c547cSRui Paulo 		 * requires to perform a CAC first.
14045b9c547cSRui Paulo 		 */
1405*a90b9d01SCy Schubert 		channel_type = DFS_ANY_CHANNEL;
1406c1d255d3SCy Schubert 		channel = dfs_downgrade_bandwidth(iface, &secondary_channel,
1407206b73d0SCy Schubert 						  &oper_centr_freq_seg0_idx,
1408206b73d0SCy Schubert 						  &oper_centr_freq_seg1_idx,
1409*a90b9d01SCy Schubert 						  &channel_type);
14105b9c547cSRui Paulo 		if (!channel) {
1411c1d255d3SCy Schubert 			/*
1412c1d255d3SCy Schubert 			 * Toggle interface state to enter DFS state
1413c1d255d3SCy Schubert 			 * until NOP is finished.
1414c1d255d3SCy Schubert 			 */
1415c1d255d3SCy Schubert 			hostapd_disable_iface(iface);
1416c1d255d3SCy Schubert 			hostapd_enable_iface(iface);
1417c1d255d3SCy Schubert 			return 0;
14185b9c547cSRui Paulo 		}
14195b9c547cSRui Paulo 
1420*a90b9d01SCy Schubert 		if (channel_type == DFS_ANY_CHANNEL) {
14215b9c547cSRui Paulo 			iface->freq = channel->freq;
14225b9c547cSRui Paulo 			iface->conf->channel = channel->chan;
14235b9c547cSRui Paulo 			iface->conf->secondary_channel = secondary_channel;
1424c1d255d3SCy Schubert 			hostapd_set_oper_centr_freq_seg0_idx(
1425c1d255d3SCy Schubert 				iface->conf, oper_centr_freq_seg0_idx);
1426c1d255d3SCy Schubert 			hostapd_set_oper_centr_freq_seg1_idx(
1427c1d255d3SCy Schubert 				iface->conf, oper_centr_freq_seg1_idx);
14285b9c547cSRui Paulo 
14295b9c547cSRui Paulo 			hostapd_disable_iface(iface);
14305b9c547cSRui Paulo 			hostapd_enable_iface(iface);
14315b9c547cSRui Paulo 			return 0;
14325b9c547cSRui Paulo 		}
1433c1d255d3SCy Schubert 	}
14345b9c547cSRui Paulo 
1435*a90b9d01SCy Schubert 	return hostapd_dfs_request_channel_switch(iface, channel->chan,
14365b9c547cSRui Paulo 						  channel->freq,
14375b9c547cSRui Paulo 						  secondary_channel,
1438*a90b9d01SCy Schubert 						  current_vht_oper_chwidth,
1439206b73d0SCy Schubert 						  oper_centr_freq_seg0_idx,
1440206b73d0SCy Schubert 						  oper_centr_freq_seg1_idx);
14415b9c547cSRui Paulo }
14425b9c547cSRui Paulo 
14435b9c547cSRui Paulo 
hostapd_dfs_radar_detected(struct hostapd_iface * iface,int freq,int ht_enabled,int chan_offset,int chan_width,int cf1,int cf2)14445b9c547cSRui Paulo int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq,
14455b9c547cSRui Paulo 			       int ht_enabled, int chan_offset, int chan_width,
14465b9c547cSRui Paulo 			       int cf1, int cf2)
14475b9c547cSRui Paulo {
14485b9c547cSRui Paulo 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_RADAR_DETECTED
14495b9c547cSRui Paulo 		"freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d",
14505b9c547cSRui Paulo 		freq, ht_enabled, chan_offset, chan_width, cf1, cf2);
14515b9c547cSRui Paulo 
1452*a90b9d01SCy Schubert 	iface->radar_detected = true;
1453*a90b9d01SCy Schubert 
14545b9c547cSRui Paulo 	/* Proceed only if DFS is not offloaded to the driver */
14555b9c547cSRui Paulo 	if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD)
14565b9c547cSRui Paulo 		return 0;
14575b9c547cSRui Paulo 
14585b9c547cSRui Paulo 	if (!iface->conf->ieee80211h)
14595b9c547cSRui Paulo 		return 0;
14605b9c547cSRui Paulo 
14615b9c547cSRui Paulo 	/* mark radar frequency as invalid */
1462*a90b9d01SCy Schubert 	if (!set_dfs_state(iface, freq, ht_enabled, chan_offset, chan_width,
1463*a90b9d01SCy Schubert 			   cf1, cf2, HOSTAPD_CHAN_DFS_UNAVAILABLE))
1464c1d255d3SCy Schubert 		return 0;
14655b9c547cSRui Paulo 
1466*a90b9d01SCy Schubert 	if (!hostapd_dfs_is_background_event(iface, freq)) {
14675b9c547cSRui Paulo 		/* Skip if reported radar event not overlapped our channels */
1468*a90b9d01SCy Schubert 		if (!dfs_are_channels_overlapped(iface, freq, chan_width,
1469*a90b9d01SCy Schubert 						 cf1, cf2))
14705b9c547cSRui Paulo 			return 0;
1471*a90b9d01SCy Schubert 	}
14725b9c547cSRui Paulo 
1473*a90b9d01SCy Schubert 	if (hostapd_dfs_background_start_channel_switch(iface, freq)) {
1474*a90b9d01SCy Schubert 		/* Radar detected while operating, switch the channel. */
1475*a90b9d01SCy Schubert 		return hostapd_dfs_start_channel_switch(iface);
1476*a90b9d01SCy Schubert 	}
14775b9c547cSRui Paulo 
1478*a90b9d01SCy Schubert 	return 0;
14795b9c547cSRui Paulo }
14805b9c547cSRui Paulo 
14815b9c547cSRui Paulo 
hostapd_dfs_nop_finished(struct hostapd_iface * iface,int freq,int ht_enabled,int chan_offset,int chan_width,int cf1,int cf2)14825b9c547cSRui Paulo int hostapd_dfs_nop_finished(struct hostapd_iface *iface, int freq,
14835b9c547cSRui Paulo 			     int ht_enabled, int chan_offset, int chan_width,
14845b9c547cSRui Paulo 			     int cf1, int cf2)
14855b9c547cSRui Paulo {
14865b9c547cSRui Paulo 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NOP_FINISHED
14875b9c547cSRui Paulo 		"freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d",
14885b9c547cSRui Paulo 		freq, ht_enabled, chan_offset, chan_width, cf1, cf2);
14895b9c547cSRui Paulo 
14905b9c547cSRui Paulo 	/* Proceed only if DFS is not offloaded to the driver */
14915b9c547cSRui Paulo 	if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD)
14925b9c547cSRui Paulo 		return 0;
14935b9c547cSRui Paulo 
14945b9c547cSRui Paulo 	/* TODO add correct implementation here */
14955b9c547cSRui Paulo 	set_dfs_state(iface, freq, ht_enabled, chan_offset, chan_width,
14965b9c547cSRui Paulo 		      cf1, cf2, HOSTAPD_CHAN_DFS_USABLE);
1497780fb4a2SCy Schubert 
1498*a90b9d01SCy Schubert 	if (iface->state == HAPD_IFACE_DFS && !iface->cac_started) {
1499780fb4a2SCy Schubert 		/* Handle cases where all channels were initially unavailable */
1500780fb4a2SCy Schubert 		hostapd_handle_dfs(iface);
1501*a90b9d01SCy Schubert 	} else if (dfs_use_radar_background(iface) &&
1502*a90b9d01SCy Schubert 		   iface->radar_background.channel == -1) {
1503*a90b9d01SCy Schubert 		/* Reset radar background chain if disabled */
1504*a90b9d01SCy Schubert 		hostapd_dfs_update_background_chain(iface);
1505*a90b9d01SCy Schubert 	}
1506780fb4a2SCy Schubert 
15075b9c547cSRui Paulo 	return 0;
15085b9c547cSRui Paulo }
15095b9c547cSRui Paulo 
15105b9c547cSRui Paulo 
hostapd_is_dfs_required(struct hostapd_iface * iface)15115b9c547cSRui Paulo int hostapd_is_dfs_required(struct hostapd_iface *iface)
15125b9c547cSRui Paulo {
15135b9c547cSRui Paulo 	int n_chans, n_chans1, start_chan_idx, start_chan_idx1, res;
15145b9c547cSRui Paulo 
1515c1d255d3SCy Schubert 	if ((!(iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) &&
1516c1d255d3SCy Schubert 	     !iface->conf->ieee80211h) ||
1517c1d255d3SCy Schubert 	    !iface->current_mode ||
15185b9c547cSRui Paulo 	    iface->current_mode->mode != HOSTAPD_MODE_IEEE80211A)
15195b9c547cSRui Paulo 		return 0;
15205b9c547cSRui Paulo 
15215b9c547cSRui Paulo 	/* Get start (first) channel for current configuration */
15225b9c547cSRui Paulo 	start_chan_idx = dfs_get_start_chan_idx(iface, &start_chan_idx1);
15235b9c547cSRui Paulo 	if (start_chan_idx == -1)
15245b9c547cSRui Paulo 		return -1;
15255b9c547cSRui Paulo 
15265b9c547cSRui Paulo 	/* Get number of used channels, depend on width */
15275b9c547cSRui Paulo 	n_chans = dfs_get_used_n_chans(iface, &n_chans1);
15285b9c547cSRui Paulo 
15295b9c547cSRui Paulo 	/* Check if any of configured channels require DFS */
15305b9c547cSRui Paulo 	res = dfs_check_chans_radar(iface, start_chan_idx, n_chans);
15315b9c547cSRui Paulo 	if (res)
15325b9c547cSRui Paulo 		return res;
15335b9c547cSRui Paulo 	if (start_chan_idx1 >= 0 && n_chans1 > 0)
15345b9c547cSRui Paulo 		res = dfs_check_chans_radar(iface, start_chan_idx1, n_chans1);
15355b9c547cSRui Paulo 	return res;
15365b9c547cSRui Paulo }
15375b9c547cSRui Paulo 
15385b9c547cSRui Paulo 
hostapd_dfs_start_cac(struct hostapd_iface * iface,int freq,int ht_enabled,int chan_offset,int chan_width,int cf1,int cf2)15395b9c547cSRui Paulo int hostapd_dfs_start_cac(struct hostapd_iface *iface, int freq,
15405b9c547cSRui Paulo 			  int ht_enabled, int chan_offset, int chan_width,
15415b9c547cSRui Paulo 			  int cf1, int cf2)
15425b9c547cSRui Paulo {
1543*a90b9d01SCy Schubert 	if (hostapd_dfs_is_background_event(iface, freq)) {
1544*a90b9d01SCy Schubert 		iface->radar_background.cac_started = 1;
1545*a90b9d01SCy Schubert 	} else {
1546*a90b9d01SCy Schubert 		/* This is called when the driver indicates that an offloaded
1547*a90b9d01SCy Schubert 		 * DFS has started CAC. radar_detected might be set for previous
1548*a90b9d01SCy Schubert 		 * DFS channel. Clear it for this new CAC process. */
1549c1d255d3SCy Schubert 		hostapd_set_state(iface, HAPD_IFACE_DFS);
1550*a90b9d01SCy Schubert 		iface->cac_started = 1;
1551*a90b9d01SCy Schubert 
1552*a90b9d01SCy Schubert 		/* Clear radar_detected in case it is for the previous
1553*a90b9d01SCy Schubert 		 * frequency. Also remove disabled link's information in RNR
1554*a90b9d01SCy Schubert 		 * element from other links. */
1555*a90b9d01SCy Schubert 		iface->radar_detected = false;
1556*a90b9d01SCy Schubert 		if (iface->interfaces && iface->interfaces->count > 1)
1557*a90b9d01SCy Schubert 			ieee802_11_set_beacons(iface);
1558*a90b9d01SCy Schubert 	}
1559c1d255d3SCy Schubert 	/* TODO: How to check CAC time for ETSI weather channels? */
1560c1d255d3SCy Schubert 	iface->dfs_cac_ms = 60000;
15615b9c547cSRui Paulo 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START
15625b9c547cSRui Paulo 		"freq=%d chan=%d chan_offset=%d width=%d seg0=%d "
1563*a90b9d01SCy Schubert 		"seg1=%d cac_time=%ds%s",
1564c1d255d3SCy Schubert 		freq, (freq - 5000) / 5, chan_offset, chan_width, cf1, cf2,
1565*a90b9d01SCy Schubert 		iface->dfs_cac_ms / 1000,
1566*a90b9d01SCy Schubert 		hostapd_dfs_is_background_event(iface, freq) ?
1567*a90b9d01SCy Schubert 		" (background)" : "");
1568*a90b9d01SCy Schubert 
1569c1d255d3SCy Schubert 	os_get_reltime(&iface->dfs_cac_start);
15705b9c547cSRui Paulo 	return 0;
15715b9c547cSRui Paulo }
15725b9c547cSRui Paulo 
15735b9c547cSRui Paulo 
15745b9c547cSRui Paulo /*
15755b9c547cSRui Paulo  * Main DFS handler for offloaded case.
15765b9c547cSRui Paulo  * 2 - continue channel/AP setup for non-DFS channel
15775b9c547cSRui Paulo  * 1 - continue channel/AP setup for DFS channel
15785b9c547cSRui Paulo  * 0 - channel/AP setup will be continued after CAC
15795b9c547cSRui Paulo  * -1 - hit critical error
15805b9c547cSRui Paulo  */
hostapd_handle_dfs_offload(struct hostapd_iface * iface)15815b9c547cSRui Paulo int hostapd_handle_dfs_offload(struct hostapd_iface *iface)
15825b9c547cSRui Paulo {
1583c1d255d3SCy Schubert 	int dfs_res;
1584c1d255d3SCy Schubert 
15855b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "%s: iface->cac_started: %d",
15865b9c547cSRui Paulo 		   __func__, iface->cac_started);
15875b9c547cSRui Paulo 
15885b9c547cSRui Paulo 	/*
15895b9c547cSRui Paulo 	 * If DFS has already been started, then we are being called from a
15905b9c547cSRui Paulo 	 * callback to continue AP/channel setup. Reset the CAC start flag and
15915b9c547cSRui Paulo 	 * return.
15925b9c547cSRui Paulo 	 */
15935b9c547cSRui Paulo 	if (iface->cac_started) {
15945b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "%s: iface->cac_started: %d",
15955b9c547cSRui Paulo 			   __func__, iface->cac_started);
15965b9c547cSRui Paulo 		iface->cac_started = 0;
15975b9c547cSRui Paulo 		return 1;
15985b9c547cSRui Paulo 	}
15995b9c547cSRui Paulo 
1600c1d255d3SCy Schubert 	dfs_res = hostapd_is_dfs_required(iface);
1601c1d255d3SCy Schubert 	if (dfs_res > 0) {
1602c1d255d3SCy Schubert 		wpa_printf(MSG_DEBUG,
1603c1d255d3SCy Schubert 			   "%s: freq %d MHz requires DFS for %d chans",
1604c1d255d3SCy Schubert 			   __func__, iface->freq, dfs_res);
16055b9c547cSRui Paulo 		return 0;
16065b9c547cSRui Paulo 	}
16075b9c547cSRui Paulo 
16085b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG,
16095b9c547cSRui Paulo 		   "%s: freq %d MHz does not require DFS. Continue channel/AP setup",
16105b9c547cSRui Paulo 		   __func__, iface->freq);
16115b9c547cSRui Paulo 	return 2;
16125b9c547cSRui Paulo }
1613c1d255d3SCy Schubert 
1614c1d255d3SCy Schubert 
hostapd_is_dfs_overlap(struct hostapd_iface * iface,enum chan_width width,int center_freq)1615c1d255d3SCy Schubert int hostapd_is_dfs_overlap(struct hostapd_iface *iface, enum chan_width width,
1616c1d255d3SCy Schubert 			   int center_freq)
1617c1d255d3SCy Schubert {
1618c1d255d3SCy Schubert 	struct hostapd_channel_data *chan;
1619c1d255d3SCy Schubert 	struct hostapd_hw_modes *mode = iface->current_mode;
1620c1d255d3SCy Schubert 	int half_width;
1621c1d255d3SCy Schubert 	int res = 0;
1622c1d255d3SCy Schubert 	int i;
1623c1d255d3SCy Schubert 
1624c1d255d3SCy Schubert 	if (!iface->conf->ieee80211h || !mode ||
1625c1d255d3SCy Schubert 	    mode->mode != HOSTAPD_MODE_IEEE80211A)
1626c1d255d3SCy Schubert 		return 0;
1627c1d255d3SCy Schubert 
1628c1d255d3SCy Schubert 	switch (width) {
1629c1d255d3SCy Schubert 	case CHAN_WIDTH_20_NOHT:
1630c1d255d3SCy Schubert 	case CHAN_WIDTH_20:
1631c1d255d3SCy Schubert 		half_width = 10;
1632c1d255d3SCy Schubert 		break;
1633c1d255d3SCy Schubert 	case CHAN_WIDTH_40:
1634c1d255d3SCy Schubert 		half_width = 20;
1635c1d255d3SCy Schubert 		break;
1636c1d255d3SCy Schubert 	case CHAN_WIDTH_80:
1637c1d255d3SCy Schubert 	case CHAN_WIDTH_80P80:
1638c1d255d3SCy Schubert 		half_width = 40;
1639c1d255d3SCy Schubert 		break;
1640c1d255d3SCy Schubert 	case CHAN_WIDTH_160:
1641c1d255d3SCy Schubert 		half_width = 80;
1642c1d255d3SCy Schubert 		break;
1643c1d255d3SCy Schubert 	default:
1644c1d255d3SCy Schubert 		wpa_printf(MSG_WARNING, "DFS chanwidth %d not supported",
1645c1d255d3SCy Schubert 			   width);
1646c1d255d3SCy Schubert 		return 0;
1647c1d255d3SCy Schubert 	}
1648c1d255d3SCy Schubert 
1649c1d255d3SCy Schubert 	for (i = 0; i < mode->num_channels; i++) {
1650c1d255d3SCy Schubert 		chan = &mode->channels[i];
1651c1d255d3SCy Schubert 
1652c1d255d3SCy Schubert 		if (!(chan->flag & HOSTAPD_CHAN_RADAR))
1653c1d255d3SCy Schubert 			continue;
1654c1d255d3SCy Schubert 
1655c1d255d3SCy Schubert 		if ((chan->flag & HOSTAPD_CHAN_DFS_MASK) ==
1656c1d255d3SCy Schubert 		    HOSTAPD_CHAN_DFS_AVAILABLE)
1657c1d255d3SCy Schubert 			continue;
1658c1d255d3SCy Schubert 
1659c1d255d3SCy Schubert 		if (center_freq - chan->freq < half_width &&
1660c1d255d3SCy Schubert 		    chan->freq - center_freq < half_width)
1661c1d255d3SCy Schubert 			res++;
1662c1d255d3SCy Schubert 	}
1663c1d255d3SCy Schubert 
1664c1d255d3SCy Schubert 	wpa_printf(MSG_DEBUG, "DFS CAC required: (%d, %d): in range: %s",
1665c1d255d3SCy Schubert 		   center_freq - half_width, center_freq + half_width,
1666c1d255d3SCy Schubert 		   res ? "yes" : "no");
1667c1d255d3SCy Schubert 
1668c1d255d3SCy Schubert 	return res;
1669c1d255d3SCy Schubert }
1670