wifi: rtw89: regd: support loading regd table from fw element

Regd table is a table that we use to describe how to map Realtek RF TX
power settings on different countries. Originally, a common regd table
for all chips is implemented in driver. However, in order to work on all
chips, the common regd table might have some trade-off. So now, there are
also an individual regd table for some chips. And, we support loading it
from FW element.

Signed-off-by: Zong-Zhe Yang <kevin_yang@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/20250120032723.19042-2-pkshih@realtek.com
This commit is contained in:
Zong-Zhe Yang 2025-01-20 11:27:21 +08:00 committed by Ping-Ke Shih
parent 076652f56e
commit e7196b32a4
4 changed files with 166 additions and 22 deletions

View File

@ -22,6 +22,7 @@ struct rtw89_h2c_rf_tssi;
struct rtw89_fw_txpwr_track_cfg;
struct rtw89_phy_rfk_log_fmt;
struct rtw89_debugfs;
struct rtw89_regd_data;
extern const struct ieee80211_ops rtw89_ops;
@ -718,6 +719,7 @@ enum rtw89_ofdma_type {
RTW89_OFDMA_NUM,
};
/* neither insert new in the middle, nor change any given definition */
enum rtw89_regulation_type {
RTW89_WW = 0,
RTW89_ETSI = 1,
@ -4539,6 +4541,7 @@ struct rtw89_fw_elm_info {
struct rtw89_phy_table *rf_nctl;
struct rtw89_fw_txpwr_track_cfg *txpwr_trk;
struct rtw89_phy_rfk_log_fmt *rfk_log_fmt;
const struct rtw89_regd_data *regd;
};
enum rtw89_fw_mss_dev_type {
@ -5153,11 +5156,22 @@ struct rtw89_regd {
u8 txpwr_regd[RTW89_BAND_NUM];
};
struct rtw89_regd_data {
unsigned int nr;
struct rtw89_regd map[] __counted_by(nr);
};
struct rtw89_regd_ctrl {
unsigned int nr;
const struct rtw89_regd *map;
};
#define RTW89_REGD_MAX_COUNTRY_NUM U8_MAX
#define RTW89_5GHZ_UNII4_CHANNEL_NUM 3
#define RTW89_5GHZ_UNII4_START_INDEX 25
struct rtw89_regulatory_info {
struct rtw89_regd_ctrl ctrl;
const struct rtw89_regd *regd;
enum rtw89_reg_6ghz_power reg_6ghz_power;
struct rtw89_reg_6ghz_tpe reg_6ghz_tpe;

View File

@ -1056,6 +1056,89 @@ int rtw89_build_rfk_log_fmt_from_elm(struct rtw89_dev *rtwdev,
return 0;
}
static bool rtw89_regd_entcpy(struct rtw89_regd *regd, const void *cursor,
u8 cursor_size)
{
/* fill default values if needed for backward compatibility */
struct rtw89_fw_regd_entry entry = {
.rule_2ghz = RTW89_NA,
.rule_5ghz = RTW89_NA,
.rule_6ghz = RTW89_NA,
};
u8 valid_size = min_t(u8, sizeof(entry), cursor_size);
memcpy(&entry, cursor, valid_size);
memset(regd, 0, sizeof(*regd));
regd->alpha2[0] = entry.alpha2_0;
regd->alpha2[1] = entry.alpha2_1;
regd->alpha2[2] = '\0';
/* also need to consider forward compatibility */
regd->txpwr_regd[RTW89_BAND_2G] = entry.rule_2ghz < RTW89_REGD_NUM ?
entry.rule_2ghz : RTW89_NA;
regd->txpwr_regd[RTW89_BAND_5G] = entry.rule_5ghz < RTW89_REGD_NUM ?
entry.rule_5ghz : RTW89_NA;
regd->txpwr_regd[RTW89_BAND_6G] = entry.rule_6ghz < RTW89_REGD_NUM ?
entry.rule_6ghz : RTW89_NA;
return true;
}
#define rtw89_for_each_in_regd_element(regd, element) \
for (const void *cursor = (element)->content, \
*end = (element)->content + \
le32_to_cpu((element)->num_ents) * (element)->ent_sz; \
cursor < end; cursor += (element)->ent_sz) \
if (rtw89_regd_entcpy(regd, cursor, (element)->ent_sz))
static
int rtw89_recognize_regd_from_elm(struct rtw89_dev *rtwdev,
const struct rtw89_fw_element_hdr *elm,
const union rtw89_fw_element_arg arg)
{
const struct __rtw89_fw_regd_element *regd_elm = &elm->u.regd;
struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info;
u32 num_ents = le32_to_cpu(regd_elm->num_ents);
struct rtw89_regd_data *p;
struct rtw89_regd regd;
u32 i = 0;
if (num_ents > RTW89_REGD_MAX_COUNTRY_NUM) {
rtw89_warn(rtwdev,
"regd element ents (%d) are over max num (%d)\n",
num_ents, RTW89_REGD_MAX_COUNTRY_NUM);
rtw89_warn(rtwdev,
"regd element ignore and take another/common\n");
return 1;
}
if (elm_info->regd) {
rtw89_debug(rtwdev, RTW89_DBG_REGD,
"regd element take the latter\n");
devm_kfree(rtwdev->dev, elm_info->regd);
elm_info->regd = NULL;
}
p = devm_kzalloc(rtwdev->dev, struct_size(p, map, num_ents), GFP_KERNEL);
if (!p)
return -ENOMEM;
p->nr = num_ents;
rtw89_for_each_in_regd_element(&regd, regd_elm)
p->map[i++] = regd;
if (i != num_ents) {
rtw89_err(rtwdev, "regd element has %d invalid ents\n",
num_ents - i);
devm_kfree(rtwdev->dev, p);
return -EINVAL;
}
elm_info->regd = p;
return 0;
}
static const struct rtw89_fw_element_handler __fw_element_handlers[] = {
[RTW89_FW_ELEMENT_ID_BBMCU0] = {__rtw89_fw_recognize_from_elm,
{ .fw_type = RTW89_FW_BBMCU0 }, NULL},
@ -1114,6 +1197,9 @@ static const struct rtw89_fw_element_handler __fw_element_handlers[] = {
[RTW89_FW_ELEMENT_ID_RFKLOG_FMT] = {
rtw89_build_rfk_log_fmt_from_elm, {}, NULL,
},
[RTW89_FW_ELEMENT_ID_REGD] = {
rtw89_recognize_regd_from_elm, {}, "REGD",
},
};
int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev)

View File

@ -3882,6 +3882,7 @@ enum rtw89_fw_element_id {
RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT_RU = 17,
RTW89_FW_ELEMENT_ID_TXPWR_TRK = 18,
RTW89_FW_ELEMENT_ID_RFKLOG_FMT = 19,
RTW89_FW_ELEMENT_ID_REGD = 20,
RTW89_FW_ELEMENT_ID_NUM,
};
@ -3925,6 +3926,15 @@ struct __rtw89_fw_txpwr_element {
u8 content[];
} __packed;
struct __rtw89_fw_regd_element {
u8 rsvd0;
u8 rsvd1;
u8 rsvd2;
u8 ent_sz;
__le32 num_ents;
u8 content[];
} __packed;
enum rtw89_fw_txpwr_trk_type {
__RTW89_FW_TXPWR_TRK_TYPE_6GHZ_START = 0,
RTW89_FW_TXPWR_TRK_TYPE_6GB_N = 0,
@ -4016,6 +4026,7 @@ struct rtw89_fw_element_hdr {
__le16 offset[];
} __packed rfk_log_fmt;
struct __rtw89_fw_txpwr_element txpwr;
struct __rtw89_fw_regd_element regd;
} __packed u;
} __packed;
@ -4874,6 +4885,17 @@ int rtw89_chip_h2c_ba_cam(struct rtw89_dev *rtwdev, struct rtw89_sta *rtwsta,
return 0;
}
/* Must consider compatibility; don't insert new in the mid.
* Fill each field's default value in rtw89_regd_entcpy().
*/
struct rtw89_fw_regd_entry {
u8 alpha2_0;
u8 alpha2_1;
u8 rule_2ghz;
u8 rule_5ghz;
u8 rule_6ghz;
} __packed;
/* must consider compatibility; don't insert new in the mid */
struct rtw89_fw_txpwr_byrate_entry {
u8 band;

View File

@ -7,9 +7,12 @@
#include "ps.h"
#include "util.h"
#define COUNTRY_REGD(_alpha2, _txpwr_regd...) \
{.alpha2 = (_alpha2), \
.txpwr_regd = {_txpwr_regd}, \
#define COUNTRY_REGD(_alpha2, _rule_2ghz, _rule_5ghz, _rule_6ghz) \
{ \
.alpha2 = _alpha2, \
.txpwr_regd[RTW89_BAND_2G] = _rule_2ghz, \
.txpwr_regd[RTW89_BAND_5G] = _rule_5ghz, \
.txpwr_regd[RTW89_BAND_6G] = _rule_6ghz, \
}
static const struct rtw89_regd rtw89_ww_regd =
@ -295,13 +298,16 @@ static const char rtw89_alpha2_list_eu[][3] = {
"RO",
};
static const struct rtw89_regd *rtw89_regd_find_reg_by_name(const char *alpha2)
static const struct rtw89_regd *rtw89_regd_find_reg_by_name(struct rtw89_dev *rtwdev,
const char *alpha2)
{
struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory;
const struct rtw89_regd_ctrl *regd_ctrl = &regulatory->ctrl;
u32 i;
for (i = 0; i < ARRAY_SIZE(rtw89_regd_map); i++) {
if (!memcmp(rtw89_regd_map[i].alpha2, alpha2, 2))
return &rtw89_regd_map[i];
for (i = 0; i < regd_ctrl->nr; i++) {
if (!memcmp(regd_ctrl->map[i].alpha2, alpha2, 2))
return &regd_ctrl->map[i];
}
return &rtw89_ww_regd;
@ -312,22 +318,25 @@ static bool rtw89_regd_is_ww(const struct rtw89_regd *regd)
return regd == &rtw89_ww_regd;
}
static u8 rtw89_regd_get_index(const struct rtw89_regd *regd)
static u8 rtw89_regd_get_index(struct rtw89_dev *rtwdev, const struct rtw89_regd *regd)
{
struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory;
const struct rtw89_regd_ctrl *regd_ctrl = &regulatory->ctrl;
BUILD_BUG_ON(ARRAY_SIZE(rtw89_regd_map) > RTW89_REGD_MAX_COUNTRY_NUM);
if (rtw89_regd_is_ww(regd))
return RTW89_REGD_MAX_COUNTRY_NUM;
return regd - rtw89_regd_map;
return regd - regd_ctrl->map;
}
static u8 rtw89_regd_get_index_by_name(const char *alpha2)
static u8 rtw89_regd_get_index_by_name(struct rtw89_dev *rtwdev, const char *alpha2)
{
const struct rtw89_regd *regd;
regd = rtw89_regd_find_reg_by_name(alpha2);
return rtw89_regd_get_index(regd);
regd = rtw89_regd_find_reg_by_name(rtwdev, alpha2);
return rtw89_regd_get_index(rtwdev, regd);
}
#define rtw89_debug_regd(_dev, _regd, _desc, _argv...) \
@ -345,6 +354,7 @@ static void rtw89_regd_setup_unii4(struct rtw89_dev *rtwdev,
struct wiphy *wiphy)
{
struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory;
const struct rtw89_regd_ctrl *regd_ctrl = &regulatory->ctrl;
const struct rtw89_chip_info *chip = rtwdev->chip;
struct ieee80211_supported_band *sband;
struct rtw89_acpi_dsm_result res = {};
@ -382,8 +392,8 @@ static void rtw89_regd_setup_unii4(struct rtw89_dev *rtwdev,
"acpi: eval if allow unii-4: 0x%x\n", val);
bottom:
for (i = 0; i < ARRAY_SIZE(rtw89_regd_map); i++) {
const struct rtw89_regd *regd = &rtw89_regd_map[i];
for (i = 0; i < regd_ctrl->nr; i++) {
const struct rtw89_regd *regd = &regd_ctrl->map[i];
switch (regd->txpwr_regd[RTW89_BAND_5G]) {
case RTW89_FCC:
@ -406,7 +416,7 @@ static void __rtw89_regd_setup_policy_6ghz(struct rtw89_dev *rtwdev, bool block,
struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory;
u8 index;
index = rtw89_regd_get_index_by_name(alpha2);
index = rtw89_regd_get_index_by_name(rtwdev, alpha2);
if (index == RTW89_REGD_MAX_COUNTRY_NUM) {
rtw89_debug(rtwdev, RTW89_DBG_REGD, "%s: unknown alpha2 %c%c\n",
__func__, alpha2[0], alpha2[1]);
@ -474,6 +484,7 @@ static void rtw89_regd_setup_policy_6ghz(struct rtw89_dev *rtwdev)
static void rtw89_regd_setup_policy_6ghz_sp(struct rtw89_dev *rtwdev)
{
struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory;
const struct rtw89_regd_ctrl *regd_ctrl = &regulatory->ctrl;
const struct rtw89_acpi_policy_6ghz_sp *ptr;
struct rtw89_acpi_dsm_result res = {};
bool enable_by_us;
@ -505,8 +516,8 @@ static void rtw89_regd_setup_policy_6ghz_sp(struct rtw89_dev *rtwdev)
enable_by_us = u8_get_bits(ptr->conf, RTW89_ACPI_CONF_6GHZ_SP_US);
for (i = 0; i < ARRAY_SIZE(rtw89_regd_map); i++) {
const struct rtw89_regd *tmp = &rtw89_regd_map[i];
for (i = 0; i < regd_ctrl->nr; i++) {
const struct rtw89_regd *tmp = &regd_ctrl->map[i];
if (enable_by_us && memcmp(tmp->alpha2, "US", 2) == 0)
clear_bit(i, regulatory->block_6ghz_sp);
@ -573,8 +584,19 @@ static void rtw89_regd_setup_6ghz(struct rtw89_dev *rtwdev, struct wiphy *wiphy)
int rtw89_regd_setup(struct rtw89_dev *rtwdev)
{
struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory;
struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info;
const struct rtw89_regd_data *regd_data = elm_info->regd;
struct wiphy *wiphy = rtwdev->hw->wiphy;
if (regd_data) {
regulatory->ctrl.nr = regd_data->nr;
regulatory->ctrl.map = regd_data->map;
} else {
regulatory->ctrl.nr = ARRAY_SIZE(rtw89_regd_map);
regulatory->ctrl.map = rtw89_regd_map;
}
if (!wiphy)
return -EINVAL;
@ -599,7 +621,7 @@ int rtw89_regd_init(struct rtw89_dev *rtwdev,
if (!wiphy)
return -EINVAL;
chip_regd = rtw89_regd_find_reg_by_name(rtwdev->efuse.country_code);
chip_regd = rtw89_regd_find_reg_by_name(rtwdev, rtwdev->efuse.country_code);
if (!rtw89_regd_is_ww(chip_regd)) {
rtwdev->regulatory.regd = chip_regd;
/* Ignore country ie if there is a country domain programmed in chip */
@ -637,7 +659,7 @@ static void rtw89_regd_apply_policy_unii4(struct rtw89_dev *rtwdev,
if (!chip->support_unii4)
return;
index = rtw89_regd_get_index(regd);
index = rtw89_regd_get_index(rtwdev, regd);
if (index != RTW89_REGD_MAX_COUNTRY_NUM &&
!test_bit(index, regulatory->block_unii4))
return;
@ -655,7 +677,7 @@ static bool regd_is_6ghz_blocked(struct rtw89_dev *rtwdev)
const struct rtw89_regd *regd = regulatory->regd;
u8 index;
index = rtw89_regd_get_index(regd);
index = rtw89_regd_get_index(rtwdev, regd);
if (index != RTW89_REGD_MAX_COUNTRY_NUM &&
!test_bit(index, regulatory->block_6ghz))
return false;
@ -700,7 +722,7 @@ static void rtw89_regd_notifier_apply(struct rtw89_dev *rtwdev,
struct wiphy *wiphy,
struct regulatory_request *request)
{
rtwdev->regulatory.regd = rtw89_regd_find_reg_by_name(request->alpha2);
rtwdev->regulatory.regd = rtw89_regd_find_reg_by_name(rtwdev, request->alpha2);
/* This notification might be set from the system of distros,
* and it does not expect the regulatory will be modified by
* connecting to an AP (i.e. country ie).
@ -925,7 +947,7 @@ static bool __rtw89_reg_6ghz_power_recalc(struct rtw89_dev *rtwdev)
sel = RTW89_REG_6GHZ_POWER_DFLT;
if (sel == RTW89_REG_6GHZ_POWER_STD) {
index = rtw89_regd_get_index(regd);
index = rtw89_regd_get_index(rtwdev, regd);
if (index == RTW89_REGD_MAX_COUNTRY_NUM ||
test_bit(index, regulatory->block_6ghz_sp)) {
rtw89_debug(rtwdev, RTW89_DBG_REGD,