blob: 357ca69578f892d4f6fa169cf45433ad3fc6332e [file] [log] [blame]
developer617abbd2024-04-23 14:50:01 +08001From c635af2f526c7dc7a862e5c6fed5f2015d8e85b6 Mon Sep 17 00:00:00 2001
2From: Evelyn Tsai <evelyn.tsai@mediatek.com>
3Date: Wed, 20 Mar 2024 07:18:37 +0800
4Subject: [PATCH 025/104] backport: hostapd: ap: add AFC client support
5
6Introduce Automated Frequency Coordination (AFC) support for UNII-5 and
7UNII-7 6GHz bands.
8AFC client will connect to AFCD providing AP related parameter for AFC
9coordinator (e.g. geolocation, supported frequencies, ..).
10AFC is required for Standard Power Devices (SPDs) to determine a lists
11of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
12AFC hostapd client is tested with AFC DUT Test Harness [0].
13
14[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main
15
16Tested-by: Allen Ye <allen.ye@mediatek.com>
17Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
18---
19 hostapd/Makefile | 6 +
20 hostapd/config_file.c | 262 ++++++++++++
21 hostapd/defconfig | 3 +
22 hostapd/hostapd.conf | 42 ++
23 src/ap/afc.c | 918 ++++++++++++++++++++++++++++++++++++++++++
24 src/ap/ap_config.c | 16 +
25 src/ap/ap_config.h | 47 +++
26 src/ap/hostapd.c | 60 +++
27 src/ap/hostapd.h | 29 ++
28 src/ap/hw_features.c | 2 +
29 10 files changed, 1385 insertions(+)
30 create mode 100644 src/ap/afc.c
31
32diff --git a/hostapd/Makefile b/hostapd/Makefile
33index b3cb68673..405e05e5f 100644
34--- a/hostapd/Makefile
35+++ b/hostapd/Makefile
36@@ -103,6 +103,12 @@ CFLAGS += -DCONFIG_TAXONOMY
37 OBJS += ../src/ap/taxonomy.o
38 endif
39
40+ifdef CONFIG_AFC
41+CFLAGS += -DCONFIG_AFC
42+OBJS += ../src/ap/afc.o
43+LIBS += -ljson-c
44+endif
45+
46 ifdef CONFIG_MODULE_TESTS
47 CFLAGS += -DCONFIG_MODULE_TESTS
48 OBJS += hapd_module_tests.o
49diff --git a/hostapd/config_file.c b/hostapd/config_file.c
50index 56b2df3ae..261905368 100644
51--- a/hostapd/config_file.c
52+++ b/hostapd/config_file.c
53@@ -2436,6 +2436,191 @@ static int get_u16(const char *pos, int line, u16 *ret_val)
54 #endif /* CONFIG_IEEE80211BE */
55
56
57+#ifdef CONFIG_AFC
58+static int hostapd_afc_parse_cert_ids(struct hostapd_config *conf, char *pos)
59+{
60+ struct cert_id *c = NULL;
61+ int i, count = 0;
62+
63+ while (pos && pos[0]) {
64+ char *p;
65+
66+ c = os_realloc_array(c, count + 1, sizeof(*c));
67+ if (!c)
68+ return -ENOMEM;
69+
70+ i = count;
71+ count++;
72+
73+ p = os_strchr(pos, ':');
74+ if (!p)
75+ goto error;
76+
77+ *p++ = '\0';
78+ if (!p || !p[0])
79+ goto error;
80+
81+ c[i].rulset = os_malloc(os_strlen(pos) + 1);
82+ if (!c[i].rulset)
83+ goto error;
84+
85+ os_strlcpy(c[i].rulset, pos, os_strlen(pos) + 1);
86+ pos = p;
87+ p = os_strchr(pos, ',');
88+ if (p)
89+ *p++ = '\0';
90+
91+ c[i].id = os_malloc(os_strlen(pos) + 1);
92+ if (!c[i].id)
93+ goto error;
94+
95+ os_strlcpy(c[i].id, pos, os_strlen(pos) + 1);
96+ pos = p;
97+ }
98+
99+ conf->afc.n_cert_ids = count;
100+ conf->afc.cert_ids = c;
101+
102+ return 0;
103+
104+error:
105+ for (i = 0; i < count; i++) {
106+ os_free(c[i].rulset);
107+ os_free(c[i].id);
108+ }
109+ os_free(c);
110+
111+ return -ENOMEM;
112+}
113+
114+
115+static int hostapd_afc_parse_position_data(struct afc_linear_polygon **data,
116+ unsigned int *n_linear_polygon_data,
117+ char *pos)
118+{
119+ struct afc_linear_polygon *d = NULL;
120+ int i, count = 0;
121+
122+ while (pos && pos[0]) {
123+ char *p, *end;
124+
125+ d = os_realloc_array(d, count + 1, sizeof(*d));
126+ if (!d)
127+ return -ENOMEM;
128+
129+ i = count;
130+ count++;
131+
132+ p = os_strchr(pos, ':');
133+ if (!p)
134+ goto error;
135+
136+ *p++ = '\0';
137+ if (!p || !p[0])
138+ goto error;
139+
140+ d[i].longitude = strtod(pos, &end);
141+ if (*end)
142+ goto error;
143+
144+ pos = p;
145+ p = os_strchr(pos, ',');
146+ if (p)
147+ *p++ = '\0';
148+
149+ d[i].latitude = strtod(pos, &end);
150+ if (*end)
151+ goto error;
152+
153+ pos = p;
154+ }
155+
156+ *n_linear_polygon_data = count;
157+ *data = d;
158+
159+ return 0;
160+
161+error:
162+ os_free(d);
163+ return -ENOMEM;
164+}
165+
166+
167+static int hostapd_afc_parse_freq_range(struct hostapd_config *conf, char *pos)
168+{
169+ struct afc_freq_range *f = NULL;
170+ int i, count = 0;
171+
172+ while (pos && pos[0]) {
173+ char *p;
174+
175+ f = os_realloc_array(f, count + 1, sizeof(*f));
176+ if (!f)
177+ return -ENOMEM;
178+
179+ i = count;
180+ count++;
181+
182+ p = os_strchr(pos, ':');
183+ if (!p)
184+ goto error;
185+
186+ *p++ = '\0';
187+ if (!p || !p[0])
188+ goto error;
189+
190+ f[i].low_freq = atoi(pos);
191+ pos = p;
192+ p = os_strchr(pos, ',');
193+ if (p)
194+ *p++ = '\0';
195+
196+ f[i].high_freq = atoi(pos);
197+ pos = p;
198+ }
199+
200+ conf->afc.n_freq_range = count;
201+ conf->afc.freq_range = f;
202+
203+ return 0;
204+
205+error:
206+ os_free(f);
207+ return -ENOMEM;
208+}
209+
210+
211+static int hostapd_afc_parse_op_class(struct hostapd_config *conf, char *pos)
212+{
213+ unsigned int *oc = NULL;
214+ int i, count = 0;
215+
216+ while (pos && pos[0]) {
217+ char *p;
218+
219+ oc = os_realloc_array(oc, count + 1, sizeof(*oc));
220+ if (!oc)
221+ return -ENOMEM;
222+
223+ i = count;
224+ count++;
225+
226+ p = os_strchr(pos, ',');
227+ if (p)
228+ *p++ = '\0';
229+
230+ oc[i] = atoi(pos);
231+ pos = p;
232+ }
233+
234+ conf->afc.n_op_class = count;
235+ conf->afc.op_class = oc;
236+
237+ return 0;
238+}
239+#endif /* CONFIG_AFC */
240+
241+
242 static int hostapd_config_fill(struct hostapd_config *conf,
243 struct hostapd_bss_config *bss,
244 const char *buf, char *pos, int line)
245@@ -3862,6 +4047,83 @@ static int hostapd_config_fill(struct hostapd_config *conf,
246 return 1;
247 }
248 bss->unsol_bcast_probe_resp_interval = val;
249+#ifdef CONFIG_AFC
250+ } else if (os_strcmp(buf, "afcd_sock") == 0) {
251+ conf->afc.socket = os_malloc(os_strlen(pos) + 1);
252+ if (!conf->afc.socket)
253+ return 1;
254+
255+ os_strlcpy(conf->afc.socket, pos, os_strlen(pos) + 1);
256+ } else if (os_strcmp(buf, "afc_request_version") == 0) {
257+ conf->afc.request.version = os_malloc(os_strlen(pos) + 1);
258+ if (!conf->afc.request.version)
259+ return 1;
260+
261+ os_strlcpy(conf->afc.request.version, pos, os_strlen(pos) + 1);
262+ } else if (os_strcmp(buf, "afc_request_id") == 0) {
263+ conf->afc.request.id = os_malloc(os_strlen(pos) + 1);
264+ if (!conf->afc.request.id)
265+ return 1;
266+
267+ os_strlcpy(conf->afc.request.id, pos, os_strlen(pos) + 1);
268+ } else if (os_strcmp(buf, "afc_serial_number") == 0) {
269+ conf->afc.request.sn = os_malloc(os_strlen(pos) + 1);
270+ if (!conf->afc.request.sn)
271+ return 1;
272+
273+ os_strlcpy(conf->afc.request.sn, pos, os_strlen(pos) + 1);
274+ } else if (os_strcmp(buf, "afc_cert_ids") == 0) {
275+ if (hostapd_afc_parse_cert_ids(conf, pos))
276+ return 1;
277+ } else if (os_strcmp(buf, "afc_location_type") == 0) {
278+ conf->afc.location.type = atoi(pos);
279+ if (conf->afc.location.type != ELLIPSE &&
280+ conf->afc.location.type != LINEAR_POLYGON &&
281+ conf->afc.location.type != RADIAL_POLYGON)
282+ return 1;
283+ } else if (os_strcmp(buf, "afc_linear_polygon") == 0) {
284+ if (hostapd_afc_parse_position_data(
285+ &conf->afc.location.linear_polygon_data,
286+ &conf->afc.location.n_linear_polygon_data,
287+ pos))
288+ return 1;
289+ } else if (os_strcmp(buf, "afc_radial_polygon") == 0) {
290+ if (hostapd_afc_parse_position_data(
291+ (struct afc_linear_polygon **)
292+ &conf->afc.location.radial_polygon_data,
293+ &conf->afc.location.n_radial_polygon_data,
294+ pos))
295+ return 1;
296+ } else if (os_strcmp(buf, "afc_major_axis") == 0) {
297+ conf->afc.location.major_axis = atoi(pos);
298+ } else if (os_strcmp(buf, "afc_minor_axis") == 0) {
299+ conf->afc.location.minor_axis = atoi(pos);
300+ } else if (os_strcmp(buf, "afc_orientation") == 0) {
301+ conf->afc.location.orientation = atoi(pos);
302+ } else if (os_strcmp(buf, "afc_height") == 0) {
303+ char *end;
304+
305+ conf->afc.location.height = strtod(pos, &end);
306+ if (*end)
307+ return 1;
308+ } else if (os_strcmp(buf, "afc_height_type") == 0) {
309+ conf->afc.location.height_type = os_malloc(os_strlen(pos) + 1);
310+ if (!conf->afc.location.height_type)
311+ return 1;
312+
313+ os_strlcpy(conf->afc.location.height_type, pos,
314+ os_strlen(pos) + 1);
315+ } else if (os_strcmp(buf, "afc_vertical_tolerance") == 0) {
316+ conf->afc.location.vertical_tolerance = atoi(pos);
317+ } else if (os_strcmp(buf, "afc_min_power") == 0) {
318+ conf->afc.min_power = atoi(pos);
319+ } else if (os_strcmp(buf, "afc_freq_range") == 0) {
320+ if (hostapd_afc_parse_freq_range(conf, pos))
321+ return 1;
322+ } else if (os_strcmp(buf, "afc_op_class") == 0) {
323+ if (hostapd_afc_parse_op_class(conf, pos))
324+ return 1;
325+#endif /* CONFIG_AFC */
326 } else if (os_strcmp(buf, "mbssid") == 0) {
327 int mbssid = atoi(pos);
328 if (mbssid < 0 || mbssid > ENHANCED_MBSSID_ENABLED) {
329diff --git a/hostapd/defconfig b/hostapd/defconfig
330index 550db697b..66bf894eb 100644
331--- a/hostapd/defconfig
332+++ b/hostapd/defconfig
333@@ -425,3 +425,6 @@ CONFIG_DPP2=y
334
335 # Wi-Fi Aware unsynchronized service discovery (NAN USD)
336 #CONFIG_NAN_USD=y
337+
338+# Enable Automated Frequency Coordination for 6GHz outdoor
339+#CONFIG_AFC=y
340diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
341index d80abcac0..0d10998af 100644
342--- a/hostapd/hostapd.conf
343+++ b/hostapd/hostapd.conf
344@@ -1005,6 +1005,48 @@ wmm_ac_vo_acm=0
345 # Valid range: 0..20 TUs; default is 0 (disabled)
346 #unsol_bcast_probe_resp_interval=0
347
348+##### Automated Frequency Coordination for 6GHz UNII-5 and UNII-7 bands #######
349+
350+# AFC daemon connection socket
351+#afcd_sock=/var/run/afcd.sock
352+
353+# AFC request identification parameters
354+#afc_request_version=1.1
355+#afc_request_id=11235813
356+#afc_serial_number=abcdefg
357+#afc_cert_ids=US_47_CFR_PART_15_SUBPART_E:CID000
358+#
359+# AFC location type:
360+# 0 = ellipse
361+# 1 = linear polygon
362+# 2 = radial polygon
363+#afc_location_type=0
364+#
365+# AFC ellipse or linear polygon coordinations
366+#afc_linear_polygon=-122.984157:37.425056
367+#
368+# AFC radial polygon coordinations
369+#afc_radial_polygon=118.8:92.76,76.44:87.456,98.56:123.33
370+#
371+# AFC ellipse major/minor axis and orientation
372+#afc_major_axis=100
373+#afc_minor_axis=50
374+#afc_orientation=70
375+#
376+# AFC device elevation parameters
377+#afc_height=3.0
378+#afc_height_type=AGL
379+#afc_vertical_tolerance=7
380+#
381+# AFC minimum desired TX power (dbm)
382+#afc_min_power=24
383+#
384+# AFC request frequency ranges
385+#afc_freq_range=5925:6425,6525:6875
386+#
387+# AFC request operation classes
388+#afc_op_class=131,132,133,134,136
389+
390 ##### IEEE 802.11be related configuration #####################################
391
392 #ieee80211be: Whether IEEE 802.11be (EHT) is enabled
393diff --git a/src/ap/afc.c b/src/ap/afc.c
394new file mode 100644
395index 000000000..c75d5d582
396--- /dev/null
397+++ b/src/ap/afc.c
398@@ -0,0 +1,918 @@
399+/*
400+ * Automated Frequency Coordination
401+ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
402+ *
403+ * This software may be distributed under the terms of the BSD license.
404+ * See README for more details.
405+ */
406+
407+#include <json-c/json.h>
408+#include <sys/un.h>
409+#include <time.h>
410+
411+#include "utils/includes.h"
412+#include "utils/common.h"
413+#include "utils/eloop.h"
414+#include "hostapd.h"
415+#include "acs.h"
416+#include "hw_features.h"
417+
418+#define HOSTAPD_AFC_RETRY_TIMEOUT 180
419+#define HOSTAPD_AFC_TIMEOUT 86400 /* 24h */
420+#define HOSTAPD_AFC_BUFSIZE 4096
421+
422+static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx);
423+
424+
425+static struct json_object *
426+hostapd_afc_build_location_request(struct hostapd_iface *iface)
427+{
428+ struct json_object *location_obj, *center_obj, *ellipse_obj;
429+ struct json_object *elevation_obj, *str_obj;
430+ struct hostapd_config *iconf = iface->conf;
431+ bool is_ap_indoor = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type);
432+
433+ location_obj = json_object_new_object();
434+ if (!location_obj)
435+ return NULL;
436+
437+ if (iconf->afc.location.type != LINEAR_POLYGON) {
438+ struct afc_linear_polygon *lp =
439+ &iconf->afc.location.linear_polygon_data[0];
440+
441+ ellipse_obj = json_object_new_object();
442+ if (!ellipse_obj)
443+ goto error;
444+
445+ center_obj = json_object_new_object();
446+ if (!center_obj)
447+ goto error;
448+
449+ json_object_object_add(ellipse_obj, "center", center_obj);
450+
451+ str_obj = json_object_new_double(lp->longitude);
452+ if (!str_obj)
453+ goto error;
454+
455+ json_object_object_add(center_obj, "longitude", str_obj);
456+ str_obj = json_object_new_double(lp->latitude);
457+ if (!str_obj)
458+ goto error;
459+
460+ json_object_object_add(center_obj, "latitude", str_obj);
461+
462+ }
463+
464+ switch (iconf->afc.location.type) {
465+ case LINEAR_POLYGON: {
466+ struct json_object *outer_boundary_obj;
467+ int i;
468+
469+ outer_boundary_obj = json_object_new_object();
470+ if (!outer_boundary_obj)
471+ goto error;
472+
473+ json_object_object_add(location_obj, "linearPolygon",
474+ outer_boundary_obj);
475+ ellipse_obj = json_object_new_array();
476+ if (!ellipse_obj)
477+ goto error;
478+
479+ json_object_object_add(outer_boundary_obj, "outerBoundary",
480+ ellipse_obj);
481+ for (i = 0;
482+ i < iconf->afc.location.n_linear_polygon_data; i++) {
483+ struct afc_linear_polygon *lp =
484+ &iconf->afc.location.linear_polygon_data[i];
485+
486+ center_obj = json_object_new_object();
487+ if (!center_obj)
488+ goto error;
489+
490+ json_object_array_add(ellipse_obj, center_obj);
491+ str_obj = json_object_new_double(lp->longitude);
492+ if (!str_obj)
493+ goto error;
494+
495+ json_object_object_add(center_obj, "longitude",
496+ str_obj);
497+ str_obj = json_object_new_double(lp->latitude);
498+ if (!str_obj)
499+ goto error;
500+
501+ json_object_object_add(center_obj, "latitude",
502+ str_obj);
503+ }
504+ break;
505+ }
506+ case RADIAL_POLYGON: {
507+ struct json_object *outer_boundary_obj;
508+ int i;
509+
510+ json_object_object_add(location_obj, "radialPolygon",
511+ ellipse_obj);
512+
513+ outer_boundary_obj = json_object_new_array();
514+ if (!outer_boundary_obj)
515+ goto error;
516+
517+ json_object_object_add(ellipse_obj, "outerBoundary",
518+ outer_boundary_obj);
519+ for (i = 0;
520+ i < iconf->afc.location.n_radial_polygon_data; i++) {
521+ struct afc_radial_polygon *rp =
522+ &iconf->afc.location.radial_polygon_data[i];
523+ struct json_object *angle_obj;
524+
525+ angle_obj = json_object_new_object();
526+ if (!angle_obj)
527+ goto error;
528+
529+ json_object_array_add(outer_boundary_obj, angle_obj);
530+
531+ str_obj = json_object_new_double(rp->angle);
532+ if (!str_obj)
533+ goto error;
534+
535+ json_object_object_add(angle_obj, "angle", str_obj);
536+ str_obj = json_object_new_double(rp->length);
537+ if (!str_obj)
538+ goto error;
539+
540+ json_object_object_add(angle_obj, "length", str_obj);
541+ }
542+ break;
543+ }
544+ case ELLIPSE:
545+ default:
546+ json_object_object_add(location_obj, "ellipse", ellipse_obj);
547+
548+ str_obj = json_object_new_int(iconf->afc.location.major_axis);
549+ if (!str_obj)
550+ goto error;
551+
552+ json_object_object_add(ellipse_obj, "majorAxis", str_obj);
553+ str_obj = json_object_new_int(iconf->afc.location.minor_axis);
554+ if (!str_obj)
555+ goto error;
556+
557+ json_object_object_add(ellipse_obj, "minorAxis", str_obj);
558+ str_obj = json_object_new_int(iconf->afc.location.orientation);
559+ if (!str_obj)
560+ goto error;
561+
562+ json_object_object_add(ellipse_obj, "orientation", str_obj);
563+ break;
564+ }
565+
566+ elevation_obj = json_object_new_object();
567+ if (!elevation_obj)
568+ goto error;
569+
570+ json_object_object_add(location_obj, "elevation",
571+ elevation_obj);
572+ str_obj = json_object_new_double(iconf->afc.location.height);
573+ if (!str_obj)
574+ goto error;
575+
576+ json_object_object_add(elevation_obj, "height", str_obj);
577+ str_obj = json_object_new_string(iconf->afc.location.height_type);
578+ if (!str_obj)
579+ goto error;
580+
581+ json_object_object_add(elevation_obj, "heightType", str_obj);
582+ str_obj = json_object_new_int(iconf->afc.location.vertical_tolerance);
583+ if (!str_obj)
584+ goto error;
585+
586+ json_object_object_add(elevation_obj, "verticalUncertainty",
587+ str_obj);
588+ str_obj = json_object_new_int(is_ap_indoor);
589+ if (!str_obj)
590+ goto error;
591+
592+ json_object_object_add(location_obj, "indoorDeployment", str_obj);
593+
594+ return location_obj;
595+
596+error:
597+ json_object_put(location_obj);
598+ return NULL;
599+}
600+
601+
602+static struct json_object * hostapd_afc_get_opclass_chan_list(u8 op_class)
603+{
604+ struct json_object *chan_list_obj, *str_obj;
605+ const struct oper_class_map *oper_class;
606+ int chan_offset, chan;
607+
608+ oper_class = get_oper_class(NULL, op_class);
609+ if (!oper_class)
610+ return NULL;
611+
612+ chan_list_obj = json_object_new_array();
613+ if (!chan_list_obj)
614+ return NULL;
615+
616+ switch (op_class) {
617+ case 132: /* 40MHz */
618+ chan_offset = 2;
619+ break;
620+ case 133: /* 80MHz */
621+ chan_offset = 6;
622+ break;
623+ case 134: /* 160MHz */
624+ chan_offset = 14;
625+ break;
626+ default:
627+ chan_offset = 0;
628+ break;
629+ }
630+
631+ for (chan = oper_class->min_chan; chan <= oper_class->max_chan;
632+ chan += oper_class->inc) {
633+ str_obj = json_object_new_int(chan + chan_offset);
634+ if (!str_obj) {
635+ json_object_put(chan_list_obj);
636+ return NULL;
637+ }
638+ json_object_array_add(chan_list_obj, str_obj);
639+ }
640+
641+ return chan_list_obj;
642+}
643+
644+
645+static struct json_object *
646+hostapd_afc_build_req_chan_list(struct hostapd_iface *iface)
647+{
648+ struct json_object *op_class_list_obj, *str_obj;
649+ struct hostapd_config *iconf = iface->conf;
650+ int i;
651+
652+ op_class_list_obj = json_object_new_array();
653+ if (!op_class_list_obj)
654+ return NULL;
655+
656+ for (i = 0; i < iconf->afc.n_op_class; i++) {
657+ struct json_object *op_class_obj, *chan_list_obj;
658+ u8 op_class = iconf->afc.op_class[i];
659+
660+ if (!is_6ghz_op_class(op_class))
661+ continue;
662+
663+ op_class_obj = json_object_new_object();
664+ if (!op_class_obj)
665+ goto error;
666+
667+ json_object_array_add(op_class_list_obj, op_class_obj);
668+ str_obj = json_object_new_int(op_class);
669+ if (!str_obj)
670+ goto error;
671+
672+ json_object_object_add(op_class_obj, "globalOperatingClass",
673+ str_obj);
674+
675+ chan_list_obj = hostapd_afc_get_opclass_chan_list(op_class);
676+ if (!chan_list_obj)
677+ goto error;
678+
679+ json_object_object_add(op_class_obj, "channelCfi",
680+ chan_list_obj);
681+ }
682+
683+ return op_class_list_obj;
684+
685+error:
686+ json_object_put(op_class_list_obj);
687+ return NULL;
688+}
689+
690+
691+static struct json_object *
692+hostapd_afc_build_request(struct hostapd_iface *iface)
693+{
694+ struct json_object *l1_obj, *l2_obj, *la1_obj, *la2_obj;
695+ struct json_object *s2_obj, *str_obj, *location_obj;
696+ struct hostapd_config *iconf = iface->conf;
697+ struct json_object *op_class_list_obj;
698+ int i;
699+
700+ l1_obj = json_object_new_object();
701+ if (!l1_obj)
702+ return NULL;
703+
704+ if (iconf->afc.request.version) {
705+ str_obj = json_object_new_string(iconf->afc.request.version);
706+ if (!str_obj)
707+ goto error;
708+
709+ json_object_object_add(l1_obj, "version", str_obj);
710+ }
711+
712+ la1_obj = json_object_new_array();
713+ if (!la1_obj)
714+ goto error;
715+
716+ json_object_object_add(l1_obj, "availableSpectrumInquiryRequests",
717+ la1_obj);
718+ l2_obj = json_object_new_object();
719+ if (!l2_obj)
720+ goto error;
721+
722+ json_object_array_add(la1_obj, l2_obj);
723+ if (iconf->afc.request.id) {
724+ str_obj = json_object_new_string(iconf->afc.request.id);
725+ if (!str_obj)
726+ goto error;
727+
728+ json_object_object_add(l2_obj, "requestId", str_obj);
729+ }
730+
731+ s2_obj = json_object_new_object();
732+ if (!s2_obj)
733+ goto error;
734+
735+ json_object_object_add(l2_obj, "deviceDescriptor", s2_obj);
736+ if (iconf->afc.request.sn) {
737+ str_obj = json_object_new_string(iconf->afc.request.sn);
738+ if (!str_obj)
739+ goto error;
740+
741+ json_object_object_add(s2_obj, "serialNumber", str_obj);
742+ }
743+
744+ la2_obj = json_object_new_array();
745+ if (!la2_obj)
746+ goto error;
747+
748+ json_object_object_add(s2_obj, "certificationId", la2_obj);
749+ for (i = 0; i < iconf->afc.n_cert_ids; i++) {
750+ struct json_object *obj;
751+
752+ obj = json_object_new_object();
753+ if (!obj)
754+ goto error;
755+
756+ json_object_array_add(la2_obj, obj);
757+ str_obj =
758+ json_object_new_string(iconf->afc.cert_ids[i].rulset);
759+ if (!str_obj)
760+ goto error;
761+
762+ json_object_object_add(obj, "rulesetId", str_obj);
763+ str_obj = json_object_new_string(iconf->afc.cert_ids[i].id);
764+ if (!str_obj)
765+ goto error;
766+
767+ json_object_object_add(obj, "id", str_obj);
768+ }
769+
770+ location_obj = hostapd_afc_build_location_request(iface);
771+ if (!location_obj)
772+ goto error;
773+
774+ json_object_object_add(l2_obj, "location", location_obj);
775+ str_obj = json_object_new_int(iconf->afc.min_power);
776+ if (!str_obj)
777+ goto error;
778+
779+ json_object_object_add(l2_obj, "minDesiredPower", str_obj);
780+
781+ if (iconf->afc.n_freq_range) {
782+ struct json_object *freq_obj;
783+
784+ freq_obj = json_object_new_array();
785+ if (!freq_obj)
786+ goto error;
787+
788+ json_object_object_add(l2_obj, "inquiredFrequencyRange",
789+ freq_obj);
790+ for (i = 0; i < iconf->afc.n_freq_range; i++) {
791+ struct afc_freq_range *fr = &iconf->afc.freq_range[i];
792+ struct json_object *obj;
793+
794+ obj = json_object_new_object();
795+ if (!obj)
796+ goto error;
797+
798+ json_object_array_add(freq_obj, obj);
799+ str_obj = json_object_new_int(fr->low_freq);
800+ if (!str_obj)
801+ goto error;
802+
803+ json_object_object_add(obj, "lowFrequency", str_obj);
804+ str_obj = json_object_new_int(fr->high_freq);
805+ if (!str_obj)
806+ goto error;
807+
808+ json_object_object_add(obj, "highFrequency", str_obj);
809+ }
810+ }
811+
812+ op_class_list_obj = hostapd_afc_build_req_chan_list(iface);
813+ if (!op_class_list_obj)
814+ goto error;
815+
816+ json_object_object_add(l2_obj, "inquiredChannels", op_class_list_obj);
817+
818+ wpa_printf(MSG_DEBUG, "Pending AFC request: %s",
819+ json_object_get_string(l1_obj));
820+
821+ return l1_obj;
822+
823+error:
824+ json_object_put(l1_obj);
825+
826+ return NULL;
827+}
828+
829+
830+static int
831+hostad_afc_parse_available_freq_info(struct hostapd_iface *iface,
832+ struct json_object *reply_elem_obj)
833+{
834+ struct afc_freq_range_elem *f = NULL;
835+ struct json_object *obj;
836+ int i, count = 0;
837+
838+ if (!json_object_object_get_ex(reply_elem_obj,
839+ "availableFrequencyInfo", &obj))
840+ return 0;
841+
842+ for (i = 0; i < json_object_array_length(obj); i++) {
843+ struct json_object *range_elem_obj, *freq_range_obj;
844+ struct json_object *high_freq_obj, *low_freq_obj;
845+ struct json_object *max_psd_obj;
846+
847+ range_elem_obj = json_object_array_get_idx(obj, i);
848+ if (!json_object_object_get_ex(range_elem_obj,
849+ "frequencyRange",
850+ &freq_range_obj))
851+ continue;
852+
853+ if (!json_object_object_get_ex(freq_range_obj,
854+ "lowFrequency",
855+ &low_freq_obj))
856+ continue;
857+
858+ if (!json_object_object_get_ex(freq_range_obj,
859+ "highFrequency",
860+ &high_freq_obj))
861+ continue;
862+
863+ if (!json_object_object_get_ex(range_elem_obj, "maxPsd",
864+ &max_psd_obj) &&
865+ !json_object_object_get_ex(range_elem_obj, "maxPSD",
866+ &max_psd_obj))
867+ continue;
868+
869+ f = os_realloc_array(f, count + 1, sizeof(*f));
870+ if (!f)
871+ return -ENOMEM;
872+
873+ f[count].low_freq = json_object_get_int(low_freq_obj);
874+ f[count].high_freq = json_object_get_int(high_freq_obj);
875+ f[count++].max_psd = json_object_get_int(max_psd_obj);
876+ }
877+ iface->afc.freq_range = f;
878+ iface->afc.num_freq_range = count;
879+
880+ return 0;
881+}
882+
883+
884+static int hostad_afc_update_chan_info(struct afc_chan_info_elem **chan_list,
885+ int *chan_list_size, u8 op_class,
886+ int center_chan, int power)
887+{
888+ int num_low_subchan, ch, count = *chan_list_size;
889+ struct afc_chan_info_elem *c = *chan_list;
890+
891+ switch (op_class) {
892+ case 132: /* 40MHz */
893+ num_low_subchan = 2;
894+ break;
895+ case 133: /* 80MHz */
896+ num_low_subchan = 6;
897+ break;
898+ case 134: /* 160MHz */
899+ num_low_subchan = 14;
900+ break;
901+ default:
902+ num_low_subchan = 0;
903+ break;
904+ }
905+
906+ for (ch = center_chan - num_low_subchan;
907+ ch <= center_chan + num_low_subchan; ch += 4) {
908+ int i;
909+
910+ for (i = 0; i < count; i++) {
911+ if (c[i].chan == ch)
912+ break;
913+ }
914+
915+ if (i == count) {
916+ c = os_realloc_array(c, count + 1, sizeof(*c));
917+ if (!c)
918+ return -ENOMEM;
919+
920+ c[count].chan = ch;
921+ c[count++].power = power;
922+ }
923+ }
924+
925+ *chan_list_size = count;
926+ *chan_list = c;
927+
928+ return 0;
929+}
930+
931+
932+static int
933+hostad_afc_parse_available_chan_info(struct hostapd_iface *iface,
934+ struct json_object *reply_elem_obj)
935+{
936+ struct afc_chan_info_elem *c = NULL;
937+ struct json_object *obj;
938+ int i, count = 0;
939+
940+ if (!json_object_object_get_ex(reply_elem_obj,
941+ "availableChannelInfo", &obj))
942+ return 0;
943+
944+ for (i = 0; i < json_object_array_length(obj); i++) {
945+ struct json_object *range_elem_obj, *op_class_obj;
946+ struct json_object *chan_cfi_obj, *max_eirp_obj;
947+ int ch, op_class;
948+
949+ range_elem_obj = json_object_array_get_idx(obj, i);
950+ if (!json_object_object_get_ex(range_elem_obj,
951+ "globalOperatingClass",
952+ &op_class_obj))
953+ continue;
954+
955+ if (!json_object_object_get_ex(range_elem_obj, "maxEirp",
956+ &max_eirp_obj))
957+ continue;
958+
959+ if (!json_object_object_get_ex(range_elem_obj, "channelCfi",
960+ &chan_cfi_obj))
961+ continue;
962+
963+ op_class = json_object_get_int(op_class_obj);
964+ for (ch = 0;
965+ ch < json_object_array_length(chan_cfi_obj); ch++) {
966+ struct json_object *pwr_obj;
967+ struct json_object *ch_obj;
968+ int channel, power;
969+
970+ ch_obj = json_object_array_get_idx(chan_cfi_obj, ch);
971+ if (!ch_obj)
972+ continue;
973+
974+ pwr_obj = json_object_array_get_idx(max_eirp_obj, ch);
975+ if (!pwr_obj)
976+ continue;
977+
978+ channel = json_object_get_int(ch_obj);
979+ power = json_object_get_int(pwr_obj);
980+
981+ hostad_afc_update_chan_info(&c, &count, op_class,
982+ channel, power);
983+ }
984+ iface->afc.chan_info_list = c;
985+ iface->afc.num_chan_info = count;
986+ }
987+
988+ return 0;
989+}
990+
991+
992+static int hostad_afc_get_timeout(struct json_object *obj)
993+{
994+ time_t t, now;
995+ struct tm tm;
996+
997+ if (sscanf(json_object_get_string(obj), "%d-%d-%dT%d:%d:%dZ",
998+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,
999+ &tm.tm_min, &tm.tm_sec) <= 0)
1000+ return HOSTAPD_AFC_TIMEOUT;
1001+
1002+ tm.tm_year -= 1900;
1003+ tm.tm_mon -= 1;
1004+ tm.tm_isdst = -1;
1005+ t = mktime(&tm);
1006+ time(&now);
1007+
1008+ return now > t ? HOSTAPD_AFC_RETRY_TIMEOUT : (t - now) * 80 / 100;
1009+}
1010+
1011+
1012+static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply)
1013+{
1014+ struct json_object *payload_obj, *reply_obj, *version_obj;
1015+ struct hostapd_config *iconf = iface->conf;
1016+ int i, request_timeout = -1, ret = -EINVAL;
1017+
1018+ wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply);
1019+ payload_obj = json_tokener_parse(reply);
1020+ if (!payload_obj)
1021+ return -EINVAL;
1022+
1023+ if (!json_object_object_get_ex(payload_obj, "version", &version_obj))
1024+ return -EINVAL;
1025+
1026+ if (iconf->afc.request.version &&
1027+ os_strcmp(iconf->afc.request.version,
1028+ json_object_get_string(version_obj)))
1029+ return -EINVAL;
1030+
1031+ if (!json_object_object_get_ex(payload_obj,
1032+ "availableSpectrumInquiryResponses",
1033+ &reply_obj))
1034+ return -EINVAL;
1035+
1036+ for (i = 0; i < json_object_array_length(reply_obj); i++) {
1037+ struct json_object *reply_elem_obj, *obj, *status_obj;
1038+ int j, status = -EINVAL;
1039+
1040+ reply_elem_obj = json_object_array_get_idx(reply_obj, i);
1041+ if (!reply_elem_obj)
1042+ continue;
1043+
1044+ if (!json_object_object_get_ex(reply_elem_obj, "requestId",
1045+ &obj))
1046+ continue;
1047+
1048+ if (iconf->afc.request.id &&
1049+ os_strcmp(iconf->afc.request.id,
1050+ json_object_get_string(obj)))
1051+ continue;
1052+
1053+ if (!json_object_object_get_ex(reply_elem_obj, "rulesetId",
1054+ &obj))
1055+ continue;
1056+
1057+ for (j = 0; j < iconf->afc.n_cert_ids; j++) {
1058+ if (!os_strcmp(iconf->afc.cert_ids[j].rulset,
1059+ json_object_get_string(obj)))
1060+ break;
1061+ }
1062+
1063+ if (j == iconf->afc.n_cert_ids)
1064+ continue;
1065+
1066+ if (!json_object_object_get_ex(reply_elem_obj, "response",
1067+ &obj))
1068+ continue;
1069+
1070+ if (json_object_object_get_ex(obj, "shortDescription",
1071+ &status_obj))
1072+ wpa_printf(MSG_DEBUG, "AFC reply element %d: %s",
1073+ i, json_object_get_string(status_obj));
1074+
1075+ if (json_object_object_get_ex(obj, "responseCode",
1076+ &status_obj))
1077+ status = json_object_get_int(status_obj);
1078+
1079+ if (status < 0)
1080+ continue;
1081+
1082+ if (hostad_afc_parse_available_freq_info(iface,
1083+ reply_elem_obj) ||
1084+ hostad_afc_parse_available_chan_info(iface,
1085+ reply_elem_obj))
1086+ continue;
1087+
1088+ if (json_object_object_get_ex(reply_elem_obj,
1089+ "availabilityExpireTime",
1090+ &obj)) {
1091+ int timeout = hostad_afc_get_timeout(obj);
1092+
1093+ if (request_timeout < 0 || timeout < request_timeout)
1094+ request_timeout = timeout;
1095+ }
1096+
1097+ ret = status;
1098+ }
1099+
1100+ iface->afc.data_valid = true;
1101+ iface->afc.timeout = request_timeout;
1102+ if (iface->afc.timeout < 0)
1103+ iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
1104+
1105+ return ret;
1106+}
1107+
1108+
1109+static int hostapd_afc_send_receive(struct hostapd_iface *iface)
1110+{
1111+ struct hostapd_config *iconf = iface->conf;
1112+ json_object *request_obj = NULL;
1113+ struct timeval sock_timeout = {
1114+ .tv_sec = 5,
1115+ };
1116+ struct sockaddr_un addr = {
1117+ .sun_family = AF_UNIX,
1118+#ifdef __FreeBSD__
1119+ .sun_len = sizeof(addr),
1120+#endif /* __FreeBSD__ */
1121+ };
1122+ char buf[HOSTAPD_AFC_BUFSIZE] = {};
1123+ const char *request;
1124+ int sockfd, ret;
1125+ fd_set read_set;
1126+
1127+ if (iface->afc.data_valid) {
1128+ /* AFC data already downloaded from the server */
1129+ return 0;
1130+ }
1131+
1132+ iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
1133+ if (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) {
1134+ wpa_printf(MSG_ERROR, "Malformed AFC socket string %s",
1135+ iconf->afc.socket);
1136+ return -EINVAL;
1137+ }
1138+
1139+ os_strlcpy(addr.sun_path, iconf->afc.socket, sizeof(addr.sun_path));
1140+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
1141+ if (sockfd < 0) {
1142+ wpa_printf(MSG_ERROR, "Failed creating AFC socket");
1143+ return sockfd;
1144+ }
1145+
1146+ if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
1147+ wpa_printf(MSG_ERROR, "Failed connecting AFC socket");
1148+ ret = -EIO;
1149+ goto close_sock;
1150+ }
1151+
1152+ request_obj = hostapd_afc_build_request(iface);
1153+ if (!request_obj) {
1154+ ret = -ENOMEM;
1155+ goto close_sock;
1156+ }
1157+
1158+ request = json_object_to_json_string(request_obj);
1159+ if (send(sockfd, request, strlen(request), 0) < 0) {
1160+ wpa_printf(MSG_ERROR, "Failed sending AFC request");
1161+ ret = -EIO;
1162+ goto close_sock;
1163+ }
1164+
1165+ FD_ZERO(&read_set);
1166+ FD_SET(sockfd, &read_set);
1167+ if (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) {
1168+ wpa_printf(MSG_ERROR, "Select failed on AFC socket");
1169+ ret = -errno;
1170+ goto close_sock;
1171+ }
1172+
1173+ if (!FD_ISSET(sockfd, &read_set)) {
1174+ ret = -EIO;
1175+ goto close_sock;
1176+ }
1177+
1178+ ret = recv(sockfd, buf, sizeof(buf), 0);
1179+ if (ret <= 0)
1180+ goto close_sock;
1181+
1182+ ret = hostapd_afc_parse_reply(iface, buf);
1183+close_sock:
1184+ json_object_put(request_obj);
1185+ close(sockfd);
1186+
1187+ return ret;
1188+}
1189+
1190+
1191+static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface)
1192+{
1193+ const struct oper_class_map *oper_class;
1194+ int ch;
1195+
1196+ oper_class = get_oper_class(NULL, iface->conf->op_class);
1197+ if (!oper_class)
1198+ return false;
1199+
1200+ for (ch = oper_class->min_chan; ch <= oper_class->max_chan;
1201+ ch += oper_class->inc) {
1202+ struct hostapd_hw_modes *mode = iface->current_mode;
1203+ int i;
1204+
1205+ for (i = 0; i < mode->num_channels; i++) {
1206+ struct hostapd_channel_data *chan = &mode->channels[i];
1207+
1208+ if (chan->chan == ch &&
1209+ !(chan->flag & HOSTAPD_CHAN_DISABLED))
1210+ return true;
1211+ }
1212+ }
1213+
1214+ return false;
1215+}
1216+
1217+
1218+int hostapd_afc_handle_request(struct hostapd_iface *iface)
1219+{
1220+ struct hostapd_config *iconf = iface->conf;
1221+ int ret;
1222+
1223+ /* AFC is required just for standard power AP */
1224+ if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))
1225+ return 1;
1226+
1227+ if (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq))
1228+ return 1;
1229+
1230+ if (iface->state == HAPD_IFACE_ACS)
1231+ return 1;
1232+
1233+ ret = hostapd_afc_send_receive(iface);
1234+ if (ret < 0) {
1235+ /*
1236+ * If the connection to the AFCD failed, resched for a
1237+ * future attempt.
1238+ */
1239+ wpa_printf(MSG_ERROR, "AFC connection failed: %d", ret);
1240+ if (ret == -EIO)
1241+ ret = 0;
1242+ goto resched;
1243+ }
1244+
1245+ hostap_afc_disable_channels(iface);
1246+ if (!hostapd_afc_has_usable_chans(iface))
1247+ goto resched;
1248+
1249+ /* Trigger an ACS freq scan */
1250+ iconf->channel = 0;
1251+ iface->freq = 0;
1252+
1253+ if (acs_init(iface) != HOSTAPD_CHAN_ACS) {
1254+ wpa_printf(MSG_ERROR, "Could not start ACS");
1255+ ret = -EINVAL;
1256+ }
1257+
1258+resched:
1259+ eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
1260+ eloop_register_timeout(iface->afc.timeout, 0,
1261+ hostapd_afc_timeout_handler, iface, NULL);
1262+
1263+ return ret;
1264+}
1265+
1266+
1267+static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface)
1268+{
1269+ os_free(iface->afc.chan_info_list);
1270+ os_free(iface->afc.freq_range);
1271+
1272+ iface->afc.num_freq_range = 0;
1273+ iface->afc.num_chan_info = 0;
1274+
1275+ iface->afc.chan_info_list = NULL;
1276+ iface->afc.freq_range = NULL;
1277+
1278+ iface->afc.data_valid = false;
1279+}
1280+
1281+
1282+static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx)
1283+{
1284+ struct hostapd_iface *iface = eloop_ctx;
1285+ bool restart_iface = true;
1286+
1287+ hostapd_afc_delete_data_from_server(iface);
1288+ if (iface->state != HAPD_IFACE_ENABLED) {
1289+ /* Hostapd is not fully enabled yet, toogle the interface */
1290+ goto restart_interface;
1291+ }
1292+
1293+ if (hostapd_afc_send_receive(iface) < 0 ||
1294+ hostapd_get_hw_features(iface)) {
1295+ restart_iface = false;
1296+ goto restart_interface;
1297+ }
1298+
1299+ if (hostapd_is_usable_chans(iface))
1300+ goto resched;
1301+
1302+ restart_iface = hostapd_afc_has_usable_chans(iface);
1303+ if (restart_iface) {
1304+ /* Trigger an ACS freq scan */
1305+ iface->conf->channel = 0;
1306+ iface->freq = 0;
1307+ }
1308+
1309+restart_interface:
1310+ hostapd_disable_iface(iface);
1311+ if (restart_iface)
1312+ hostapd_enable_iface(iface);
1313+resched:
1314+ eloop_register_timeout(iface->afc.timeout, 0,
1315+ hostapd_afc_timeout_handler, iface, NULL);
1316+}
1317diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
1318index 1a18df617..ca67aeb41 100644
1319--- a/src/ap/ap_config.c
1320+++ b/src/ap/ap_config.c
1321@@ -1035,6 +1035,22 @@ void hostapd_config_free(struct hostapd_config *conf)
1322 #endif /* CONFIG_ACS */
1323 wpabuf_free(conf->lci);
1324 wpabuf_free(conf->civic);
1325+#ifdef CONFIG_AFC
1326+ os_free(conf->afc.socket);
1327+ os_free(conf->afc.request.version);
1328+ os_free(conf->afc.request.id);
1329+ os_free(conf->afc.request.sn);
1330+ for (i = 0; i < conf->afc.n_cert_ids; i++) {
1331+ os_free(conf->afc.cert_ids[i].rulset);
1332+ os_free(conf->afc.cert_ids[i].id);
1333+ }
1334+ os_free(conf->afc.cert_ids);
1335+ os_free(conf->afc.location.height_type);
1336+ os_free(conf->afc.location.linear_polygon_data);
1337+ os_free(conf->afc.location.radial_polygon_data);
1338+ os_free(conf->afc.freq_range);
1339+ os_free(conf->afc.op_class);
1340+#endif /* CONFIG_AFC */
1341
1342 os_free(conf);
1343 }
1344diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
1345index 754d55331..2330163c4 100644
1346--- a/src/ap/ap_config.h
1347+++ b/src/ap/ap_config.h
1348@@ -1225,6 +1225,53 @@ struct hostapd_config {
1349 MBSSID_ENABLED = 1,
1350 ENHANCED_MBSSID_ENABLED = 2,
1351 } mbssid;
1352+
1353+#ifdef CONFIG_AFC
1354+ struct {
1355+ char *socket;
1356+ struct {
1357+ char *version;
1358+ char *id;
1359+ char *sn;
1360+ } request;
1361+ unsigned int n_cert_ids;
1362+ struct cert_id {
1363+ char *rulset;
1364+ char *id;
1365+ } *cert_ids;
1366+ struct {
1367+ enum afc_location_type {
1368+ ELLIPSE,
1369+ LINEAR_POLYGON,
1370+ RADIAL_POLYGON,
1371+ } type;
1372+ unsigned int n_linear_polygon_data;
1373+ struct afc_linear_polygon {
1374+ double longitude;
1375+ double latitude;
1376+ } *linear_polygon_data;
1377+ unsigned int n_radial_polygon_data;
1378+ struct afc_radial_polygon {
1379+ double length;
1380+ double angle;
1381+ } *radial_polygon_data;
1382+ int major_axis;
1383+ int minor_axis;
1384+ int orientation;
1385+ double height;
1386+ char *height_type;
1387+ int vertical_tolerance;
1388+ } location;
1389+ unsigned int n_freq_range;
1390+ struct afc_freq_range {
1391+ int low_freq;
1392+ int high_freq;
1393+ } *freq_range;
1394+ unsigned int n_op_class;
1395+ unsigned int *op_class;
1396+ int min_power;
1397+ } afc;
1398+#endif /* CONFIG_AFC */
1399 };
1400
1401
1402diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
1403index ff1d8f9d0..916ac00c4 100644
1404--- a/src/ap/hostapd.c
1405+++ b/src/ap/hostapd.c
1406@@ -2497,6 +2497,16 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface,
1407 }
1408 #endif /* CONFIG_MESH */
1409
1410+#ifdef CONFIG_AFC
1411+ /* check AFC for 6GHz channels. */
1412+ res = hostapd_afc_handle_request(iface);
1413+ if (res <= 0) {
1414+ if (res < 0)
1415+ goto fail;
1416+ return res;
1417+ }
1418+#endif /* CONFIG_AFC */
1419+
1420 if (!delay_apply_cfg &&
1421 hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq,
1422 hapd->iconf->channel,
1423@@ -2968,6 +2978,10 @@ void hostapd_interface_free(struct hostapd_iface *iface)
1424 __func__, iface->bss[j]);
1425 os_free(iface->bss[j]);
1426 }
1427+#ifdef CONFIG_AFC
1428+ os_free(iface->afc.chan_info_list);
1429+ os_free(iface->afc.freq_range);
1430+#endif
1431 hostapd_cleanup_iface(iface);
1432 }
1433
1434@@ -4888,3 +4902,49 @@ u16 hostapd_get_punct_bitmap(struct hostapd_data *hapd)
1435
1436 return punct_bitmap;
1437 }
1438+
1439+
1440+void hostap_afc_disable_channels(struct hostapd_iface *iface)
1441+{
1442+#ifdef CONFIG_AFC
1443+ struct hostapd_hw_modes *mode;
1444+ int i;
1445+
1446+ if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A)
1447+ return;
1448+
1449+ if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))
1450+ return;
1451+
1452+ if (!iface->afc.data_valid)
1453+ return;
1454+
1455+ mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A];
1456+ for (i = 0; i < mode->num_channels; i++) {
1457+ struct hostapd_channel_data *chan = &mode->channels[i];
1458+ int j;
1459+
1460+ if (!is_6ghz_freq(chan->freq))
1461+ continue;
1462+
1463+ for (j = 0; j < iface->afc.num_freq_range; j++) {
1464+ if (chan->freq >= iface->afc.freq_range[j].low_freq &&
1465+ chan->freq <= iface->afc.freq_range[j].high_freq)
1466+ break;
1467+ }
1468+
1469+ if (j != iface->afc.num_freq_range)
1470+ continue;
1471+
1472+ for (j = 0; j < iface->afc.num_chan_info; j++) {
1473+ if (chan->chan == iface->afc.chan_info_list[j].chan)
1474+ break;
1475+ }
1476+
1477+ if (j != iface->afc.num_chan_info)
1478+ continue;
1479+
1480+ chan->flag |= HOSTAPD_CHAN_DISABLED;
1481+ }
1482+#endif /* CONFIG_AFC */
1483+}
1484diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
1485index d12efb104..18bcb82d9 100644
1486--- a/src/ap/hostapd.h
1487+++ b/src/ap/hostapd.h
1488@@ -700,9 +700,38 @@ struct hostapd_iface {
1489
1490 /* Configured freq of interface is NO_IR */
1491 bool is_no_ir;
1492+
1493+#ifdef CONFIG_AFC
1494+ struct {
1495+ int timeout;
1496+ unsigned int num_freq_range;
1497+ struct afc_freq_range_elem {
1498+ int low_freq;
1499+ int high_freq;
1500+ /**
1501+ * max eirp power spectral density received from
1502+ * the AFC coordinator for this band
1503+ */
1504+ int max_psd;
1505+ } *freq_range;
1506+ unsigned int num_chan_info;
1507+ struct afc_chan_info_elem {
1508+ int chan;
1509+ /**
1510+ * max eirp power received from the AFC coordinator
1511+ * for this channel
1512+ */
1513+ int power;
1514+ } *chan_info_list;
1515+ bool data_valid;
1516+ } afc;
1517+#endif /* CONFIG_AFC */
1518 };
1519
1520 /* hostapd.c */
1521+void hostap_afc_disable_channels(struct hostapd_iface *iface);
1522+int hostapd_afc_handle_request(struct hostapd_iface *iface);
1523+
1524 int hostapd_for_each_interface(struct hapd_interfaces *interfaces,
1525 int (*cb)(struct hostapd_iface *iface,
1526 void *ctx), void *ctx);
1527diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
1528index e652d7504..222f3dc05 100644
1529--- a/src/ap/hw_features.c
1530+++ b/src/ap/hw_features.c
1531@@ -114,6 +114,8 @@ int hostapd_get_hw_features(struct hostapd_iface *iface)
1532 iface->hw_features = modes;
1533 iface->num_hw_features = num_modes;
1534
1535+ hostap_afc_disable_channels(iface);
1536+
1537 for (i = 0; i < num_modes; i++) {
1538 struct hostapd_hw_modes *feature = &modes[i];
1539 int dfs_enabled = hapd->iconf->ieee80211h &&
1540--
15412.39.2
1542