@@ -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,
@@ -4537,6 +4539,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 {
@@ -5152,11 +5155,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;
@@ -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(®d, 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)
@@ -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;
@@ -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 = ®ulatory->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 ®d_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 = ®ulatory->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 = ®ulatory->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 = ®d_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 = ®ulatory->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 = ®d_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,