Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2219760/?format=api
{ "id": 2219760, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2219760/?format=api", "web_url": "http://patchwork.ozlabs.org/project/hostap/patch/20260402095356.1401176-6-allen.ye@mediatek.com/", "project": { "id": 22, "url": "http://patchwork.ozlabs.org/api/1.1/projects/22/?format=api", "name": "HostAP Development", "link_name": "hostap", "list_id": "hostap.lists.infradead.org", "list_email": "hostap@lists.infradead.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260402095356.1401176-6-allen.ye@mediatek.com>", "date": "2026-04-02T09:53:54", "name": "[v11,5/7] hostapd: ap: Add AFC client support", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "180ddcebd80a33ed1dbbc4804f27438827e194b8", "submitter": { "id": 86975, "url": "http://patchwork.ozlabs.org/api/1.1/people/86975/?format=api", "name": "Allen Ye", "email": "Allen.Ye@mediatek.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/hostap/patch/20260402095356.1401176-6-allen.ye@mediatek.com/mbox/", "series": [ { "id": 498460, "url": "http://patchwork.ozlabs.org/api/1.1/series/498460/?format=api", "web_url": "http://patchwork.ozlabs.org/project/hostap/list/?series=498460", "date": "2026-04-02T09:53:56", "name": "Introduce Automated Frequency Coordination (AFC) support", "version": 11, "mbox": "http://patchwork.ozlabs.org/series/498460/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2219760/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2219760/checks/", "tags": {}, "headers": { "Return-Path": "\n <hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n secure) header.d=lists.infradead.org header.i=@lists.infradead.org\n header.a=rsa-sha256 header.s=bombadil.20210309 header.b=Ghev/2j9;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=mediatek.com header.i=@mediatek.com header.a=rsa-sha256\n header.s=dk header.b=BFnPp9mb;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=none (no SPF record) smtp.mailfrom=lists.infradead.org\n (client-ip=2607:7c80:54:3::133; helo=bombadil.infradead.org;\n envelope-from=hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org;\n receiver=patchwork.ozlabs.org)" ], "Received": [ "from bombadil.infradead.org (bombadil.infradead.org\n [IPv6:2607:7c80:54:3::133])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fnpHQ1yNCz1yGr\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 04 Apr 2026 19:12:50 +1100 (AEDT)", "from localhost ([::1] helo=bombadil.infradead.org)\n\tby bombadil.infradead.org with esmtp (Exim 4.98.2 #2 (Red Hat Linux))\n\tid 1w8w75-00000003GyD-2B1M;\n\tSat, 04 Apr 2026 08:12:15 +0000", "from mailgw02.mediatek.com ([216.200.240.185])\n\tby bombadil.infradead.org with esmtps (Exim 4.98.2 #2 (Red Hat Linux))\n\tid 1w8EzS-0000000HMAw-1758\n\tfor hostap@lists.infradead.org;\n\tThu, 02 Apr 2026 10:09:32 +0000", "from mtkmbs10n1.mediatek.inc [(172.21.101.34)] by\n mailgw02.mediatek.com\n\t(envelope-from <allen.ye@mediatek.com>)\n\t(musrelay.mediatek.com ESMTP with TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384\n 256/256)\n\twith ESMTP id 1512228470; Thu, 02 Apr 2026 02:54:17 -0700", "from mtkmbs13n2.mediatek.inc (172.21.101.108) by\n MTKMBS14N2.mediatek.inc (172.21.101.76) with Microsoft SMTP Server\n (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n 15.2.2562.29; Thu, 2 Apr 2026 17:54:14 +0800", "from mtksitap99.mediatek.inc (10.233.130.16) by\n mtkmbs13n2.mediatek.inc (172.21.101.73) with Microsoft SMTP Server id\n 15.2.2562.29 via Frontend Transport; Thu, 2 Apr 2026 17:54:14 +0800" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;\n\td=lists.infradead.org; s=bombadil.20210309; h=Sender:\n\tContent-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post:\n\tList-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To:\n\tMessage-ID:Date:Subject:CC:To:From:Reply-To:Content-ID:Content-Description:\n\tResent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:\n\tList-Owner; bh=V2wtZTUlokmGXGOClY0EwdPa5oa5OwXd7nlceRvbWoA=; b=Ghev/2j9BULvtX\n\t519vuJ3BhGUIYHscw+11gwFp1eIIqJ5M8CYlTDft1F2i2jWGSuXODtq9xky80ptqpvabV+yljeWpH\n\twRH73OjOGXG74/UXLDXEzmIPHsmbOnIrhB3uNIekGzw7xchSkSdqxtVefNwNQQE/o0BxS5UsDUCcf\n\ty13yW32U3irakNJZC97TLJVfOPYsgUNszpdErAu82r/9WzJ5ifnl36B8so4urdKw3UYPq3IoPBpqc\n\tWhXfrjA5x7IwYv7l1ESWLCoN8ualXKlniuS91NKW6T2wzyyFNCYR8D6GdZkNby0L2n3WR/+Jfz0Ah\n\tHrO/LQ4jxga/d4QjrKjQ==;", "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;\n d=mediatek.com; s=dk;\n\th=Content-Type:Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:CC:To:From;\n bh=qW2pECivHuh7g6foV188pOcyDxPgRK5SYdfoUk7c8RE=;\n\tb=BFnPp9mb9jCXfm5cVusijxLdX148j5ozqZckAVmp/J/PVvKx9wB+t2z6ba/DNddzEdvptleNCRYlpK+duShHarKRzT8F/Ehe88dt5ko0oTdOxJhheuP3A8Hqp7c4PoZjwEhNKnC6NX5Leaxq0+5xXcD0Fta8g1+5cozctRO7a/Y=;" ], "X-UUID": [ "e9e23d562e7911f1a6de359d7043e138-20260402", "e9e23d562e7911f1a6de359d7043e138-20260402" ], "X-CID-P-RULE": "Release_Ham", "X-CID-O-INFO": "VERSION:1.3.12,REQID:9dbf589a-c3e5-4c87-8e5b-55493ea787b6,IP:0,U\n\tRL:0,TC:0,Content:0,EDM:0,RT:0,SF:0,FILE:0,BULK:0,RULE:Release_Ham,ACTION:\n\trelease,TS:0", "X-CID-META": "VersionHash:e7bac3a,CLOUDID:e0f9fd8e-6df4-4a3d-a7a4-fbdc42d669ce,B\n\tulkID:nil,BulkQuantity:0,Recheck:0,SF:81|82|102|836|865|888|898,TC:-5,Cont\n\tent:0|15|50,EDM:-3,IP:nil,URL:1,File:130,RT:0,Bulk:nil,QS:nil,BEC:-1,COL:0\n\t,OSI:0,OSA:0,AV:0,LES:1,SPR:NO,DKR:0,DKP:0,BRR:0,BRE:0,ARC:0", "X-CID-BVR": "2,SSN|SDN", "X-CID-BAS": "2,SSN|SDN,0,_", "X-CID-FACTOR": "TF_CID_SPAM_SNR,TF_CID_SPAM_ULS", "X-CID-RHF": "D41D8CD98F00B204E9800998ECF8427E", "From": "Allen Ye <allen.ye@mediatek.com>", "To": "Jouni Malinen <j@w1.fi>", "CC": "<hostap@lists.infradead.org>, Felix Fietkau <nbd@nbd.name>, Evelyn Tsai\n\t<evelyn.tsai@mediatek.com>, \"Mark-MC . Lee\" <mark-mc.lee@mediatek.com>, Tarun\n Radhakrishnan <tarun.r@tataelxsi.co.in>, Lorenzo Bianconi\n\t<lorenzo@kernel.org>, Allen Ye <allen.ye@mediatek.com>, Krishna Chaitanya\n\t<chaitanya.mgit@gmail.com>", "Subject": "[PATCH v11 5/7] hostapd: ap: Add AFC client support", "Date": "Thu, 2 Apr 2026 17:53:54 +0800", "Message-ID": "<20260402095356.1401176-6-allen.ye@mediatek.com>", "X-Mailer": "git-send-email 2.45.2", "In-Reply-To": "<20260402095356.1401176-1-allen.ye@mediatek.com>", "References": "<20260402095356.1401176-1-allen.ye@mediatek.com>", "MIME-Version": "1.0", "X-MTK": "N", "X-CRM114-Version": "20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 ", "X-CRM114-CacheID": "sfid-20260402_030930_346105_B557B0F5 ", "X-CRM114-Status": "GOOD ( 21.05 )", "X-Spam-Score": "-2.1 (--)", "X-Spam-Report": "Spam detection software,\n running on the system \"bombadil.infradead.org\",\n has NOT identified this incoming email as spam. The original\n message has been attached to this so you can view it or label\n similar future email. If you have any questions, see\n the administrator of that system for details.\n Content preview: From: Lorenzo Bianconi <lorenzo@kernel.org> Introduce\n Automated\n Frequency Coordination (AFC) support for UNII-5 and UNII-7 6GHz bands. AFC\n client will connect to AFCD providing AP related parameters for AFC\n coordinator\n (e.g. geolocation, suppor [...]\n Content analysis details: (-2.1 points, 5.0 required)\n pts rule name description\n ---- ----------------------\n --------------------------------------------------\n 0.0 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED RBL: ADMINISTRATOR NOTICE: The\n query to Validity was blocked. See\n https://knowledge.validity.com/hc/en-us/articles/20961730681243\n for more information.\n [216.200.240.185 listed in\n sa-trusted.bondedsender.org]\n 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to\n Validity was blocked. See\n https://knowledge.validity.com/hc/en-us/articles/20961730681243\n for more information.\n [216.200.240.185 listed in\n bl.score.senderscore.com]\n 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to\n Validity was blocked. See\n https://knowledge.validity.com/hc/en-us/articles/20961730681243\n for more information.\n [216.200.240.185 listed in\n sa-accredit.habeas.com]\n -0.0 SPF_PASS SPF: sender matches SPF record\n -0.0 SPF_HELO_PASS SPF: HELO matches SPF record\n 0.1 DKIM_SIGNED Message has a DKIM or DK signature,\n not necessarily valid\n -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from\n envelope-from domain\n -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from\n author's\n domain\n -0.1 DKIM_VALID Message has at least one valid DKIM or DK\n signature\n -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1%\n [score: 0.0000]\n 0.0 UNPARSEABLE_RELAY Informational: message has unparseable relay\n lines", "X-Mailman-Approved-At": "Sat, 04 Apr 2026 01:12:14 -0700", "X-BeenThere": "hostap@lists.infradead.org", "X-Mailman-Version": "2.1.34", "Precedence": "list", "List-Id": "<hostap.lists.infradead.org>", "List-Unsubscribe": "<http://lists.infradead.org/mailman/options/hostap>,\n <mailto:hostap-request@lists.infradead.org?subject=unsubscribe>", "List-Archive": "<http://lists.infradead.org/pipermail/hostap/>", "List-Post": "<mailto:hostap@lists.infradead.org>", "List-Help": "<mailto:hostap-request@lists.infradead.org?subject=help>", "List-Subscribe": "<http://lists.infradead.org/mailman/listinfo/hostap>,\n <mailto:hostap-request@lists.infradead.org?subject=subscribe>", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Sender": "\"Hostap\" <hostap-bounces@lists.infradead.org>", "Errors-To": "hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org" }, "content": "From: Lorenzo Bianconi <lorenzo@kernel.org>\n\nIntroduce Automated Frequency Coordination (AFC) support for UNII-5 and\nUNII-7 6GHz bands.\nAFC client will connect to AFCD providing AP related parameters for AFC\ncoordinator (e.g. geolocation, supported frequencies, etc.).\nAFC is required for Standard Power Devices (SPDs) to determine a lists\nof channels and EIRP/PSD powers that are available in the 6GHz spectrum.\nAFC hostapd client is tested with AFC DUT Test Harness [0].\n\n[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main\n\nTested-by: Felix Fietkau <nbd@nbd.name>\nTested-by: Allen Ye <allen.ye@mediatek.com>\nTested-by: Krishna Chaitanya <chaitanya.mgit@gmail.com>\nSigned-off-by: Lorenzo Bianconi <lorenzo@kernel.org>\nSigned-off-by: Allen Ye <allen.ye@mediatek.com>\n---\n hostapd/Makefile | 8 +\n hostapd/config_file.c | 250 ++++++++++\n hostapd/ctrl_iface.c | 47 ++\n hostapd/defconfig | 3 +\n hostapd/hostapd.conf | 41 ++\n hostapd/hostapd_cli.c | 29 ++\n src/ap/afc.c | 1080 +++++++++++++++++++++++++++++++++++++++++\n src/ap/ap_config.c | 15 +\n src/ap/ap_config.h | 46 ++\n src/ap/hostapd.c | 24 +\n src/ap/hostapd.h | 52 ++\n src/ap/hw_features.c | 2 +\n src/common/wpa_ctrl.h | 3 +\n 13 files changed, 1600 insertions(+)\n create mode 100644 src/ap/afc.c", "diff": "diff --git a/hostapd/Makefile b/hostapd/Makefile\nindex b2420e847..dc27c397a 100644\n--- a/hostapd/Makefile\n+++ b/hostapd/Makefile\n@@ -104,6 +104,14 @@ CFLAGS += -DCONFIG_TAXONOMY\n OBJS += ../src/ap/taxonomy.o\n endif\n \n+ifdef CONFIG_IEEE80211AX\n+ifdef CONFIG_AFC\n+CFLAGS += -DCONFIG_AFC\n+OBJS += ../src/ap/afc.o\n+NEED_JSON=y\n+endif\n+endif\n+\n ifdef CONFIG_MODULE_TESTS\n CFLAGS += -DCONFIG_MODULE_TESTS\n OBJS += hapd_module_tests.o\ndiff --git a/hostapd/config_file.c b/hostapd/config_file.c\nindex afb983e02..5515ce4ef 100644\n--- a/hostapd/config_file.c\n+++ b/hostapd/config_file.c\n@@ -1281,6 +1281,198 @@ static int hostapd_parse_he_srg_bitmap(u8 *bitmap, char *val)\n \treturn 0;\n }\n \n+\n+#ifdef CONFIG_AFC\n+static int hostapd_afc_parse_cert_ids(struct hostapd_config *conf, char *pos)\n+{\n+\tstruct cert_id *c = NULL, *tmp;\n+\tunsigned int i, count = 0;\n+\n+\twhile (pos && pos[0]) {\n+\t\tchar *p;\n+\n+\t\ttmp = os_realloc_array(c, count + 1, sizeof(*c));\n+\t\tif (!tmp)\n+\t\t\tgoto error;\n+\t\tc = tmp;\n+\n+\t\ti = count;\n+\t\tcount++;\n+\t\tos_memset(&c[i], 0, sizeof(struct cert_id));\n+\n+\t\tp = os_strchr(pos, ':');\n+\t\tif (!p)\n+\t\t\tgoto error;\n+\n+\t\t*p++ = '\\0';\n+\t\tif (!p || !p[0])\n+\t\t\tgoto error;\n+\n+\t\tc[i].rulset = os_strdup(pos);\n+\t\tif (!c[i].rulset)\n+\t\t\tgoto error;\n+\n+\t\tpos = p;\n+\t\tp = os_strchr(pos, ',');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\n+\t\tc[i].id = os_strdup(pos);\n+\t\tif (!c[i].id)\n+\t\t\tgoto error;\n+\n+\t\tpos = p;\n+\t}\n+\n+\tfor (i = 0; i < conf->afc.n_cert_ids; i++) {\n+\t\tos_free(conf->afc.cert_ids[i].rulset);\n+\t\tos_free(conf->afc.cert_ids[i].id);\n+\t}\n+\tos_free(conf->afc.cert_ids);\n+\n+\tconf->afc.n_cert_ids = count;\n+\tconf->afc.cert_ids = c;\n+\n+\treturn 0;\n+\n+error:\n+\tfor (i = 0; i < count; i++) {\n+\t\tos_free(c[i].rulset);\n+\t\tos_free(c[i].id);\n+\t}\n+\tos_free(c);\n+\n+\treturn -ENOMEM;\n+}\n+\n+\n+static int hostapd_afc_parse_position_data(struct afc_linear_polygon **data,\n+\t\t\t\t\t unsigned int *n_linear_polygon_data,\n+\t\t\t\t\t char *pos)\n+{\n+\tstruct afc_linear_polygon *d = NULL, *tmp;\n+\tint count = 0;\n+\n+\twhile (pos && pos[0]) {\n+\t\tchar *p, *end;\n+\n+\t\ttmp = os_realloc_array(d, count + 1, sizeof(*d));\n+\t\tif (!tmp)\n+\t\t\tgoto error;\n+\t\td = tmp;\n+\n+\t\tp = os_strchr(pos, ':');\n+\t\tif (!p)\n+\t\t\tgoto error;\n+\n+\t\t*p++ = '\\0';\n+\t\tif (!p || !p[0])\n+\t\t\tgoto error;\n+\n+\t\td[count].longitude = strtod(pos, &end);\n+\t\tif (*end)\n+\t\t\tgoto error;\n+\n+\t\tpos = p;\n+\t\tp = os_strchr(pos, ',');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\n+\t\td[count].latitude = strtod(pos, &end);\n+\t\tif (*end)\n+\t\t\tgoto error;\n+\n+\t\tpos = p;\n+\t\tcount++;\n+\t}\n+\n+\tos_free(*data);\n+\t*n_linear_polygon_data = count;\n+\t*data = d;\n+\n+\treturn 0;\n+\n+error:\n+\tos_free(d);\n+\treturn -ENOMEM;\n+}\n+\n+\n+static int hostapd_afc_parse_freq_range(struct hostapd_config *conf, char *pos)\n+{\n+\tstruct afc_freq_range *f = NULL, *tmp;\n+\tint count = 0;\n+\n+\twhile (pos && pos[0]) {\n+\t\tchar *p;\n+\n+\t\ttmp = os_realloc_array(f, count + 1, sizeof(*f));\n+\t\tif (!tmp)\n+\t\t\tgoto error;\n+\t\tf = tmp;\n+\n+\t\tp = os_strchr(pos, ':');\n+\t\tif (!p)\n+\t\t\tgoto error;\n+\n+\t\t*p++ = '\\0';\n+\t\tif (!p || !p[0])\n+\t\t\tgoto error;\n+\n+\t\tf[count].low_freq = atoi(pos);\n+\t\tpos = p;\n+\t\tp = os_strchr(pos, ',');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\n+\t\tf[count].high_freq = atoi(pos);\n+\t\tpos = p;\n+\t\tcount++;\n+\t}\n+\n+\tos_free(conf->afc.freq_range);\n+\tconf->afc.n_freq_range = count;\n+\tconf->afc.freq_range = f;\n+\n+\treturn 0;\n+\n+error:\n+\tos_free(f);\n+\treturn -ENOMEM;\n+}\n+\n+\n+static int hostapd_afc_parse_op_class(struct hostapd_config *conf, char *pos)\n+{\n+\tunsigned int *oc = NULL, *tmp;\n+\tint count = 0;\n+\n+\twhile (pos && pos[0]) {\n+\t\tchar *p;\n+\n+\t\ttmp = os_realloc_array(oc, count + 1, sizeof(*oc));\n+\t\tif (!tmp) {\n+\t\t\tos_free(oc);\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\t\toc = tmp;\n+\n+\t\tp = os_strchr(pos, ',');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\n+\t\toc[count] = atoi(pos);\n+\t\tpos = p;\n+\t\tcount++;\n+\t}\n+\n+\tos_free(conf->afc.op_class);\n+\tconf->afc.n_op_class = count;\n+\tconf->afc.op_class = oc;\n+\n+\treturn 0;\n+}\n+#endif /* CONFIG_AFC */\n #endif /* CONFIG_IEEE80211AX */\n \n \n@@ -3738,6 +3930,64 @@ static int hostapd_config_fill(struct hostapd_config *conf,\n \t\t\treturn 1;\n \t\t}\n \t\tbss->unsol_bcast_probe_resp_interval = val;\n+#ifdef CONFIG_AFC\n+\t} else if (os_strcmp(buf, \"afcd_sock\") == 0) {\n+\t\tos_free(conf->afc.socket);\n+\t\tconf->afc.socket = os_strdup(pos);\n+\t} else if (os_strcmp(buf, \"afc_request_version\") == 0) {\n+\t\tos_free(conf->afc.request.version);\n+\t\tconf->afc.request.version = os_strdup((pos));\n+\t} else if (os_strcmp(buf, \"afc_serial_number\") == 0) {\n+\t\tos_free(conf->afc.request.sn);\n+\t\tconf->afc.request.sn = os_strdup((pos));\n+\t} else if (os_strcmp(buf, \"afc_cert_ids\") == 0) {\n+\t\tif (hostapd_afc_parse_cert_ids(conf, pos))\n+\t\t\treturn 1;\n+\t} else if (os_strcmp(buf, \"afc_location_type\") == 0) {\n+\t\tconf->afc.location.type = atoi(pos);\n+\t\tif (conf->afc.location.type != ELLIPSE &&\n+\t\t conf->afc.location.type != LINEAR_POLYGON &&\n+\t\t conf->afc.location.type != RADIAL_POLYGON)\n+\t\t\treturn 1;\n+\t} else if (os_strcmp(buf, \"afc_linear_polygon\") == 0) {\n+\t\tif (hostapd_afc_parse_position_data(\n+\t\t\t&conf->afc.location.linear_polygon_data,\n+\t\t\t&conf->afc.location.n_linear_polygon_data,\n+\t\t\tpos))\n+\t\t\treturn 1;\n+\t} else if (os_strcmp(buf, \"afc_radial_polygon\") == 0) {\n+\t\tif (hostapd_afc_parse_position_data(\n+\t\t\t(struct afc_linear_polygon **)\n+\t\t\t&conf->afc.location.radial_polygon_data,\n+\t\t\t&conf->afc.location.n_radial_polygon_data,\n+\t\t\tpos))\n+\t\t\treturn 1;\n+\t} else if (os_strcmp(buf, \"afc_major_axis\") == 0) {\n+\t\tconf->afc.location.major_axis = atoi(pos);\n+\t} else if (os_strcmp(buf, \"afc_minor_axis\") == 0) {\n+\t\tconf->afc.location.minor_axis = atoi(pos);\n+\t} else if (os_strcmp(buf, \"afc_orientation\") == 0) {\n+\t\tconf->afc.location.orientation = atoi(pos);\n+\t} else if (os_strcmp(buf, \"afc_height\") == 0) {\n+\t\tchar *end;\n+\n+\t\tconf->afc.location.height = strtod(pos, &end);\n+\t\tif (*end)\n+\t\t\treturn 1;\n+\t} else if (os_strcmp(buf, \"afc_height_type\") == 0) {\n+\t\tos_free(conf->afc.location.height_type);\n+\t\tconf->afc.location.height_type = os_strdup(pos);\n+\t} else if (os_strcmp(buf, \"afc_vertical_tolerance\") == 0) {\n+\t\tconf->afc.location.vertical_tolerance = atoi(pos);\n+\t} else if (os_strcmp(buf, \"afc_min_power\") == 0) {\n+\t\tconf->afc.min_power = atoi(pos);\n+\t} else if (os_strcmp(buf, \"afc_freq_range\") == 0) {\n+\t\tif (hostapd_afc_parse_freq_range(conf, pos))\n+\t\t\treturn 1;\n+\t} else if (os_strcmp(buf, \"afc_op_class\") == 0) {\n+\t\tif (hostapd_afc_parse_op_class(conf, pos))\n+\t\t\treturn 1;\n+#endif /* CONFIG_AFC */\n \t} else if (os_strcmp(buf, \"mbssid\") == 0) {\n \t\tint mbssid = atoi(pos);\n \t\tif (mbssid < 0 || mbssid > ENHANCED_MBSSID_ENABLED) {\ndiff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c\nindex 317356618..25c070b0a 100644\n--- a/hostapd/ctrl_iface.c\n+++ b/hostapd/ctrl_iface.c\n@@ -3951,6 +3951,40 @@ static int hostapd_ctrl_iface_proc_coord_test(struct hostapd_data *hapd,\n #endif /* CONFIG_TESTING_OPTIONS */\n \n \n+#ifdef CONFIG_AFC\n+static int\n+hostapd_cli_cmd_afc_get_request(struct hostapd_data *hapd, char *cmd,\n+\t\t\t\tchar *buf, size_t buflen)\n+{\n+\tif (!hapd->iface->afc_request)\n+\t\treturn os_snprintf(buf, buflen, \"%s\\n\",\n+\t\t\t\t \"request data is NULL!\");\n+\n+\treturn os_snprintf(buf, buflen, \"%s\\n\",\n+\t\t\t (const char *) wpabuf_head(hapd->iface->afc_request));\n+}\n+\n+static int\n+hostapd_cli_cmd_afc_get_response(struct hostapd_data *hapd, char *cmd,\n+\t\t\t\t char *buf, size_t buflen)\n+{\n+\tif (!hapd->iface->afc_response)\n+\t\treturn os_snprintf(buf, buflen, \"%s\\n\",\n+\t\t\t\t \"response data is NULL!\");\n+\n+\treturn os_snprintf(buf, buflen, \"%s\\n\", hapd->iface->afc_response);\n+}\n+\n+static int\n+hostapd_cli_cmd_afc_send_request(struct hostapd_data *hapd, char *cmd,\n+\t\t\t\t char *buf, size_t buflen)\n+{\n+\thostapd_afc_send_request(hapd->iface);\n+\treturn os_snprintf(buf, buflen, \"OK\\n\");\n+}\n+#endif /* CONFIG_AFC */\n+\n+\n static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,\n \t\t\t\t\t char *buf, char *reply,\n \t\t\t\t\t int reply_size,\n@@ -4571,6 +4605,19 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,\n \t\t\treply_len = -1;\n #endif /* CONFIG_PROCESS_COORDINATION */\n #endif /* CONFIG_TESTING_OPTIONS */\n+#ifdef CONFIG_AFC\n+\t} else if (os_strncmp(buf, \"AFC_GET_REQUEST\", 15) == 0) {\n+\t\treply_len = hostapd_cli_cmd_afc_get_request(hapd, buf + 15,\n+\t\t\t\t\t\t\t reply, reply_size);\n+\t} else if (os_strncmp(buf, \"AFC_GET_RESPONSE\", 16) == 0) {\n+\t\treply_len = hostapd_cli_cmd_afc_get_response(hapd, buf + 16,\n+\t\t\t\t\t\t\t reply,\n+\t\t\t\t\t\t\t reply_size);\n+\t} else if (os_strncmp(buf, \"AFC_SEND_REQUEST\", 16) == 0) {\n+\t\treply_len = hostapd_cli_cmd_afc_send_request(hapd, buf + 16,\n+\t\t\t\t\t\t\t reply,\n+\t\t\t\t\t\t\t reply_size);\n+#endif /* CONFIG_AFC */\n \t} else {\n \t\tos_memcpy(reply, \"UNKNOWN COMMAND\\n\", 16);\n \t\treply_len = 16;\ndiff --git a/hostapd/defconfig b/hostapd/defconfig\nindex 176776677..2e552f638 100644\n--- a/hostapd/defconfig\n+++ b/hostapd/defconfig\n@@ -428,3 +428,6 @@ CONFIG_DPP2=y\n # IEEE P802.11bi/D4.0, 12.16.5 (IEEE 802.1X authentication utilizing\n # Authentication frames)\n #CONFIG_IEEE8021X_AUTH=y\n+\n+# Enable Automated Frequency Coordination for 6GHz outdoor\n+#CONFIG_AFC=y\ndiff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf\nindex fac07cd37..abfba65f7 100644\n--- a/hostapd/hostapd.conf\n+++ b/hostapd/hostapd.conf\n@@ -1050,6 +1050,47 @@ wmm_ac_vo_acm=0\n # 1 = Supports Peer-to-peer TWT\n #peer_to_peer_twt=0\n \n+##### Automated Frequency Coordination for 6GHz UNII-5 and UNII-7 bands #######\n+\n+# AFC daemon connection socket\n+#afcd_sock=/var/run/afcd.sock\n+\n+# AFC request identification parameters\n+#afc_request_version=1.1\n+#afc_serial_number=abcdefg\n+#afc_cert_ids=US_47_CFR_PART_15_SUBPART_E:CID000\n+#\n+# AFC location type:\n+# 0 = ellipse\n+# 1 = linear polygon\n+# 2 = radial polygon\n+#afc_location_type=0\n+#\n+# AFC ellipse or linear polygon coordinations\n+#afc_linear_polygon=-122.984157:37.425056\n+#\n+# AFC radial polygon coordinations\n+#afc_radial_polygon=118.8:92.76,76.44:87.456,98.56:123.33\n+#\n+# AFC ellipse major/minor axis and orientation\n+#afc_major_axis=100\n+#afc_minor_axis=50\n+#afc_orientation=70\n+#\n+# AFC device elevation parameters\n+#afc_height=3.0\n+#afc_height_type=AGL\n+#afc_vertical_tolerance=7\n+#\n+# AFC minimum desired TX power (dbm)\n+#afc_min_power=24\n+#\n+# AFC request frequency ranges\n+#afc_freq_range=5925:6425,6525:6875\n+#\n+# AFC request operation classes\n+#afc_op_class=131,132,133,134,136,137\n+\n ##### IEEE 802.11be related configuration #####################################\n \n #ieee80211be: Whether IEEE 802.11be (EHT) is enabled\ndiff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c\nindex 371f990fe..f649b7693 100644\n--- a/hostapd/hostapd_cli.c\n+++ b/hostapd/hostapd_cli.c\n@@ -1683,6 +1683,27 @@ static int hostapd_cli_cmd_driver(struct wpa_ctrl *ctrl, int argc, char *argv[])\n #endif /* ANDROID */\n \n \n+#ifdef CONFIG_AFC\n+static int hostapd_cli_cmd_afc_get_request(struct wpa_ctrl *ctrl, int argc,\n+\t\t\t\t\t char *argv[])\n+{\n+\treturn hostapd_cli_cmd(ctrl, \"AFC_GET_REQUEST\", 0, 0, NULL);\n+}\n+\n+static int hostapd_cli_cmd_afc_get_response(struct wpa_ctrl *ctrl, int argc,\n+\t\t\t\t\t char *argv[])\n+{\n+\treturn hostapd_cli_cmd(ctrl, \"AFC_GET_RESPONSE\", 0, 0, NULL);\n+}\n+\n+static int hostapd_cli_cmd_afc_send_request(struct wpa_ctrl *ctrl, int argc,\n+\t\t\t\t\t char *argv[])\n+{\n+\treturn hostapd_cli_cmd(ctrl, \"AFC_SEND_REQUEST\", 0, 0, NULL);\n+}\n+#endif /* CONFIG_AFC */\n+\n+\n struct hostapd_cli_cmd {\n \tconst char *cmd;\n \tint (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]);\n@@ -1919,6 +1940,14 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {\n \t{ \"driver\", hostapd_cli_cmd_driver, NULL,\n \t \"<driver sub command> [<hex formatted data>] = send driver command data\" },\n #endif /* ANDROID */\n+#ifdef CONFIG_AFC\n+\t{ \"afc_get_request\", hostapd_cli_cmd_afc_get_request, NULL,\n+\t \" = Show latest AFC request data\"},\n+\t{ \"afc_get_response\", hostapd_cli_cmd_afc_get_response, NULL,\n+\t \" = Show latest AFC response data\"},\n+\t{ \"afc_send_request\", hostapd_cli_cmd_afc_send_request, NULL,\n+\t \" = Send another AFC request and upadte the timeout\"},\n+#endif /* CONFIG_AFC */\n \t{ NULL, NULL, NULL, NULL }\n };\n \ndiff --git a/src/ap/afc.c b/src/ap/afc.c\nnew file mode 100644\nindex 000000000..e6b9769c0\n--- /dev/null\n+++ b/src/ap/afc.c\n@@ -0,0 +1,1080 @@\n+/*\n+ * Automated Frequency Coordination\n+ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>\n+ *\n+ * This software may be distributed under the terms of the BSD license.\n+ * See README for more details.\n+ */\n+\n+#include \"utils/includes.h\"\n+#include <sys/un.h>\n+#include <time.h>\n+\n+#include \"utils/common.h\"\n+#include \"utils/eloop.h\"\n+#include \"utils/json.h\"\n+#include \"common/hw_features_common.h\"\n+#include \"common/wpa_ctrl.h\"\n+#include \"hostapd.h\"\n+#include \"acs.h\"\n+#include \"hw_features.h\"\n+\n+#define HOSTAPD_AFC_RETRY_TIMEOUT\t180\n+#define HOSTAPD_AFC_TIMEOUT\t\t86400 /* 24h */\n+#define HOSTAPD_AFC_BUFSIZE\t\t8192\n+\n+static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx);\n+static int hostapd_afc_reset_channels(struct hostapd_iface *iface);\n+\n+static struct wpabuf *\n+hostapd_afc_build_location_request(struct hostapd_iface *iface)\n+{\n+\tstruct wpabuf *location_obj, *ellipse_obj = NULL;\n+\tstruct hostapd_config *iconf = iface->conf;\n+\tbool is_ap_indoor = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type);\n+\tsize_t location_len = 1024, ellipse_len = 128;\n+\tunsigned int i;\n+\n+\tlocation_obj = wpabuf_alloc(location_len);\n+\tif (!location_obj)\n+\t\treturn NULL;\n+\n+\tjson_start_object(location_obj, \"location\");\n+\tif (iconf->afc.location.type != LINEAR_POLYGON) {\n+\t\tstruct afc_linear_polygon *lp =\n+\t\t\t&iconf->afc.location.linear_polygon_data[0];\n+\n+\t\tif (!lp)\n+\t\t\tgoto error;\n+\n+\t\tellipse_obj = wpabuf_alloc(ellipse_len);\n+\t\tif (!ellipse_obj)\n+\t\t\tgoto error;\n+\n+\t\tjson_start_object(ellipse_obj, \"center\");\n+\n+\t\tjson_add_double(ellipse_obj, \"longitude\", lp->longitude);\n+\t\tjson_value_sep(ellipse_obj);\n+\n+\t\tjson_add_double(ellipse_obj, \"latitude\", lp->latitude);\n+\t\tjson_end_object(ellipse_obj);\n+\t\tjson_value_sep(ellipse_obj);\n+\t}\n+\n+\tswitch (iconf->afc.location.type) {\n+\tcase LINEAR_POLYGON: {\n+\t\tjson_start_object(location_obj, \"linearPolygon\");\n+\t\tjson_start_array(location_obj, \"outerBoundary\");\n+\t\tfor (i = 0;\n+\t\t i < iconf->afc.location.n_linear_polygon_data; i++) {\n+\t\t\tstruct afc_linear_polygon *lp =\n+\t\t\t\t&iconf->afc.location.linear_polygon_data[i];\n+\n+\t\t\tjson_start_object(location_obj, NULL);\n+\n+\t\t\tjson_add_double(location_obj, \"longitude\",\n+\t\t\t\t\tlp->longitude);\n+\t\t\tjson_value_sep(location_obj);\n+\n+\t\t\tjson_add_double(location_obj, \"latitude\",\n+\t\t\t\t\tlp->latitude);\n+\n+\t\t\tjson_end_object(location_obj);\n+\t\t\tif (i < iconf->afc.location.n_linear_polygon_data - 1)\n+\t\t\t\tjson_value_sep(location_obj);\n+\t\t}\n+\t\tjson_end_array(location_obj);\n+\n+\t\t/* linearPolygon */\n+\t\tjson_end_object(location_obj);\n+\t\tjson_value_sep(location_obj);\n+\t\tbreak;\n+\t}\n+\tcase RADIAL_POLYGON: {\n+\t\tjson_start_object(location_obj, \"radialPolygon\");\n+\t\twpabuf_put_buf(location_obj, ellipse_obj);\n+\n+\t\tjson_start_array(location_obj, \"outerBoundary\");\n+\t\tfor (i = 0;\n+\t\t i < iconf->afc.location.n_radial_polygon_data; i++) {\n+\t\t\tstruct afc_radial_polygon *rp =\n+\t\t\t\t&iconf->afc.location.radial_polygon_data[i];\n+\n+\t\t\tjson_start_object(location_obj, NULL);\n+\t\t\tjson_add_double(location_obj, \"length\", rp->length);\n+\t\t\tjson_value_sep(location_obj);\n+\n+\t\t\tjson_add_double(location_obj, \"angle\", rp->angle);\n+\t\t\tjson_end_object(location_obj);\n+\t\t\tif (i < iconf->afc.location.n_radial_polygon_data - 1)\n+\t\t\t\tjson_value_sep(location_obj);\n+\t\t}\n+\t\tjson_end_array(location_obj);\n+\n+\t\t/* radialPolygon */\n+\t\tjson_end_object(location_obj);\n+\t\tjson_value_sep(location_obj);\n+\t\tbreak;\n+\t}\n+\tcase ELLIPSE:\n+\tdefault:\n+\t\tjson_start_object(location_obj, \"ellipse\");\n+\t\twpabuf_put_buf(location_obj, ellipse_obj);\n+\n+\t\tjson_add_int(location_obj, \"majorAxis\",\n+\t\t\t iconf->afc.location.major_axis);\n+\t\tjson_value_sep(location_obj);\n+\t\tjson_add_int(location_obj, \"minorAxis\",\n+\t\t\t iconf->afc.location.minor_axis);\n+\t\tjson_value_sep(location_obj);\n+\t\tjson_add_int(location_obj, \"orientation\",\n+\t\t\t iconf->afc.location.orientation);\n+\t\t/* ellipse */\n+\t\tjson_end_object(location_obj);\n+\t\tjson_value_sep(location_obj);\n+\t\tbreak;\n+\t}\n+\n+\tjson_start_object(location_obj, \"elevation\");\n+\n+\tjson_add_double(location_obj, \"height\", iconf->afc.location.height);\n+\tjson_value_sep(location_obj);\n+\tif (iconf->afc.location.height_type) {\n+\t\tjson_add_string(location_obj, \"heightType\",\n+\t\t\t\ticonf->afc.location.height_type);\n+\t\tjson_value_sep(location_obj);\n+\t}\n+\tjson_add_int(location_obj, \"verticalUncertainty\",\n+\t\t iconf->afc.location.vertical_tolerance);\n+\n+\t/* elevation */\n+\tjson_end_object(location_obj);\n+\tjson_value_sep(location_obj);\n+\n+\tjson_add_int(location_obj, \"indoorDeployment\", is_ap_indoor);\n+\n+\t/* location */\n+\tjson_end_object(location_obj);\n+\tjson_value_sep(location_obj);\n+\n+\twpabuf_free(ellipse_obj);\n+\treturn location_obj;\n+\n+error:\n+\twpabuf_free(location_obj);\n+\twpabuf_free(ellipse_obj);\n+\n+\treturn NULL;\n+}\n+\n+\n+static struct wpabuf * hostapd_afc_get_opclass_chan_list(u8 op_class)\n+{\n+\tstruct wpabuf *chan_list_obj;\n+\tconst struct oper_class_map *oper_class;\n+\tint chan_offset, chan;\n+\tsize_t ch_len = 512;\n+\n+\toper_class = get_oper_class(NULL, op_class);\n+\tif (!oper_class)\n+\t\treturn NULL;\n+\n+\tchan_list_obj = wpabuf_alloc(ch_len);\n+\tif (!chan_list_obj)\n+\t\treturn NULL;\n+\tjson_start_array(chan_list_obj, \"channelCfi\");\n+\n+\tswitch (op_class) {\n+\tcase 132: /* 40MHz */\n+\t\tchan_offset = 2;\n+\t\tbreak;\n+\tcase 133: /* 80MHz */\n+\t\tchan_offset = 6;\n+\t\tbreak;\n+\tcase 134: /* 160MHz */\n+\t\tchan_offset = 14;\n+\t\tbreak;\n+\tcase 137: /* 320MHz */\n+\t\t/*\n+\t\t * global_op_class already use the central channels for\n+\t\t * 320 MHz, so fallthrough and use 0 for chan_offset.\n+\t\t */\n+\tdefault:\n+\t\tchan_offset = 0;\n+\t\tbreak;\n+\t}\n+\n+\tfor (chan = oper_class->min_chan; chan <= oper_class->max_chan;\n+\t chan += oper_class->inc) {\n+\t\tchar ch_str[32] = {0};\n+\n+\t\tif (chan + chan_offset > oper_class->max_chan)\n+\t\t\tbreak;\n+\n+\t\tsnprintf(ch_str, sizeof(ch_str), \"%s%d\",\n+\t\t\t chan != oper_class->min_chan ? \", \":\"\",\n+\t\t\t chan + chan_offset);\n+\t\twpabuf_put_str(chan_list_obj, ch_str);\n+\t}\n+\tjson_end_array(chan_list_obj);\n+\n+\treturn chan_list_obj;\n+}\n+\n+\n+static struct wpabuf *\n+hostapd_afc_build_req_chan_list(struct hostapd_iface *iface)\n+{\n+\tstruct wpabuf *op_class_list_obj;\n+\tstruct hostapd_config *iconf = iface->conf;\n+\tunsigned int i;\n+\n+\top_class_list_obj = wpabuf_alloc(HOSTAPD_AFC_BUFSIZE);\n+\tif (!op_class_list_obj)\n+\t\treturn NULL;\n+\n+\tjson_start_array(op_class_list_obj, \"inquiredChannels\");\n+\tfor (i = 0; i < iconf->afc.n_op_class; i++) {\n+\t\tstruct wpabuf *chan_list_obj = NULL;\n+\t\tu8 op_class = iconf->afc.op_class[i];\n+\n+\t\tif (!is_6ghz_op_class(op_class))\n+\t\t\tcontinue;\n+\n+\t\tjson_start_object(op_class_list_obj, NULL);\n+\n+\t\tjson_add_int(op_class_list_obj, \"globalOperatingClass\",\n+\t\t\t op_class);\n+\t\tjson_value_sep(op_class_list_obj);\n+\n+\t\tchan_list_obj = hostapd_afc_get_opclass_chan_list(op_class);\n+\t\tif (!chan_list_obj)\n+\t\t\tgoto error;\n+\n+\t\twpabuf_put_buf(op_class_list_obj, chan_list_obj);\n+\t\tjson_end_object(op_class_list_obj);\n+\n+\t\tif (i < iconf->afc.n_op_class - 1)\n+\t\t\tjson_value_sep(op_class_list_obj);\n+\n+\t\twpabuf_free(chan_list_obj);\n+\t\tchan_list_obj = NULL;\n+\t}\n+\tjson_end_array(op_class_list_obj);\n+\n+\treturn op_class_list_obj;\n+\n+error:\n+\twpabuf_free(op_class_list_obj);\n+\treturn NULL;\n+}\n+\n+\n+static struct wpabuf *\n+hostapd_afc_build_request(struct hostapd_iface *iface)\n+{\n+\tstruct wpabuf *req_obj, *location_obj, *op_class_list_obj = NULL;\n+\tstruct hostapd_config *iconf = iface->conf;\n+\tunsigned int i;\n+\tchar request_id_str[16];\n+\n+\treq_obj = wpabuf_alloc(HOSTAPD_AFC_BUFSIZE);\n+\tif (!req_obj)\n+\t\treturn NULL;\n+\n+\tjson_start_object(req_obj, NULL);\n+\n+\tif (iconf->afc.request.version) {\n+\t\tjson_add_string(req_obj, \"version\",\n+\t\t\t\ticonf->afc.request.version);\n+\t\tjson_value_sep(req_obj);\n+\t}\n+\n+\tjson_start_array(req_obj, \"availableSpectrumInquiryRequests\");\n+\tjson_start_object(req_obj, NULL);\n+\n+\tiface->afc.request_id++;\n+\tsnprintf(request_id_str, sizeof(request_id_str), \"%u\",\n+\t\t iface->afc.request_id);\n+\tjson_add_string(req_obj, \"requestId\", request_id_str);\n+\tjson_value_sep(req_obj);\n+\n+\tjson_start_object(req_obj, \"deviceDescriptor\");\n+\tif (iconf->afc.request.sn) {\n+\t\tjson_add_string(req_obj, \"serialNumber\",\n+\t\t\t\ticonf->afc.request.sn);\n+\t\tjson_value_sep(req_obj);\n+\t}\n+\n+\tjson_start_array(req_obj, \"certificationId\");\n+\n+\tfor (i = 0; i < iconf->afc.n_cert_ids; i++) {\n+\t\tjson_start_object(req_obj, NULL);\n+\t\tjson_add_string(req_obj, \"rulesetId\",\n+\t\t\t\ticonf->afc.cert_ids[i].rulset);\n+\t\tjson_value_sep(req_obj);\n+\n+\t\tjson_add_string(req_obj, \"id\", iconf->afc.cert_ids[i].id);\n+\t\tjson_end_object(req_obj);\n+\n+\t\tif (i < iconf->afc.n_cert_ids - 1)\n+\t\t\tjson_value_sep(req_obj);\n+\t}\n+\t/* certificationId */\n+\tjson_end_array(req_obj);\n+\n+\t/* deviceDescriptor */\n+\tjson_end_object(req_obj);\n+\tjson_value_sep(req_obj);\n+\n+\tlocation_obj = hostapd_afc_build_location_request(iface);\n+\tif (!location_obj)\n+\t\tgoto error;\n+\n+\twpabuf_put_buf(req_obj, location_obj);\n+\n+\tif (iconf->afc.n_freq_range) {\n+\t\tjson_start_array(req_obj, \"inquiredFrequencyRange\");\n+\t\tfor (i = 0; i < iconf->afc.n_freq_range; i++) {\n+\t\t\tstruct afc_freq_range *fr = &iconf->afc.freq_range[i];\n+\n+\t\t\tjson_start_object(req_obj, NULL);\n+\n+\t\t\tjson_add_int(req_obj, \"lowFrequency\", fr->low_freq);\n+\t\t\tjson_value_sep(req_obj);\n+\t\t\tjson_add_int(req_obj, \"highFrequency\", fr->high_freq);\n+\n+\t\t\tjson_end_object(req_obj);\n+\t\t\tif (i < iconf->afc.n_freq_range - 1)\n+\t\t\t\tjson_value_sep(req_obj);\n+\t\t}\n+\t\tjson_end_array(req_obj);\n+\t\tjson_value_sep(req_obj);\n+\t}\n+\n+\top_class_list_obj = hostapd_afc_build_req_chan_list(iface);\n+\tif (!op_class_list_obj)\n+\t\tgoto error;\n+\n+\twpabuf_put_buf(req_obj, op_class_list_obj);\n+\n+\tif (iconf->afc.min_power) {\n+\t\tjson_value_sep(req_obj);\n+\t\tjson_add_int(req_obj, \"minDesiredPower\", iconf->afc.min_power);\n+\t}\n+\n+\t/* availableSpectrumInquiryRequests */\n+\tjson_end_object(req_obj);\n+\tjson_end_array(req_obj);\n+\n+\tjson_end_object(req_obj);\n+\twpa_printf(MSG_DEBUG, \"Pending AFC request: %s\",\n+\t\t (const char *) wpabuf_head(req_obj));\n+\n+\twpabuf_free(location_obj);\n+\twpabuf_free(op_class_list_obj);\n+\n+\treturn req_obj;\n+\n+error:\n+\twpabuf_free(req_obj);\n+\twpabuf_free(location_obj);\n+\twpabuf_free(op_class_list_obj);\n+\n+\treturn NULL;\n+}\n+\n+\n+static int\n+hostad_afc_parse_available_freq_info(struct hostapd_iface *iface,\n+\t\t\t\t struct json_token *freq_info)\n+{\n+\tstruct afc_freq_range_elem *f = NULL, *tmp;\n+\tstruct json_token *freq_obj;\n+\tint count = 0;\n+\n+\tfor (freq_obj = freq_info->child; freq_obj;\n+\t freq_obj = freq_obj->sibling) {\n+\t\tstruct json_token *freq_range_obj, *token;\n+\t\tint low_freq, high_freq;\n+\t\tdouble max_psd;\n+\n+\t\tfreq_range_obj = json_get_member(freq_obj, \"frequencyRange\");\n+\t\tif (!freq_range_obj || freq_range_obj->type != JSON_OBJECT)\n+\t\t\tcontinue;\n+\n+\t\ttoken = json_get_member(freq_range_obj, \"lowFrequency\");\n+\t\tif (!token || token->type != JSON_NUMBER)\n+\t\t\tcontinue;\n+\t\tlow_freq = token->number;\n+\n+\t\ttoken = json_get_member(freq_range_obj, \"highFrequency\");\n+\t\tif (!token || token->type != JSON_NUMBER)\n+\t\t\tcontinue;\n+\t\thigh_freq = token->number;\n+\n+\t\ttoken = json_get_member(freq_obj, \"maxPsd\");\n+\t\tif (!token)\n+\t\t\ttoken = json_get_member(freq_obj, \"maxPSD\");\n+\t\tif (!token || (token->type != JSON_DOUBLE &&\n+\t\t\t token->type != JSON_NUMBER))\n+\t\t\tcontinue;\n+\t\tmax_psd = token->type == JSON_DOUBLE ?\n+\t\t\ttoken->dnumber : (double) token->number;\n+\n+\t\ttmp = os_realloc_array(f, count + 1, sizeof(*f));\n+\t\tif (!tmp) {\n+\t\t\tos_free(f);\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\t\tf = tmp;\n+\n+\t\tf[count].low_freq = low_freq;\n+\t\tf[count].high_freq = high_freq;\n+\t\tf[count++].max_psd = max_psd;\n+\t}\n+\tiface->afc.freq_range = f;\n+\tiface->afc.num_freq_range = count;\n+\n+\treturn 0;\n+}\n+\n+\n+static int hostad_afc_update_chan_info(struct afc_chan_info_elem **chan_list,\n+\t\t\t\t int *chan_list_size, u8 op_class,\n+\t\t\t\t int center_chan, double power)\n+{\n+\tint op_class_pwr_index, num_low_subchan, channel;\n+\tint count = *chan_list_size;\n+\tstruct afc_chan_info_elem *c = *chan_list, *tmp;\n+\n+\tswitch (op_class) {\n+\tcase 132: /* 40MHz */\n+\t\top_class_pwr_index = 1;\n+\t\tnum_low_subchan = 2;\n+\t\tbreak;\n+\tcase 133: /* 80MHz */\n+\t\top_class_pwr_index = 2;\n+\t\tnum_low_subchan = 6;\n+\t\tbreak;\n+\tcase 134: /* 160MHz */\n+\t\top_class_pwr_index = 3;\n+\t\tnum_low_subchan = 14;\n+\t\tbreak;\n+\tcase 137: /* 320MHz */\n+\t\top_class_pwr_index = 4;\n+\t\tnum_low_subchan = 30;\n+\t\tbreak;\n+\tdefault:\n+\t\top_class_pwr_index = 0;\n+\t\tnum_low_subchan = 0;\n+\t\tbreak;\n+\t}\n+\n+\tfor (channel = center_chan - num_low_subchan;\n+\t channel <= center_chan + num_low_subchan; channel += 4) {\n+\t\tint i;\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tif (c[i].chan == channel)\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (i == count) {\n+\t\t\ttmp = os_realloc_array(c, count + 1, sizeof(*c));\n+\t\t\tif (!tmp) {\n+\t\t\t\tos_free(c);\n+\t\t\t\t*chan_list = NULL;\n+\t\t\t\t*chan_list_size = 0;\n+\t\t\t\treturn -ENOMEM;\n+\t\t\t}\n+\t\t\tc = tmp;\n+\n+\t\t\tc[count].chan = channel;\n+\t\t\tmemset(c[count].power, 0, sizeof(c[count].power));\n+\t\t\tcount++;\n+\t\t}\n+\t\tc[i].power[op_class_pwr_index] = power;\n+\t}\n+\n+\t*chan_list_size = count;\n+\t*chan_list = c;\n+\n+\treturn 0;\n+}\n+\n+\n+static int\n+hostad_afc_parse_available_chan_info(struct hostapd_iface *iface,\n+\t\t\t\t struct json_token *chan_info)\n+{\n+\tstruct afc_chan_info_elem *c = NULL;\n+\tstruct json_token *chan_obj;\n+\tint count = 0;\n+\n+\tfor (chan_obj = chan_info->child; chan_obj;\n+\t chan_obj = chan_obj->sibling) {\n+\t\tstruct json_token *chan_cfi, *max_eirp;\n+\t\tstruct json_token *token, *chan_arr, *power_arr;\n+\t\tint op_class;\n+\n+\t\ttoken = json_get_member(chan_obj, \"globalOperatingClass\");\n+\t\tif (!token || token->type != JSON_NUMBER)\n+\t\t\tcontinue;\n+\t\top_class = token->number;\n+\n+\t\tchan_cfi = json_get_member(chan_obj, \"channelCfi\");\n+\t\tif (!chan_cfi || chan_cfi->type != JSON_ARRAY)\n+\t\t\tcontinue;\n+\n+\t\tmax_eirp = json_get_member(chan_obj, \"maxEirp\");\n+\t\tif (!max_eirp || max_eirp->type != JSON_ARRAY)\n+\t\t\tcontinue;\n+\n+\t\tchan_arr = chan_cfi->child;\n+\t\tpower_arr = max_eirp->child;\n+\t\twhile (chan_arr && power_arr) {\n+\t\t\tint channel, ret;\n+\t\t\tdouble power;\n+\n+\t\t\tif (chan_arr->type != JSON_NUMBER ||\n+\t\t\t (power_arr->type != JSON_DOUBLE &&\n+\t\t\t power_arr->type != JSON_NUMBER)) {\n+\t\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t\t \"Failed to parse array at opclass %d\",\n+\t\t\t\t\t op_class);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tchannel = chan_arr->number;\n+\t\t\tpower = power_arr->type == JSON_DOUBLE ?\n+\t\t\t\tpower_arr->dnumber : (double) power_arr->number;\n+\n+\t\t\tret = hostad_afc_update_chan_info(&c, &count, op_class,\n+\t\t\t\t\t\t\t channel, power);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t\tchan_arr = chan_arr->sibling;\n+\t\t\tpower_arr = power_arr->sibling;\n+\t\t}\n+\t\tiface->afc.chan_info_list = c;\n+\t\tiface->afc.num_chan_info = count;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+\n+static int hostad_afc_get_timeout(struct json_token *obj)\n+{\n+\ttime_t t, now;\n+\tstruct tm tm;\n+\n+\tif (sscanf(obj->string, \"%d-%d-%dT%d:%d:%dZ\",\n+\t\t &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,\n+\t\t &tm.tm_min, &tm.tm_sec) <= 0)\n+\t\treturn HOSTAPD_AFC_TIMEOUT;\n+\n+\ttm.tm_year -= 1900;\n+\ttm.tm_mon -= 1;\n+\ttm.tm_isdst = -1;\n+\tt = mktime(&tm);\n+\ttime(&now);\n+\n+\treturn now > t ? HOSTAPD_AFC_RETRY_TIMEOUT : (t - now) * 80 / 100;\n+}\n+\n+\n+static int\n+hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply, size_t len)\n+{\n+\tstruct hostapd_config *iconf = iface->conf;\n+\tint request_timeout = -1, ret = -EINVAL;\n+\tstruct json_token *root = NULL, *reply_obj, *token;\n+\n+\twpa_printf(MSG_DEBUG,\n+\t\t \"Received AFC reply: %s, len: %lu, os_strlen: %lu\",\n+\t\t reply, len, os_strlen(reply));\n+\n+\tiface->afc_response = os_strdup(reply);\n+\tif (!iface->afc_response) {\n+\t\twpa_printf(MSG_ERROR,\n+\t\t\t \"Failed to alloc memory for storing AFC reply\");\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\troot = json_parse((const char *) reply, os_strlen(reply));\n+\tif (!root) {\n+\t\twpa_printf(MSG_ERROR, \"Failed to parse AFC reply payload\");\n+\t\tgoto fail;\n+\t}\n+\n+\ttoken = json_get_member(root, \"version\");\n+\tif (!token || token->type != JSON_STRING) {\n+\t\twpa_printf(MSG_ERROR, \"Missing version in AFC reply\");\n+\t\tgoto fail;\n+\t}\n+\tif (iconf->afc.request.version &&\n+\t os_strcmp(iconf->afc.request.version, token->string)) {\n+\t\twpa_printf(MSG_ERROR, \"Mismatch in AFC reply version\");\n+\t\tgoto fail;\n+\t}\n+\n+\treply_obj = json_get_member(root, \"availableSpectrumInquiryResponses\");\n+\tif (!reply_obj || reply_obj->type != JSON_ARRAY) {\n+\t\twpa_printf(MSG_ERROR,\n+\t\t\t \"Missing availableSpectrumInquiry in AFC reply\");\n+\t\tgoto fail;\n+\t}\n+\tfor (token = reply_obj->child; token; token = token->sibling) {\n+\t\tstruct json_token *reply_elem, *response;\n+\t\tint status = -EINVAL, timeout;\n+\t\tunsigned int j;\n+\t\tchar request_id_str[16];\n+\n+\t\treply_elem = json_get_member(token, \"requestId\");\n+\t\tif (!reply_elem || reply_elem->type != JSON_STRING) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"Missing requestId in reply element\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tsnprintf(request_id_str, sizeof(request_id_str), \"%u\",\n+\t\t\t iface->afc.request_id);\n+\t\tif (os_strcmp(request_id_str, reply_elem->string)) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"RequestId mismatch in reply element\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treply_elem = json_get_member(token, \"rulesetId\");\n+\t\tif (!reply_elem || reply_elem->type != JSON_STRING) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"Missing rulesetId in reply element\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tfor (j = 0; j < iconf->afc.n_cert_ids; j++) {\n+\t\t\tif (!os_strcmp(iconf->afc.cert_ids[j].rulset,\n+\t\t\t\t reply_elem->string))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (j == iconf->afc.n_cert_ids) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"RulesetId mismatch in reply element\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tresponse = json_get_member(token, \"response\");\n+\t\tif (!response || response->type != JSON_OBJECT) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"Missing response field in reply element\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treply_elem = json_get_member(response, \"shortDescription\");\n+\t\tif (reply_elem && reply_elem->type == JSON_STRING)\n+\t\t\twpa_printf(MSG_DEBUG, \"AFC reply element: %s\",\n+\t\t\t\t reply_elem->string);\n+\n+\t\treply_elem = json_get_member(response, \"responseCode\");\n+\t\tif (reply_elem && reply_elem->type == JSON_NUMBER)\n+\t\t\tstatus = reply_elem->number;\n+\n+\t\tif (status < 0) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"Reply invalid responseCode: %d\", status);\n+\t\t\tcontinue;\n+\t\t}\n+\t\treply_elem = json_get_member(token, \"availableFrequencyInfo\");\n+\t\tif (reply_elem && reply_elem->type == JSON_ARRAY &&\n+\t\t hostad_afc_parse_available_freq_info(iface, reply_elem)) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"Failed to parse availableFrequencyInfo\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treply_elem = json_get_member(token, \"availableChannelInfo\");\n+\t\tif (reply_elem && reply_elem->type == JSON_ARRAY &&\n+\t\t hostad_afc_parse_available_chan_info(iface, reply_elem)) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"Failed to parse availableChannelInfo\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treply_elem = json_get_member(token, \"availabilityExpireTime\");\n+\t\tif (!reply_elem || reply_elem->type != JSON_STRING) {\n+\t\t\twpa_printf(MSG_DEBUG,\n+\t\t\t\t \"Missing expire time, use default timeout\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\ttimeout = hostad_afc_get_timeout(reply_elem);\n+\t\tif (request_timeout < 0 || timeout < request_timeout)\n+\t\t\trequest_timeout = timeout;\n+\n+\t\tret = status;\n+\t}\n+\n+\tiface->afc.data_valid = true;\n+\tiface->afc.timeout = request_timeout;\n+\tif (iface->afc.timeout < 0)\n+\t\tiface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;\n+\n+fail:\n+\tjson_free(root);\n+\treturn ret;\n+}\n+\n+\n+static int hostapd_afc_send_receive(struct hostapd_iface *iface)\n+{\n+\tstruct hostapd_config *iconf = iface->conf;\n+\tstruct wpabuf *request_obj = NULL;\n+\tstruct timeval sock_timeout = {\n+\t\t.tv_sec = 10,\n+\t};\n+\tstruct sockaddr_un addr = {\n+\t\t.sun_family = AF_UNIX,\n+#ifdef __FreeBSD__\n+\t\t.sun_len = sizeof(addr),\n+#endif /* __FreeBSD__ */\n+\t};\n+\tchar *buf = NULL;\n+\tint sockfd, ret;\n+\tfd_set read_set;\n+\n+\tif (iface->afc.data_valid) {\n+\t\t/* AFC data already downloaded from the server */\n+\t\treturn 0;\n+\t}\n+\n+\tiface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;\n+\tif (!iconf->afc.socket) {\n+\t\twpa_printf(MSG_ERROR, \"Missing AFC socket string\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) {\n+\t\twpa_printf(MSG_ERROR, \"Malformed AFC socket string %s\",\n+\t\t\t iconf->afc.socket);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tos_strlcpy(addr.sun_path, iconf->afc.socket, sizeof(addr.sun_path));\n+\tsockfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (sockfd < 0) {\n+\t\twpa_printf(MSG_ERROR, \"Failed creating AFC socket\");\n+\t\treturn sockfd;\n+\t}\n+\n+\tif (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {\n+\t\twpa_printf(MSG_ERROR, \"Failed connecting AFC socket\");\n+\t\tret = -EIO;\n+\t\tgoto close_sock;\n+\t}\n+\n+\trequest_obj = hostapd_afc_build_request(iface);\n+\tif (!request_obj) {\n+\t\tret = -EINVAL;\n+\t\tgoto close_sock;\n+\t}\n+\n+\tret = send(sockfd, wpabuf_head(request_obj),\n+\t\t wpabuf_len(request_obj), 0);\n+\tif (ret < 0) {\n+\t\twpa_printf(MSG_ERROR, \"Failed sending AFC request\");\n+\t\tret = -EIO;\n+\t\tgoto close_sock;\n+\t}\n+\n+\tFD_ZERO(&read_set);\n+\tFD_SET(sockfd, &read_set);\n+\tif (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) {\n+\t\twpa_printf(MSG_ERROR, \"Select failed on AFC socket\");\n+\t\tret = -errno;\n+\t\tgoto close_sock;\n+\t}\n+\n+\tif (!FD_ISSET(sockfd, &read_set)) {\n+\t\tret = -EIO;\n+\t\tgoto close_sock;\n+\t}\n+\n+\tbuf = os_zalloc(HOSTAPD_AFC_BUFSIZE);\n+\tif (!buf) {\n+\t\tret = -ENOMEM;\n+\t\tgoto close_sock;\n+\t}\n+\n+\tret = recv(sockfd, buf, HOSTAPD_AFC_BUFSIZE - 1, 0);\n+\tif (!ret)\n+\t\tret = -EIO;\n+\tif (ret < 0)\n+\t\tgoto close_sock;\n+\n+\tret = hostapd_afc_parse_reply(iface, buf, ret);\n+\tif (ret) {\n+\t\twpa_printf(MSG_ERROR, \"Failed parsing AFC reply: %d\", ret);\n+\t\tgoto close_sock;\n+\t}\n+\twpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AFC_EVENT_RECEIVED);\n+close_sock:\n+\tos_free(buf);\n+\tiface->afc_request = request_obj;\n+\tclose(sockfd);\n+\n+\treturn ret;\n+}\n+\n+\n+static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface)\n+{\n+\tconst struct oper_class_map *oper_class;\n+\tint channel, chan_num;\n+\n+\toper_class = get_oper_class(NULL, iface->conf->op_class);\n+\tif (!oper_class)\n+\t\treturn false;\n+\n+\tswitch (iface->conf->op_class) {\n+\tcase 132: /* 40MHz */\n+\t\tchan_num = 2;\n+\t\tbreak;\n+\tcase 133: /* 80MHz */\n+\t\tchan_num = 4;\n+\t\tbreak;\n+\tcase 134: /* 160MHz */\n+\t\tchan_num = 8;\n+\t\tbreak;\n+\tcase 137: /* 320MHz */\n+\t\tchan_num = 16;\n+\t\tbreak;\n+\tdefault:\n+\t\tchan_num = 1;\n+\t\tbreak;\n+\t}\n+\n+\tfor (channel = oper_class->min_chan; channel <= oper_class->max_chan;\n+\t channel += oper_class->inc) {\n+\t\tstruct hostapd_hw_modes *mode = iface->current_mode;\n+\t\tstruct hostapd_channel_data *chan = NULL;\n+\t\tint i, start_ch = channel;\n+\n+\t\tif (iface->conf->op_class == 137)\n+\t\t\tstart_ch = channel - 30;\n+\n+\t\tfor (i = 0; i < chan_num; i++) {\n+\t\t\tchan = hw_get_channel_chan(mode, start_ch + i * 4,\n+\t\t\t\t\t\t NULL);\n+\n+\t\t\tif (!chan || chan->flag & HOSTAPD_CHAN_DISABLED)\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (i == chan_num)\n+\t\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+\n+int hostapd_afc_handle_request(struct hostapd_iface *iface)\n+{\n+\tstruct hostapd_config *iconf = iface->conf;\n+\tint ret;\n+\n+\t/* AFC is required just for standard power AP */\n+\tif (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))\n+\t\treturn 1;\n+\n+\tif (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq))\n+\t\treturn 1;\n+\n+\tif (iface->state == HAPD_IFACE_ACS)\n+\t\treturn 1;\n+\n+\tiface->afc.request_id = os_random();\n+\tret = hostapd_afc_send_receive(iface);\n+\tif (ret < 0) {\n+\t\t/*\n+\t\t * If the connection to the AFCD failed, resched for a\n+\t\t * future attempt.\n+\t\t */\n+\t\twpa_printf(MSG_ERROR, \"AFC connection failed: %d\", ret);\n+\t\tif (ret == -EIO)\n+\t\t\tret = 0;\n+\t\tgoto resched;\n+\t}\n+\n+\thostap_afc_disable_channels(iface);\n+\tif (!hostapd_afc_has_usable_chans(iface))\n+\t\tgoto resched;\n+\n+\tret = hostapd_is_usable_chans(iface);\n+\tif (ret != 1) {\n+\t\t/* Trigger an ACS freq scan */\n+\t\ticonf->channel = 0;\n+\t\tiface->freq = 0;\n+\t\thostapd_set_and_check_bw320_offset(iface->conf, 0);\n+\n+\t\tif (!ret && acs_init(iface) != HOSTAPD_CHAN_ACS) {\n+\t\t\twpa_printf(MSG_ERROR, \"Could not start ACS\");\n+\t\t\tret = -EINVAL;\n+\t\t}\n+\t}\n+\n+resched:\n+\teloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);\n+\teloop_register_timeout(iface->afc.timeout, 0,\n+\t\t\t hostapd_afc_timeout_handler, iface, NULL);\n+\n+\treturn ret;\n+}\n+\n+\n+static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface)\n+{\n+\twpabuf_free(iface->afc_request);\n+\tos_free(iface->afc_response);\n+\tos_free(iface->afc.chan_info_list);\n+\tos_free(iface->afc.freq_range);\n+\n+\tiface->afc_request = NULL;\n+\tiface->afc_response = NULL;\n+\n+\tiface->afc.num_freq_range = 0;\n+\tiface->afc.num_chan_info = 0;\n+\n+\tiface->afc.chan_info_list = NULL;\n+\tiface->afc.freq_range = NULL;\n+\n+\tiface->afc.data_valid = false;\n+}\n+\n+\n+static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx)\n+{\n+\tstruct hostapd_iface *iface = eloop_ctx;\n+\tbool restart_iface = true;\n+\n+\thostapd_afc_delete_data_from_server(iface);\n+\tif (iface->state != HAPD_IFACE_ENABLED) {\n+\t\t/* Hostapd is not fully enabled yet, toggle the interface */\n+\t\tgoto restart_interface;\n+\t}\n+\n+\tif (hostapd_afc_send_receive(iface) < 0 ||\n+\t hostapd_afc_reset_channels(iface)) {\n+\t\trestart_iface = false;\n+\t\tgoto restart_interface;\n+\t}\n+\n+\tif (hostapd_is_usable_chans(iface)) {\n+\t\twpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AFC_EVENT_COMPLETE);\n+\t\tgoto resched;\n+\t}\n+\n+\trestart_iface = hostapd_afc_has_usable_chans(iface);\n+\tif (restart_iface) {\n+\t\t/* Trigger an ACS freq scan */\n+\t\tiface->conf->channel = 0;\n+\t\tiface->freq = 0;\n+\t\thostapd_set_and_check_bw320_offset(iface->conf, 0);\n+\t} else\n+\t\twpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AFC_EVENT_COMPLETE);\n+\n+restart_interface:\n+\thostapd_disable_iface(iface);\n+\tif (restart_iface)\n+\t\thostapd_enable_iface(iface);\n+resched:\n+\teloop_register_timeout(iface->afc.timeout, 0,\n+\t\t\t hostapd_afc_timeout_handler, iface, NULL);\n+}\n+\n+\n+void hostapd_afc_send_request(struct hostapd_iface *iface)\n+{\n+\teloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);\n+\teloop_register_timeout(0, 0, hostapd_afc_timeout_handler, iface, NULL);\n+}\n+\n+\n+void hostapd_afc_stop(struct hostapd_iface *iface)\n+{\n+\teloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);\n+}\n+\n+\n+void hostap_afc_disable_channels(struct hostapd_iface *iface)\n+{\n+\tstruct hostapd_hw_modes *mode = NULL;\n+\tint i;\n+\n+\tfor (i = 0; i < iface->num_hw_features; i++) {\n+\t\tmode = &iface->hw_features[i];\n+\t\tif (mode->mode == HOSTAPD_MODE_IEEE80211A &&\n+\t\t mode->is_6ghz)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (i == iface->num_hw_features)\n+\t\treturn;\n+\n+\tif (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))\n+\t\treturn;\n+\n+\tif (!iface->afc.data_valid)\n+\t\treturn;\n+\n+\tfor (i = 0; i < mode->num_channels; i++) {\n+\t\tstruct hostapd_channel_data *chan = &mode->channels[i];\n+\t\tunsigned int j;\n+\n+\t\tif (!is_6ghz_freq(chan->freq))\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; j < iface->afc.num_freq_range; j++) {\n+\t\t\tif (chan->freq >= iface->afc.freq_range[j].low_freq &&\n+\t\t\t chan->freq <= iface->afc.freq_range[j].high_freq)\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (j != iface->afc.num_freq_range)\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; j < iface->afc.num_chan_info; j++) {\n+\t\t\tif (chan->chan == iface->afc.chan_info_list[j].chan)\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (j != iface->afc.num_chan_info)\n+\t\t\tcontinue;\n+\n+\t\tchan->flag |= HOSTAPD_CHAN_DISABLED;\n+\t\twpa_printf(MSG_MSGDUMP,\n+\t\t\t \"Disabling freq=%d MHz (not allowed by AFC)\",\n+\t\t\t chan->freq);\n+\t}\n+}\n+\n+\n+static int hostapd_afc_reset_channels(struct hostapd_iface *iface)\n+{\n+\tint ret;\n+\n+\tret = hostapd_get_hw_features(iface);\n+\tif (ret) {\n+\t\twpa_printf(MSG_ERROR, \"Failed to reset channel flags\");\n+\t\treturn ret;\n+\t}\n+\n+\tret = hostapd_set_current_hw_info(iface, iface->freq);\n+\tif (ret)\n+\t\twpa_printf(MSG_ERROR, \"Failed to set current hardware info\");\n+\n+\treturn ret;\n+}\ndiff --git a/src/ap/ap_config.c b/src/ap/ap_config.c\nindex 5dc8a66c5..fc9491460 100644\n--- a/src/ap/ap_config.c\n+++ b/src/ap/ap_config.c\n@@ -1045,6 +1045,21 @@ void hostapd_config_free(struct hostapd_config *conf)\n #endif /* CONFIG_ACS */\n \twpabuf_free(conf->lci);\n \twpabuf_free(conf->civic);\n+#ifdef CONFIG_AFC\n+\tos_free(conf->afc.socket);\n+\tos_free(conf->afc.request.version);\n+\tos_free(conf->afc.request.sn);\n+\tfor (i = 0; i < conf->afc.n_cert_ids; i++) {\n+\t\tos_free(conf->afc.cert_ids[i].rulset);\n+\t\tos_free(conf->afc.cert_ids[i].id);\n+\t}\n+\tos_free(conf->afc.cert_ids);\n+\tos_free(conf->afc.location.height_type);\n+\tos_free(conf->afc.location.linear_polygon_data);\n+\tos_free(conf->afc.location.radial_polygon_data);\n+\tos_free(conf->afc.freq_range);\n+\tos_free(conf->afc.op_class);\n+#endif /* CONFIG_AFC */\n \n \tos_free(conf);\n }\ndiff --git a/src/ap/ap_config.h b/src/ap/ap_config.h\nindex 98a2f1bb8..740363256 100644\n--- a/src/ap/ap_config.h\n+++ b/src/ap/ap_config.h\n@@ -1256,6 +1256,52 @@ struct hostapd_config {\n \n \t/* Disable MCS15 Subfield in EHT operation element */\n \tbool disable_mcs15_rx;\n+\n+#ifdef CONFIG_AFC\n+\tstruct {\n+\t\tchar *socket;\n+\t\tstruct {\n+\t\t\tchar *version;\n+\t\t\tchar *sn;\n+\t\t} request;\n+\t\tunsigned int n_cert_ids;\n+\t\tstruct cert_id {\n+\t\t\tchar *rulset;\n+\t\t\tchar *id;\n+\t\t} *cert_ids;\n+\t\tstruct {\n+\t\t\tenum afc_location_type {\n+\t\t\t\tELLIPSE,\n+\t\t\t\tLINEAR_POLYGON,\n+\t\t\t\tRADIAL_POLYGON,\n+\t\t\t} type;\n+\t\t\tunsigned int n_linear_polygon_data;\n+\t\t\tstruct afc_linear_polygon {\n+\t\t\t\tdouble longitude;\n+\t\t\t\tdouble latitude;\n+\t\t\t} *linear_polygon_data;\n+\t\t\tunsigned int n_radial_polygon_data;\n+\t\t\tstruct afc_radial_polygon {\n+\t\t\t\tdouble length;\n+\t\t\t\tdouble angle;\n+\t\t\t} *radial_polygon_data;\n+\t\t\tint major_axis;\n+\t\t\tint minor_axis;\n+\t\t\tint orientation;\n+\t\t\tdouble height;\n+\t\t\tchar *height_type;\n+\t\t\tint vertical_tolerance;\n+\t\t} location;\n+\t\tunsigned int n_freq_range;\n+\t\tstruct afc_freq_range {\n+\t\t\tint low_freq;\n+\t\t\tint high_freq;\n+\t\t} *freq_range;\n+\t\tunsigned int n_op_class;\n+\t\tunsigned int *op_class;\n+\t\tint min_power;\n+\t} afc;\n+#endif /* CONFIG_AFC */\n };\n \n \ndiff --git a/src/ap/hostapd.c b/src/ap/hostapd.c\nindex 1b4a8895d..e1f3b2343 100644\n--- a/src/ap/hostapd.c\n+++ b/src/ap/hostapd.c\n@@ -756,6 +756,7 @@ void hostapd_cleanup_iface_partial(struct hostapd_iface *iface)\n static void hostapd_cleanup_iface(struct hostapd_iface *iface)\n {\n \twpa_printf(MSG_DEBUG, \"%s(%p)\", __func__, iface);\n+\thostapd_afc_stop(iface);\n \teloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,\n \t\t\t NULL);\n \n@@ -765,6 +766,12 @@ static void hostapd_cleanup_iface(struct hostapd_iface *iface)\n \n \tos_free(iface->config_fname);\n \tos_free(iface->bss);\n+#ifdef CONFIG_AFC\n+\twpabuf_free(iface->afc_request);\n+\tos_free(iface->afc_response);\n+\tos_free(iface->afc.chan_info_list);\n+\tos_free(iface->afc.freq_range);\n+#endif\n \twpa_printf(MSG_DEBUG, \"%s: free iface=%p\", __func__, iface);\n \tos_free(iface);\n }\n@@ -2641,6 +2648,16 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface,\n \t\t}\n #endif /* CONFIG_MESH */\n \n+#ifdef CONFIG_IEEE80211AX\n+\t\t/* check AFC for 6GHz channels. */\n+\t\tres = hostapd_afc_handle_request(iface);\n+\t\tif (res <= 0) {\n+\t\t\tif (res < 0)\n+\t\t\t\tgoto fail;\n+\t\t\treturn res;\n+\t\t}\n+#endif /* CONFIG_IEEE80211AX */\n+\n \t\tif (!delay_apply_cfg &&\n \t\t hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq,\n \t\t\t\t hapd->iconf->channel,\n@@ -2766,6 +2783,11 @@ dfs_offload:\n \t}\n #endif /* CONFIG_FST */\n \n+#ifdef CONFIG_AFC\n+\tif (iface->afc.data_valid)\n+\t\twpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AFC_EVENT_COMPLETE);\n+#endif /* CONFIG_AFC */\n+\n \thostapd_set_state(iface, HAPD_IFACE_ENABLED);\n \thostapd_owe_update_trans(iface);\n \tairtime_policy_update_init(iface);\n@@ -3025,6 +3047,7 @@ void hostapd_interface_deinit(struct hostapd_iface *iface)\n \n \thostapd_set_state(iface, HAPD_IFACE_DISABLED);\n \n+\thostapd_afc_stop(iface);\n \teloop_cancel_timeout(channel_list_update_timeout, iface, NULL);\n \tiface->wait_channel_update = 0;\n \tiface->is_no_ir = false;\n@@ -3098,6 +3121,7 @@ void hostapd_interface_free(struct hostapd_iface *iface)\n \t\t\t __func__, iface->bss[j]);\n \t\tos_free(iface->bss[j]);\n \t}\n+\n \thostapd_cleanup_iface(iface);\n }\n \ndiff --git a/src/ap/hostapd.h b/src/ap/hostapd.h\nindex 7b67dcfe2..d24697ede 100644\n--- a/src/ap/hostapd.h\n+++ b/src/ap/hostapd.h\n@@ -761,6 +761,35 @@ struct hostapd_iface {\n \t/* Use assisted DFS functionality from the driver. This is used only\n \t * in wpa_supplicant builds for P2P GO functionality. */\n \tbool assisted_dfs;\n+\n+#ifdef CONFIG_AFC\n+\tstruct wpabuf *afc_request;\n+\tchar *afc_response;\n+\tstruct {\n+\t\tunsigned int request_id;\n+\t\tint timeout;\n+\t\tunsigned int num_freq_range;\n+\t\tstruct afc_freq_range_elem {\n+\t\t\tint low_freq;\n+\t\t\tint high_freq;\n+\t\t\t/**\n+\t\t\t * max eirp power spectral density received from\n+\t\t\t * the AFC coordinator for this band\n+\t\t\t */\n+\t\t\tdouble max_psd;\n+\t\t} *freq_range;\n+\t\tunsigned int num_chan_info;\n+\t\tstruct afc_chan_info_elem {\n+\t\t\tint chan;\n+\t\t\t/**\n+\t\t\t * max eirp power received from the AFC coordinator\n+\t\t\t * for this channel for each op_class\n+\t\t\t */\n+\t\t\tdouble power[5];\n+\t\t} *chan_info_list;\n+\t\tbool data_valid;\n+\t} afc;\n+#endif /* CONFIG_AFC */\n };\n \n /* hostapd.c */\n@@ -817,6 +846,29 @@ void hostapd_ocv_check_csa_sa_query(void *eloop_ctx, void *timeout_ctx);\n \n void hostapd_switch_color(struct hostapd_data *hapd, u64 bitmap);\n void hostapd_cleanup_cca_params(struct hostapd_data *hapd);\n+#ifdef CONFIG_AFC\n+int hostapd_afc_handle_request(struct hostapd_iface *iface);\n+void hostapd_afc_send_request(struct hostapd_iface *iface);\n+void hostapd_afc_stop(struct hostapd_iface *iface);\n+void hostap_afc_disable_channels(struct hostapd_iface *iface);\n+#else\n+static inline int hostapd_afc_handle_request(struct hostapd_iface *iface)\n+{\n+\treturn 1;\n+}\n+\n+static inline void hostapd_afc_send_request(struct hostapd_iface *iface)\n+{\n+}\n+\n+static inline void hostapd_afc_stop(struct hostapd_iface *iface)\n+{\n+}\n+\n+static inline void hostap_afc_disable_channels(struct hostapd_iface *iface)\n+{\n+}\n+#endif /* CONFIG_AFC */\n \n /* utils.c */\n int hostapd_register_probereq_cb(struct hostapd_data *hapd,\ndiff --git a/src/ap/hw_features.c b/src/ap/hw_features.c\nindex 984de976e..627479d21 100644\n--- a/src/ap/hw_features.c\n+++ b/src/ap/hw_features.c\n@@ -221,6 +221,8 @@ int hostapd_get_hw_features(struct hostapd_iface *iface)\n \tiface->hw_features = modes;\n \tiface->num_hw_features = num_modes;\n \n+\thostap_afc_disable_channels(iface);\n+\n \tif (orig_mode_valid && !iface->current_mode) {\n \t\twpa_printf(MSG_ERROR,\n \t\t\t \"%s: Could not update iface->current_mode\",\ndiff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h\nindex 963d30421..5c5606c60 100644\n--- a/src/common/wpa_ctrl.h\n+++ b/src/common/wpa_ctrl.h\n@@ -480,6 +480,9 @@ extern \"C\" {\n /* Proximity Ranging parameters to use in ranging */\n #define PR_RANGING_PARAMS \"PR-RANGING-PARAMS \"\n \n+#define AFC_EVENT_RECEIVED \"AFC-EVENT-RECEIVED \"\n+#define AFC_EVENT_COMPLETE \"AFC-EVENT-COMPLETE \"\n+\n /* BSS command information masks */\n \n #define WPA_BSS_MASK_ALL\t\t0xFFFDFFFF\n", "prefixes": [ "v11", "5/7" ] }