diff mbox series

[v4,3/4] hostapd: ap: add AFC client support

Message ID fc17b1a9bc501892a3cf2e45db27b48b0d3b9891.1712755468.git.lorenzo@kernel.org
State Superseded
Headers show
Series Introduce Automated Frequency Coordination (AFC) support | expand

Commit Message

Lorenzo Bianconi April 10, 2024, 1:32 p.m. UTC
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>
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          | 978 ++++++++++++++++++++++++++++++++++++++++++
 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, 1418 insertions(+)
 create mode 100644 src/ap/afc.c

Comments

Krishna Chaitanya April 14, 2024, 5:18 p.m. UTC | #1
On Wed, Apr 10, 2024 at 7:05 PM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
>
> 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>
> 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          | 978 ++++++++++++++++++++++++++++++++++++++++++
>  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, 1418 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 56b2df3ae..c674e8ef7 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 */
>
>
> @@ -3862,6 +4046,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 d80abcac0..0d10998af 100644
> --- a/hostapd/hostapd.conf
> +++ b/hostapd/hostapd.conf
> @@ -1005,6 +1005,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..40af3cb4a
> --- /dev/null
> +++ b/src/ap/afc.c
> @@ -0,0 +1,978 @@
> +/*
> + * 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            4096
With the afc-reply.json, the response is 4842 bytes, so, this needs to
be increased.
> +
> +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];
> +
> +               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 the config file doesn't have the entries, then these pointers NULL
and hostapd
crashes.
> +               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);
> +       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)
> +               return -EINVAL;
This function could use some error logs, esp. as its related to parsing.
> +
> +       if (!json_object_object_get_ex(payload_obj, "version", &version_obj))
> +               return -EINVAL;
> +
> +       if (iconf->afc.request.version &&
> +           os_strcmp(iconf->afc.request.version,
> +                     json_object_get_string(version_obj)))
> +               return -EINVAL;
> +
> +       if (!json_object_object_get_ex(payload_obj,
> +                                      "availableSpectrumInquiryResponses",
> +                                      &reply_obj))
> +               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)
> +                       continue;
> +
> +               if (!json_object_object_get_ex(reply_elem_obj, "requestId",
> +                                              &obj))
> +                       continue;
> +
> +               if (iconf->afc.request.id &&
> +                   os_strcmp(iconf->afc.request.id,
> +                             json_object_get_string(obj)))
> +                       continue;
> +
> +               if (!json_object_object_get_ex(reply_elem_obj, "rulesetId",
> +                                              &obj))
> +                       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)
> +                       continue;
> +
> +               if (!json_object_object_get_ex(reply_elem_obj, "response",
> +                                              &obj))
> +                       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)
> +                       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__ */
> +       };
> +       char buf[HOSTAPD_AFC_BUFSIZE] = {};
> +       const char *request;
> +       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;
> +       }
> +
> +       ret = recv(sockfd, buf, sizeof(buf) - 1, 0);
> +       if (ret <= 0)
> +               goto close_sock;
> +
> +       ret = hostapd_afc_parse_reply(iface, buf);
> +close_sock:
> +       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;
> +
> +       /* 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;
> +       }
> +
> +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;
> +       int i;
> +
> +       if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A)
> +               return;
> +
> +       if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))
> +               return;
> +
> +       if (!iface->afc.data_valid)
> +               return;
> +
> +       mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A];
> +       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;
Please add a debug print with channel info.
> +       }
> +}
> diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
> index 1a18df617..ca67aeb41 100644
> --- a/src/ap/ap_config.c
> +++ b/src/ap/ap_config.c
> @@ -1035,6 +1035,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 754d55331..2330163c4 100644
> --- a/src/ap/ap_config.h
> +++ b/src/ap/ap_config.h
> @@ -1225,6 +1225,53 @@ struct hostapd_config {
>                 MBSSID_ENABLED = 1,
>                 ENHANCED_MBSSID_ENABLED = 2,
>         } mbssid;
> +
> +#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 f8cb6432d..940a2a0af 100644
> --- a/src/ap/hostapd.c
> +++ b/src/ap/hostapd.c
> @@ -714,6 +714,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(channel_list_update_timeout, iface, NULL);
>         eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,
>                              NULL);
> @@ -2454,6 +2455,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,
> @@ -2852,6 +2863,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;
> @@ -2925,6 +2937,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 affe4f604..b74dc75f6 100644
> --- a/src/ap/hostapd.h
> +++ b/src/ap/hostapd.h
> @@ -700,9 +700,54 @@ struct hostapd_iface {
>
>         /* Configured freq of interface is NO_IR */
>         bool is_no_ir;
> +
> +#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 e652d7504..222f3dc05 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 &&
> --
Lorenzo Bianconi April 16, 2024, 3:46 p.m. UTC | #2
[...]
> > +
> > +#define HOSTAPD_AFC_RETRY_TIMEOUT      180
> > +#define HOSTAPD_AFC_TIMEOUT            86400 /* 24h */
> > +#define HOSTAPD_AFC_BUFSIZE            4096
> With the afc-reply.json, the response is 4842 bytes, so, this needs to
> be increased.

ack, I will fix it.

> > +
> > +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];
> > +
> > +               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 the config file doesn't have the entries, then these pointers NULL
> and hostapd
> crashes.

ack, I will fix it.

> > +               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);
> > +       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)
> > +               return -EINVAL;
> This function could use some error logs, esp. as its related to parsing.

ack, I will fix it.

> > +
> > +       if (!json_object_object_get_ex(payload_obj, "version", &version_obj))
> > +               return -EINVAL;
> > +
> > +       if (iconf->afc.request.version &&
> > +           os_strcmp(iconf->afc.request.version,
> > +                     json_object_get_string(version_obj)))
> > +               return -EINVAL;
> > +
> > +       if (!json_object_object_get_ex(payload_obj,
> > +                                      "availableSpectrumInquiryResponses",
> > +                                      &reply_obj))
> > +               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)
> > +                       continue;
> > +
> > +               if (!json_object_object_get_ex(reply_elem_obj, "requestId",
> > +                                              &obj))
> > +                       continue;
> > +
> > +               if (iconf->afc.request.id &&
> > +                   os_strcmp(iconf->afc.request.id,
> > +                             json_object_get_string(obj)))
> > +                       continue;
> > +
> > +               if (!json_object_object_get_ex(reply_elem_obj, "rulesetId",
> > +                                              &obj))
> > +                       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)
> > +                       continue;
> > +
> > +               if (!json_object_object_get_ex(reply_elem_obj, "response",
> > +                                              &obj))
> > +                       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)
> > +                       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__ */
> > +       };
> > +       char buf[HOSTAPD_AFC_BUFSIZE] = {};
> > +       const char *request;
> > +       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;
> > +       }
> > +
> > +       ret = recv(sockfd, buf, sizeof(buf) - 1, 0);
> > +       if (ret <= 0)
> > +               goto close_sock;
> > +
> > +       ret = hostapd_afc_parse_reply(iface, buf);
> > +close_sock:
> > +       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;
> > +
> > +       /* 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;
> > +       }
> > +
> > +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;
> > +       int i;
> > +
> > +       if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A)
> > +               return;
> > +
> > +       if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))
> > +               return;
> > +
> > +       if (!iface->afc.data_valid)
> > +               return;
> > +
> > +       mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A];
> > +       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;
> Please add a debug print with channel info.

ack, I will fix it.

Regards,
Lorenzo

> > +       }
> > +}
> > diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
> > index 1a18df617..ca67aeb41 100644
> > --- a/src/ap/ap_config.c
> > +++ b/src/ap/ap_config.c
> > @@ -1035,6 +1035,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 754d55331..2330163c4 100644
> > --- a/src/ap/ap_config.h
> > +++ b/src/ap/ap_config.h
> > @@ -1225,6 +1225,53 @@ struct hostapd_config {
> >                 MBSSID_ENABLED = 1,
> >                 ENHANCED_MBSSID_ENABLED = 2,
> >         } mbssid;
> > +
> > +#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 f8cb6432d..940a2a0af 100644
> > --- a/src/ap/hostapd.c
> > +++ b/src/ap/hostapd.c
> > @@ -714,6 +714,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(channel_list_update_timeout, iface, NULL);
> >         eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,
> >                              NULL);
> > @@ -2454,6 +2455,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,
> > @@ -2852,6 +2863,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;
> > @@ -2925,6 +2937,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 affe4f604..b74dc75f6 100644
> > --- a/src/ap/hostapd.h
> > +++ b/src/ap/hostapd.h
> > @@ -700,9 +700,54 @@ struct hostapd_iface {
> >
> >         /* Configured freq of interface is NO_IR */
> >         bool is_no_ir;
> > +
> > +#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 e652d7504..222f3dc05 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 &&
> > --
diff mbox series

Patch

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 56b2df3ae..c674e8ef7 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 */
 
 
@@ -3862,6 +4046,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 d80abcac0..0d10998af 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1005,6 +1005,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..40af3cb4a
--- /dev/null
+++ b/src/ap/afc.c
@@ -0,0 +1,978 @@ 
+/*
+ * 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		4096
+
+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];
+
+		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);
+	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)
+		return -EINVAL;
+
+	if (!json_object_object_get_ex(payload_obj, "version", &version_obj))
+		return -EINVAL;
+
+	if (iconf->afc.request.version &&
+	    os_strcmp(iconf->afc.request.version,
+		      json_object_get_string(version_obj)))
+		return -EINVAL;
+
+	if (!json_object_object_get_ex(payload_obj,
+				       "availableSpectrumInquiryResponses",
+				       &reply_obj))
+		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)
+			continue;
+
+		if (!json_object_object_get_ex(reply_elem_obj, "requestId",
+					       &obj))
+			continue;
+
+		if (iconf->afc.request.id &&
+		    os_strcmp(iconf->afc.request.id,
+			      json_object_get_string(obj)))
+			continue;
+
+		if (!json_object_object_get_ex(reply_elem_obj, "rulesetId",
+					       &obj))
+			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)
+			continue;
+
+		if (!json_object_object_get_ex(reply_elem_obj, "response",
+					       &obj))
+			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)
+			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__ */
+	};
+	char buf[HOSTAPD_AFC_BUFSIZE] = {};
+	const char *request;
+	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;
+	}
+
+	ret = recv(sockfd, buf, sizeof(buf) - 1, 0);
+	if (ret <= 0)
+		goto close_sock;
+
+	ret = hostapd_afc_parse_reply(iface, buf);
+close_sock:
+	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;
+
+	/* 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;
+	}
+
+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;
+	int i;
+
+	if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A)
+		return;
+
+	if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))
+		return;
+
+	if (!iface->afc.data_valid)
+		return;
+
+	mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A];
+	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;
+	}
+}
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 1a18df617..ca67aeb41 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -1035,6 +1035,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 754d55331..2330163c4 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -1225,6 +1225,53 @@  struct hostapd_config {
 		MBSSID_ENABLED = 1,
 		ENHANCED_MBSSID_ENABLED = 2,
 	} mbssid;
+
+#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 f8cb6432d..940a2a0af 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -714,6 +714,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(channel_list_update_timeout, iface, NULL);
 	eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,
 			     NULL);
@@ -2454,6 +2455,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,
@@ -2852,6 +2863,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;
@@ -2925,6 +2937,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 affe4f604..b74dc75f6 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -700,9 +700,54 @@  struct hostapd_iface {
 
 	/* Configured freq of interface is NO_IR */
 	bool is_no_ir;
+
+#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 e652d7504..222f3dc05 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 &&