blob: 357b17ef3fe50146a12d809a8f3154ba75f983e0 [file] [log] [blame]
developer05f3b2b2024-08-19 19:17:34 +08001From 330346c577b4623e52767ad0939cd735a83d9837 Mon Sep 17 00:00:00 2001
2From: Allen Ye <allen.ye@mediatek.com>
3Date: Mon, 22 Jul 2024 20:51:18 +0800
4Subject: [PATCH 124/126] mtk: hostapd: Add AFC and lpi driver power support
5
6Add AFC and lpi driver power support
7This patch parse the AFC response into mtk sku power table format and send
8it to driver by vendor cmd. The table format is like below:
9col\row bw20 bw40 ... ru26 ... ru3472
10chan 1
11chan 5
12...
13chan 233
14
15 - Once the afc procedure start failed, the device would set as lpi mode by
16telling driver lpi_sku_index to use specify sku-index in dst.
17 - Add afc_max_timeout conf is use to limit the maximum interval between the
18two afc requests.
19 - Set default use lpi & sp mode
20
21Signed-off-by: Allen Ye <allen.ye@mediatek.com>
22---
23 hostapd/config_file.c | 4 +
24 src/ap/afc.c | 365 +++++++++++++++++++++++++++++++++--
25 src/ap/ap_config.c | 3 +-
26 src/ap/ap_config.h | 2 +
27 src/ap/ap_drv_ops.c | 34 +++-
28 src/ap/hostapd.h | 60 ++++++
29 src/common/mtk_vendor.h | 2 +
30 src/drivers/driver.h | 4 +-
31 src/drivers/driver_nl80211.c | 36 +++-
32 9 files changed, 477 insertions(+), 33 deletions(-)
33
34diff --git a/hostapd/config_file.c b/hostapd/config_file.c
35index 944669270..11c5f8947 100644
36--- a/hostapd/config_file.c
37+++ b/hostapd/config_file.c
38@@ -4248,6 +4248,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
39 } else if (os_strcmp(buf, "afc_op_class") == 0) {
40 if (hostapd_afc_parse_op_class(conf, pos))
41 return 1;
42+ } else if (os_strcmp(buf, "afc_max_timeout") == 0) {
43+ conf->afc.max_timeout = atoi(pos);
44 #endif /* CONFIG_AFC */
45 } else if (os_strcmp(buf, "mbssid") == 0) {
46 int mbssid = atoi(pos);
47@@ -5575,6 +5577,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
48 conf->lpi_psd = !!en;
49 } else if (os_strcmp(buf, "sku_idx") == 0) {
50 conf->sku_idx = strtol(pos, NULL, 10);
51+ } else if (os_strcmp(buf, "lpi_sku_idx") == 0) {
52+ conf->lpi_sku_idx = strtol(pos, NULL, 10);
53 } else if (os_strcmp(buf, "lpi_bcn_enhance") == 0) {
54 u8 en = strtol(pos, NULL, 10);
55 conf->lpi_bcn_enhance = !!en;
56diff --git a/src/ap/afc.c b/src/ap/afc.c
57index 361ecb575..d36ce00d7 100644
58--- a/src/ap/afc.c
59+++ b/src/ap/afc.c
60@@ -16,6 +16,7 @@
61 #include "hostapd.h"
62 #include "acs.h"
63 #include "hw_features.h"
64+#include "ap_drv_ops.h"
65
66 #define HOSTAPD_AFC_RETRY_TIMEOUT 180
67 #define HOSTAPD_AFC_TIMEOUT 86400 /* 24h */
68@@ -749,6 +750,9 @@ static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply)
69 iface->afc.timeout = request_timeout;
70 if (iface->afc.timeout < 0)
71 iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
72+ else if (iface->afc.timeout > iconf->afc.max_timeout &&
73+ iconf->afc.max_timeout >= HOSTAPD_AFC_RETRY_TIMEOUT)
74+ iface->afc.timeout = iconf->afc.max_timeout;
75
76 return ret;
77 }
78@@ -772,6 +776,7 @@ static int hostapd_afc_send_receive(struct hostapd_iface *iface)
79 int sockfd, ret;
80 fd_set read_set;
81
82+ iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
83 if (iface->afc.data_valid) {
84 /* AFC data already downloaded from the server */
85 return 0;
86@@ -782,7 +787,6 @@ static int hostapd_afc_send_receive(struct hostapd_iface *iface)
87 return -EINVAL;
88 }
89
90- iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
91 if (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) {
92 wpa_printf(MSG_ERROR, "Malformed AFC socket string %s",
93 iconf->afc.socket);
94@@ -880,7 +884,19 @@ static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface)
95 int hostapd_afc_handle_request(struct hostapd_iface *iface)
96 {
97 struct hostapd_config *iconf = iface->conf;
98+ bool lpi_mode;
99 int ret;
100+ int afc_status = AFC_CONTINUE;
101+
102+ lpi_mode = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type);
103+ if (lpi_mode && !he_reg_is_sp(iconf->he_6ghz_reg_pwr_type)) {
104+ iface->afc.lpi_mode = true;
105+ return 1;
106+ }
107+
108+ if (strncmp(iconf->country, "US", 2) != 0 &&
109+ strncmp(iconf->country, "CA", 2) != 0)
110+ return 1;
111
112 /* AFC is required just for standard power AP */
113 if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))
114@@ -894,34 +910,50 @@ int hostapd_afc_handle_request(struct hostapd_iface *iface)
115
116 ret = hostapd_afc_send_receive(iface);
117 if (ret < 0) {
118- /*
119- * If the connection to the AFCD failed, resched for a
120- * future attempt.
121- */
122- wpa_printf(MSG_ERROR, "AFC connection failed: %d", ret);
123- if (ret == -EIO)
124- ret = 0;
125+ afc_status = lpi_mode ? AFC_LPI : AFC_DISABLE;
126 goto resched;
127 }
128
129 hostap_afc_disable_channels(iface);
130- if (!hostapd_afc_has_usable_chans(iface))
131+ if (!hostapd_afc_has_usable_chans(iface)) {
132+ afc_status = lpi_mode ? AFC_LPI : AFC_DISABLE;
133 goto resched;
134+ }
135
136 if (!hostapd_is_usable_chans(iface)) {
137 /* Trigger an ACS freq scan */
138+ afc_status = AFC_RESTART_IFACE;
139 iconf->channel = 0;
140 iface->freq = 0;
141
142 if (acs_init(iface) != HOSTAPD_CHAN_ACS) {
143 wpa_printf(MSG_ERROR, "Could not start ACS");
144+ afc_status = AFC_DISABLE;
145 ret = -EINVAL;
146 }
147 } else {
148+ afc_status = AFC_CONTINUE;
149 ret = 1;
150 }
151
152 resched:
153+ switch(afc_status) {
154+ case AFC_LPI:
155+ iface->afc.lpi_mode = true;
156+ hostapd_afc_enable_lpi_channels(iface);
157+ ret = 1;
158+ break;
159+ /* Disable and restart iface would be finished in hostapd setup flow. */
160+ case AFC_RESTART_IFACE:
161+ ret = 0;
162+ fallthrough;
163+ case AFC_DISABLE:
164+ case AFC_CONTINUE:
165+ break;
166+ default:
167+ break;
168+ }
169+
170 eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
171 eloop_register_timeout(iface->afc.timeout, 0,
172 hostapd_afc_timeout_handler, iface, NULL);
173@@ -948,34 +980,58 @@ static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface)
174 static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx)
175 {
176 struct hostapd_iface *iface = eloop_ctx;
177- bool restart_iface = true;
178+ bool lpi_mode;
179+ int afc_status = AFC_CONTINUE, ret;
180+
181+ lpi_mode = he_reg_is_indoor(iface->conf->he_6ghz_reg_pwr_type);
182+ iface->afc.lpi_mode = false;
183
184 hostapd_afc_delete_data_from_server(iface);
185 if (iface->state != HAPD_IFACE_ENABLED) {
186+ afc_status = AFC_RESTART_IFACE;
187 /* Hostapd is not fully enabled yet, toggle the interface */
188 goto restart_interface;
189 }
190
191 if (hostapd_afc_send_receive(iface) < 0 ||
192 hostapd_get_hw_features(iface)) {
193- restart_iface = false;
194+ afc_status = lpi_mode ? AFC_LPI : AFC_DISABLE;
195 goto restart_interface;
196 }
197
198- if (hostapd_is_usable_chans(iface))
199- goto resched;
200+ ret = hostapd_is_usable_chans(iface);
201+ if (ret != 1) {
202+ afc_status = lpi_mode && ret == 0 ? AFC_LPI : AFC_DISABLE;
203+ goto restart_interface;
204+ }
205
206- restart_iface = hostapd_afc_has_usable_chans(iface);
207- if (restart_iface) {
208+ ret = hostapd_afc_has_usable_chans(iface);
209+ if (ret) {
210 /* Trigger an ACS freq scan */
211+ afc_status = AFC_RESTART_IFACE;
212 iface->conf->channel = 0;
213 iface->freq = 0;
214 }
215
216 restart_interface:
217- hostapd_disable_iface(iface);
218- if (restart_iface)
219+ switch(afc_status) {
220+ case AFC_DISABLE:
221+ hostapd_disable_iface(iface);
222+ break;
223+ case AFC_RESTART_IFACE:
224+ hostapd_disable_iface(iface);
225 hostapd_enable_iface(iface);
226+ break;
227+ case AFC_LPI:
228+ iface->afc.lpi_mode = true;
229+ hostapd_afc_enable_lpi_channels(iface);
230+ hostapd_drv_txpower_ctrl(iface->bss[0]);
231+ break;
232+ case AFC_CONTINUE:
233+ break;
234+ default:
235+ break;
236+ }
237 resched:
238 eloop_register_timeout(iface->afc.timeout, 0,
239 hostapd_afc_timeout_handler, iface, NULL);
240@@ -1040,6 +1096,37 @@ void hostap_afc_disable_channels(struct hostapd_iface *iface)
241 }
242 }
243
244+void hostapd_afc_enable_lpi_channels(struct hostapd_iface *iface)
245+{
246+ struct hostapd_hw_modes *mode = NULL;
247+ int i;
248+
249+ for (i = 0; i < iface->num_hw_features; i++) {
250+ mode = &iface->hw_features[i];
251+ if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
252+ mode->is_6ghz)
253+ break;
254+ }
255+
256+ if (i == iface->num_hw_features)
257+ return;
258+
259+ if (!he_reg_is_indoor(iface->conf->he_6ghz_reg_pwr_type))
260+ return;
261+
262+ for (i = 0; i < mode->num_channels; i++) {
263+ struct hostapd_channel_data *chan = &mode->channels[i];
264+
265+ if (!is_6ghz_freq(chan->freq))
266+ continue;
267+
268+ chan->flag &= ~HOSTAPD_CHAN_DISABLED;
269+ wpa_printf(MSG_MSGDUMP,
270+ "Enabling freq=%d MHz for lpi mode",
271+ chan->freq);
272+ }
273+}
274+
275
276 int hostap_afc_get_chan_max_eirp_power(struct hostapd_iface *iface, bool psd,
277 int *power)
278@@ -1076,3 +1163,247 @@ int hostap_afc_get_chan_max_eirp_power(struct hostapd_iface *iface, bool psd,
279 }
280 return -EINVAL;
281 }
282+
283+void hostapd_afc_init_power_table(s8 ***power_table)
284+{
285+ int table_idx, bw;
286+ s8 *chan_power_list;
287+
288+ /* init power table */
289+ for (table_idx = 0; table_idx < MAX_CHANNEL_NUM_6G; table_idx++) {
290+ chan_power_list = (*power_table)[table_idx];
291+ for (bw = 0; bw < afc_power_table_num; bw++)
292+ chan_power_list[bw] = AFC_INVALID_POWER;
293+ }
294+}
295+
296+int hostapd_afc_parse_psd_to_dbm(struct hostapd_iface *iface, s8 ***power_table)
297+{
298+ int i, freq, channel, bw, table_idx, target_power;
299+ s8 *chan_power_list;
300+
301+ for (i = 0; i < iface->afc.num_freq_range; i++) {
302+ struct afc_freq_range_elem *freq_range = &iface->afc.freq_range[i];
303+
304+ if (!freq_range)
305+ continue;
306+
307+ freq = freq_range->low_freq + 10;
308+ channel = hostapd_hw_get_channel(iface->bss[0], freq);
309+ if (channel == 0)
310+ return -EINVAL;
311+
312+ table_idx = channel / 4;
313+
314+ if (table_idx >= MAX_CHANNEL_NUM_6G)
315+ return -EINVAL;
316+
317+ chan_power_list = (*power_table)[table_idx];
318+ for (bw = 0; bw < afc_power_bw320_2; bw++) {
319+ target_power = freq_range->max_psd * 2 + PSD_TO_DBM_OFFSET +
320+ bw * DOUBLE_BW_POWER;
321+ target_power = MIN(AFC_MAXIMUM_POWER, target_power);
322+ chan_power_list[bw] = MIN(chan_power_list[bw],
323+ target_power);
324+ }
325+ chan_power_list[afc_power_bw320_2] = chan_power_list[afc_power_bw320_1];
326+ }
327+ return 0;
328+}
329+
330+int hostapd_afc_parse_eirp_to_dbm(struct hostapd_iface *iface, s8 ***power_table)
331+{
332+ int i, bw, table_idx, target_power;
333+ s8 *chan_power_list;
334+
335+ for (i = 0; i < iface->afc.num_chan_info; i++) {
336+ struct afc_chan_info_elem *chan_info = &iface->afc.chan_info_list[i];
337+
338+ if (!chan_info)
339+ continue;
340+
341+ table_idx = chan_info->chan / 4;
342+
343+ if (table_idx >= MAX_CHANNEL_NUM_6G)
344+ return -EINVAL;
345+
346+ chan_power_list = (*power_table)[table_idx];
347+ target_power = MIN(AFC_MAXIMUM_POWER, chan_info->power * 2);
348+ /* FIXME: wider bandwidth power is not stored. */
349+ chan_power_list[afc_power_bw20] = MIN(chan_power_list[afc_power_bw20],
350+ target_power);
351+ }
352+ return 0;
353+}
354+
355+int afc_get_ru_be_offset(int bw, int *target_bw, int *offset)
356+{
357+ switch (bw) {
358+ case afc_power_ru26:
359+ *target_bw = afc_power_bw20;
360+ *offset = RU26_OFFSET_20MHZ;
361+ break;
362+ case afc_power_ru52:
363+ *target_bw = afc_power_bw20;
364+ *offset = RU52_OFFSET_20MHZ;
365+ break;
366+ case afc_power_ru78:
367+ *target_bw = afc_power_bw20;
368+ *offset = RU78_OFFSET_20MHZ;
369+ break;
370+ case afc_power_ru106:
371+ *target_bw = afc_power_bw20;
372+ *offset = RU106_OFFSET_20MHZ;
373+ break;
374+ case afc_power_ru132:
375+ *target_bw = afc_power_bw20;
376+ *offset = RU132_OFFSET_20MHZ;
377+ break;
378+ case afc_power_ru726:
379+ *target_bw = afc_power_bw80;
380+ *offset = RU726_OFFSET_80MHZ;
381+ break;
382+ case afc_power_ru1480:
383+ *target_bw = afc_power_bw160;
384+ *offset = RU1480_OFFSET_160MHZ;
385+ break;
386+ case afc_power_ru1772:
387+ *target_bw = afc_power_bw160;
388+ *offset = RU1772_OFFSET_160MHZ;
389+ break;
390+ case afc_power_ru2476:
391+ *target_bw = afc_power_bw320_1;
392+ *offset = RU2476_OFFSET_320MHZ;
393+ break;
394+ case afc_power_ru2988:
395+ *target_bw = afc_power_bw320_1;
396+ *offset = RU2988_OFFSET_320MHZ;
397+ break;
398+ case afc_power_ru3472:
399+ *target_bw = afc_power_bw320_1;
400+ *offset = RU3472_OFFSET_320MHZ;
401+ break;
402+ default:
403+ return -EINVAL;
404+ }
405+ return 0;
406+}
407+
408+int hostapd_afc_fill_wide_bandwidth_power(s8 ***power_table)
409+{
410+ int table_idx, bw;
411+ s8 *chan_power_list;
412+
413+ for (table_idx = 0; table_idx < MAX_CHANNEL_NUM_6G; table_idx++) {
414+ int target_power, ru26_power;
415+
416+ if ((*power_table)[table_idx][afc_power_bw20] == AFC_INVALID_POWER)
417+ continue;
418+
419+ chan_power_list = (*power_table)[table_idx];
420+
421+ /* Check wide bandwidth power minimum or valid. */
422+ for (bw = afc_power_bw40; bw <= afc_power_bw320_2; bw++) {
423+ int bw_ch_num, first_ch, last_ch;
424+
425+ target_power = AFC_INVALID_POWER;
426+ switch (bw) {
427+ case afc_power_bw40:
428+ bw_ch_num = 2;
429+ break;
430+ case afc_power_bw80:
431+ bw_ch_num = 4;
432+ break;
433+ case afc_power_bw160:
434+ bw_ch_num = 8;
435+ break;
436+ case afc_power_bw320_1:
437+ case afc_power_bw320_2:
438+ bw_ch_num = 16;
439+ break;
440+ }
441+ if ((bw == afc_power_bw320_1 && table_idx > 47) ||
442+ (bw == afc_power_bw320_2 && table_idx < 8))
443+ continue;
444+
445+ if (bw == afc_power_bw320_2)
446+ first_ch = table_idx - (table_idx + 8) % bw_ch_num;
447+ else
448+ first_ch = table_idx - table_idx % bw_ch_num;
449+ last_ch = first_ch + bw_ch_num;
450+ for (int ch = first_ch; ch < last_ch; ch++) {
451+ if ((*power_table)[ch][bw] == AFC_INVALID_POWER) {
452+ target_power = AFC_INVALID_POWER;
453+ break;
454+ }
455+ target_power = MIN((*power_table)[ch][bw], target_power);
456+ }
457+ chan_power_list[bw] = target_power;
458+ }
459+
460+ /* Update remain ru */
461+ for (bw = afc_power_ru26; bw < afc_power_table_num; bw++) {
462+ int target_bw, offset;
463+
464+ if (afc_get_ru_be_offset(bw, &target_bw, &offset))
465+ return -EINVAL;
466+
467+ if (target_bw == afc_power_bw320_1 &&
468+ chan_power_list[target_bw] == AFC_INVALID_POWER)
469+ target_bw++;
470+
471+ if (chan_power_list[target_bw] == AFC_INVALID_POWER) {
472+ chan_power_list[bw] = AFC_INVALID_POWER;
473+ continue;
474+ }
475+
476+ target_power = chan_power_list[target_bw] - offset;
477+ chan_power_list[bw] = target_power;
478+ }
479+ }
480+ return 0;
481+}
482+
483+
484+int hostapd_afc_translate_table(struct hostapd_iface *iface,
485+ s8 ***power_table)
486+{
487+ int i, ret, bw320_offset;
488+
489+ if (!iface->afc.data_valid)
490+ return -EINVAL;
491+
492+ *power_table = (s8**)os_zalloc(MAX_CHANNEL_NUM_6G * sizeof(s8*));
493+
494+ if (!(*power_table))
495+ return -ENOMEM;
496+
497+ for (i = 0; i < MAX_CHANNEL_NUM_6G; i++) {
498+ (*power_table)[i] = (s8*)os_zalloc(afc_power_table_num * sizeof(s8));
499+ if (!(*power_table)[i])
500+ goto out;
501+ }
502+
503+ hostapd_afc_init_power_table(power_table);
504+
505+ ret = hostapd_afc_parse_psd_to_dbm(iface, power_table);
506+ if (ret)
507+ goto out;
508+
509+ ret = hostapd_afc_parse_eirp_to_dbm(iface, power_table);
510+ if (ret)
511+ goto out;
512+
513+ ret = hostapd_afc_fill_wide_bandwidth_power(power_table);
514+ if (ret)
515+ goto out;
516+
517+ return 0;
518+out:
519+ for (i = 0; i < MAX_CHANNEL_NUM_6G; i++)
520+ os_free((*power_table)[i]);
521+
522+ os_free(*power_table);
523+ power_table = NULL;
524+ return -ENOMEM;
525+}
526diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
527index bb5ec78a1..185dae089 100644
528--- a/src/ap/ap_config.c
529+++ b/src/ap/ap_config.c
530@@ -288,7 +288,7 @@ struct hostapd_config * hostapd_config_defaults(void)
531 conf->he_6ghz_max_ampdu_len_exp = 7;
532 conf->he_6ghz_rx_ant_pat = 1;
533 conf->he_6ghz_tx_ant_pat = 1;
534- conf->he_6ghz_reg_pwr_type = HE_REG_INFO_6GHZ_AP_TYPE_VLP;
535+ conf->he_6ghz_reg_pwr_type = HE_REG_INFO_6GHZ_AP_TYPE_INDOOR;
536 conf->reg_def_cli_eirp_psd = -1;
537 conf->reg_sub_cli_eirp_psd = -1;
538 conf->reg_def_cli_eirp = -1;
539@@ -317,6 +317,7 @@ struct hostapd_config * hostapd_config_defaults(void)
540
541 conf->lpi_psd = 0;
542 conf->sku_idx = 0;
543+ conf->lpi_sku_idx = 0;
544 conf->lpi_bcn_enhance = 0;
545
546 hostapd_set_and_check_bw320_offset(conf, 0);
547diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
548index ea8507cea..966a02d6a 100644
549--- a/src/ap/ap_config.h
550+++ b/src/ap/ap_config.h
551@@ -1343,6 +1343,7 @@ struct hostapd_config {
552 unsigned int n_op_class;
553 unsigned int *op_class;
554 int min_power;
555+ int max_timeout;
556 } afc;
557 #endif /* CONFIG_AFC */
558
559@@ -1358,6 +1359,7 @@ struct hostapd_config {
560 u8 band_idx;
561 u8 lpi_psd;
562 u8 sku_idx;
563+ u8 lpi_sku_idx;
564 u8 lpi_bcn_enhance;
565 };
566
567diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
568index 64fd0c04c..bd710832a 100644
569--- a/src/ap/ap_drv_ops.c
570+++ b/src/ap/ap_drv_ops.c
571@@ -1397,7 +1397,8 @@ int hostapd_drv_get_aval_bss_color_bmp(struct hostapd_data *hapd, u64 *aval_colo
572
573 int hostapd_drv_txpower_ctrl(struct hostapd_data *hapd)
574 {
575- s8 link_id = -1;
576+ s8 link_id = -1, sku_idx = hapd->iconf->sku_idx, ret = 0, i;
577+ s8 **afc_power_table = NULL;
578
579 if (!hapd->driver || !hapd->driver->txpower_ctrl)
580 return 0;
581@@ -1405,10 +1406,33 @@ int hostapd_drv_txpower_ctrl(struct hostapd_data *hapd)
582 if (hapd->conf->mld_ap)
583 link_id = hapd->mld_link_id;
584
585- return hapd->driver->txpower_ctrl(hapd->drv_priv, hapd->iconf->lpi_psd,
586- hapd->iconf->sku_idx,
587- hapd->iconf->lpi_bcn_enhance,
588- link_id);
589+#ifdef CONFIG_AFC
590+ if (hapd->iface->current_mode->is_6ghz &&
591+ he_reg_is_sp(hapd->iface->conf->he_6ghz_reg_pwr_type) &&
592+ !hapd->iface->afc.lpi_mode) {
593+ ret = hostapd_afc_translate_table(hapd->iface, &afc_power_table);
594+ if (ret)
595+ goto out;
596+ }
597+
598+ if (hapd->iface->afc.lpi_mode == true)
599+ sku_idx = hapd->iconf->lpi_sku_idx;
600+#endif /* CONFIG_AFC */
601+
602+ ret = hapd->driver->txpower_ctrl(hapd->drv_priv, hapd->iconf->lpi_psd,
603+ sku_idx,
604+ hapd->iconf->lpi_bcn_enhance,
605+ link_id,
606+ afc_power_table,
607+ hapd->iface->afc.lpi_mode);
608+#ifdef CONFIG_AFC
609+out:
610+ if (afc_power_table)
611+ for (i = 0; i < MAX_CHANNEL_NUM_6G; i++)
612+ os_free(afc_power_table[i]);
613+ os_free(afc_power_table);
614+#endif /* CONFIG_AFC */
615+ return ret;
616 }
617
618 int hostapd_drv_ap_wireless(struct hostapd_data *hapd, u8 sub_vendor_id, int value)
619diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
620index f69fa0062..0625bb762 100644
621--- a/src/ap/hostapd.h
622+++ b/src/ap/hostapd.h
623@@ -783,17 +783,71 @@ struct hostapd_iface {
624 int power;
625 } *chan_info_list;
626 bool data_valid;
627+ bool lpi_mode;
628 } afc;
629 #endif /* CONFIG_AFC */
630 };
631
632 /* hostapd.c */
633 #ifdef CONFIG_AFC
634+
635+enum afc_state {
636+ AFC_DISABLE,
637+ AFC_RESTART_IFACE,
638+ AFC_LPI,
639+ AFC_CONTINUE,
640+};
641+
642+#define MAX_CHANNEL_NUM_6G 59
643+
644+/* The power unit is 0.5 dBm */
645+#define AFC_MAXIMUM_POWER 72
646+#define AFC_INVALID_POWER 127
647+#define PSD_TO_DBM_OFFSET 26
648+#define BW20_TO_RU26_OFFSET 20
649+#define DOUBLE_BW_POWER 6
650+
651+#define RU26_OFFSET_20MHZ 20
652+#define RU52_OFFSET_20MHZ 14
653+#define RU78_OFFSET_20MHZ 10
654+#define RU106_OFFSET_20MHZ 8
655+#define RU132_OFFSET_20MHZ 6
656+
657+#define RU726_OFFSET_80MHZ 2
658+#define RU1480_OFFSET_160MHZ 2
659+#define RU1772_OFFSET_160MHZ 1
660+#define RU2476_OFFSET_320MHZ 4
661+#define RU2988_OFFSET_320MHZ 2
662+#define RU3472_OFFSET_320MHZ 1
663+
664+enum afc_table_info {
665+ afc_power_bw20,
666+ afc_power_bw40,
667+ afc_power_bw80,
668+ afc_power_bw160,
669+ afc_power_bw320_1,
670+ afc_power_bw320_2,
671+ afc_power_ru26,
672+ afc_power_ru52,
673+ afc_power_ru78,
674+ afc_power_ru106,
675+ afc_power_ru132,
676+ afc_power_ru726,
677+ afc_power_ru1480,
678+ afc_power_ru1772,
679+ afc_power_ru2476,
680+ afc_power_ru2988,
681+ afc_power_ru3472,
682+ afc_power_table_num,
683+};
684+
685 int hostap_afc_get_chan_max_eirp_power(struct hostapd_iface *iface, bool psd,
686 int *power);
687 int hostapd_afc_handle_request(struct hostapd_iface *iface);
688 void hostapd_afc_stop(struct hostapd_iface *iface);
689 void hostap_afc_disable_channels(struct hostapd_iface *iface);
690+int hostapd_afc_translate_table(struct hostapd_iface *iface,
691+ s8 ***power_table);
692 #else
693 static inline int
694 hostap_afc_get_chan_max_eirp_power(struct hostapd_iface *iface, bool psd,
695@@ -814,6 +868,12 @@ static inline void hostapd_afc_stop(struct hostapd_iface *iface)
696 static inline void hostap_afc_disable_channels(struct hostapd_iface *iface)
697 {
698 }
699+
700+int hostapd_afc_translate_table(struct hostapd_iface *iface,
701+ s8 ***power_table)
702+{
703+ return -EINVAL;
704+}
705 #endif /* CONFIG_AFC */
706
707 int hostapd_for_each_interface(struct hapd_interfaces *interfaces,
708diff --git a/src/common/mtk_vendor.h b/src/common/mtk_vendor.h
709index 1fe459126..4b900162b 100644
710--- a/src/common/mtk_vendor.h
711+++ b/src/common/mtk_vendor.h
712@@ -319,6 +319,8 @@ enum mtk_vendor_attr_txpower_ctrl {
713 MTK_VENDOR_ATTR_TXPOWER_CTRL_SKU_IDX,
714 MTK_VENDOR_ATTR_TXPOWER_CTRL_LPI_BCN_ENHANCE,
715 MTK_VENDOR_ATTR_TXPOWER_CTRL_LINK_ID,
716+ MTK_VENDOR_ATTR_TXPOWER_CTRL_AFC_TABLE,
717+ MTK_VENDOR_ATTR_TXPOWER_CTRL_AFC_LPI,
718
719 /* keep last */
720 NUM_MTK_VENDOR_ATTRS_TXPOWER_CTRL,
721diff --git a/src/drivers/driver.h b/src/drivers/driver.h
722index 829274bfe..6aac87ce1 100644
723--- a/src/drivers/driver.h
724+++ b/src/drivers/driver.h
725@@ -5481,9 +5481,11 @@ struct wpa_driver_ops {
726 * @lpi_bcn_enhance: 1 to enable beacon duplicate enhancement in 6G lpi mode, 0 to disable enhancement
727 * @sku_idx: index used to indicate which sku table should be used
728 * @link_id: MLD link id. -1 if this is an non-MLD AP
729+ * @power_table: power table generated from AFC response
730+ * @lpi_mode: specify the current mode is whether lpi
731 */
732 int (*txpower_ctrl)(void *priv, u8 lpi_psd, u8 sku_idx, u8 lpi_bcn_enhance,
733- u8 link_id);
734+ u8 link_id, s8 **power_table, u8 lpi_mode);
735 };
736
737 /**
738diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
739index d2064fbd6..efee210b9 100644
740--- a/src/drivers/driver_nl80211.c
741+++ b/src/drivers/driver_nl80211.c
742@@ -41,6 +41,7 @@
743 #include "driver_nl80211.h"
744 #include "common/mtk_vendor.h"
745 #include "ap/ap_config.h"
746+#include "ap/hostapd.h"
747
748 #ifdef CONFIG_IEEE80211BE
749 #include "ap/scs.h"
750@@ -205,6 +206,8 @@ txpower_ctrl_policy[NUM_MTK_VENDOR_ATTRS_TXPOWER_CTRL] = {
751 [MTK_VENDOR_ATTR_TXPOWER_CTRL_SKU_IDX] = { .type = NLA_U8 },
752 [MTK_VENDOR_ATTR_TXPOWER_CTRL_LPI_BCN_ENHANCE] = { .type = NLA_U8 },
753 [MTK_VENDOR_ATTR_TXPOWER_CTRL_LINK_ID] = { .type = NLA_U8 },
754+ [MTK_VENDOR_ATTR_TXPOWER_CTRL_AFC_TABLE] = { .type = NLA_BINARY },
755+ [MTK_VENDOR_ATTR_TXPOWER_CTRL_AFC_LPI] = { .type = NLA_U8 },
756 };
757
758 static struct nl_sock * nl_create_handle(struct nl_cb *cb, const char *dbg)
759@@ -15662,38 +15665,53 @@ fail:
760 }
761
762 static int nl80211_txpower_ctrl(void *priv, u8 lpi_psd, u8 sku_idx, u8 lpi_bcn_enhance,
763- u8 link_id)
764+ u8 link_id, s8 **power_table, u8 lpi_mode)
765 {
766 struct i802_bss *bss = priv;
767 struct wpa_driver_nl80211_data *drv = bss->drv;
768 struct nl_msg *msg;
769 struct nlattr *data;
770- int ret;
771+ struct nlattr *table_attr, *channel_list;
772+ int ret = 0;
773
774 if (!drv->mtk_txpower_vendor_cmd_avail) {
775 wpa_printf(MSG_INFO,
776 "nl80211: Driver does not support setting txpower control");
777- return 0;
778+ goto fail;
779 }
780
781 msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR);
782- if (!msg)
783+ if (!msg) {
784+ ret = -ENOBUFS;
785 goto fail;
786+ }
787
788 if (nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
789 nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
790- MTK_NL80211_VENDOR_SUBCMD_TXPOWER_CTRL))
791+ MTK_NL80211_VENDOR_SUBCMD_TXPOWER_CTRL)) {
792+ ret = -ENOBUFS;
793 goto fail;
794+ }
795
796- data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
797- if (!data)
798+ data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
799+ if (!data) {
800+ ret = -ENOBUFS;
801 goto fail;
802+ }
803
804 nla_put_u8(msg, MTK_VENDOR_ATTR_TXPOWER_CTRL_LPI_PSD, lpi_psd);
805 nla_put_u8(msg, MTK_VENDOR_ATTR_TXPOWER_CTRL_SKU_IDX, sku_idx);
806 nla_put_u8(msg, MTK_VENDOR_ATTR_TXPOWER_CTRL_LPI_BCN_ENHANCE, lpi_bcn_enhance);
807- nla_put_u8(msg, MTK_VENDOR_ATTR_TXPOWER_CTRL_LINK_ID, link_id);
808
809+ if (link_id > -1)
810+ nla_put_u8(msg, MTK_VENDOR_ATTR_TXPOWER_CTRL_LINK_ID, link_id);
811+
812+ if (power_table && *power_table) {
813+ nla_put(msg, MTK_VENDOR_ATTR_TXPOWER_CTRL_AFC_TABLE,
814+ MAX_CHANNEL_NUM_6G * afc_power_table_num, power_table);
815+ }
816+
817+ nla_put_u8(msg, MTK_VENDOR_ATTR_TXPOWER_CTRL_AFC_LPI, lpi_mode);
818 nla_nest_end(msg, data);
819 ret = send_and_recv_cmd(drv, msg);
820 if (ret)
821@@ -15704,7 +15722,7 @@ static int nl80211_txpower_ctrl(void *priv, u8 lpi_psd, u8 sku_idx, u8 lpi_bcn_e
822
823 fail:
824 nlmsg_free(msg);
825- return -ENOBUFS;
826+ return ret;
827 }
828
829 const struct wpa_driver_ops wpa_driver_nl80211_ops = {
830--
8312.18.0
832