developer | e638cf4 | 2021-12-09 15:25:49 +0800 | [diff] [blame] | 1 | 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 |
| 2 | new file mode 100644 |
| 3 | index 0000000..417c311 |
| 4 | --- /dev/null |
| 5 | +++ b/package/network/services/hostapd/patches/904-2102-zero-wait_dfs.patch |
| 6 | @@ -0,0 +1,848 @@ |
| 7 | +diff --git a/hostapd/config_file.c b/hostapd/config_file.c |
| 8 | +index 6b6101f..704afbb 100644 |
| 9 | +--- a/hostapd/config_file.c |
| 10 | ++++ b/hostapd/config_file.c |
| 11 | +@@ -2524,6 +2524,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, |
| 12 | + conf->ieee80211d = atoi(pos); |
| 13 | + } else if (os_strcmp(buf, "ieee80211h") == 0) { |
| 14 | + conf->ieee80211h = atoi(pos); |
| 15 | ++ } else if (os_strcmp(buf, "radar_offchan") == 0) { |
| 16 | ++ conf->radar_offchan = atoi(pos); |
| 17 | + } else if (os_strcmp(buf, "ieee8021x") == 0) { |
| 18 | + bss->ieee802_1x = atoi(pos); |
| 19 | + } else if (os_strcmp(buf, "eapol_version") == 0) { |
| 20 | +diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf |
| 21 | +index c71f2ac..58c7034 100644 |
| 22 | +--- a/hostapd/hostapd.conf |
| 23 | ++++ b/hostapd/hostapd.conf |
| 24 | +@@ -143,6 +143,13 @@ ssid=test |
| 25 | + # ieee80211d=1 and local_pwr_constraint configured. |
| 26 | + #spectrum_mgmt_required=1 |
| 27 | + |
| 28 | ++# Enable radar/CAC detection through a dedicated offchannel chain available on |
| 29 | ++# some hw. The chain can't be used to transmits or receives frames. |
| 30 | ++# This feature allows to avoid CAC downtime switching on a different channel |
| 31 | ++# during CAC detection on the selected radar channel. |
| 32 | ++# (default: 0 = disabled, 1 = enabled) |
| 33 | ++#radar_offchan=0 |
| 34 | ++ |
| 35 | + # Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz), |
| 36 | + # g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used |
| 37 | + # with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this |
| 38 | +diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h |
| 39 | +index 920dba1..44d3b85 100644 |
| 40 | +--- a/src/ap/ap_config.h |
| 41 | ++++ b/src/ap/ap_config.h |
| 42 | +@@ -965,6 +965,7 @@ struct hostapd_config { |
| 43 | + int ieee80211d; |
| 44 | + |
| 45 | + int ieee80211h; /* DFS */ |
| 46 | ++ int radar_offchan; |
| 47 | + |
| 48 | + /* |
| 49 | + * Local power constraint is an octet encoded as an unsigned integer in |
| 50 | +diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c |
| 51 | +index 85728d4..ed16d1f 100644 |
| 52 | +--- a/src/ap/ap_drv_ops.c |
| 53 | ++++ b/src/ap/ap_drv_ops.c |
| 54 | +@@ -810,7 +810,8 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, |
| 55 | + int channel, int ht_enabled, int vht_enabled, |
| 56 | + int he_enabled, |
| 57 | + int sec_channel_offset, int oper_chwidth, |
| 58 | +- int center_segment0, int center_segment1) |
| 59 | ++ int center_segment0, int center_segment1, |
| 60 | ++ int radar_offchan) |
| 61 | + { |
| 62 | + struct hostapd_data *hapd = iface->bss[0]; |
| 63 | + struct hostapd_freq_params data; |
| 64 | +@@ -836,10 +837,14 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, |
| 65 | + wpa_printf(MSG_ERROR, "Can't set freq params"); |
| 66 | + return -1; |
| 67 | + } |
| 68 | ++ data.radar_offchan = radar_offchan; |
| 69 | + |
| 70 | + res = hapd->driver->start_dfs_cac(hapd->drv_priv, &data); |
| 71 | + if (!res) { |
| 72 | +- iface->cac_started = 1; |
| 73 | ++ if (radar_offchan) |
| 74 | ++ iface->radar_offchan.cac_started = 1; |
| 75 | ++ else |
| 76 | ++ iface->cac_started = 1; |
| 77 | + os_get_reltime(&iface->dfs_cac_start); |
| 78 | + } |
| 79 | + |
| 80 | +diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h |
| 81 | +index 5738c1c..12c87b0 100644 |
| 82 | +--- a/src/ap/ap_drv_ops.h |
| 83 | ++++ b/src/ap/ap_drv_ops.h |
| 84 | +@@ -130,7 +130,8 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, |
| 85 | + int channel, int ht_enabled, int vht_enabled, |
| 86 | + int he_enabled, |
| 87 | + int sec_channel_offset, int oper_chwidth, |
| 88 | +- int center_segment0, int center_segment1); |
| 89 | ++ int center_segment0, int center_segment1, |
| 90 | ++ int radar_offchan); |
| 91 | + int hostapd_drv_do_acs(struct hostapd_data *hapd); |
| 92 | + int hostapd_drv_update_dh_ie(struct hostapd_data *hapd, const u8 *peer, |
| 93 | + u16 reason_code, const u8 *ie, size_t ielen); |
| 94 | +diff --git a/src/ap/dfs.c b/src/ap/dfs.c |
| 95 | +index 54a61b0..217c0fc 100644 |
| 96 | +--- a/src/ap/dfs.c |
| 97 | ++++ b/src/ap/dfs.c |
| 98 | +@@ -51,16 +51,31 @@ static int dfs_get_used_n_chans(struct hostapd_iface *iface, int *seg1) |
| 99 | + return n_chans; |
| 100 | + } |
| 101 | + |
| 102 | +- |
| 103 | ++/* |
| 104 | ++ * flags: |
| 105 | ++ * - 0: any channel |
| 106 | ++ * - 1: non-radar channel or radar available one |
| 107 | ++ * - 2: radar-only channel not yet available |
| 108 | ++ */ |
| 109 | + static int dfs_channel_available(struct hostapd_channel_data *chan, |
| 110 | +- int skip_radar) |
| 111 | ++ int flags) |
| 112 | + { |
| 113 | ++ if (flags == 2) { |
| 114 | ++ /* Select only radar channel where CAC has not been |
| 115 | ++ * performed yet |
| 116 | ++ */ |
| 117 | ++ if ((chan->flag & HOSTAPD_CHAN_RADAR) && |
| 118 | ++ (chan->flag & HOSTAPD_CHAN_DFS_MASK) == |
| 119 | ++ HOSTAPD_CHAN_DFS_USABLE) |
| 120 | ++ return 1; |
| 121 | ++ return 0; |
| 122 | ++ } |
| 123 | + /* |
| 124 | + * When radar detection happens, CSA is performed. However, there's no |
| 125 | + * time for CAC, so radar channels must be skipped when finding a new |
| 126 | + * channel for CSA, unless they are available for immediate use. |
| 127 | + */ |
| 128 | +- if (skip_radar && (chan->flag & HOSTAPD_CHAN_RADAR) && |
| 129 | ++ if (flags && (chan->flag & HOSTAPD_CHAN_RADAR) && |
| 130 | + ((chan->flag & HOSTAPD_CHAN_DFS_MASK) != |
| 131 | + HOSTAPD_CHAN_DFS_AVAILABLE)) |
| 132 | + return 0; |
| 133 | +@@ -136,10 +151,15 @@ dfs_get_chan_data(struct hostapd_hw_modes *mode, int freq, int first_chan_idx) |
| 134 | + return NULL; |
| 135 | + } |
| 136 | + |
| 137 | +- |
| 138 | ++/* |
| 139 | ++ * flags: |
| 140 | ++ * - 0: any channel |
| 141 | ++ * - 1: non-radar channel or radar available one |
| 142 | ++ * - 2: radar-only channel not yet available |
| 143 | ++ */ |
| 144 | + static int dfs_chan_range_available(struct hostapd_hw_modes *mode, |
| 145 | + int first_chan_idx, int num_chans, |
| 146 | +- int skip_radar) |
| 147 | ++ int flags) |
| 148 | + { |
| 149 | + struct hostapd_channel_data *first_chan, *chan; |
| 150 | + int i; |
| 151 | +@@ -178,7 +198,7 @@ static int dfs_chan_range_available(struct hostapd_hw_modes *mode, |
| 152 | + return 0; |
| 153 | + } |
| 154 | + |
| 155 | +- if (!dfs_channel_available(chan, skip_radar)) { |
| 156 | ++ if (!dfs_channel_available(chan, flags)) { |
| 157 | + wpa_printf(MSG_DEBUG, "DFS: channel not available %d", |
| 158 | + first_chan->freq + i * 20); |
| 159 | + return 0; |
| 160 | +@@ -205,10 +225,15 @@ static int is_in_chanlist(struct hostapd_iface *iface, |
| 161 | + * - hapd->secondary_channel |
| 162 | + * - hapd->vht/he_oper_centr_freq_seg0_idx |
| 163 | + * - hapd->vht/he_oper_centr_freq_seg1_idx |
| 164 | ++ * |
| 165 | ++ * flags: |
| 166 | ++ * - 0: any channel |
| 167 | ++ * - 1: non-radar channel or radar available one |
| 168 | ++ * - 2: radar-only channel not yet available |
| 169 | + */ |
| 170 | + static int dfs_find_channel(struct hostapd_iface *iface, |
| 171 | + struct hostapd_channel_data **ret_chan, |
| 172 | +- int idx, int skip_radar) |
| 173 | ++ int idx, int flags) |
| 174 | + { |
| 175 | + struct hostapd_hw_modes *mode; |
| 176 | + struct hostapd_channel_data *chan; |
| 177 | +@@ -233,7 +258,7 @@ static int dfs_find_channel(struct hostapd_iface *iface, |
| 178 | + } |
| 179 | + |
| 180 | + /* Skip incompatible chandefs */ |
| 181 | +- if (!dfs_chan_range_available(mode, i, n_chans, skip_radar)) { |
| 182 | ++ if (!dfs_chan_range_available(mode, i, n_chans, flags)) { |
| 183 | + wpa_printf(MSG_DEBUG, |
| 184 | + "DFS: range not available for %d (%d)", |
| 185 | + chan->freq, chan->chan); |
| 186 | +@@ -467,13 +492,18 @@ static int dfs_check_chans_unavailable(struct hostapd_iface *iface, |
| 187 | + return res; |
| 188 | + } |
| 189 | + |
| 190 | +- |
| 191 | ++/* |
| 192 | ++ * flags: |
| 193 | ++ * - 0: any channel |
| 194 | ++ * - 1: non-radar channel or radar available one |
| 195 | ++ * - 2: radar-only channel not yet available |
| 196 | ++ */ |
| 197 | + static struct hostapd_channel_data * |
| 198 | + dfs_get_valid_channel(struct hostapd_iface *iface, |
| 199 | + int *secondary_channel, |
| 200 | + u8 *oper_centr_freq_seg0_idx, |
| 201 | + u8 *oper_centr_freq_seg1_idx, |
| 202 | +- int skip_radar) |
| 203 | ++ int flags) |
| 204 | + { |
| 205 | + struct hostapd_hw_modes *mode; |
| 206 | + struct hostapd_channel_data *chan = NULL; |
| 207 | +@@ -502,7 +532,7 @@ dfs_get_valid_channel(struct hostapd_iface *iface, |
| 208 | + return NULL; |
| 209 | + |
| 210 | + /* Get the count first */ |
| 211 | +- num_available_chandefs = dfs_find_channel(iface, NULL, 0, skip_radar); |
| 212 | ++ num_available_chandefs = dfs_find_channel(iface, NULL, 0, flags); |
| 213 | + wpa_printf(MSG_DEBUG, "DFS: num_available_chandefs=%d", |
| 214 | + num_available_chandefs); |
| 215 | + if (num_available_chandefs == 0) |
| 216 | +@@ -523,7 +553,7 @@ dfs_get_valid_channel(struct hostapd_iface *iface, |
| 217 | + return NULL; |
| 218 | + |
| 219 | + chan_idx = _rand % num_available_chandefs; |
| 220 | +- dfs_find_channel(iface, &chan, chan_idx, skip_radar); |
| 221 | ++ dfs_find_channel(iface, &chan, chan_idx, flags); |
| 222 | + if (!chan) { |
| 223 | + wpa_printf(MSG_DEBUG, "DFS: no random channel found"); |
| 224 | + return NULL; |
| 225 | +@@ -552,7 +582,7 @@ dfs_get_valid_channel(struct hostapd_iface *iface, |
| 226 | + for (i = 0; i < num_available_chandefs - 1; i++) { |
| 227 | + /* start from chan_idx + 1, end when chan_idx - 1 */ |
| 228 | + chan_idx2 = (chan_idx + 1 + i) % num_available_chandefs; |
| 229 | +- dfs_find_channel(iface, &chan2, chan_idx2, skip_radar); |
| 230 | ++ dfs_find_channel(iface, &chan2, chan_idx2, flags); |
| 231 | + if (chan2 && abs(chan2->chan - chan->chan) > 12) { |
| 232 | + /* two channels are not adjacent */ |
| 233 | + sec_chan_idx_80p80 = chan2->chan; |
| 234 | +@@ -582,6 +612,27 @@ dfs_get_valid_channel(struct hostapd_iface *iface, |
| 235 | + return chan; |
| 236 | + } |
| 237 | + |
| 238 | ++static int dfs_set_valid_channel(struct hostapd_iface *iface, int skip_radar) |
| 239 | ++{ |
| 240 | ++ struct hostapd_channel_data *channel; |
| 241 | ++ u8 cf1 = 0, cf2 = 0; |
| 242 | ++ int sec = 0; |
| 243 | ++ |
| 244 | ++ channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2, |
| 245 | ++ skip_radar); |
| 246 | ++ if (!channel) { |
| 247 | ++ wpa_printf(MSG_ERROR, "could not get valid channel"); |
| 248 | ++ return -1; |
| 249 | ++ } |
| 250 | ++ |
| 251 | ++ iface->freq = channel->freq; |
| 252 | ++ iface->conf->channel = channel->chan; |
| 253 | ++ iface->conf->secondary_channel = sec; |
| 254 | ++ hostapd_set_oper_centr_freq_seg0_idx(iface->conf, cf1); |
| 255 | ++ hostapd_set_oper_centr_freq_seg1_idx(iface->conf, cf2); |
| 256 | ++ |
| 257 | ++ return 0; |
| 258 | ++} |
| 259 | + |
| 260 | + static int set_dfs_state_freq(struct hostapd_iface *iface, int freq, u32 state) |
| 261 | + { |
| 262 | +@@ -761,6 +812,11 @@ static unsigned int dfs_get_cac_time(struct hostapd_iface *iface, |
| 263 | + return cac_time_ms; |
| 264 | + } |
| 265 | + |
| 266 | ++static int hostapd_is_radar_offchan_enabled(struct hostapd_iface *iface) |
| 267 | ++{ |
| 268 | ++ return (iface->drv_flags2 & WPA_DRIVER_RADAR_OFFCHAN) && |
| 269 | ++ iface->conf->radar_offchan; |
| 270 | ++} |
| 271 | + |
| 272 | + /* |
| 273 | + * Main DFS handler |
| 274 | +@@ -770,9 +826,8 @@ static unsigned int dfs_get_cac_time(struct hostapd_iface *iface, |
| 275 | + */ |
| 276 | + int hostapd_handle_dfs(struct hostapd_iface *iface) |
| 277 | + { |
| 278 | +- struct hostapd_channel_data *channel; |
| 279 | + int res, n_chans, n_chans1, start_chan_idx, start_chan_idx1; |
| 280 | +- int skip_radar = 0; |
| 281 | ++ int skip_radar = 0, radar_offchan; |
| 282 | + |
| 283 | + if (is_6ghz_freq(iface->freq)) |
| 284 | + return 1; |
| 285 | +@@ -825,28 +880,18 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) |
| 286 | + wpa_printf(MSG_DEBUG, "DFS %d chans unavailable - choose other channel: %s", |
| 287 | + res, res ? "yes": "no"); |
| 288 | + if (res) { |
| 289 | +- int sec = 0; |
| 290 | +- u8 cf1 = 0, cf2 = 0; |
| 291 | +- |
| 292 | +- channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2, |
| 293 | +- skip_radar); |
| 294 | +- if (!channel) { |
| 295 | +- wpa_printf(MSG_ERROR, "could not get valid channel"); |
| 296 | ++ if (dfs_set_valid_channel(iface, skip_radar) < 0) { |
| 297 | + hostapd_set_state(iface, HAPD_IFACE_DFS); |
| 298 | + return 0; |
| 299 | + } |
| 300 | +- |
| 301 | +- iface->freq = channel->freq; |
| 302 | +- iface->conf->channel = channel->chan; |
| 303 | +- iface->conf->secondary_channel = sec; |
| 304 | +- hostapd_set_oper_centr_freq_seg0_idx(iface->conf, cf1); |
| 305 | +- hostapd_set_oper_centr_freq_seg1_idx(iface->conf, cf2); |
| 306 | + } |
| 307 | + } while (res); |
| 308 | + |
| 309 | + /* Finally start CAC */ |
| 310 | + hostapd_set_state(iface, HAPD_IFACE_DFS); |
| 311 | +- wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz", iface->freq); |
| 312 | ++ radar_offchan = hostapd_is_radar_offchan_enabled(iface); |
| 313 | ++ wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz offchan %d", |
| 314 | ++ iface->freq, radar_offchan); |
| 315 | + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START |
| 316 | + "freq=%d chan=%d sec_chan=%d, width=%d, seg0=%d, seg1=%d, cac_time=%ds", |
| 317 | + iface->freq, |
| 318 | +@@ -863,13 +908,37 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) |
| 319 | + iface->conf->secondary_channel, |
| 320 | + hostapd_get_oper_chwidth(iface->conf), |
| 321 | + hostapd_get_oper_centr_freq_seg0_idx(iface->conf), |
| 322 | +- hostapd_get_oper_centr_freq_seg1_idx(iface->conf)); |
| 323 | ++ hostapd_get_oper_centr_freq_seg1_idx(iface->conf), |
| 324 | ++ radar_offchan); |
| 325 | + |
| 326 | + if (res) { |
| 327 | + wpa_printf(MSG_ERROR, "DFS start_dfs_cac() failed, %d", res); |
| 328 | + return -1; |
| 329 | + } |
| 330 | + |
| 331 | ++ if (radar_offchan) { |
| 332 | ++ /* Cache offchannel radar parameters */ |
| 333 | ++ iface->radar_offchan.channel = iface->conf->channel; |
| 334 | ++ iface->radar_offchan.secondary_channel = |
| 335 | ++ iface->conf->secondary_channel; |
| 336 | ++ iface->radar_offchan.freq = iface->freq; |
| 337 | ++ iface->radar_offchan.centr_freq_seg0_idx = |
| 338 | ++ hostapd_get_oper_centr_freq_seg0_idx(iface->conf); |
| 339 | ++ iface->radar_offchan.centr_freq_seg1_idx = |
| 340 | ++ hostapd_get_oper_centr_freq_seg1_idx(iface->conf); |
| 341 | ++ |
| 342 | ++ /* |
| 343 | ++ * Let's select a random channel for the moment |
| 344 | ++ * and perform CAC on dedicated radar chain |
| 345 | ++ */ |
| 346 | ++ res = dfs_set_valid_channel(iface, 1); |
| 347 | ++ if (res < 0) |
| 348 | ++ return res; |
| 349 | ++ |
| 350 | ++ iface->radar_offchan.temp_ch = 1; |
| 351 | ++ return 1; |
| 352 | ++ } |
| 353 | ++ |
| 354 | + return 0; |
| 355 | + } |
| 356 | + |
| 357 | +@@ -890,6 +959,157 @@ int hostapd_is_dfs_chan_available(struct hostapd_iface *iface) |
| 358 | + return dfs_check_chans_available(iface, start_chan_idx, n_chans); |
| 359 | + } |
| 360 | + |
| 361 | ++static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface, |
| 362 | ++ int channel, int freq, |
| 363 | ++ int secondary_channel, |
| 364 | ++ u8 oper_centr_freq_seg0_idx, |
| 365 | ++ u8 oper_centr_freq_seg1_idx) |
| 366 | ++{ |
| 367 | ++ struct hostapd_hw_modes *cmode = iface->current_mode; |
| 368 | ++ int ieee80211_mode = IEEE80211_MODE_AP, err, i; |
| 369 | ++ struct csa_settings csa_settings; |
| 370 | ++ u8 new_vht_oper_chwidth; |
| 371 | ++ |
| 372 | ++ wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", channel); |
| 373 | ++ wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL |
| 374 | ++ "freq=%d chan=%d sec_chan=%d", freq, channel, |
| 375 | ++ secondary_channel); |
| 376 | ++ |
| 377 | ++ new_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf); |
| 378 | ++ hostapd_set_oper_chwidth(iface->conf, |
| 379 | ++ hostapd_get_oper_chwidth(iface->conf)); |
| 380 | ++ |
| 381 | ++ /* Setup CSA request */ |
| 382 | ++ os_memset(&csa_settings, 0, sizeof(csa_settings)); |
| 383 | ++ csa_settings.cs_count = 5; |
| 384 | ++ csa_settings.block_tx = 1; |
| 385 | ++#ifdef CONFIG_MESH |
| 386 | ++ if (iface->mconf) |
| 387 | ++ ieee80211_mode = IEEE80211_MODE_MESH; |
| 388 | ++#endif /* CONFIG_MESH */ |
| 389 | ++ err = hostapd_set_freq_params(&csa_settings.freq_params, |
| 390 | ++ iface->conf->hw_mode, |
| 391 | ++ freq, channel, |
| 392 | ++ iface->conf->enable_edmg, |
| 393 | ++ iface->conf->edmg_channel, |
| 394 | ++ iface->conf->ieee80211n, |
| 395 | ++ iface->conf->ieee80211ac, |
| 396 | ++ iface->conf->ieee80211ax, |
| 397 | ++ secondary_channel, |
| 398 | ++ new_vht_oper_chwidth, |
| 399 | ++ oper_centr_freq_seg0_idx, |
| 400 | ++ oper_centr_freq_seg1_idx, |
| 401 | ++ cmode->vht_capab, |
| 402 | ++ &cmode->he_capab[ieee80211_mode]); |
| 403 | ++ |
| 404 | ++ if (err) { |
| 405 | ++ wpa_printf(MSG_ERROR, "DFS failed to calculate CSA freq params"); |
| 406 | ++ hostapd_disable_iface(iface); |
| 407 | ++ return err; |
| 408 | ++ } |
| 409 | ++ |
| 410 | ++ for (i = 0; i < iface->num_bss; i++) { |
| 411 | ++ err = hostapd_switch_channel(iface->bss[i], &csa_settings); |
| 412 | ++ if (err) |
| 413 | ++ break; |
| 414 | ++ } |
| 415 | ++ |
| 416 | ++ if (err) { |
| 417 | ++ wpa_printf(MSG_WARNING, "DFS failed to schedule CSA (%d) - trying fallback", |
| 418 | ++ err); |
| 419 | ++ iface->freq = freq; |
| 420 | ++ iface->conf->channel = channel; |
| 421 | ++ iface->conf->secondary_channel = secondary_channel; |
| 422 | ++ hostapd_set_oper_chwidth(iface->conf, new_vht_oper_chwidth); |
| 423 | ++ hostapd_set_oper_centr_freq_seg0_idx(iface->conf, |
| 424 | ++ oper_centr_freq_seg0_idx); |
| 425 | ++ hostapd_set_oper_centr_freq_seg1_idx(iface->conf, |
| 426 | ++ oper_centr_freq_seg1_idx); |
| 427 | ++ |
| 428 | ++ hostapd_disable_iface(iface); |
| 429 | ++ hostapd_enable_iface(iface); |
| 430 | ++ |
| 431 | ++ return 0; |
| 432 | ++ } |
| 433 | ++ |
| 434 | ++ /* Channel configuration will be updated once CSA completes and |
| 435 | ++ * ch_switch_notify event is received */ |
| 436 | ++ wpa_printf(MSG_DEBUG, "DFS waiting channel switch event"); |
| 437 | ++ |
| 438 | ++ return 0; |
| 439 | ++} |
| 440 | ++ |
| 441 | ++static struct hostapd_channel_data * |
| 442 | ++dfs_downgrade_bandwidth(struct hostapd_iface *iface, int *secondary_channel, |
| 443 | ++ u8 *oper_centr_freq_seg0_idx, |
| 444 | ++ u8 *oper_centr_freq_seg1_idx, int *skip_radar); |
| 445 | ++ |
| 446 | ++static void |
| 447 | ++hostpad_dfs_update_offchannel_chain(struct hostapd_iface *iface) |
| 448 | ++{ |
| 449 | ++ struct hostapd_channel_data *channel; |
| 450 | ++ int sec = 0, flags = 2; |
| 451 | ++ u8 cf1 = 0, cf2 = 0; |
| 452 | ++ |
| 453 | ++ channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2, 2); |
| 454 | ++ if (!channel || channel->chan == iface->conf->channel) |
| 455 | ++ channel = dfs_downgrade_bandwidth(iface, &sec, &cf1, &cf2, |
| 456 | ++ &flags); |
| 457 | ++ if (!channel || |
| 458 | ++ hostapd_start_dfs_cac(iface, iface->conf->hw_mode, |
| 459 | ++ channel->freq, channel->chan, |
| 460 | ++ iface->conf->ieee80211n, |
| 461 | ++ iface->conf->ieee80211ac, |
| 462 | ++ iface->conf->ieee80211ax, |
| 463 | ++ sec, hostapd_get_oper_chwidth(iface->conf), |
| 464 | ++ cf1, cf2, 1)) { |
| 465 | ++ /* |
| 466 | ++ * Toggle interface state to enter DFS state |
| 467 | ++ * until NOP is finished. |
| 468 | ++ */ |
| 469 | ++ wpa_printf(MSG_ERROR, "DFS failed start CAC offchannel"); |
| 470 | ++ return; |
| 471 | ++ } |
| 472 | ++ |
| 473 | ++ wpa_printf(MSG_DEBUG, "%s: setting offchannel chain to chan %d (%d MHz)", |
| 474 | ++ __func__, channel->chan, channel->freq); |
| 475 | ++ |
| 476 | ++ iface->radar_offchan.channel = channel->chan; |
| 477 | ++ iface->radar_offchan.freq = channel->freq; |
| 478 | ++ iface->radar_offchan.secondary_channel = sec; |
| 479 | ++ iface->radar_offchan.centr_freq_seg0_idx = cf1; |
| 480 | ++ iface->radar_offchan.centr_freq_seg1_idx = cf2; |
| 481 | ++} |
| 482 | ++ |
| 483 | ++/* FIXME: check if all channel bandwith */ |
| 484 | ++static int |
| 485 | ++hostapd_dfs_is_offchan_event(struct hostapd_iface *iface, int freq) |
| 486 | ++{ |
| 487 | ++ if (iface->radar_offchan.freq != freq) |
| 488 | ++ return 0; |
| 489 | ++ |
| 490 | ++ return 1; |
| 491 | ++} |
| 492 | ++ |
| 493 | ++static int |
| 494 | ++hostapd_dfs_start_channel_switch_offchan(struct hostapd_iface *iface) |
| 495 | ++{ |
| 496 | ++ iface->conf->channel = iface->radar_offchan.channel; |
| 497 | ++ iface->freq = iface->radar_offchan.freq; |
| 498 | ++ iface->conf->secondary_channel = |
| 499 | ++ iface->radar_offchan.secondary_channel; |
| 500 | ++ hostapd_set_oper_centr_freq_seg0_idx(iface->conf, |
| 501 | ++ iface->radar_offchan.centr_freq_seg0_idx); |
| 502 | ++ hostapd_set_oper_centr_freq_seg1_idx(iface->conf, |
| 503 | ++ iface->radar_offchan.centr_freq_seg1_idx); |
| 504 | ++ |
| 505 | ++ hostpad_dfs_update_offchannel_chain(iface); |
| 506 | ++ |
| 507 | ++ return hostapd_dfs_request_channel_switch(iface, iface->conf->channel, |
| 508 | ++ iface->freq, iface->conf->secondary_channel, |
| 509 | ++ hostapd_get_oper_centr_freq_seg0_idx(iface->conf), |
| 510 | ++ hostapd_get_oper_centr_freq_seg1_idx(iface->conf)); |
| 511 | ++} |
| 512 | + |
| 513 | + int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq, |
| 514 | + int ht_enabled, int chan_offset, int chan_width, |
| 515 | +@@ -911,6 +1131,23 @@ int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq, |
| 516 | + set_dfs_state(iface, freq, ht_enabled, chan_offset, |
| 517 | + chan_width, cf1, cf2, |
| 518 | + HOSTAPD_CHAN_DFS_AVAILABLE); |
| 519 | ++ |
| 520 | ++ /* |
| 521 | ++ * radar event from offchannel chain for selected |
| 522 | ++ * channel. Perfrom CSA, move main chain to selected |
| 523 | ++ * channel and configure offchannel chain to a new DFS |
| 524 | ++ * channel |
| 525 | ++ */ |
| 526 | ++ if (hostapd_is_radar_offchan_enabled(iface) && |
| 527 | ++ hostapd_dfs_is_offchan_event(iface, freq)) { |
| 528 | ++ iface->radar_offchan.cac_started = 0; |
| 529 | ++ if (iface->radar_offchan.temp_ch) { |
| 530 | ++ iface->radar_offchan.temp_ch = 0; |
| 531 | ++ return hostapd_dfs_start_channel_switch_offchan(iface); |
| 532 | ++ } |
| 533 | ++ return 0; |
| 534 | ++ } |
| 535 | ++ |
| 536 | + /* |
| 537 | + * Just mark the channel available when CAC completion |
| 538 | + * event is received in enabled state. CAC result could |
| 539 | +@@ -927,6 +1164,10 @@ int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq, |
| 540 | + iface->cac_started = 0; |
| 541 | + } |
| 542 | + } |
| 543 | ++ } else if (hostapd_is_radar_offchan_enabled(iface) && |
| 544 | ++ hostapd_dfs_is_offchan_event(iface, freq)) { |
| 545 | ++ iface->radar_offchan.cac_started = 0; |
| 546 | ++ hostpad_dfs_update_offchannel_chain(iface); |
| 547 | + } |
| 548 | + |
| 549 | + return 0; |
| 550 | +@@ -1035,6 +1276,44 @@ static int hostapd_dfs_start_channel_switch_cac(struct hostapd_iface *iface) |
| 551 | + return err; |
| 552 | + } |
| 553 | + |
| 554 | ++static int |
| 555 | ++hostapd_dfs_offchan_start_channel_switch(struct hostapd_iface *iface, int freq) |
| 556 | ++{ |
| 557 | ++ if (!hostapd_is_radar_offchan_enabled(iface)) |
| 558 | ++ return -1; /* Offchannel chain not supported */ |
| 559 | ++ |
| 560 | ++ wpa_printf(MSG_DEBUG, |
| 561 | ++ "%s called (offchannel CAC active: %s, CSA active: %s)", |
| 562 | ++ __func__, iface->radar_offchan.cac_started ? "yes" : "no", |
| 563 | ++ hostapd_csa_in_progress(iface) ? "yes" : "no"); |
| 564 | ++ |
| 565 | ++ /* Check if CSA in progress */ |
| 566 | ++ if (hostapd_csa_in_progress(iface)) |
| 567 | ++ return 0; |
| 568 | ++ |
| 569 | ++ /* |
| 570 | ++ * If offchannel radar detation is supported and radar channel |
| 571 | ++ * monitored by offchain is available switch to it without waiting |
| 572 | ++ * for the CAC otherwise let's keep a random channel. |
| 573 | ++ * If radar pattern is reported on offchannel chain, just switch to |
| 574 | ++ * monitor another radar channel. |
| 575 | ++ */ |
| 576 | ++ if (hostapd_dfs_is_offchan_event(iface, freq)) { |
| 577 | ++ hostpad_dfs_update_offchannel_chain(iface); |
| 578 | ++ return 0; |
| 579 | ++ } |
| 580 | ++ |
| 581 | ++ /* Offchannel not availanle yet. Perform CAC on main chain */ |
| 582 | ++ if (iface->radar_offchan.cac_started) { |
| 583 | ++ /* We want to switch to monitored channel as soon as |
| 584 | ++ * CAC is completed. |
| 585 | ++ */ |
| 586 | ++ iface->radar_offchan.temp_ch = 1; |
| 587 | ++ return -1; |
| 588 | ++ } |
| 589 | ++ |
| 590 | ++ return hostapd_dfs_start_channel_switch_offchan(iface); |
| 591 | ++} |
| 592 | + |
| 593 | + static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface) |
| 594 | + { |
| 595 | +@@ -1042,13 +1321,7 @@ static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface) |
| 596 | + int secondary_channel; |
| 597 | + u8 oper_centr_freq_seg0_idx; |
| 598 | + u8 oper_centr_freq_seg1_idx; |
| 599 | +- u8 new_vht_oper_chwidth; |
| 600 | + int skip_radar = 1; |
| 601 | +- struct csa_settings csa_settings; |
| 602 | +- unsigned int i; |
| 603 | +- int err = 1; |
| 604 | +- struct hostapd_hw_modes *cmode = iface->current_mode; |
| 605 | +- u8 current_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf); |
| 606 | + |
| 607 | + wpa_printf(MSG_DEBUG, "%s called (CAC active: %s, CSA active: %s)", |
| 608 | + __func__, iface->cac_started ? "yes" : "no", |
| 609 | +@@ -1110,69 +1383,11 @@ static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface) |
| 610 | + } |
| 611 | + } |
| 612 | + |
| 613 | +- wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", |
| 614 | +- channel->chan); |
| 615 | +- wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL |
| 616 | +- "freq=%d chan=%d sec_chan=%d", channel->freq, |
| 617 | +- channel->chan, secondary_channel); |
| 618 | +- |
| 619 | +- new_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf); |
| 620 | +- hostapd_set_oper_chwidth(iface->conf, current_vht_oper_chwidth); |
| 621 | +- |
| 622 | +- /* Setup CSA request */ |
| 623 | +- os_memset(&csa_settings, 0, sizeof(csa_settings)); |
| 624 | +- csa_settings.cs_count = 5; |
| 625 | +- csa_settings.block_tx = 1; |
| 626 | +- err = hostapd_set_freq_params(&csa_settings.freq_params, |
| 627 | +- iface->conf->hw_mode, |
| 628 | +- channel->freq, |
| 629 | +- channel->chan, |
| 630 | +- iface->conf->enable_edmg, |
| 631 | +- iface->conf->edmg_channel, |
| 632 | +- iface->conf->ieee80211n, |
| 633 | +- iface->conf->ieee80211ac, |
| 634 | +- iface->conf->ieee80211ax, |
| 635 | +- secondary_channel, |
| 636 | +- new_vht_oper_chwidth, |
| 637 | +- oper_centr_freq_seg0_idx, |
| 638 | +- oper_centr_freq_seg1_idx, |
| 639 | +- cmode->vht_capab, |
| 640 | +- &cmode->he_capab[iface->conf->hw_mode]); |
| 641 | +- |
| 642 | +- if (err) { |
| 643 | +- wpa_printf(MSG_ERROR, "DFS failed to calculate CSA freq params"); |
| 644 | +- hostapd_disable_iface(iface); |
| 645 | +- return err; |
| 646 | +- } |
| 647 | +- |
| 648 | +- for (i = 0; i < iface->num_bss; i++) { |
| 649 | +- err = hostapd_switch_channel(iface->bss[i], &csa_settings); |
| 650 | +- if (err) |
| 651 | +- break; |
| 652 | +- } |
| 653 | +- |
| 654 | +- if (err) { |
| 655 | +- wpa_printf(MSG_WARNING, "DFS failed to schedule CSA (%d) - trying fallback", |
| 656 | +- err); |
| 657 | +- iface->freq = channel->freq; |
| 658 | +- iface->conf->channel = channel->chan; |
| 659 | +- iface->conf->secondary_channel = secondary_channel; |
| 660 | +- hostapd_set_oper_chwidth(iface->conf, new_vht_oper_chwidth); |
| 661 | +- hostapd_set_oper_centr_freq_seg0_idx(iface->conf, |
| 662 | +- oper_centr_freq_seg0_idx); |
| 663 | +- hostapd_set_oper_centr_freq_seg1_idx(iface->conf, |
| 664 | +- oper_centr_freq_seg1_idx); |
| 665 | +- |
| 666 | +- hostapd_disable_iface(iface); |
| 667 | +- hostapd_enable_iface(iface); |
| 668 | +- return 0; |
| 669 | +- } |
| 670 | +- |
| 671 | +- /* Channel configuration will be updated once CSA completes and |
| 672 | +- * ch_switch_notify event is received */ |
| 673 | +- |
| 674 | +- wpa_printf(MSG_DEBUG, "DFS waiting channel switch event"); |
| 675 | +- return 0; |
| 676 | ++ return hostapd_dfs_request_channel_switch(iface, channel->chan, |
| 677 | ++ channel->freq, |
| 678 | ++ secondary_channel, |
| 679 | ++ oper_centr_freq_seg0_idx, |
| 680 | ++ oper_centr_freq_seg1_idx); |
| 681 | + } |
| 682 | + |
| 683 | + |
| 684 | +@@ -1199,15 +1414,19 @@ int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq, |
| 685 | + if (!res) |
| 686 | + return 0; |
| 687 | + |
| 688 | +- /* Skip if reported radar event not overlapped our channels */ |
| 689 | +- res = dfs_are_channels_overlapped(iface, freq, chan_width, cf1, cf2); |
| 690 | +- if (!res) |
| 691 | +- return 0; |
| 692 | ++ if (!hostapd_dfs_is_offchan_event(iface, freq)) { |
| 693 | ++ /* Skip if reported radar event not overlapped our channels */ |
| 694 | ++ res = dfs_are_channels_overlapped(iface, freq, chan_width, |
| 695 | ++ cf1, cf2); |
| 696 | ++ if (!res) |
| 697 | ++ return 0; |
| 698 | ++ } |
| 699 | + |
| 700 | +- /* radar detected while operating, switch the channel. */ |
| 701 | +- res = hostapd_dfs_start_channel_switch(iface); |
| 702 | ++ if (hostapd_dfs_offchan_start_channel_switch(iface, freq)) |
| 703 | ++ /* radar detected while operating, switch the channel. */ |
| 704 | ++ return hostapd_dfs_start_channel_switch(iface); |
| 705 | + |
| 706 | +- return res; |
| 707 | ++ return 0; |
| 708 | + } |
| 709 | + |
| 710 | + |
| 711 | +@@ -1275,7 +1494,11 @@ int hostapd_dfs_start_cac(struct hostapd_iface *iface, int freq, |
| 712 | + "seg1=%d cac_time=%ds", |
| 713 | + freq, (freq - 5000) / 5, chan_offset, chan_width, cf1, cf2, |
| 714 | + iface->dfs_cac_ms / 1000); |
| 715 | +- iface->cac_started = 1; |
| 716 | ++ |
| 717 | ++ if (hostapd_dfs_is_offchan_event(iface, freq)) |
| 718 | ++ iface->radar_offchan.cac_started = 1; |
| 719 | ++ else |
| 720 | ++ iface->cac_started = 1; |
| 721 | + os_get_reltime(&iface->dfs_cac_start); |
| 722 | + return 0; |
| 723 | + } |
| 724 | +diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h |
| 725 | +index 54b3674..e9b5500 100644 |
| 726 | +--- a/src/ap/hostapd.h |
| 727 | ++++ b/src/ap/hostapd.h |
| 728 | +@@ -517,6 +517,21 @@ struct hostapd_iface { |
| 729 | + int *basic_rates; |
| 730 | + int freq; |
| 731 | + |
| 732 | ++ /* Offchanel chain configuration */ |
| 733 | ++ struct { |
| 734 | ++ int channel; |
| 735 | ++ int secondary_channel; |
| 736 | ++ int freq; |
| 737 | ++ int centr_freq_seg0_idx; |
| 738 | ++ int centr_freq_seg1_idx; |
| 739 | ++ /* Main chain is on temporary channel during |
| 740 | ++ * CAC detection on radar offchain |
| 741 | ++ */ |
| 742 | ++ unsigned int temp_ch:1; |
| 743 | ++ /* CAC started on radar offchain */ |
| 744 | ++ unsigned int cac_started:1; |
| 745 | ++ } radar_offchan; |
| 746 | ++ |
| 747 | + u16 hw_flags; |
| 748 | + |
| 749 | + /* Number of associated Non-ERP stations (i.e., stations using 802.11b |
| 750 | +diff --git a/src/drivers/driver.h b/src/drivers/driver.h |
| 751 | +index 05ec9c2..0521c20 100644 |
| 752 | +--- a/src/drivers/driver.h |
| 753 | ++++ b/src/drivers/driver.h |
| 754 | +@@ -776,6 +776,11 @@ struct hostapd_freq_params { |
| 755 | + * for IEEE 802.11ay EDMG configuration. |
| 756 | + */ |
| 757 | + struct ieee80211_edmg_config edmg; |
| 758 | ++ |
| 759 | ++ /** |
| 760 | ++ * radar_offchan - Whether radar/CAC offchannel is requested |
| 761 | ++ */ |
| 762 | ++ int radar_offchan; |
| 763 | + }; |
| 764 | + |
| 765 | + /** |
| 766 | +@@ -1938,6 +1943,8 @@ struct wpa_driver_capa { |
| 767 | + |
| 768 | + /** Driver supports a separate control port RX for EAPOL frames */ |
| 769 | + #define WPA_DRIVER_FLAGS2_CONTROL_PORT_RX 0x0000000000000001ULL |
| 770 | ++/** Driver supports offchannel radar/CAC detection */ |
| 771 | ++#define WPA_DRIVER_RADAR_OFFCHAN 0x0000000000000200ULL |
| 772 | + u64 flags2; |
| 773 | + |
| 774 | + #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \ |
| 775 | +diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c |
| 776 | +index 72ae074..09e0bc9 100644 |
| 777 | +--- a/src/drivers/driver_nl80211.c |
| 778 | ++++ b/src/drivers/driver_nl80211.c |
| 779 | +@@ -4576,6 +4576,7 @@ static int nl80211_put_freq_params(struct nl_msg *msg, |
| 780 | + wpa_printf(MSG_DEBUG, " * he_enabled=%d", freq->he_enabled); |
| 781 | + wpa_printf(MSG_DEBUG, " * vht_enabled=%d", freq->vht_enabled); |
| 782 | + wpa_printf(MSG_DEBUG, " * ht_enabled=%d", freq->ht_enabled); |
| 783 | ++ wpa_printf(MSG_DEBUG, " * radar_offchan=%d", freq->radar_offchan); |
| 784 | + |
| 785 | + hw_mode = ieee80211_freq_to_chan(freq->freq, &channel); |
| 786 | + is_24ghz = hw_mode == HOSTAPD_MODE_IEEE80211G || |
| 787 | +@@ -4653,6 +4654,9 @@ static int nl80211_put_freq_params(struct nl_msg *msg, |
| 788 | + NL80211_CHAN_NO_HT)) |
| 789 | + return -ENOBUFS; |
| 790 | + } |
| 791 | ++ if (freq->radar_offchan) |
| 792 | ++ nla_put_flag(msg, NL80211_ATTR_RADAR_OFFCHAN); |
| 793 | ++ |
| 794 | + return 0; |
| 795 | + } |
| 796 | + |
| 797 | +diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c |
| 798 | +index 3e8dcef..1dcdbe6 100644 |
| 799 | +--- a/src/drivers/driver_nl80211_capa.c |
| 800 | ++++ b/src/drivers/driver_nl80211_capa.c |
| 801 | +@@ -639,6 +639,10 @@ static void wiphy_info_ext_feature_flags(struct wiphy_info_data *info, |
| 802 | + if (ext_feature_isset(ext_features, len, |
| 803 | + NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS)) |
| 804 | + info->drv->multicast_registrations = 1; |
| 805 | ++ |
| 806 | ++ if (ext_feature_isset(ext_features, len, |
| 807 | ++ NL80211_EXT_FEATURE_RADAR_OFFCHAN)) |
| 808 | ++ capa->flags2 |= WPA_DRIVER_RADAR_OFFCHAN; |
| 809 | + } |
| 810 | + |
| 811 | + |
| 812 | +diff --git a/src/drivers/nl80211_copy.h b/src/drivers/nl80211_copy.h |
| 813 | +index ab84efc..31a227c 100644 |
| 814 | +--- a/src/drivers/nl80211_copy.h |
| 815 | ++++ b/src/drivers/nl80211_copy.h |
| 816 | +@@ -2534,6 +2534,10 @@ enum nl80211_commands { |
| 817 | + * @NL80211_ATTR_WIPHY_ANTENNA_GAIN: Configured antenna gain. Used to reduce |
| 818 | + * transmit power to stay within regulatory limits. u32, dBi. |
| 819 | + * |
| 820 | ++ * @NL80211_ATTR_RADAR_OFFCHAN: Configure dedicated chain available for radar |
| 821 | ++ * detection on some hw. The chain can't be used to transmits or receives |
| 822 | ++ * frames. The driver is supposed to implement CAC management in sw or fw. |
| 823 | ++ * |
| 824 | + * @NUM_NL80211_ATTR: total number of nl80211_attrs available |
| 825 | + * @NL80211_ATTR_MAX: highest attribute number currently defined |
| 826 | + * @__NL80211_ATTR_AFTER_LAST: internal use |
| 827 | +@@ -3039,6 +3043,8 @@ enum nl80211_attrs { |
| 828 | + |
| 829 | + NL80211_ATTR_WIPHY_ANTENNA_GAIN, |
| 830 | + |
| 831 | ++ NL80211_ATTR_RADAR_OFFCHAN, |
| 832 | ++ |
| 833 | + /* add attributes here, update the policy in nl80211.c */ |
| 834 | + |
| 835 | + __NL80211_ATTR_AFTER_LAST, |
| 836 | +@@ -5823,6 +5829,9 @@ enum nl80211_feature_flags { |
| 837 | + * @NL80211_EXT_FEATURE_BSS_COLOR: The driver supports BSS color collision |
| 838 | + * detection and change announcemnts. |
| 839 | + * |
| 840 | ++ * @NL80211_EXT_FEATURE_RADAR_OFFCHAN: Device supports offchannel radar/CAC |
| 841 | ++ * detection. |
| 842 | ++ * |
| 843 | + * @NUM_NL80211_EXT_FEATURES: number of extended features. |
| 844 | + * @MAX_NL80211_EXT_FEATURES: highest extended feature index. |
| 845 | + */ |
| 846 | +@@ -5888,6 +5897,7 @@ enum nl80211_ext_feature_index { |
| 847 | + NL80211_EXT_FEATURE_SECURE_RTT, |
| 848 | + NL80211_EXT_FEATURE_PROT_RANGE_NEGO_AND_MEASURE, |
| 849 | + NL80211_EXT_FEATURE_BSS_COLOR, |
| 850 | ++ NL80211_EXT_FEATURE_RADAR_OFFCHAN, |
| 851 | + |
| 852 | + /* add new features before the definition below */ |
| 853 | + NUM_NL80211_EXT_FEATURES, |
| 854 | + |