wifi: rtw89: regd: support loading regd table from fw element
authorZong-Zhe Yang <kevin_yang@realtek.com>
Mon, 20 Jan 2025 03:27:21 +0000 (11:27 +0800)
committerPing-Ke Shih <pkshih@realtek.com>
Mon, 3 Feb 2025 02:04:46 +0000 (10:04 +0800)
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
drivers/net/wireless/realtek/rtw89/core.h
drivers/net/wireless/realtek/rtw89/fw.c
drivers/net/wireless/realtek/rtw89/fw.h
drivers/net/wireless/realtek/rtw89/regd.c

index 970730291e567991e5a56cb0d4609de44ef4b8db..680e80b06f014832347d33cb466f388b6826dcd3 100644 (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;
index 5cc9ab78c09f79b11a6b44cefaa424e9ec2af5c1..7b7994b8e1aa9ae7cfc6846c10574076f2c03b8a 100644 (file)
@@ -1056,6 +1056,89 @@ allocated:
        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)
index 3cc514de073abd90572cad9a1e3cdc2c49527af4..b98da24e9af3e75823e314aa53c58c0117e3e60b 100644 (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;
index 80b2f74589eb9fe6d283caeb6c22e6cb93e84537..aea37dae8ef91d6cfdeac63a22d7d693544c930e 100644 (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 @@ out:
 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 @@ bottom:
 
 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,