1 /*
2 * Copyright (c) 2017-2021 The Linux Foundation. All rights reserved.
3 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for
6 * any purpose with or without fee is hereby granted, provided that the
7 * above copyright notice and this permission notice appear in all
8 * copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 /**
21 * DOC: contains 6 GHz scan manager functionality
22 */
23
24 #include "wlan_scan_main.h"
25 #include "wlan_utility.h"
26 #include <wlan_reg_services_api.h>
27 #include "wlan_scan_manager.h"
28
29 /* Beacon/probe weightage multiplier */
30 #define BCN_PROBE_WEIGHTAGE 5
31
32 /* maximum number of 6 GHz hints can be sent per scan request */
33 #define MAX_HINTS_PER_SCAN_REQ 15
34
35 /* Saved profile weightage multiplier */
36 #define SAVED_PROFILE_WEIGHTAGE 10
37
38 #ifdef CONFIG_BAND_6GHZ
39 #ifdef FEATURE_6G_SCAN_CHAN_SORT_ALGO
40
41 /**
42 * scm_sort_6ghz_channel_list() - Sort the 6 GHz channels based on weightage
43 * @vdev: vdev on which scan request is issued
44 * @chan_list: channel info of the scan request
45 *
46 * Calculate weightage of each channel based on beacon weightage and saved
47 * profile weightage. Sort the channels based on this weight in descending order
48 * to scan the most preferred channels first compared other 6 GHz channels.
49 *
50 * Return: None
51 */
52 static void
scm_sort_6ghz_channel_list(struct wlan_objmgr_vdev * vdev,struct chan_list * chan_list)53 scm_sort_6ghz_channel_list(struct wlan_objmgr_vdev *vdev,
54 struct chan_list *chan_list)
55 {
56 uint8_t i, j = 0, max, tmp_list_count;
57 struct meta_rnr_channel *channel;
58 struct chan_info temp_list[MAX_6GHZ_CHANNEL];
59 struct rnr_chan_weight *rnr_chan_info, temp;
60 uint32_t weight;
61 struct wlan_objmgr_psoc *psoc;
62
63 psoc = wlan_vdev_get_psoc(vdev);
64 if (!psoc) {
65 scm_err("Psoc is NULL");
66 return;
67 }
68
69 for (i = 0; i < chan_list->num_chan; i++)
70 if (WLAN_REG_IS_6GHZ_CHAN_FREQ(chan_list->chan[i].freq))
71 temp_list[j++] = chan_list->chan[i];
72
73 tmp_list_count = j;
74 scm_debug("Total 6 GHz channels %d", tmp_list_count);
75
76 /* No Need to sort if the 6 GHz channels are less than or one */
77 if (tmp_list_count <= 1)
78 return;
79
80 rnr_chan_info = qdf_mem_malloc(sizeof(*rnr_chan_info) * tmp_list_count);
81 if (!rnr_chan_info)
82 return;
83
84 /* compute the weightage */
85 for (i = 0, j = 0; i < tmp_list_count; i++) {
86 channel = scm_get_chan_meta(psoc, temp_list[i].freq);
87 if (!channel)
88 continue;
89 weight = channel->bss_beacon_probe_count * BCN_PROBE_WEIGHTAGE +
90 channel->saved_profile_count * SAVED_PROFILE_WEIGHTAGE;
91 rnr_chan_info[j].weight = weight;
92 rnr_chan_info[j].chan_freq = temp_list[i].freq;
93 rnr_chan_info[j].phymode = temp_list[i].phymode;
94 rnr_chan_info[j].flags = temp_list[i].flags;
95 j++;
96 /*
97 * Log the info only if weight or bss_beacon_probe_count are
98 * non-zero to avoid excessive logging.
99 */
100 if (weight || channel->bss_beacon_probe_count)
101 scm_debug("Freq %d weight %d bcn_cnt %d",
102 temp_list[i].freq, weight,
103 channel->bss_beacon_probe_count);
104 }
105
106 /* Sort the channel using selection sort - descending order */
107 for (i = 0; i < tmp_list_count - 1; i++) {
108 max = i;
109 for (j = i + 1; j < tmp_list_count; j++) {
110 if (rnr_chan_info[j].weight >
111 rnr_chan_info[max].weight)
112 max = j;
113 }
114 if (max != i) {
115 qdf_mem_copy(&temp, &rnr_chan_info[max],
116 sizeof(*rnr_chan_info));
117 qdf_mem_copy(&rnr_chan_info[max], &rnr_chan_info[i],
118 sizeof(*rnr_chan_info));
119 qdf_mem_copy(&rnr_chan_info[i], &temp,
120 sizeof(*rnr_chan_info));
121 }
122 }
123
124 /* update the 6g list based on the weightage */
125 for (i = 0, j = 0; (i < NUM_CHANNELS && j < tmp_list_count); i++)
126 if (wlan_reg_is_6ghz_chan_freq(chan_list->chan[i].freq)) {
127 chan_list->chan[i].freq = rnr_chan_info[j].chan_freq;
128 chan_list->chan[i].flags = rnr_chan_info[j].flags;
129 chan_list->chan[i].phymode = rnr_chan_info[j++].phymode;
130 }
131
132 qdf_mem_free(rnr_chan_info);
133 }
134
scm_update_rnr_info(struct wlan_objmgr_psoc * psoc,struct scan_start_request * req)135 static void scm_update_rnr_info(struct wlan_objmgr_psoc *psoc,
136 struct scan_start_request *req)
137 {
138 uint8_t i, num_bssid = 0, num_ssid = 0;
139 uint8_t total_count = MAX_HINTS_PER_SCAN_REQ;
140 uint32_t freq;
141 struct meta_rnr_channel *chan;
142 qdf_list_node_t *cur_node, *next_node = NULL;
143 struct scan_rnr_node *rnr_node;
144 struct chan_list *chan_list;
145 QDF_STATUS status;
146 bool hint = false;
147
148 if (!req)
149 return;
150
151 chan_list = &req->scan_req.chan_list;
152 for (i = 0; i < chan_list->num_chan; i++) {
153 freq = chan_list->chan[i].freq;
154
155 chan = scm_get_chan_meta(psoc, freq);
156 if (!chan || qdf_list_empty(&chan->rnr_list))
157 continue;
158
159 qdf_list_peek_front(&chan->rnr_list, &cur_node);
160 while (cur_node && total_count) {
161 rnr_node = qdf_container_of(cur_node,
162 struct scan_rnr_node,
163 node);
164 if (!qdf_is_macaddr_zero(&rnr_node->entry.bssid) &&
165 req->scan_req.num_hint_bssid <
166 WLAN_SCAN_MAX_HINT_BSSID) {
167 qdf_mem_copy(&req->scan_req.hint_bssid[
168 num_bssid].bssid,
169 &rnr_node->entry.bssid,
170 QDF_MAC_ADDR_SIZE);
171 req->scan_req.hint_bssid[
172 num_bssid++].freq_flags = freq << 16;
173 req->scan_req.num_hint_bssid++;
174 hint = true;
175 }
176 if (rnr_node->entry.short_ssid &&
177 req->scan_req.num_hint_s_ssid <
178 WLAN_SCAN_MAX_HINT_S_SSID) {
179 req->scan_req.hint_s_ssid[
180 num_ssid].short_ssid =
181 rnr_node->entry.short_ssid;
182 req->scan_req.hint_s_ssid[
183 num_ssid++].freq_flags = freq << 16;
184 req->scan_req.num_hint_s_ssid++;
185 hint = true;
186 }
187
188 if (hint) {
189 total_count--;
190 hint = false;
191 }
192 status = qdf_list_peek_next(&chan->rnr_list, cur_node,
193 &next_node);
194 if (QDF_IS_STATUS_ERROR(status))
195 break;
196 cur_node = next_node;
197 next_node = NULL;
198 }
199 }
200 }
201
202 /**
203 * scm_add_rnr_info() - Add the cached RNR info to scan request
204 * @pdev: pdev on which scan request is issued
205 * @req: Scan start request
206 *
207 * Fetch the cached RNR info from scan db and update it to the scan request to
208 * include RNR channels in the scan request.
209 *
210 * Return: None
211 */
scm_add_rnr_info(struct wlan_objmgr_pdev * pdev,struct scan_start_request * req)212 static void scm_add_rnr_info(struct wlan_objmgr_pdev *pdev,
213 struct scan_start_request *req)
214 {
215 struct wlan_objmgr_psoc *psoc;
216 struct channel_list_db *rnr_db;
217
218 psoc = wlan_pdev_get_psoc(pdev);
219 if (!psoc)
220 return;
221 rnr_db = scm_get_rnr_channel_db(psoc);
222 if (!rnr_db)
223 return;
224
225 rnr_db->scan_count++;
226 if (rnr_db->scan_count >= RNR_UPDATE_SCAN_CNT_THRESHOLD) {
227 rnr_db->scan_count = 0;
228 scm_rnr_db_flush(psoc);
229 scm_update_rnr_from_scan_cache(pdev);
230 }
231
232 scm_update_rnr_info(psoc, req);
233 }
234 #else
235 static void
scm_sort_6ghz_channel_list(struct wlan_objmgr_vdev * vdev,struct chan_list * chan_list)236 scm_sort_6ghz_channel_list(struct wlan_objmgr_vdev *vdev,
237 struct chan_list *chan_list)
238 {
239 }
240
scm_add_rnr_info(struct wlan_objmgr_pdev * pdev,struct scan_start_request * req)241 static void scm_add_rnr_info(struct wlan_objmgr_pdev *pdev,
242 struct scan_start_request *req)
243 {
244 }
245 #endif
246
247 static inline bool
scm_is_full_scan_by_userspace(struct chan_list * chan_list)248 scm_is_full_scan_by_userspace(struct chan_list *chan_list)
249 {
250 return (chan_list->num_chan >= FULL_SCAN_CH_COUNT_MIN_BY_USERSPACE);
251 }
252
253 static inline bool
scm_is_scan_type_exempted_from_optimization(struct scan_start_request * req)254 scm_is_scan_type_exempted_from_optimization(struct scan_start_request *req)
255 {
256 /* Dont modify the channel list for RRM type*/
257 return (req->scan_req.scan_type == SCAN_TYPE_RRM);
258 }
259
scm_add_all_valid_6g_channels(struct wlan_objmgr_pdev * pdev,struct chan_list * chan_list,uint8_t * num_scan_ch,bool is_colocated_6ghz_scan_enabled)260 void scm_add_all_valid_6g_channels(struct wlan_objmgr_pdev *pdev,
261 struct chan_list *chan_list,
262 uint8_t *num_scan_ch,
263 bool is_colocated_6ghz_scan_enabled)
264 {
265 uint8_t i, j;
266 enum channel_enum freq_idx;
267 struct regulatory_channel *cur_chan_list;
268 bool found;
269 QDF_STATUS status;
270 uint8_t temp_num_chan = 0;
271
272 if (!is_colocated_6ghz_scan_enabled) {
273 scm_debug("flag is not set in scan req");
274 return;
275 }
276
277 cur_chan_list = qdf_mem_malloc(NUM_CHANNELS *
278 sizeof(struct regulatory_channel));
279 if (!cur_chan_list)
280 return;
281
282 status = wlan_reg_get_current_chan_list(pdev, cur_chan_list);
283 if (QDF_IS_STATUS_ERROR(status)) {
284 qdf_mem_free(cur_chan_list);
285 scm_debug("Failed to get cur_chan list");
286 return;
287 }
288
289 freq_idx =
290 wlan_reg_get_chan_enum_for_freq(wlan_reg_min_6ghz_chan_freq());
291 if (reg_is_chan_enum_invalid(freq_idx))
292 return;
293
294 scm_debug("freq_idx:%d", freq_idx);
295 temp_num_chan = chan_list->num_chan;
296 for (i = freq_idx; i < NUM_CHANNELS; i++) {
297 found = false;
298 for (j = 0; j < temp_num_chan; j++) {
299 if (cur_chan_list[i].center_freq ==
300 chan_list->chan[j].freq) {
301 found = true;
302 break;
303 }
304 }
305 if (!found && cur_chan_list[i].state != CHANNEL_STATE_DISABLE &&
306 cur_chan_list[i].state != CHANNEL_STATE_INVALID) {
307 chan_list->chan[chan_list->num_chan].freq =
308 cur_chan_list[i].center_freq;
309 chan_list->chan[chan_list->num_chan].flags =
310 FLAG_SCAN_ONLY_IF_RNR_FOUND;
311 chan_list->num_chan++;
312 }
313 }
314
315 scm_debug("prev num_chan:%d, current num_chan:%d", temp_num_chan,
316 chan_list->num_chan);
317 *num_scan_ch = chan_list->num_chan;
318 qdf_mem_free(cur_chan_list);
319 }
320
321 static void
scm_copy_valid_channels(struct wlan_objmgr_psoc * psoc,enum scan_mode_6ghz scan_mode,struct scan_start_request * req,uint8_t * num_scan_ch)322 scm_copy_valid_channels(struct wlan_objmgr_psoc *psoc,
323 enum scan_mode_6ghz scan_mode,
324 struct scan_start_request *req,
325 uint8_t *num_scan_ch)
326 {
327 uint8_t i, num_ch = *num_scan_ch;
328 struct chan_list *chan_list = &req->scan_req.chan_list;
329 qdf_freq_t freq;
330
331 switch (scan_mode) {
332 case SCAN_MODE_6G_NO_CHANNEL:
333 /* Don't add any 6g channels */
334 for (i = 0; i < chan_list->num_chan; i++)
335 if (!wlan_reg_is_6ghz_chan_freq(
336 chan_list->chan[i].freq))
337 chan_list->chan[num_ch++] =
338 chan_list->chan[i];
339 break;
340 case SCAN_MODE_6G_PSC_CHANNEL:
341 case SCAN_MODE_6G_PSC_DUTY_CYCLE:
342 /*
343 * Filter out non-PSC 6g channels if firmware doesn't
344 * supports RNR_ONLY scan flag/feature and the scan type is
345 * allowed to be optimized.
346 */
347 if (!scm_is_6ghz_scan_optimization_supported(psoc) &&
348 !scm_is_scan_type_exempted_from_optimization(req)) {
349 for (i = 0; i < chan_list->num_chan; i++) {
350 freq = chan_list->chan[i].freq;
351 if (!wlan_reg_is_6ghz_chan_freq(freq) ||
352 (wlan_reg_is_6ghz_chan_freq(freq) &&
353 wlan_reg_is_6ghz_psc_chan_freq(freq)))
354 chan_list->chan[num_ch++] =
355 chan_list->chan[i];
356 }
357 break;
358 }
359 /*
360 * Consider the complete channel list if firmware supports
361 * RNR_ONLY scan flag/feature.
362 */
363 fallthrough;
364 default:
365 /*
366 * Allow all 2g/5g/6g channels. Below are also covered in this
367 * 1. SCAN_MODE_6G_ALL_CHANNEL: Copy all channels and RNR flag
368 * won't be set for any channel.
369 * 2. SCAN_MODE_6G_PSC_CHANNEL: Copy all channels and RNR flag
370 * will be set for non-PSC.
371 * 3. SCAN_MODE_6G_PSC_DUTY_CYCLE: Copy all channels and RNR
372 * flag will be set for non-PSC for all scans and RNR flag
373 * will be set for PSC channels only for duty cycle scan.
374 */
375 num_ch = chan_list->num_chan;
376 }
377
378 *num_scan_ch = num_ch;
379 }
380
381 static inline void
scm_set_rnr_flag_non_psc_6g_ch(struct chan_info * chan,uint8_t num_chan)382 scm_set_rnr_flag_non_psc_6g_ch(struct chan_info *chan, uint8_t num_chan)
383 {
384 uint8_t i;
385
386 for (i = 0; i < num_chan; i++)
387 if (wlan_reg_is_6ghz_chan_freq(chan[i].freq) &&
388 !wlan_reg_is_6ghz_psc_chan_freq(chan[i].freq))
389 chan[i].flags = FLAG_SCAN_ONLY_IF_RNR_FOUND;
390 }
391
392 static inline void
scm_set_rnr_flag_all_6g_ch(struct chan_info * chan,uint8_t num_chan)393 scm_set_rnr_flag_all_6g_ch(struct chan_info *chan, uint8_t num_chan)
394 {
395 uint8_t i;
396
397 for (i = 0; i < num_chan; i++)
398 if (wlan_reg_is_6ghz_chan_freq(chan[i].freq))
399 chan[i].flags = FLAG_SCAN_ONLY_IF_RNR_FOUND;
400 }
401
scm_is_duty_cycle_scan(struct wlan_scan_obj * scan_obj)402 static bool scm_is_duty_cycle_scan(struct wlan_scan_obj *scan_obj)
403 {
404 bool duty_cycle = false;
405
406 scan_obj->duty_cycle_cnt_6ghz++;
407 if (scan_obj->duty_cycle_cnt_6ghz == 1)
408 duty_cycle = true;
409 if (scan_obj->scan_def.duty_cycle_6ghz == scan_obj->duty_cycle_cnt_6ghz)
410 scan_obj->duty_cycle_cnt_6ghz = 0;
411
412 return duty_cycle;
413 }
414
415 inline bool
scm_is_6ghz_scan_optimization_supported(struct wlan_objmgr_psoc * psoc)416 scm_is_6ghz_scan_optimization_supported(struct wlan_objmgr_psoc *psoc)
417 {
418 return wlan_psoc_nif_fw_ext_cap_get(psoc,
419 WLAN_SOC_CEXT_SCAN_PER_CH_CONFIG);
420 }
421
scm_add_channel_flags(struct wlan_objmgr_vdev * vdev,struct chan_list * chan_list,uint8_t * num_chan,bool is_colocated_6ghz_scan_enabled,bool is_pno_scan)422 void scm_add_channel_flags(struct wlan_objmgr_vdev *vdev,
423 struct chan_list *chan_list,
424 uint8_t *num_chan,
425 bool is_colocated_6ghz_scan_enabled,
426 bool is_pno_scan)
427 {
428 struct wlan_scan_obj *scan_obj;
429 enum scan_mode_6ghz scan_mode;
430 struct wlan_objmgr_pdev *pdev;
431 uint8_t num_scan_chan = *num_chan;
432
433 pdev = wlan_vdev_get_pdev(vdev);
434 if (!pdev)
435 return;
436 scan_obj = wlan_vdev_get_scan_obj(vdev);
437 if (!scan_obj) {
438 scm_err("scan_obj is NULL");
439 return;
440 }
441
442 scan_mode = scan_obj->scan_def.scan_mode_6g;
443
444 switch (scan_mode) {
445 case SCAN_MODE_6G_RNR_ONLY:
446 /*
447 * When the ini is set to SCAN_MODE_6G_RNR_ONLY
448 * always set RNR flag for all(PSC and non-PSC) channels.
449 */
450 scm_set_rnr_flag_all_6g_ch(&chan_list->chan[0], num_scan_chan);
451 break;
452 case SCAN_MODE_6G_PSC_CHANNEL:
453 /*
454 * When the ini is set to SCAN_MODE_6G_PSC_CHANNEL,
455 * always set RNR flag for non-PSC channels.
456 */
457 scm_set_rnr_flag_non_psc_6g_ch(&chan_list->chan[0],
458 num_scan_chan);
459 break;
460 case SCAN_MODE_6G_PSC_DUTY_CYCLE:
461 case SCAN_MODE_6G_ALL_DUTY_CYCLE:
462 if (!is_pno_scan && !scm_is_duty_cycle_scan(scan_obj))
463 scm_set_rnr_flag_all_6g_ch(&chan_list->chan[0],
464 num_scan_chan);
465 else if (scan_mode == SCAN_MODE_6G_PSC_DUTY_CYCLE) {
466 if (!is_pno_scan)
467 scm_set_rnr_flag_non_psc_6g_ch(&chan_list->chan[0],
468 num_scan_chan);
469 }
470
471 fallthrough;
472 /* Even when the scan mode is SCAN_MODE_6G_PSC_DUTY_CYCLE or
473 * SCAN_MODE_6G_ALL_DUTY_CYCLE, it is better to add other 6 GHz
474 * channels to the channel list and set the bit
475 * FLAG_SCAN_ONLY_IF_RNR_FOUND for these new channels.
476 * This can help to find the APs which have co-located APs in
477 * given 2 GHz/5 GHz channels.
478 * Let it fallthrough as this is already addressed through the
479 * scan mode SCAN_MODE_6G_ALL_CHANNEL.
480 */
481 case SCAN_MODE_6G_ALL_CHANNEL:
482 /*
483 * When the ini is set to SCAN_MODE_6G_ALL_CHANNEL,
484 * Host fills all remaining (other than channel(s) present in
485 * host scan req) valid 6 GHz channel(s) to scan requests and
486 * set the flag FLAG_SCAN_ONLY_IF_RNR_FOUND for each remaining
487 * channels.
488 */
489 scm_add_all_valid_6g_channels(pdev, chan_list, num_chan,
490 is_colocated_6ghz_scan_enabled);
491 break;
492 default:
493 /*
494 * Don't set the RNR flag for SCAN_MODE_6G_NO_CHANNEL/
495 * SCAN_MODE_6G_RNR_ONLY
496 */
497 break;
498 }
499 }
500
501 void
scm_update_6ghz_channel_list(struct scan_start_request * req,struct wlan_scan_obj * scan_obj)502 scm_update_6ghz_channel_list(struct scan_start_request *req,
503 struct wlan_scan_obj *scan_obj)
504 {
505 struct wlan_objmgr_vdev *vdev = req->vdev;
506 struct wlan_objmgr_pdev *pdev;
507 struct chan_list *chan_list = &req->scan_req.chan_list;
508 enum scan_mode_6ghz scan_mode;
509 uint8_t num_scan_ch = 0;
510 enum QDF_OPMODE op_mode;
511 struct wlan_objmgr_psoc *psoc;
512
513 pdev = wlan_vdev_get_pdev(vdev);
514 if (!pdev)
515 return;
516 psoc = wlan_pdev_get_psoc(pdev);
517
518 /* Dont update the channel list for not STA mode */
519 op_mode = wlan_vdev_mlme_get_opmode(req->vdev);
520 if (op_mode == QDF_SAP_MODE ||
521 op_mode == QDF_P2P_DEVICE_MODE ||
522 op_mode == QDF_P2P_CLIENT_MODE ||
523 op_mode == QDF_P2P_GO_MODE)
524 return;
525
526 if (!wlan_reg_is_6ghz_band_set(pdev)) {
527 scm_debug("6 GHz band disabled.");
528 return;
529 }
530
531 scan_mode = scan_obj->scan_def.scan_mode_6g;
532 scm_debug("6g scan mode %d", scan_mode);
533
534 /*
535 * Host has learned RNR info/channels from previous scan. Add them to
536 * the scan request and don't set RNR_ONLY flag to scan them without
537 * optimization. Don't add RNR info if the scan type is exempted from
538 * optimization.
539 */
540 if (scan_mode != SCAN_MODE_6G_NO_CHANNEL &&
541 scm_is_full_scan_by_userspace(chan_list) &&
542 !scm_is_scan_type_exempted_from_optimization(req))
543 scm_add_rnr_info(pdev, req);
544
545 /* copy all the channels given by userspace */
546 scm_copy_valid_channels(psoc, scan_mode, req, &num_scan_ch);
547
548 /* No more optimizations are needed in the below cases */
549 if (!scm_is_full_scan_by_userspace(chan_list) ||
550 !scm_is_6ghz_scan_optimization_supported(psoc) ||
551 scm_is_scan_type_exempted_from_optimization(req))
552 goto end;
553
554 scm_add_channel_flags(vdev, chan_list, &num_scan_ch,
555 req->scan_req.scan_policy_colocated_6ghz, false);
556
557 end:
558 chan_list->num_chan = num_scan_ch;
559
560 scm_sort_6ghz_channel_list(req->vdev, &req->scan_req.chan_list);
561 }
562 #endif
563