[rdkb][common][bsp][Refactor and sync wifi from openwrt]

[Description]
3a2eef0b [MAC80211][Release][Update release note for Filogic 880/860 MLO Beta release]
cfbd2411 [MAC80211][Release][Filogic 880/860 MLO Beta release]
6c180e3f [MAC80211][WiFi7][misc][Add Eagle BE14000 efem default bin]
a55f34db [MAC80211][Release][Prepare for Filogic 880/860 release]
5b45ebca [MAC80211][WiFi7][hostapd][Add puncture bitmap to ucode]
95bbea73 [MAC80211][WiFi6][mt76][Add PID to only report data-frame TX rate]
b15ced26 [MAC80211][WiFi6][hostapd][Fix DFS channel selection issue]
d59133cb [MAC80211][WiFi6][mt76][Fix pse info not correct information]
3921b4b2 [MAC80211][WiFi6][mt76][Fix incomplete QoS-map setting to FW]
4e7690c7 [MAC80211][WiFi6/7][app][Change ATECHANNEL mapping cmd]
eb37af90 [MAC80211][WiFi7][app][Add support for per-packet bw & primary selection]
0ea82adf [MAC80211][WiFi6][core][Fix DFS CAC issue after CSA]

[Release-log]

Change-Id: I9bec97ec1b2e1c49ed43a812a07a5b21fcbb70a6
diff --git a/recipes-wifi/wpa-supplicant/files/patches-2.10.3/0007-hostapd-ap-add-AFC-client-support.patch b/recipes-wifi/wpa-supplicant/files/patches-2.10.3/0007-hostapd-ap-add-AFC-client-support.patch
new file mode 100644
index 0000000..0dce399
--- /dev/null
+++ b/recipes-wifi/wpa-supplicant/files/patches-2.10.3/0007-hostapd-ap-add-AFC-client-support.patch
@@ -0,0 +1,1650 @@
+From 3bd1d33a94f25337fd70df60ee5a42a60f95cba9 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 May 2024 11:50:27 +0200
+Subject: [PATCH 007/126] hostapd: ap: add AFC client support
+
+Introduce Automated Frequency Coordination (AFC) support for UNII-5 and
+UNII-7 6GHz bands.
+AFC client will connect to AFCD providing AP related parameter for AFC
+coordinator (e.g. geolocation, supported frequencies, ..).
+AFC is required for Standard Power Devices (SPDs) to determine a lists
+of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
+AFC hostapd client is tested with AFC DUT Test Harness [0].
+
+[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main
+
+Tested-by: Felix Fietkau <nbd@nbd.name>
+Tested-by: Allen Ye <allen.ye@mediatek.com>
+Tested-by: Krishna Chaitanya <chaitanya.mgit@gmail.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ hostapd/Makefile      |    8 +
+ hostapd/config_file.c |  261 +++++++++++
+ hostapd/defconfig     |    3 +
+ hostapd/hostapd.conf  |   42 ++
+ src/ap/afc.c          | 1041 +++++++++++++++++++++++++++++++++++++++++
+ src/ap/ap_config.c    |   16 +
+ src/ap/ap_config.h    |   47 ++
+ src/ap/hostapd.c      |   16 +
+ src/ap/hostapd.h      |   45 ++
+ src/ap/hw_features.c  |    2 +
+ 10 files changed, 1481 insertions(+)
+ create mode 100644 src/ap/afc.c
+
+diff --git a/hostapd/Makefile b/hostapd/Makefile
+index ca4439234..78171025e 100644
+--- a/hostapd/Makefile
++++ b/hostapd/Makefile
+@@ -104,6 +104,14 @@ CFLAGS += -DCONFIG_TAXONOMY
+ OBJS += ../src/ap/taxonomy.o
+ endif
+ 
++ifdef CONFIG_IEEE80211AX
++ifdef CONFIG_AFC
++CFLAGS += -DCONFIG_AFC
++OBJS += ../src/ap/afc.o
++LIBS += -ljson-c
++endif
++endif
++
+ ifdef CONFIG_MODULE_TESTS
+ CFLAGS += -DCONFIG_MODULE_TESTS
+ OBJS += hapd_module_tests.o
+diff --git a/hostapd/config_file.c b/hostapd/config_file.c
+index 96f1b1749..a86621ed9 100644
+--- a/hostapd/config_file.c
++++ b/hostapd/config_file.c
+@@ -1281,6 +1281,190 @@ static int hostapd_parse_he_srg_bitmap(u8 *bitmap, char *val)
+ 	return 0;
+ }
+ 
++
++#ifdef CONFIG_AFC
++static int hostapd_afc_parse_cert_ids(struct hostapd_config *conf, char *pos)
++{
++	struct cert_id *c = NULL;
++	int i, count = 0;
++
++	while (pos && pos[0]) {
++		char *p;
++
++		c = os_realloc_array(c, count + 1, sizeof(*c));
++		if (!c)
++			return -ENOMEM;
++
++		i = count;
++		count++;
++
++		p = os_strchr(pos, ':');
++		if (!p)
++			goto error;
++
++		*p++ = '\0';
++		if (!p || !p[0])
++			goto error;
++
++		c[i].rulset = os_malloc(os_strlen(pos) + 1);
++		if (!c[i].rulset)
++			goto error;
++
++		os_strlcpy(c[i].rulset, pos, os_strlen(pos) + 1);
++		pos = p;
++		p = os_strchr(pos, ',');
++		if (p)
++			*p++ = '\0';
++
++		c[i].id = os_malloc(os_strlen(pos) + 1);
++		if (!c[i].id)
++			goto error;
++
++		os_strlcpy(c[i].id, pos, os_strlen(pos) + 1);
++		pos = p;
++	}
++
++	conf->afc.n_cert_ids = count;
++	conf->afc.cert_ids = c;
++
++	return 0;
++
++error:
++	for (i = 0; i < count; i++) {
++		os_free(c[i].rulset);
++		os_free(c[i].id);
++	}
++	os_free(c);
++
++	return -ENOMEM;
++}
++
++
++static int hostapd_afc_parse_position_data(struct afc_linear_polygon **data,
++					   unsigned int *n_linear_polygon_data,
++					   char *pos)
++{
++	struct afc_linear_polygon *d = NULL;
++	int i, count = 0;
++
++	while (pos && pos[0]) {
++		char *p, *end;
++
++		d = os_realloc_array(d, count + 1, sizeof(*d));
++		if (!d)
++			return -ENOMEM;
++
++		i = count;
++		count++;
++
++		p = os_strchr(pos, ':');
++		if (!p)
++			goto error;
++
++		*p++ = '\0';
++		if (!p || !p[0])
++			goto error;
++
++		d[i].longitude = strtod(pos, &end);
++		if (*end)
++			goto error;
++
++		pos = p;
++		p = os_strchr(pos, ',');
++		if (p)
++			*p++ = '\0';
++
++		d[i].latitude = strtod(pos, &end);
++		if (*end)
++			goto error;
++
++		pos = p;
++	}
++
++	*n_linear_polygon_data = count;
++	*data = d;
++
++	return 0;
++
++error:
++	os_free(d);
++	return -ENOMEM;
++}
++
++
++static int hostapd_afc_parse_freq_range(struct hostapd_config *conf, char *pos)
++{
++	struct afc_freq_range *f = NULL;
++	int i, count = 0;
++
++	while (pos && pos[0]) {
++		char *p;
++
++		f = os_realloc_array(f, count + 1, sizeof(*f));
++		if (!f)
++			return -ENOMEM;
++
++		i = count;
++		count++;
++
++		p = os_strchr(pos, ':');
++		if (!p)
++			goto error;
++
++		*p++ = '\0';
++		if (!p || !p[0])
++			goto error;
++
++		f[i].low_freq = atoi(pos);
++		pos = p;
++		p = os_strchr(pos, ',');
++		if (p)
++			*p++ = '\0';
++
++		f[i].high_freq = atoi(pos);
++		pos = p;
++	}
++
++	conf->afc.n_freq_range = count;
++	conf->afc.freq_range = f;
++
++	return 0;
++
++error:
++	os_free(f);
++	return -ENOMEM;
++}
++
++
++static int hostapd_afc_parse_op_class(struct hostapd_config *conf, char *pos)
++{
++	unsigned int *oc = NULL;
++	int i, count = 0;
++
++	while (pos && pos[0]) {
++		char *p;
++
++		oc = os_realloc_array(oc, count + 1, sizeof(*oc));
++		if (!oc)
++			return -ENOMEM;
++
++		i = count;
++		count++;
++
++		p = os_strchr(pos, ',');
++		if (p)
++			*p++ = '\0';
++
++		oc[i] = atoi(pos);
++		pos = p;
++	}
++
++	conf->afc.n_op_class = count;
++	conf->afc.op_class = oc;
++
++	return 0;
++}
++#endif /* CONFIG_AFC */
+ #endif /* CONFIG_IEEE80211AX */
+ 
+ 
+@@ -3955,6 +4139,83 @@ static int hostapd_config_fill(struct hostapd_config *conf,
+ 			return 1;
+ 		}
+ 		bss->unsol_bcast_probe_resp_interval = val;
++#ifdef CONFIG_AFC
++	} else if (os_strcmp(buf, "afcd_sock") == 0) {
++		conf->afc.socket = os_malloc(os_strlen(pos) + 1);
++		if (!conf->afc.socket)
++			return 1;
++
++		os_strlcpy(conf->afc.socket, pos, os_strlen(pos) + 1);
++	} else if (os_strcmp(buf, "afc_request_version") == 0) {
++		conf->afc.request.version = os_malloc(os_strlen(pos) + 1);
++		if (!conf->afc.request.version)
++			return 1;
++
++		os_strlcpy(conf->afc.request.version, pos, os_strlen(pos) + 1);
++	} else if (os_strcmp(buf, "afc_request_id") == 0) {
++		conf->afc.request.id = os_malloc(os_strlen(pos) + 1);
++		if (!conf->afc.request.id)
++			return 1;
++
++		os_strlcpy(conf->afc.request.id, pos, os_strlen(pos) + 1);
++	} else if (os_strcmp(buf, "afc_serial_number") == 0) {
++		conf->afc.request.sn = os_malloc(os_strlen(pos) + 1);
++		if (!conf->afc.request.sn)
++			return 1;
++
++		os_strlcpy(conf->afc.request.sn, pos, os_strlen(pos) + 1);
++	} else if (os_strcmp(buf, "afc_cert_ids") == 0) {
++		if (hostapd_afc_parse_cert_ids(conf, pos))
++			return 1;
++	} else if (os_strcmp(buf, "afc_location_type") == 0) {
++		conf->afc.location.type = atoi(pos);
++		if (conf->afc.location.type != ELLIPSE &&
++		    conf->afc.location.type != LINEAR_POLYGON &&
++		    conf->afc.location.type != RADIAL_POLYGON)
++			return 1;
++	} else if (os_strcmp(buf, "afc_linear_polygon") == 0) {
++		if (hostapd_afc_parse_position_data(
++			&conf->afc.location.linear_polygon_data,
++			&conf->afc.location.n_linear_polygon_data,
++			pos))
++			return 1;
++	} else if (os_strcmp(buf, "afc_radial_polygon") == 0) {
++		if (hostapd_afc_parse_position_data(
++			(struct afc_linear_polygon **)
++			&conf->afc.location.radial_polygon_data,
++			&conf->afc.location.n_radial_polygon_data,
++			pos))
++			return 1;
++	} else if (os_strcmp(buf, "afc_major_axis") == 0) {
++		conf->afc.location.major_axis = atoi(pos);
++	} else if (os_strcmp(buf, "afc_minor_axis") == 0) {
++		conf->afc.location.minor_axis = atoi(pos);
++	} else if (os_strcmp(buf, "afc_orientation") == 0) {
++		conf->afc.location.orientation = atoi(pos);
++	} else if (os_strcmp(buf, "afc_height") == 0) {
++		char *end;
++
++		conf->afc.location.height = strtod(pos, &end);
++		if (*end)
++			return 1;
++	} else if (os_strcmp(buf, "afc_height_type") == 0) {
++		conf->afc.location.height_type = os_malloc(os_strlen(pos) + 1);
++		if (!conf->afc.location.height_type)
++			return 1;
++
++		os_strlcpy(conf->afc.location.height_type, pos,
++			   os_strlen(pos) + 1);
++	} else if (os_strcmp(buf, "afc_vertical_tolerance") == 0) {
++		conf->afc.location.vertical_tolerance = atoi(pos);
++	} else if (os_strcmp(buf, "afc_min_power") == 0) {
++		conf->afc.min_power = atoi(pos);
++	} else if (os_strcmp(buf, "afc_freq_range") == 0) {
++		if (hostapd_afc_parse_freq_range(conf, pos))
++			return 1;
++	} else if (os_strcmp(buf, "afc_op_class") == 0) {
++		if (hostapd_afc_parse_op_class(conf, pos))
++			return 1;
++#endif /* CONFIG_AFC */
+ 	} else if (os_strcmp(buf, "mbssid") == 0) {
+ 		int mbssid = atoi(pos);
+ 		if (mbssid < 0 || mbssid > ENHANCED_MBSSID_ENABLED) {
+diff --git a/hostapd/defconfig b/hostapd/defconfig
+index 550db697b..66bf894eb 100644
+--- a/hostapd/defconfig
++++ b/hostapd/defconfig
+@@ -425,3 +425,6 @@ CONFIG_DPP2=y
+ 
+ # Wi-Fi Aware unsynchronized service discovery (NAN USD)
+ #CONFIG_NAN_USD=y
++
++# Enable Automated Frequency Coordination for 6GHz outdoor
++#CONFIG_AFC=y
+diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
+index 93524cf5d..56442c69d 100644
+--- a/hostapd/hostapd.conf
++++ b/hostapd/hostapd.conf
+@@ -1030,6 +1030,48 @@ wmm_ac_vo_acm=0
+ # Valid range: 0..20 TUs; default is 0 (disabled)
+ #unsol_bcast_probe_resp_interval=0
+ 
++##### Automated Frequency Coordination for 6GHz UNII-5 and UNII-7 bands #######
++
++# AFC daemon connection socket
++#afcd_sock=/var/run/afcd.sock
++
++# AFC request identification parameters
++#afc_request_version=1.1
++#afc_request_id=11235813
++#afc_serial_number=abcdefg
++#afc_cert_ids=US_47_CFR_PART_15_SUBPART_E:CID000
++#
++# AFC location type:
++# 0 = ellipse
++# 1 = linear polygon
++# 2 = radial polygon
++#afc_location_type=0
++#
++# AFC ellipse or linear polygon coordinations
++#afc_linear_polygon=-122.984157:37.425056
++#
++# AFC radial polygon coordinations
++#afc_radial_polygon=118.8:92.76,76.44:87.456,98.56:123.33
++#
++# AFC ellipse major/minor axis and orientation
++#afc_major_axis=100
++#afc_minor_axis=50
++#afc_orientation=70
++#
++# AFC device elevation parameters
++#afc_height=3.0
++#afc_height_type=AGL
++#afc_vertical_tolerance=7
++#
++# AFC minimum desired TX power (dbm)
++#afc_min_power=24
++#
++# AFC request frequency ranges
++#afc_freq_range=5925:6425,6525:6875
++#
++# AFC request operation classes
++#afc_op_class=131,132,133,134,136
++
+ ##### IEEE 802.11be related configuration #####################################
+ 
+ #ieee80211be: Whether IEEE 802.11be (EHT) is enabled
+diff --git a/src/ap/afc.c b/src/ap/afc.c
+new file mode 100644
+index 000000000..cfee83fe7
+--- /dev/null
++++ b/src/ap/afc.c
+@@ -0,0 +1,1041 @@
++/*
++ * Automated Frequency Coordination
++ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ */
++
++#include <json-c/json.h>
++#include <sys/un.h>
++#include <time.h>
++
++#include "utils/includes.h"
++#include "utils/common.h"
++#include "utils/eloop.h"
++#include "hostapd.h"
++#include "acs.h"
++#include "hw_features.h"
++
++#define HOSTAPD_AFC_RETRY_TIMEOUT	180
++#define HOSTAPD_AFC_TIMEOUT		86400 /* 24h */
++#define HOSTAPD_AFC_BUFSIZE		8192
++
++static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx);
++
++
++static struct json_object *
++hostapd_afc_build_location_request(struct hostapd_iface *iface)
++{
++	struct json_object *location_obj, *center_obj, *ellipse_obj;
++	struct json_object *elevation_obj, *str_obj;
++	struct hostapd_config *iconf = iface->conf;
++	bool is_ap_indoor = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type);
++
++	location_obj = json_object_new_object();
++	if (!location_obj)
++		return NULL;
++
++	if (iconf->afc.location.type != LINEAR_POLYGON) {
++		struct afc_linear_polygon *lp =
++			&iconf->afc.location.linear_polygon_data[0];
++
++		if (!lp)
++			goto error;
++
++		ellipse_obj = json_object_new_object();
++		if (!ellipse_obj)
++			goto error;
++
++		center_obj = json_object_new_object();
++		if (!center_obj)
++			goto error;
++
++		json_object_object_add(ellipse_obj, "center", center_obj);
++
++		str_obj = json_object_new_double(lp->longitude);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(center_obj, "longitude", str_obj);
++		str_obj = json_object_new_double(lp->latitude);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(center_obj, "latitude", str_obj);
++	}
++
++	switch (iconf->afc.location.type) {
++	case LINEAR_POLYGON: {
++		struct json_object *outer_boundary_obj;
++		int i;
++
++		outer_boundary_obj = json_object_new_object();
++		if (!outer_boundary_obj)
++			goto error;
++
++		json_object_object_add(location_obj, "linearPolygon",
++				       outer_boundary_obj);
++		ellipse_obj = json_object_new_array();
++		if (!ellipse_obj)
++			goto error;
++
++		json_object_object_add(outer_boundary_obj, "outerBoundary",
++				       ellipse_obj);
++		for (i = 0;
++		     i < iconf->afc.location.n_linear_polygon_data; i++) {
++			struct afc_linear_polygon *lp =
++				&iconf->afc.location.linear_polygon_data[i];
++
++			center_obj = json_object_new_object();
++			if (!center_obj)
++				goto error;
++
++			json_object_array_add(ellipse_obj, center_obj);
++			str_obj = json_object_new_double(lp->longitude);
++			if (!str_obj)
++				goto error;
++
++			json_object_object_add(center_obj, "longitude",
++					       str_obj);
++			str_obj = json_object_new_double(lp->latitude);
++			if (!str_obj)
++				goto error;
++
++			json_object_object_add(center_obj, "latitude",
++					       str_obj);
++		}
++		break;
++	}
++	case RADIAL_POLYGON: {
++		struct json_object *outer_boundary_obj;
++		int i;
++
++		json_object_object_add(location_obj, "radialPolygon",
++				       ellipse_obj);
++
++		outer_boundary_obj = json_object_new_array();
++		if (!outer_boundary_obj)
++			goto error;
++
++		json_object_object_add(ellipse_obj, "outerBoundary",
++				       outer_boundary_obj);
++		for (i = 0;
++		     i < iconf->afc.location.n_radial_polygon_data; i++) {
++			struct afc_radial_polygon *rp =
++				&iconf->afc.location.radial_polygon_data[i];
++			struct json_object *angle_obj;
++
++			angle_obj = json_object_new_object();
++			if (!angle_obj)
++				goto error;
++
++			json_object_array_add(outer_boundary_obj, angle_obj);
++
++			str_obj = json_object_new_double(rp->angle);
++			if (!str_obj)
++				goto error;
++
++			json_object_object_add(angle_obj, "angle", str_obj);
++			str_obj = json_object_new_double(rp->length);
++			if (!str_obj)
++				goto error;
++
++			json_object_object_add(angle_obj, "length", str_obj);
++		}
++		break;
++	}
++	case ELLIPSE:
++	default:
++		json_object_object_add(location_obj, "ellipse", ellipse_obj);
++
++		str_obj = json_object_new_int(iconf->afc.location.major_axis);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(ellipse_obj, "majorAxis", str_obj);
++		str_obj = json_object_new_int(iconf->afc.location.minor_axis);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(ellipse_obj, "minorAxis", str_obj);
++		str_obj = json_object_new_int(iconf->afc.location.orientation);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(ellipse_obj, "orientation", str_obj);
++		break;
++	}
++
++	elevation_obj = json_object_new_object();
++	if (!elevation_obj)
++		goto error;
++
++	json_object_object_add(location_obj, "elevation",
++			       elevation_obj);
++	str_obj = json_object_new_double(iconf->afc.location.height);
++	if (!str_obj)
++		goto error;
++
++	json_object_object_add(elevation_obj, "height", str_obj);
++	if (iconf->afc.location.height_type) {
++		str_obj = json_object_new_string(iconf->afc.location.height_type);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(elevation_obj, "heightType", str_obj);
++	}
++
++	str_obj = json_object_new_int(iconf->afc.location.vertical_tolerance);
++	if (!str_obj)
++		goto error;
++
++	json_object_object_add(elevation_obj, "verticalUncertainty",
++			       str_obj);
++	str_obj = json_object_new_int(is_ap_indoor);
++	if (!str_obj)
++		goto error;
++
++	json_object_object_add(location_obj, "indoorDeployment", str_obj);
++
++	return location_obj;
++
++error:
++	json_object_put(location_obj);
++	return NULL;
++}
++
++
++static struct json_object * hostapd_afc_get_opclass_chan_list(u8 op_class)
++{
++	struct json_object *chan_list_obj, *str_obj;
++	const struct oper_class_map *oper_class;
++	int chan_offset, chan;
++
++	oper_class = get_oper_class(NULL, op_class);
++	if (!oper_class)
++		return NULL;
++
++	chan_list_obj = json_object_new_array();
++	if (!chan_list_obj)
++		return NULL;
++
++	switch (op_class) {
++	case 132: /*  40MHz */
++		chan_offset = 2;
++		break;
++	case 133: /*  80MHz */
++		chan_offset = 6;
++		break;
++	case 134: /* 160MHz */
++		chan_offset = 14;
++		break;
++	default:
++		chan_offset = 0;
++		break;
++	}
++
++	for (chan = oper_class->min_chan; chan <= oper_class->max_chan;
++	     chan += oper_class->inc) {
++		str_obj = json_object_new_int(chan + chan_offset);
++		if (!str_obj) {
++			json_object_put(chan_list_obj);
++			return NULL;
++		}
++		json_object_array_add(chan_list_obj, str_obj);
++	}
++
++	return chan_list_obj;
++}
++
++
++static struct json_object *
++hostapd_afc_build_req_chan_list(struct hostapd_iface *iface)
++{
++	struct json_object *op_class_list_obj, *str_obj;
++	struct hostapd_config *iconf = iface->conf;
++	int i;
++
++	op_class_list_obj = json_object_new_array();
++	if (!op_class_list_obj)
++		return NULL;
++
++	for (i = 0; i < iconf->afc.n_op_class; i++) {
++		struct json_object *op_class_obj, *chan_list_obj;
++		u8 op_class = iconf->afc.op_class[i];
++
++		if (!is_6ghz_op_class(op_class))
++			continue;
++
++		op_class_obj = json_object_new_object();
++		if (!op_class_obj)
++			goto error;
++
++		json_object_array_add(op_class_list_obj, op_class_obj);
++		str_obj = json_object_new_int(op_class);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(op_class_obj, "globalOperatingClass",
++				       str_obj);
++
++		chan_list_obj = hostapd_afc_get_opclass_chan_list(op_class);
++		if (!chan_list_obj)
++			goto error;
++
++		json_object_object_add(op_class_obj, "channelCfi",
++				       chan_list_obj);
++	}
++
++	return op_class_list_obj;
++
++error:
++	json_object_put(op_class_list_obj);
++	return NULL;
++}
++
++
++static struct json_object *
++hostapd_afc_build_request(struct hostapd_iface *iface)
++{
++	struct json_object *l1_obj, *l2_obj, *la1_obj, *la2_obj;
++	struct json_object *s2_obj, *str_obj, *location_obj;
++	struct hostapd_config *iconf = iface->conf;
++	struct json_object *op_class_list_obj;
++	int i;
++
++	l1_obj = json_object_new_object();
++	if (!l1_obj)
++		return NULL;
++
++	if (iconf->afc.request.version) {
++		str_obj = json_object_new_string(iconf->afc.request.version);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(l1_obj, "version", str_obj);
++	}
++
++	la1_obj = json_object_new_array();
++	if (!la1_obj)
++		goto error;
++
++	json_object_object_add(l1_obj, "availableSpectrumInquiryRequests",
++			       la1_obj);
++	l2_obj = json_object_new_object();
++	if (!l2_obj)
++		goto error;
++
++	json_object_array_add(la1_obj, l2_obj);
++	if (iconf->afc.request.id) {
++		str_obj = json_object_new_string(iconf->afc.request.id);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(l2_obj, "requestId", str_obj);
++	}
++
++	s2_obj = json_object_new_object();
++	if (!s2_obj)
++		goto error;
++
++	json_object_object_add(l2_obj, "deviceDescriptor", s2_obj);
++	if (iconf->afc.request.sn) {
++		str_obj = json_object_new_string(iconf->afc.request.sn);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(s2_obj, "serialNumber", str_obj);
++	}
++
++	la2_obj = json_object_new_array();
++	if (!la2_obj)
++		goto error;
++
++	json_object_object_add(s2_obj, "certificationId", la2_obj);
++	for (i = 0; i < iconf->afc.n_cert_ids; i++) {
++		struct json_object *obj;
++
++		obj = json_object_new_object();
++		if (!obj)
++			goto error;
++
++		json_object_array_add(la2_obj, obj);
++		str_obj =
++			json_object_new_string(iconf->afc.cert_ids[i].rulset);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(obj, "rulesetId", str_obj);
++		str_obj = json_object_new_string(iconf->afc.cert_ids[i].id);
++		if (!str_obj)
++			goto error;
++
++		json_object_object_add(obj, "id", str_obj);
++	}
++
++	location_obj = hostapd_afc_build_location_request(iface);
++	if (!location_obj)
++		goto error;
++
++	json_object_object_add(l2_obj, "location", location_obj);
++	str_obj = json_object_new_int(iconf->afc.min_power);
++	if (!str_obj)
++		goto error;
++
++	json_object_object_add(l2_obj, "minDesiredPower", str_obj);
++
++	if (iconf->afc.n_freq_range) {
++		struct json_object *freq_obj;
++
++		freq_obj = json_object_new_array();
++		if (!freq_obj)
++			goto error;
++
++		json_object_object_add(l2_obj, "inquiredFrequencyRange",
++				       freq_obj);
++		for (i = 0; i < iconf->afc.n_freq_range; i++) {
++			struct afc_freq_range *fr = &iconf->afc.freq_range[i];
++			struct json_object *obj;
++
++			obj = json_object_new_object();
++			if (!obj)
++				goto error;
++
++			json_object_array_add(freq_obj, obj);
++			str_obj = json_object_new_int(fr->low_freq);
++			if (!str_obj)
++				goto error;
++
++			json_object_object_add(obj, "lowFrequency", str_obj);
++			str_obj = json_object_new_int(fr->high_freq);
++			if (!str_obj)
++				goto error;
++
++			json_object_object_add(obj, "highFrequency", str_obj);
++		}
++	}
++
++	op_class_list_obj = hostapd_afc_build_req_chan_list(iface);
++	if (!op_class_list_obj)
++		goto error;
++
++	json_object_object_add(l2_obj, "inquiredChannels", op_class_list_obj);
++
++	wpa_printf(MSG_DEBUG, "Pending AFC request: %s",
++		   json_object_get_string(l1_obj));
++
++	return l1_obj;
++
++error:
++	json_object_put(l1_obj);
++
++	return NULL;
++}
++
++
++static int
++hostad_afc_parse_available_freq_info(struct hostapd_iface *iface,
++				     struct json_object *reply_elem_obj)
++{
++	struct afc_freq_range_elem *f = NULL;
++	struct json_object *obj;
++	int i, count = 0;
++
++	if (!json_object_object_get_ex(reply_elem_obj,
++				       "availableFrequencyInfo", &obj))
++		return 0;
++
++	for (i = 0; i < json_object_array_length(obj); i++) {
++		struct json_object *range_elem_obj, *freq_range_obj;
++		struct json_object *high_freq_obj, *low_freq_obj;
++		struct json_object *max_psd_obj;
++
++		range_elem_obj = json_object_array_get_idx(obj, i);
++		if (!range_elem_obj)
++			continue;
++
++		if (!json_object_object_get_ex(range_elem_obj,
++					       "frequencyRange",
++					       &freq_range_obj))
++			continue;
++
++		if (!json_object_object_get_ex(freq_range_obj,
++					       "lowFrequency",
++					       &low_freq_obj))
++			continue;
++
++		if (!json_object_object_get_ex(freq_range_obj,
++					       "highFrequency",
++					       &high_freq_obj))
++			continue;
++
++		if (!json_object_object_get_ex(range_elem_obj, "maxPsd",
++					       &max_psd_obj) &&
++		    !json_object_object_get_ex(range_elem_obj, "maxPSD",
++					       &max_psd_obj))
++			continue;
++
++		f = os_realloc_array(f, count + 1, sizeof(*f));
++		if (!f)
++			return -ENOMEM;
++
++		f[count].low_freq = json_object_get_int(low_freq_obj);
++		f[count].high_freq = json_object_get_int(high_freq_obj);
++		f[count++].max_psd = json_object_get_int(max_psd_obj);
++	}
++	iface->afc.freq_range = f;
++	iface->afc.num_freq_range = count;
++
++	return 0;
++}
++
++
++static int hostad_afc_update_chan_info(struct afc_chan_info_elem **chan_list,
++				       int *chan_list_size, u8 op_class,
++				       int center_chan, int power)
++{
++	int num_low_subchan, ch, count = *chan_list_size;
++	struct afc_chan_info_elem *c = *chan_list;
++
++	switch (op_class) {
++	case 132: /*  40MHz */
++		num_low_subchan = 2;
++		break;
++	case 133: /*  80MHz */
++		num_low_subchan = 6;
++		break;
++	case 134: /* 160MHz */
++		num_low_subchan = 14;
++		break;
++	default:
++		num_low_subchan = 0;
++		break;
++	}
++
++	for (ch = center_chan - num_low_subchan;
++	     ch <= center_chan + num_low_subchan; ch += 4) {
++		int i;
++
++		for (i = 0; i < count; i++) {
++			if (c[i].chan == ch)
++				break;
++		}
++
++		if (i == count) {
++			c = os_realloc_array(c, count + 1, sizeof(*c));
++			if (!c)
++				return -ENOMEM;
++
++			c[count].chan = ch;
++			c[count++].power = power;
++		}
++	}
++
++	*chan_list_size = count;
++	*chan_list = c;
++
++	return 0;
++}
++
++
++static int
++hostad_afc_parse_available_chan_info(struct hostapd_iface *iface,
++				     struct json_object *reply_elem_obj)
++{
++	struct afc_chan_info_elem *c = NULL;
++	struct json_object *obj;
++	int i, count = 0;
++
++	if (!json_object_object_get_ex(reply_elem_obj,
++				       "availableChannelInfo", &obj))
++		return 0;
++
++	for (i = 0; i < json_object_array_length(obj); i++) {
++		struct json_object *range_elem_obj, *op_class_obj;
++		struct json_object *chan_cfi_obj, *max_eirp_obj;
++		int ch, op_class;
++
++		range_elem_obj = json_object_array_get_idx(obj, i);
++		if (!range_elem_obj)
++			continue;
++
++		if (!json_object_object_get_ex(range_elem_obj,
++					       "globalOperatingClass",
++					       &op_class_obj))
++			continue;
++
++		if (!json_object_object_get_ex(range_elem_obj, "maxEirp",
++					       &max_eirp_obj))
++			continue;
++
++		if (!json_object_object_get_ex(range_elem_obj, "channelCfi",
++					       &chan_cfi_obj))
++			continue;
++
++		op_class = json_object_get_int(op_class_obj);
++		for (ch = 0;
++		     ch < json_object_array_length(chan_cfi_obj); ch++) {
++			struct json_object *pwr_obj;
++			struct json_object *ch_obj;
++			int channel, power;
++
++			ch_obj = json_object_array_get_idx(chan_cfi_obj, ch);
++			if (!ch_obj)
++				continue;
++
++			pwr_obj = json_object_array_get_idx(max_eirp_obj, ch);
++			if (!pwr_obj)
++				continue;
++
++			channel = json_object_get_int(ch_obj);
++			power = json_object_get_int(pwr_obj);
++
++			hostad_afc_update_chan_info(&c, &count, op_class,
++						    channel, power);
++		}
++		iface->afc.chan_info_list = c;
++		iface->afc.num_chan_info = count;
++	}
++
++	return 0;
++}
++
++
++static int hostad_afc_get_timeout(struct json_object *obj)
++{
++	time_t t, now;
++	struct tm tm;
++
++	if (sscanf(json_object_get_string(obj), "%d-%d-%dT%d:%d:%dZ",
++		   &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,
++		   &tm.tm_min, &tm.tm_sec) <= 0)
++		return HOSTAPD_AFC_TIMEOUT;
++
++	tm.tm_year -= 1900;
++	tm.tm_mon -= 1;
++	tm.tm_isdst = -1;
++	t = mktime(&tm);
++	time(&now);
++
++	return now > t ? HOSTAPD_AFC_RETRY_TIMEOUT : (t - now) * 80 / 100;
++}
++
++
++static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply)
++{
++	struct json_object *payload_obj, *reply_obj, *version_obj;
++	struct hostapd_config *iconf = iface->conf;
++	int i, request_timeout = -1, ret = -EINVAL;
++
++	wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply);
++	payload_obj = json_tokener_parse(reply);
++	if (!payload_obj) {
++		wpa_printf(MSG_ERROR, "Failed to parse AFC reply payload");
++		return -EINVAL;
++	}
++
++	if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) {
++		wpa_printf(MSG_ERROR, "Missing version in AFC reply");
++		return -EINVAL;
++	}
++
++	if (iconf->afc.request.version &&
++	    os_strcmp(iconf->afc.request.version,
++		      json_object_get_string(version_obj))) {
++		wpa_printf(MSG_ERROR, "Mismatch in AFC reply version");
++		return -EINVAL;
++	}
++
++	if (!json_object_object_get_ex(payload_obj,
++				       "availableSpectrumInquiryResponses",
++				       &reply_obj)) {
++		wpa_printf(MSG_ERROR,
++			   "Missing availableSpectrumInquiry in AFC reply");
++		return -EINVAL;
++	}
++
++	for (i = 0; i < json_object_array_length(reply_obj); i++) {
++		struct json_object *reply_elem_obj, *obj, *status_obj;
++		int j, status = -EINVAL;
++
++		reply_elem_obj = json_object_array_get_idx(reply_obj, i);
++		if (!reply_elem_obj) {
++			wpa_printf(MSG_DEBUG,
++				   "Failed to get reply element at index %d",
++				   i);
++			continue;
++		}
++
++		if (!json_object_object_get_ex(reply_elem_obj, "requestId",
++					       &obj)) {
++			wpa_printf(MSG_DEBUG,
++				   "Missing requestId in reply element %d", i);
++			continue;
++		}
++
++		if (iconf->afc.request.id &&
++		    os_strcmp(iconf->afc.request.id,
++			      json_object_get_string(obj))) {
++			wpa_printf(MSG_DEBUG,
++				   "RequestId mismatch in reply element %d",
++				   i);
++			continue;
++		}
++
++		if (!json_object_object_get_ex(reply_elem_obj, "rulesetId",
++					       &obj)) {
++			wpa_printf(MSG_DEBUG,
++				   "Missing rulesetId in reply element %d", i);
++			continue;
++		}
++
++		for (j = 0; j < iconf->afc.n_cert_ids; j++) {
++			if (!os_strcmp(iconf->afc.cert_ids[j].rulset,
++				       json_object_get_string(obj)))
++				break;
++		}
++
++		if (j == iconf->afc.n_cert_ids) {
++			wpa_printf(MSG_DEBUG,
++				   "RulesetId mismatch in reply element %d",
++				   i);
++			continue;
++		}
++
++		if (!json_object_object_get_ex(reply_elem_obj, "response",
++					       &obj)) {
++			wpa_printf(MSG_DEBUG,
++				   "Missing response field in reply element %d",
++				   i);
++			continue;
++		}
++
++		if (json_object_object_get_ex(obj, "shortDescription",
++					      &status_obj))
++			wpa_printf(MSG_DEBUG, "AFC reply element %d: %s",
++				   i, json_object_get_string(status_obj));
++
++		if (json_object_object_get_ex(obj, "responseCode",
++					      &status_obj))
++			status = json_object_get_int(status_obj);
++
++		if (status < 0) {
++			wpa_printf(MSG_DEBUG,
++				   "Reply element %d invalid responseCode: %d",
++				   i, status);
++			continue;
++		}
++
++		if (hostad_afc_parse_available_freq_info(iface,
++							 reply_elem_obj) ||
++		    hostad_afc_parse_available_chan_info(iface,
++							 reply_elem_obj))
++			continue;
++
++		if (json_object_object_get_ex(reply_elem_obj,
++					      "availabilityExpireTime",
++					      &obj)) {
++			int timeout = hostad_afc_get_timeout(obj);
++
++			if (request_timeout < 0 || timeout < request_timeout)
++				request_timeout = timeout;
++		}
++
++		ret = status;
++	}
++
++	iface->afc.data_valid = true;
++	iface->afc.timeout = request_timeout;
++	if (iface->afc.timeout < 0)
++		iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
++
++	return ret;
++}
++
++
++static int hostapd_afc_send_receive(struct hostapd_iface *iface)
++{
++	struct hostapd_config *iconf = iface->conf;
++	json_object *request_obj = NULL;
++	struct timeval sock_timeout = {
++		.tv_sec = 5,
++	};
++	struct sockaddr_un addr = {
++		.sun_family = AF_UNIX,
++#ifdef __FreeBSD__
++		.sun_len = sizeof(addr),
++#endif /* __FreeBSD__ */
++	};
++	const char *request;
++	char *buf = NULL;
++	int sockfd, ret;
++	fd_set read_set;
++
++	if (iface->afc.data_valid) {
++		/* AFC data already downloaded from the server */
++		return 0;
++	}
++
++	if (!iconf->afc.socket) {
++		wpa_printf(MSG_ERROR, "Missing AFC socket string");
++		return -EINVAL;
++	}
++
++	iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
++	if (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) {
++		wpa_printf(MSG_ERROR, "Malformed AFC socket string %s",
++			   iconf->afc.socket);
++		return -EINVAL;
++	}
++
++	os_strlcpy(addr.sun_path, iconf->afc.socket, sizeof(addr.sun_path));
++	sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
++	if (sockfd < 0) {
++		wpa_printf(MSG_ERROR, "Failed creating AFC socket");
++		return sockfd;
++	}
++
++	if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
++		wpa_printf(MSG_ERROR, "Failed connecting AFC socket");
++		ret = -EIO;
++		goto close_sock;
++	}
++
++	request_obj = hostapd_afc_build_request(iface);
++	if (!request_obj) {
++		ret = -ENOMEM;
++		goto close_sock;
++	}
++
++	request = json_object_to_json_string(request_obj);
++	if (send(sockfd, request, strlen(request), 0) < 0) {
++		wpa_printf(MSG_ERROR, "Failed sending AFC request");
++		ret = -EIO;
++		goto close_sock;
++	}
++
++	FD_ZERO(&read_set);
++	FD_SET(sockfd, &read_set);
++	if (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) {
++		wpa_printf(MSG_ERROR, "Select failed on AFC socket");
++		ret = -errno;
++		goto close_sock;
++	}
++
++	if (!FD_ISSET(sockfd, &read_set)) {
++		ret = -EIO;
++		goto close_sock;
++	}
++
++	buf = os_zalloc(HOSTAPD_AFC_BUFSIZE);
++	if (!buf) {
++		ret = -ENOMEM;
++		goto close_sock;
++	}
++
++	ret = recv(sockfd, buf, HOSTAPD_AFC_BUFSIZE - 1, 0);
++	if (ret <= 0)
++		goto close_sock;
++
++	ret = hostapd_afc_parse_reply(iface, buf);
++	if (ret)
++		wpa_printf(MSG_ERROR, "Failed parsing AFC reply: %d", ret);
++close_sock:
++	os_free(buf);
++	json_object_put(request_obj);
++	close(sockfd);
++
++	return ret;
++}
++
++
++static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface)
++{
++	const struct oper_class_map *oper_class;
++	int ch;
++
++	oper_class = get_oper_class(NULL, iface->conf->op_class);
++	if (!oper_class)
++		return false;
++
++	for (ch = oper_class->min_chan; ch <= oper_class->max_chan;
++	     ch += oper_class->inc) {
++		struct hostapd_hw_modes *mode = iface->current_mode;
++		int i;
++
++		for (i = 0; i < mode->num_channels; i++) {
++			struct hostapd_channel_data *chan = &mode->channels[i];
++
++			if (chan->chan == ch &&
++			    !(chan->flag & HOSTAPD_CHAN_DISABLED))
++				return true;
++		}
++	}
++
++	return false;
++}
++
++
++int hostapd_afc_handle_request(struct hostapd_iface *iface)
++{
++	struct hostapd_config *iconf = iface->conf;
++	int ret;
++
++	/* AFC is required just for standard power AP */
++	if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))
++		return 1;
++
++	if (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq))
++		return 1;
++
++	if (iface->state == HAPD_IFACE_ACS)
++		return 1;
++
++	ret = hostapd_afc_send_receive(iface);
++	if (ret < 0) {
++		/*
++		 * If the connection to the AFCD failed, resched for a
++		 * future attempt.
++		 */
++		wpa_printf(MSG_ERROR, "AFC connection failed: %d", ret);
++		if (ret == -EIO)
++			ret = 0;
++		goto resched;
++	}
++
++	hostap_afc_disable_channels(iface);
++	if (!hostapd_afc_has_usable_chans(iface))
++		goto resched;
++
++	if (!hostapd_is_usable_chans(iface)) {
++		/* Trigger an ACS freq scan */
++		iconf->channel = 0;
++		iface->freq = 0;
++
++		if (acs_init(iface) != HOSTAPD_CHAN_ACS) {
++			wpa_printf(MSG_ERROR, "Could not start ACS");
++			ret = -EINVAL;
++		}
++	} else {
++		ret = 1;
++	}
++
++resched:
++	eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
++	eloop_register_timeout(iface->afc.timeout, 0,
++			       hostapd_afc_timeout_handler, iface, NULL);
++
++	return ret;
++}
++
++
++static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface)
++{
++	os_free(iface->afc.chan_info_list);
++	os_free(iface->afc.freq_range);
++
++	iface->afc.num_freq_range = 0;
++	iface->afc.num_chan_info = 0;
++
++	iface->afc.chan_info_list = NULL;
++	iface->afc.freq_range = NULL;
++
++	iface->afc.data_valid = false;
++}
++
++
++static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx)
++{
++	struct hostapd_iface *iface = eloop_ctx;
++	bool restart_iface = true;
++
++	hostapd_afc_delete_data_from_server(iface);
++	if (iface->state != HAPD_IFACE_ENABLED) {
++		/* Hostapd is not fully enabled yet, toggle the interface */
++		goto restart_interface;
++	}
++
++	if (hostapd_afc_send_receive(iface) < 0 ||
++	    hostapd_get_hw_features(iface)) {
++		restart_iface = false;
++		goto restart_interface;
++	}
++
++	if (hostapd_is_usable_chans(iface))
++		goto resched;
++
++	restart_iface = hostapd_afc_has_usable_chans(iface);
++	if (restart_iface) {
++		/* Trigger an ACS freq scan */
++		iface->conf->channel = 0;
++		iface->freq = 0;
++	}
++
++restart_interface:
++	hostapd_disable_iface(iface);
++	if (restart_iface)
++		hostapd_enable_iface(iface);
++resched:
++	eloop_register_timeout(iface->afc.timeout, 0,
++			       hostapd_afc_timeout_handler, iface, NULL);
++}
++
++
++void hostapd_afc_stop(struct hostapd_iface *iface)
++{
++	eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
++}
++
++
++void hostap_afc_disable_channels(struct hostapd_iface *iface)
++{
++	struct hostapd_hw_modes *mode = NULL;
++	int i;
++
++	for (i = 0; i < iface->num_hw_features; i++) {
++		mode = &iface->hw_features[i];
++		if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
++		    mode->is_6ghz)
++			break;
++	}
++
++	if (i == iface->num_hw_features)
++		return;
++
++	if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))
++		return;
++
++	if (!iface->afc.data_valid)
++		return;
++
++	for (i = 0; i < mode->num_channels; i++) {
++		struct hostapd_channel_data *chan = &mode->channels[i];
++		int j;
++
++		if (!is_6ghz_freq(chan->freq))
++			continue;
++
++		for (j = 0; j < iface->afc.num_freq_range; j++) {
++			if (chan->freq >= iface->afc.freq_range[j].low_freq &&
++			    chan->freq <= iface->afc.freq_range[j].high_freq)
++				break;
++		}
++
++		if (j != iface->afc.num_freq_range)
++			continue;
++
++		for (j = 0; j < iface->afc.num_chan_info; j++) {
++			if (chan->chan == iface->afc.chan_info_list[j].chan)
++				break;
++		}
++
++		if (j != iface->afc.num_chan_info)
++			continue;
++
++		chan->flag |= HOSTAPD_CHAN_DISABLED;
++		wpa_printf(MSG_MSGDUMP,
++			   "Disabling freq=%d MHz (not allowed by AFC)",
++			   chan->freq);
++	}
++}
+diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
+index c6aa49610..9e34e029a 100644
+--- a/src/ap/ap_config.c
++++ b/src/ap/ap_config.c
+@@ -1047,6 +1047,22 @@ void hostapd_config_free(struct hostapd_config *conf)
+ #endif /* CONFIG_ACS */
+ 	wpabuf_free(conf->lci);
+ 	wpabuf_free(conf->civic);
++#ifdef CONFIG_AFC
++	os_free(conf->afc.socket);
++	os_free(conf->afc.request.version);
++	os_free(conf->afc.request.id);
++	os_free(conf->afc.request.sn);
++	for (i = 0; i < conf->afc.n_cert_ids; i++) {
++		os_free(conf->afc.cert_ids[i].rulset);
++		os_free(conf->afc.cert_ids[i].id);
++	}
++	os_free(conf->afc.cert_ids);
++	os_free(conf->afc.location.height_type);
++	os_free(conf->afc.location.linear_polygon_data);
++	os_free(conf->afc.location.radial_polygon_data);
++	os_free(conf->afc.freq_range);
++	os_free(conf->afc.op_class);
++#endif /* CONFIG_AFC */
+ 
+ 	os_free(conf);
+ }
+diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
+index d42076785..e6669e6a3 100644
+--- a/src/ap/ap_config.h
++++ b/src/ap/ap_config.h
+@@ -1249,6 +1249,53 @@ struct hostapd_config {
+ 
+ 	/* Whether to enable TWT responder in HT and VHT modes */
+ 	bool ht_vht_twt_responder;
++
++#ifdef CONFIG_AFC
++	struct {
++		char *socket;
++		struct {
++			char *version;
++			char *id;
++			char *sn;
++		} request;
++		unsigned int n_cert_ids;
++		struct cert_id {
++			char *rulset;
++			char *id;
++		} *cert_ids;
++		struct {
++			enum afc_location_type {
++				ELLIPSE,
++				LINEAR_POLYGON,
++				RADIAL_POLYGON,
++			} type;
++			unsigned int n_linear_polygon_data;
++			struct afc_linear_polygon {
++				double longitude;
++				double latitude;
++			} *linear_polygon_data;
++			unsigned int n_radial_polygon_data;
++			struct afc_radial_polygon {
++				double length;
++				double angle;
++			} *radial_polygon_data;
++			int major_axis;
++			int minor_axis;
++			int orientation;
++			double height;
++			char *height_type;
++			int vertical_tolerance;
++		} location;
++		unsigned int n_freq_range;
++		struct afc_freq_range {
++			int low_freq;
++			int high_freq;
++		} *freq_range;
++		unsigned int n_op_class;
++		unsigned int *op_class;
++		int min_power;
++	} afc;
++#endif /* CONFIG_AFC */
+ };
+ 
+ 
+diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
+index 36d48ae09..5a8cdc90e 100644
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -715,6 +715,7 @@ void hostapd_cleanup_iface_partial(struct hostapd_iface *iface)
+ static void hostapd_cleanup_iface(struct hostapd_iface *iface)
+ {
+ 	wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface);
++	hostapd_afc_stop(iface);
+ 	eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,
+ 			     NULL);
+ 
+@@ -2559,6 +2560,16 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface,
+ 		}
+ #endif /* CONFIG_MESH */
+ 
++#ifdef CONFIG_IEEE80211AX
++		/* check AFC for 6GHz channels. */
++		res = hostapd_afc_handle_request(iface);
++		if (res <= 0) {
++			if (res < 0)
++				goto fail;
++			return res;
++		}
++#endif /* CONFIG_IEEE80211AX */
++
+ 		if (!delay_apply_cfg &&
+ 		    hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq,
+ 				     hapd->iconf->channel,
+@@ -2957,6 +2968,7 @@ void hostapd_interface_deinit(struct hostapd_iface *iface)
+ 
+ 	hostapd_set_state(iface, HAPD_IFACE_DISABLED);
+ 
++	hostapd_afc_stop(iface);
+ 	eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
+ 	iface->wait_channel_update = 0;
+ 	iface->is_no_ir = false;
+@@ -3030,6 +3042,10 @@ void hostapd_interface_free(struct hostapd_iface *iface)
+ 			   __func__, iface->bss[j]);
+ 		os_free(iface->bss[j]);
+ 	}
++#ifdef CONFIG_AFC
++	os_free(iface->afc.chan_info_list);
++	os_free(iface->afc.freq_range);
++#endif
+ 	hostapd_cleanup_iface(iface);
+ }
+ 
+diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
+index 2ef63e5f2..d67a0afa0 100644
+--- a/src/ap/hostapd.h
++++ b/src/ap/hostapd.h
+@@ -718,9 +718,54 @@ struct hostapd_iface {
+ 	bool is_no_ir;
+ 
+ 	bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */
++
++#ifdef CONFIG_AFC
++	struct {
++		int timeout;
++		unsigned int num_freq_range;
++		struct afc_freq_range_elem {
++			int low_freq;
++			int high_freq;
++			/**
++			 * max eirp power spectral density received from
++			 * the AFC coordinator for this band
++			 */
++			int max_psd;
++		} *freq_range;
++		unsigned int num_chan_info;
++		struct afc_chan_info_elem {
++			int chan;
++			/**
++			 * max eirp power received from the AFC coordinator
++			 * for this channel
++			 */
++			int power;
++		} *chan_info_list;
++		bool data_valid;
++	} afc;
++#endif /* CONFIG_AFC */
+ };
+ 
+ /* hostapd.c */
++#ifdef CONFIG_AFC
++int hostapd_afc_handle_request(struct hostapd_iface *iface);
++void hostapd_afc_stop(struct hostapd_iface *iface);
++void hostap_afc_disable_channels(struct hostapd_iface *iface);
++#else
++static inline int hostapd_afc_handle_request(struct hostapd_iface *iface)
++{
++	return 1;
++}
++
++static inline void hostapd_afc_stop(struct hostapd_iface *iface)
++{
++}
++
++static inline void hostap_afc_disable_channels(struct hostapd_iface *iface)
++{
++}
++#endif /* CONFIG_AFC */
++
+ int hostapd_for_each_interface(struct hapd_interfaces *interfaces,
+ 			       int (*cb)(struct hostapd_iface *iface,
+ 					 void *ctx), void *ctx);
+diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
+index 85e67080d..8aa0b3ab5 100644
+--- a/src/ap/hw_features.c
++++ b/src/ap/hw_features.c
+@@ -114,6 +114,8 @@ int hostapd_get_hw_features(struct hostapd_iface *iface)
+ 	iface->hw_features = modes;
+ 	iface->num_hw_features = num_modes;
+ 
++	hostap_afc_disable_channels(iface);
++
+ 	for (i = 0; i < num_modes; i++) {
+ 		struct hostapd_hw_modes *feature = &modes[i];
+ 		int dfs_enabled = hapd->iconf->ieee80211h &&
+-- 
+2.18.0
+