Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/wireless/realtek/rtw89/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 276 kB image not shown  

Quelle  fw.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright(c) 2019-2020  Realtek Corporation
 */


#include <linux/if_arp.h>
#include "cam.h"
#include "chan.h"
#include "coex.h"
#include "debug.h"
#include "fw.h"
#include "mac.h"
#include "phy.h"
#include "ps.h"
#include "reg.h"
#include "util.h"
#include "wow.h"

static bool rtw89_is_any_vif_connected_or_connecting(struct rtw89_dev *rtwdev);

struct rtw89_eapol_2_of_2 {
 u8 gtkbody[14];
 u8 key_des_ver;
 u8 rsvd[92];
} __packed;

struct rtw89_sa_query {
 u8 category;
 u8 action;
} __packed;

struct rtw89_arp_rsp {
 u8 llc_hdr[sizeof(rfc1042_header)];
 __be16 llc_type;
 struct arphdr arp_hdr;
 u8 sender_hw[ETH_ALEN];
 __be32 sender_ip;
 u8 target_hw[ETH_ALEN];
 __be32 target_ip;
} __packed;

static const u8 mss_signature[] = {0x4D, 0x53, 0x53, 0x4B, 0x50, 0x4F, 0x4F, 0x4C};

const struct rtw89_fw_blacklist rtw89_fw_blacklist_default = {
 .ver = 0x00,
 .list = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
 },
};
EXPORT_SYMBOL(rtw89_fw_blacklist_default);

union rtw89_fw_element_arg {
 size_t offset;
 enum rtw89_rf_path rf_path;
 enum rtw89_fw_type fw_type;
};

struct rtw89_fw_element_handler {
 int (*fn)(struct rtw89_dev *rtwdev,
    const struct rtw89_fw_element_hdr *elm,
    const union rtw89_fw_element_arg arg);
 const union rtw89_fw_element_arg arg;
 const char *name;
};

static void rtw89_fw_c2h_cmd_handle(struct rtw89_dev *rtwdev,
        struct sk_buff *skb);
static int rtw89_h2c_tx_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *skb,
     struct rtw89_wait_info *wait, unsigned int cond);
static int __parse_security_section(struct rtw89_dev *rtwdev,
        struct rtw89_fw_bin_info *info,
        struct rtw89_fw_hdr_section_info *section_info,
        const void *content,
        u32 *mssc_len);

static struct sk_buff *rtw89_fw_h2c_alloc_skb(struct rtw89_dev *rtwdev, u32 len,
           bool header)
{
 struct sk_buff *skb;
 u32 header_len = 0;
 u32 h2c_desc_size = rtwdev->chip->h2c_desc_size;

 if (header)
  header_len = H2C_HEADER_LEN;

 skb = dev_alloc_skb(len + header_len + h2c_desc_size);
 if (!skb)
  return NULL;
 skb_reserve(skb, header_len + h2c_desc_size);
 memset(skb->data, 0, len);

 return skb;
}

struct sk_buff *rtw89_fw_h2c_alloc_skb_with_hdr(struct rtw89_dev *rtwdev, u32 len)
{
 return rtw89_fw_h2c_alloc_skb(rtwdev, len, true);
}

struct sk_buff *rtw89_fw_h2c_alloc_skb_no_hdr(struct rtw89_dev *rtwdev, u32 len)
{
 return rtw89_fw_h2c_alloc_skb(rtwdev, len, false);
}

int rtw89_fw_check_rdy(struct rtw89_dev *rtwdev, enum rtw89_fwdl_check_type type)
{
 const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def;
 u8 val;
 int ret;

 ret = read_poll_timeout_atomic(mac->fwdl_get_status, val,
           val == RTW89_FWDL_WCPU_FW_INIT_RDY,
           1, FWDL_WAIT_CNT, false, rtwdev, type);
 if (ret) {
  switch (val) {
  case RTW89_FWDL_CHECKSUM_FAIL:
   rtw89_err(rtwdev, "fw checksum fail\n");
   return -EINVAL;

  case RTW89_FWDL_SECURITY_FAIL:
   rtw89_err(rtwdev, "fw security fail\n");
   return -EINVAL;

  case RTW89_FWDL_CV_NOT_MATCH:
   rtw89_err(rtwdev, "fw cv not match\n");
   return -EINVAL;

  default:
   rtw89_err(rtwdev, "fw unexpected status %d\n", val);
   return -EBUSY;
  }
 }

 set_bit(RTW89_FLAG_FW_RDY, rtwdev->flags);

 return 0;
}

static int rtw89_fw_hdr_parser_v0(struct rtw89_dev *rtwdev, const u8 *fw, u32 len,
      struct rtw89_fw_bin_info *info)
{
 const struct rtw89_fw_hdr *fw_hdr = (const struct rtw89_fw_hdr *)fw;
 const struct rtw89_chip_info *chip = rtwdev->chip;
 struct rtw89_fw_hdr_section_info *section_info;
 struct rtw89_fw_secure *sec = &rtwdev->fw.sec;
 const struct rtw89_fw_dynhdr_hdr *fwdynhdr;
 const struct rtw89_fw_hdr_section *section;
 const u8 *fw_end = fw + len;
 const u8 *bin;
 u32 base_hdr_len;
 u32 mssc_len;
 int ret;
 u32 i;

 if (!info)
  return -EINVAL;

 info->section_num = le32_get_bits(fw_hdr->w6, FW_HDR_W6_SEC_NUM);
 base_hdr_len = struct_size(fw_hdr, sections, info->section_num);
 info->dynamic_hdr_en = le32_get_bits(fw_hdr->w7, FW_HDR_W7_DYN_HDR);
 info->idmem_share_mode = le32_get_bits(fw_hdr->w7, FW_HDR_W7_IDMEM_SHARE_MODE);

 if (info->dynamic_hdr_en) {
  info->hdr_len = le32_get_bits(fw_hdr->w3, FW_HDR_W3_LEN);
  info->dynamic_hdr_len = info->hdr_len - base_hdr_len;
  fwdynhdr = (const struct rtw89_fw_dynhdr_hdr *)(fw + base_hdr_len);
  if (le32_to_cpu(fwdynhdr->hdr_len) != info->dynamic_hdr_len) {
   rtw89_err(rtwdev, "[ERR]invalid fw dynamic header len\n");
   return -EINVAL;
  }
 } else {
  info->hdr_len = base_hdr_len;
  info->dynamic_hdr_len = 0;
 }

 bin = fw + info->hdr_len;

 /* jump to section header */
 section_info = info->section_info;
 for (i = 0; i < info->section_num; i++) {
  section = &fw_hdr->sections[i];
  section_info->type =
   le32_get_bits(section->w1, FWSECTION_HDR_W1_SECTIONTYPE);
  section_info->len = le32_get_bits(section->w1, FWSECTION_HDR_W1_SEC_SIZE);

  if (le32_get_bits(section->w1, FWSECTION_HDR_W1_CHECKSUM))
   section_info->len += FWDL_SECTION_CHKSUM_LEN;
  section_info->redl = le32_get_bits(section->w1, FWSECTION_HDR_W1_REDL);
  section_info->dladdr =
   le32_get_bits(section->w0, FWSECTION_HDR_W0_DL_ADDR) & 0x1fffffff;
  section_info->addr = bin;

  if (section_info->type == FWDL_SECURITY_SECTION_TYPE) {
   section_info->mssc =
    le32_get_bits(section->w2, FWSECTION_HDR_W2_MSSC);

   ret = __parse_security_section(rtwdev, info, section_info,
             bin, &mssc_len);
   if (ret)
    return ret;

   if (sec->secure_boot && chip->chip_id == RTL8852B)
    section_info->len_override = 960;
  } else {
   section_info->mssc = 0;
   mssc_len = 0;
  }

  rtw89_debug(rtwdev, RTW89_DBG_FW,
       "section[%d] type=%d len=0x%-6x mssc=%d mssc_len=%d addr=%tx\n",
       i, section_info->type, section_info->len,
       section_info->mssc, mssc_len, bin - fw);
  rtw89_debug(rtwdev, RTW89_DBG_FW,
       " ignore=%d key_addr=%p (0x%tx) key_len=%d key_idx=%d\n",
       section_info->ignore, section_info->key_addr,
       section_info->key_addr ?
       section_info->key_addr - section_info->addr : 0,
       section_info->key_len, section_info->key_idx);

  bin += section_info->len + mssc_len;
  section_info++;
 }

 if (fw_end != bin) {
  rtw89_err(rtwdev, "[ERR]fw bin size\n");
  return -EINVAL;
 }

 return 0;
}

static int __get_mssc_key_idx(struct rtw89_dev *rtwdev,
         const struct rtw89_fw_mss_pool_hdr *mss_hdr,
         u32 rmp_tbl_size, u32 *key_idx)
{
 struct rtw89_fw_secure *sec = &rtwdev->fw.sec;
 u32 sel_byte_idx;
 u32 mss_sel_idx;
 u8 sel_bit_idx;
 int i;

 if (sec->mss_dev_type == RTW89_FW_MSS_DEV_TYPE_FWSEC_DEF) {
  if (!mss_hdr->defen)
   return -ENOENT;

  mss_sel_idx = sec->mss_cust_idx * le16_to_cpu(mss_hdr->msskey_num_max) +
         sec->mss_key_num;
 } else {
  if (mss_hdr->defen)
   mss_sel_idx = FWDL_MSS_POOL_DEFKEYSETS_SIZE << 3;
  else
   mss_sel_idx = 0;
  mss_sel_idx += sec->mss_dev_type * le16_to_cpu(mss_hdr->msskey_num_max) *
         le16_to_cpu(mss_hdr->msscust_max) +
          sec->mss_cust_idx * le16_to_cpu(mss_hdr->msskey_num_max) +
          sec->mss_key_num;
 }

 sel_byte_idx = mss_sel_idx >> 3;
 sel_bit_idx = mss_sel_idx & 0x7;

 if (sel_byte_idx >= rmp_tbl_size)
  return -EFAULT;

 if (!(mss_hdr->rmp_tbl[sel_byte_idx] & BIT(sel_bit_idx)))
  return -ENOENT;

 *key_idx = hweight8(mss_hdr->rmp_tbl[sel_byte_idx] & (BIT(sel_bit_idx) - 1));

 for (i = 0; i < sel_byte_idx; i++)
  *key_idx += hweight8(mss_hdr->rmp_tbl[i]);

 return 0;
}

static int __parse_formatted_mssc(struct rtw89_dev *rtwdev,
      struct rtw89_fw_bin_info *info,
      struct rtw89_fw_hdr_section_info *section_info,
      const void *content,
      u32 *mssc_len)
{
 const struct rtw89_fw_mss_pool_hdr *mss_hdr = content + section_info->len;
 const union rtw89_fw_section_mssc_content *section_content = content;
 struct rtw89_fw_secure *sec = &rtwdev->fw.sec;
 u32 rmp_tbl_size;
 u32 key_sign_len;
 u32 real_key_idx;
 u32 sb_sel_ver;
 int ret;

 if (memcmp(mss_signature, mss_hdr->signature, sizeof(mss_signature)) != 0) {
  rtw89_err(rtwdev, "[ERR] wrong MSS signature\n");
  return -ENOENT;
 }

 if (mss_hdr->rmpfmt == MSS_POOL_RMP_TBL_BITMASK) {
  rmp_tbl_size = (le16_to_cpu(mss_hdr->msskey_num_max) *
    le16_to_cpu(mss_hdr->msscust_max) *
    mss_hdr->mssdev_max) >> 3;
  if (mss_hdr->defen)
   rmp_tbl_size += FWDL_MSS_POOL_DEFKEYSETS_SIZE;
 } else {
  rtw89_err(rtwdev, "[ERR] MSS Key Pool Remap Table Format Unsupport:%X\n",
     mss_hdr->rmpfmt);
  return -EINVAL;
 }

 if (rmp_tbl_size + sizeof(*mss_hdr) != le32_to_cpu(mss_hdr->key_raw_offset)) {
  rtw89_err(rtwdev, "[ERR] MSS Key Pool Format Error:0x%X + 0x%X != 0x%X\n",
     rmp_tbl_size, (int)sizeof(*mss_hdr),
     le32_to_cpu(mss_hdr->key_raw_offset));
  return -EINVAL;
 }

 key_sign_len = le16_to_cpu(section_content->key_sign_len.v) >> 2;
 if (!key_sign_len)
  key_sign_len = 512;

 if (info->dsp_checksum)
  key_sign_len += FWDL_SECURITY_CHKSUM_LEN;

 *mssc_len = sizeof(*mss_hdr) + rmp_tbl_size +
      le16_to_cpu(mss_hdr->keypair_num) * key_sign_len;

 if (!sec->secure_boot)
  goto out;

 sb_sel_ver = get_unaligned_le32(§ion_content->sb_sel_ver.v);
 if (sb_sel_ver && sb_sel_ver != sec->sb_sel_mgn)
  goto ignore;

 ret = __get_mssc_key_idx(rtwdev, mss_hdr, rmp_tbl_size, &real_key_idx);
 if (ret)
  goto ignore;

 section_info->key_addr = content + section_info->len +
    le32_to_cpu(mss_hdr->key_raw_offset) +
    key_sign_len * real_key_idx;
 section_info->key_len = key_sign_len;
 section_info->key_idx = real_key_idx;

out:
 if (info->secure_section_exist) {
  section_info->ignore = true;
  return 0;
 }

 info->secure_section_exist = true;

 return 0;

ignore:
 section_info->ignore = true;

 return 0;
}

static int __check_secure_blacklist(struct rtw89_dev *rtwdev,
        struct rtw89_fw_bin_info *info,
        struct rtw89_fw_hdr_section_info *section_info,
        const void *content)
{
 const struct rtw89_fw_blacklist *chip_blacklist = rtwdev->chip->fw_blacklist;
 const union rtw89_fw_section_mssc_content *section_content = content;
 struct rtw89_fw_secure *sec = &rtwdev->fw.sec;
 u8 byte_idx;
 u8 bit_mask;

 if (!sec->secure_boot)
  return 0;

 if (!info->secure_section_exist || section_info->ignore)
  return 0;

 if (!chip_blacklist) {
  rtw89_warn(rtwdev, "chip no blacklist for secure firmware\n");
  return -ENOENT;
 }

 byte_idx = section_content->blacklist.bit_in_chip_list >> 3;
 bit_mask = BIT(section_content->blacklist.bit_in_chip_list & 0x7);

 if (section_content->blacklist.ver > chip_blacklist->ver) {
  rtw89_warn(rtwdev, "chip blacklist out of date (%u, %u)\n",
      section_content->blacklist.ver, chip_blacklist->ver);
  return -EINVAL;
 }

 if (chip_blacklist->list[byte_idx] & bit_mask) {
  rtw89_warn(rtwdev, "firmware %u in chip blacklist\n",
      section_content->blacklist.ver);
  return -EPERM;
 }

 return 0;
}

static int __parse_security_section(struct rtw89_dev *rtwdev,
        struct rtw89_fw_bin_info *info,
        struct rtw89_fw_hdr_section_info *section_info,
        const void *content,
        u32 *mssc_len)
{
 struct rtw89_fw_secure *sec = &rtwdev->fw.sec;
 int ret;

 if ((section_info->mssc & FORMATTED_MSSC_MASK) == FORMATTED_MSSC) {
  ret = __parse_formatted_mssc(rtwdev, info, section_info,
          content, mssc_len);
  if (ret)
   return -EINVAL;
 } else {
  *mssc_len = section_info->mssc * FWDL_SECURITY_SIGLEN;
  if (info->dsp_checksum)
   *mssc_len += section_info->mssc * FWDL_SECURITY_CHKSUM_LEN;

  if (sec->secure_boot) {
   if (sec->mss_idx >= section_info->mssc) {
    rtw89_err(rtwdev, "unexpected MSS %d >= %d\n",
       sec->mss_idx, section_info->mssc);
    return -EFAULT;
   }
   section_info->key_addr = content + section_info->len +
       sec->mss_idx * FWDL_SECURITY_SIGLEN;
   section_info->key_len = FWDL_SECURITY_SIGLEN;
  }

  info->secure_section_exist = true;
 }

 ret = __check_secure_blacklist(rtwdev, info, section_info, content);
 WARN_ONCE(ret, "Current firmware in blacklist. Please update firmware.\n");

 return 0;
}

static int rtw89_fw_hdr_parser_v1(struct rtw89_dev *rtwdev, const u8 *fw, u32 len,
      struct rtw89_fw_bin_info *info)
{
 const struct rtw89_fw_hdr_v1 *fw_hdr = (const struct rtw89_fw_hdr_v1 *)fw;
 struct rtw89_fw_hdr_section_info *section_info;
 const struct rtw89_fw_dynhdr_hdr *fwdynhdr;
 const struct rtw89_fw_hdr_section_v1 *section;
 const u8 *fw_end = fw + len;
 const u8 *bin;
 u32 base_hdr_len;
 u32 mssc_len;
 int ret;
 u32 i;

 info->section_num = le32_get_bits(fw_hdr->w6, FW_HDR_V1_W6_SEC_NUM);
 info->dsp_checksum = le32_get_bits(fw_hdr->w6, FW_HDR_V1_W6_DSP_CHKSUM);
 base_hdr_len = struct_size(fw_hdr, sections, info->section_num);
 info->dynamic_hdr_en = le32_get_bits(fw_hdr->w7, FW_HDR_V1_W7_DYN_HDR);
 info->idmem_share_mode = le32_get_bits(fw_hdr->w7, FW_HDR_V1_W7_IDMEM_SHARE_MODE);

 if (info->dynamic_hdr_en) {
  info->hdr_len = le32_get_bits(fw_hdr->w5, FW_HDR_V1_W5_HDR_SIZE);
  info->dynamic_hdr_len = info->hdr_len - base_hdr_len;
  fwdynhdr = (const struct rtw89_fw_dynhdr_hdr *)(fw + base_hdr_len);
  if (le32_to_cpu(fwdynhdr->hdr_len) != info->dynamic_hdr_len) {
   rtw89_err(rtwdev, "[ERR]invalid fw dynamic header len\n");
   return -EINVAL;
  }
 } else {
  info->hdr_len = base_hdr_len;
  info->dynamic_hdr_len = 0;
 }

 bin = fw + info->hdr_len;

 /* jump to section header */
 section_info = info->section_info;
 for (i = 0; i < info->section_num; i++) {
  section = &fw_hdr->sections[i];

  section_info->type =
   le32_get_bits(section->w1, FWSECTION_HDR_V1_W1_SECTIONTYPE);
  section_info->len =
   le32_get_bits(section->w1, FWSECTION_HDR_V1_W1_SEC_SIZE);
  if (le32_get_bits(section->w1, FWSECTION_HDR_V1_W1_CHECKSUM))
   section_info->len += FWDL_SECTION_CHKSUM_LEN;
  section_info->redl = le32_get_bits(section->w1, FWSECTION_HDR_V1_W1_REDL);
  section_info->dladdr =
   le32_get_bits(section->w0, FWSECTION_HDR_V1_W0_DL_ADDR);
  section_info->addr = bin;

  if (section_info->type == FWDL_SECURITY_SECTION_TYPE) {
   section_info->mssc =
    le32_get_bits(section->w2, FWSECTION_HDR_V1_W2_MSSC);

   ret = __parse_security_section(rtwdev, info, section_info,
             bin, &mssc_len);
   if (ret)
    return ret;
  } else {
   section_info->mssc = 0;
   mssc_len = 0;
  }

  rtw89_debug(rtwdev, RTW89_DBG_FW,
       "section[%d] type=%d len=0x%-6x mssc=%d mssc_len=%d addr=%tx\n",
       i, section_info->type, section_info->len,
       section_info->mssc, mssc_len, bin - fw);
  rtw89_debug(rtwdev, RTW89_DBG_FW,
       " ignore=%d key_addr=%p (0x%tx) key_len=%d key_idx=%d\n",
       section_info->ignore, section_info->key_addr,
       section_info->key_addr ?
       section_info->key_addr - section_info->addr : 0,
       section_info->key_len, section_info->key_idx);

  bin += section_info->len + mssc_len;
  section_info++;
 }

 if (fw_end != bin) {
  rtw89_err(rtwdev, "[ERR]fw bin size\n");
  return -EINVAL;
 }

 if (!info->secure_section_exist)
  rtw89_warn(rtwdev, "no firmware secure section\n");

 return 0;
}

static int rtw89_fw_hdr_parser(struct rtw89_dev *rtwdev,
          const struct rtw89_fw_suit *fw_suit,
          struct rtw89_fw_bin_info *info)
{
 const u8 *fw = fw_suit->data;
 u32 len = fw_suit->size;

 if (!fw || !len) {
  rtw89_err(rtwdev, "fw type %d isn't recognized\n", fw_suit->type);
  return -ENOENT;
 }

 switch (fw_suit->hdr_ver) {
 case 0:
  return rtw89_fw_hdr_parser_v0(rtwdev, fw, len, info);
 case 1:
  return rtw89_fw_hdr_parser_v1(rtwdev, fw, len, info);
 default:
  return -ENOENT;
 }
}

static
const struct rtw89_mfw_hdr *rtw89_mfw_get_hdr_ptr(struct rtw89_dev *rtwdev,
        const struct firmware *firmware)
{
 const struct rtw89_mfw_hdr *mfw_hdr;

 if (sizeof(*mfw_hdr) > firmware->size)
  return NULL;

 mfw_hdr = (const struct rtw89_mfw_hdr *)&firmware->data[0];

 if (mfw_hdr->sig != RTW89_MFW_SIG)
  return NULL;

 return mfw_hdr;
}

static int rtw89_mfw_validate_hdr(struct rtw89_dev *rtwdev,
      const struct firmware *firmware,
      const struct rtw89_mfw_hdr *mfw_hdr)
{
 const void *mfw = firmware->data;
 u32 mfw_len = firmware->size;
 u8 fw_nr = mfw_hdr->fw_nr;
 const void *ptr;

 if (fw_nr == 0) {
  rtw89_err(rtwdev, "mfw header has no fw entry\n");
  return -ENOENT;
 }

 ptr = &mfw_hdr->info[fw_nr];

 if (ptr > mfw + mfw_len) {
  rtw89_err(rtwdev, "mfw header out of address\n");
  return -EFAULT;
 }

 return 0;
}

static
int rtw89_mfw_recognize(struct rtw89_dev *rtwdev, enum rtw89_fw_type type,
   struct rtw89_fw_suit *fw_suit, bool nowarn)
{
 struct rtw89_fw_info *fw_info = &rtwdev->fw;
 const struct firmware *firmware = fw_info->req.firmware;
 const struct rtw89_mfw_info *mfw_info = NULL, *tmp;
 const struct rtw89_mfw_hdr *mfw_hdr;
 const u8 *mfw = firmware->data;
 u32 mfw_len = firmware->size;
 int ret;
 int i;

 mfw_hdr = rtw89_mfw_get_hdr_ptr(rtwdev, firmware);
 if (!mfw_hdr) {
  rtw89_debug(rtwdev, RTW89_DBG_FW, "use legacy firmware\n");
  /* legacy firmware support normal type only */
  if (type != RTW89_FW_NORMAL)
   return -EINVAL;
  fw_suit->data = mfw;
  fw_suit->size = mfw_len;
  return 0;
 }

 ret = rtw89_mfw_validate_hdr(rtwdev, firmware, mfw_hdr);
 if (ret)
  return ret;

 for (i = 0; i < mfw_hdr->fw_nr; i++) {
  tmp = &mfw_hdr->info[i];
  if (tmp->type != type)
   continue;

  if (type == RTW89_FW_LOGFMT) {
   mfw_info = tmp;
   goto found;
  }

  /* Version order of WiFi firmware in firmware file are not in order,
 * pass all firmware to find the equal or less but closest version.
 */

  if (tmp->cv <= rtwdev->hal.cv && !tmp->mp) {
   if (!mfw_info || mfw_info->cv < tmp->cv)
    mfw_info = tmp;
  }
 }

 if (mfw_info)
  goto found;

 if (!nowarn)
  rtw89_err(rtwdev, "no suitable firmware found\n");
 return -ENOENT;

found:
 fw_suit->data = mfw + le32_to_cpu(mfw_info->shift);
 fw_suit->size = le32_to_cpu(mfw_info->size);

 if (fw_suit->data + fw_suit->size > mfw + mfw_len) {
  rtw89_err(rtwdev, "fw_suit %d out of address\n", type);
  return -EFAULT;
 }

 return 0;
}

static u32 rtw89_mfw_get_size(struct rtw89_dev *rtwdev)
{
 struct rtw89_fw_info *fw_info = &rtwdev->fw;
 const struct firmware *firmware = fw_info->req.firmware;
 const struct rtw89_mfw_info *mfw_info;
 const struct rtw89_mfw_hdr *mfw_hdr;
 u32 size;
 int ret;

 mfw_hdr = rtw89_mfw_get_hdr_ptr(rtwdev, firmware);
 if (!mfw_hdr) {
  rtw89_warn(rtwdev, "not mfw format\n");
  return 0;
 }

 ret = rtw89_mfw_validate_hdr(rtwdev, firmware, mfw_hdr);
 if (ret)
  return ret;

 mfw_info = &mfw_hdr->info[mfw_hdr->fw_nr - 1];
 size = le32_to_cpu(mfw_info->shift) + le32_to_cpu(mfw_info->size);

 return size;
}

static void rtw89_fw_update_ver_v0(struct rtw89_dev *rtwdev,
       struct rtw89_fw_suit *fw_suit,
       const struct rtw89_fw_hdr *hdr)
{
 fw_suit->major_ver = le32_get_bits(hdr->w1, FW_HDR_W1_MAJOR_VERSION);
 fw_suit->minor_ver = le32_get_bits(hdr->w1, FW_HDR_W1_MINOR_VERSION);
 fw_suit->sub_ver = le32_get_bits(hdr->w1, FW_HDR_W1_SUBVERSION);
 fw_suit->sub_idex = le32_get_bits(hdr->w1, FW_HDR_W1_SUBINDEX);
 fw_suit->commitid = le32_get_bits(hdr->w2, FW_HDR_W2_COMMITID);
 fw_suit->build_year = le32_get_bits(hdr->w5, FW_HDR_W5_YEAR);
 fw_suit->build_mon = le32_get_bits(hdr->w4, FW_HDR_W4_MONTH);
 fw_suit->build_date = le32_get_bits(hdr->w4, FW_HDR_W4_DATE);
 fw_suit->build_hour = le32_get_bits(hdr->w4, FW_HDR_W4_HOUR);
 fw_suit->build_min = le32_get_bits(hdr->w4, FW_HDR_W4_MIN);
 fw_suit->cmd_ver = le32_get_bits(hdr->w7, FW_HDR_W7_CMD_VERSERION);
}

static void rtw89_fw_update_ver_v1(struct rtw89_dev *rtwdev,
       struct rtw89_fw_suit *fw_suit,
       const struct rtw89_fw_hdr_v1 *hdr)
{
 fw_suit->major_ver = le32_get_bits(hdr->w1, FW_HDR_V1_W1_MAJOR_VERSION);
 fw_suit->minor_ver = le32_get_bits(hdr->w1, FW_HDR_V1_W1_MINOR_VERSION);
 fw_suit->sub_ver = le32_get_bits(hdr->w1, FW_HDR_V1_W1_SUBVERSION);
 fw_suit->sub_idex = le32_get_bits(hdr->w1, FW_HDR_V1_W1_SUBINDEX);
 fw_suit->commitid = le32_get_bits(hdr->w2, FW_HDR_V1_W2_COMMITID);
 fw_suit->build_year = le32_get_bits(hdr->w5, FW_HDR_V1_W5_YEAR);
 fw_suit->build_mon = le32_get_bits(hdr->w4, FW_HDR_V1_W4_MONTH);
 fw_suit->build_date = le32_get_bits(hdr->w4, FW_HDR_V1_W4_DATE);
 fw_suit->build_hour = le32_get_bits(hdr->w4, FW_HDR_V1_W4_HOUR);
 fw_suit->build_min = le32_get_bits(hdr->w4, FW_HDR_V1_W4_MIN);
 fw_suit->cmd_ver = le32_get_bits(hdr->w7, FW_HDR_V1_W3_CMD_VERSERION);
}

static int rtw89_fw_update_ver(struct rtw89_dev *rtwdev,
          enum rtw89_fw_type type,
          struct rtw89_fw_suit *fw_suit)
{
 const struct rtw89_fw_hdr *v0 = (const struct rtw89_fw_hdr *)fw_suit->data;
 const struct rtw89_fw_hdr_v1 *v1 = (const struct rtw89_fw_hdr_v1 *)fw_suit->data;

 if (type == RTW89_FW_LOGFMT)
  return 0;

 fw_suit->type = type;
 fw_suit->hdr_ver = le32_get_bits(v0->w3, FW_HDR_W3_HDR_VER);

 switch (fw_suit->hdr_ver) {
 case 0:
  rtw89_fw_update_ver_v0(rtwdev, fw_suit, v0);
  break;
 case 1:
  rtw89_fw_update_ver_v1(rtwdev, fw_suit, v1);
  break;
 default:
  rtw89_err(rtwdev, "Unknown firmware header version %u\n",
     fw_suit->hdr_ver);
  return -ENOENT;
 }

 rtw89_info(rtwdev,
     "Firmware version %u.%u.%u.%u (%08x), cmd version %u, type %u\n",
     fw_suit->major_ver, fw_suit->minor_ver, fw_suit->sub_ver,
     fw_suit->sub_idex, fw_suit->commitid, fw_suit->cmd_ver, type);

 return 0;
}

static
int __rtw89_fw_recognize(struct rtw89_dev *rtwdev, enum rtw89_fw_type type,
    bool nowarn)
{
 struct rtw89_fw_suit *fw_suit = rtw89_fw_suit_get(rtwdev, type);
 int ret;

 ret = rtw89_mfw_recognize(rtwdev, type, fw_suit, nowarn);
 if (ret)
  return ret;

 return rtw89_fw_update_ver(rtwdev, type, fw_suit);
}

static
int __rtw89_fw_recognize_from_elm(struct rtw89_dev *rtwdev,
      const struct rtw89_fw_element_hdr *elm,
      const union rtw89_fw_element_arg arg)
{
 enum rtw89_fw_type type = arg.fw_type;
 struct rtw89_hal *hal = &rtwdev->hal;
 struct rtw89_fw_suit *fw_suit;

 /* Version of BB MCU is in decreasing order in firmware file, so take
 * first equal or less version, which is equal or less but closest version.
 */

 if (hal->cv < elm->u.bbmcu.cv)
  return 1; /* ignore this element */

 fw_suit = rtw89_fw_suit_get(rtwdev, type);
 if (fw_suit->data)
  return 1; /* ignore this element (a firmware is taken already) */

 fw_suit->data = elm->u.bbmcu.contents;
 fw_suit->size = le32_to_cpu(elm->size);

 return rtw89_fw_update_ver(rtwdev, type, fw_suit);
}

#define __DEF_FW_FEAT_COND(__cond, __op) \
static bool __fw_feat_cond_ ## __cond(u32 suit_ver_code, u32 comp_ver_code) \
{ \
 return suit_ver_code __op comp_ver_code; \
}

__DEF_FW_FEAT_COND(ge, >=); /* greater or equal */
__DEF_FW_FEAT_COND(le, <=); /* less or equal */
__DEF_FW_FEAT_COND(lt, <); /* less than */

struct __fw_feat_cfg {
 enum rtw89_core_chip_id chip_id;
 enum rtw89_fw_feature feature;
 u32 ver_code;
 bool (*cond)(u32 suit_ver_code, u32 comp_ver_code);
};

#define __CFG_FW_FEAT(_chip, _cond, _maj, _min, _sub, _idx, _feat) \
 { \
  .chip_id = _chip, \
  .feature = RTW89_FW_FEATURE_ ## _feat, \
  .ver_code = RTW89_FW_VER_CODE(_maj, _min, _sub, _idx), \
  .cond = __fw_feat_cond_ ## _cond, \
 }

static const struct __fw_feat_cfg fw_feat_tbl[] = {
 __CFG_FW_FEAT(RTL8851B, ge, 0, 29, 37, 1, TX_WAKE),
 __CFG_FW_FEAT(RTL8851B, ge, 0, 29, 37, 1, SCAN_OFFLOAD),
 __CFG_FW_FEAT(RTL8851B, ge, 0, 29, 41, 0, CRASH_TRIGGER_TYPE_0),
 __CFG_FW_FEAT(RTL8852A, le, 0, 13, 29, 0, OLD_HT_RA_FORMAT),
 __CFG_FW_FEAT(RTL8852A, ge, 0, 13, 35, 0, SCAN_OFFLOAD),
 __CFG_FW_FEAT(RTL8852A, ge, 0, 13, 35, 0, TX_WAKE),
 __CFG_FW_FEAT(RTL8852A, ge, 0, 13, 36, 0, CRASH_TRIGGER_TYPE_0),
 __CFG_FW_FEAT(RTL8852A, lt, 0, 13, 37, 0, NO_WOW_CPU_IO_RX),
 __CFG_FW_FEAT(RTL8852A, lt, 0, 13, 38, 0, NO_PACKET_DROP),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 26, 0, NO_LPS_PG),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 26, 0, TX_WAKE),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 29, 0, CRASH_TRIGGER_TYPE_0),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 29, 0, SCAN_OFFLOAD),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 29, 7, BEACON_FILTER),
 __CFG_FW_FEAT(RTL8852B, lt, 0, 29, 30, 0, NO_WOW_CPU_IO_RX),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 127, 0, LPS_DACK_BY_C2H_REG),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 128, 0, CRASH_TRIGGER_TYPE_1),
 __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 128, 0, SCAN_OFFLOAD_EXTRA_OP),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 74, 0, NO_LPS_PG),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 74, 0, TX_WAKE),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 90, 0, CRASH_TRIGGER_TYPE_0),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 91, 0, SCAN_OFFLOAD),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 110, 0, BEACON_FILTER),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 127, 0, SCAN_OFFLOAD_EXTRA_OP),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 127, 0, LPS_DACK_BY_C2H_REG),
 __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 127, 0, CRASH_TRIGGER_TYPE_1),
 __CFG_FW_FEAT(RTL8852C, le, 0, 27, 33, 0, NO_DEEP_PS),
 __CFG_FW_FEAT(RTL8852C, ge, 0, 0, 0, 0, RFK_NTFY_MCC_V0),
 __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 34, 0, TX_WAKE),
 __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 36, 0, SCAN_OFFLOAD),
 __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 40, 0, CRASH_TRIGGER_TYPE_0),
 __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 56, 10, BEACON_FILTER),
 __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 80, 0, WOW_REASON_V1),
 __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 128, 0, BEACON_LOSS_COUNT_V1),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 30, 0, CRASH_TRIGGER_TYPE_0),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 11, 0, MACID_PAUSE_SLEEP),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 35, 0, SCAN_OFFLOAD),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 21, 0, SCAN_OFFLOAD_BE_V0),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 12, 0, BEACON_FILTER),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 22, 0, WOW_REASON_V1),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 28, 0, RFK_IQK_V0),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 31, 0, RFK_PRE_NOTIFY_V0),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 31, 0, LPS_CH_INFO),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 42, 0, RFK_RXDCK_V0),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 46, 0, NOTIFY_AP_INFO),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 47, 0, CH_INFO_BE_V0),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 49, 0, RFK_PRE_NOTIFY_V1),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 51, 0, NO_PHYCAP_P1),
 __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 64, 0, NO_POWER_DIFFERENCE),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 71, 0, BEACON_LOSS_COUNT_V1),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 76, 0, LPS_DACK_BY_C2H_REG),
 __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 79, 0, CRASH_TRIGGER_TYPE_1),
};

static void rtw89_fw_iterate_feature_cfg(struct rtw89_fw_info *fw,
      const struct rtw89_chip_info *chip,
      u32 ver_code)
{
 int i;

 for (i = 0; i < ARRAY_SIZE(fw_feat_tbl); i++) {
  const struct __fw_feat_cfg *ent = &fw_feat_tbl[i];

  if (chip->chip_id != ent->chip_id)
   continue;

  if (ent->cond(ver_code, ent->ver_code))
   RTW89_SET_FW_FEATURE(ent->feature, fw);
 }
}

static void rtw89_fw_recognize_features(struct rtw89_dev *rtwdev)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;
 const struct rtw89_fw_suit *fw_suit;
 u32 suit_ver_code;

 fw_suit = rtw89_fw_suit_get(rtwdev, RTW89_FW_NORMAL);
 suit_ver_code = RTW89_FW_SUIT_VER_CODE(fw_suit);

 rtw89_fw_iterate_feature_cfg(&rtwdev->fw, chip, suit_ver_code);
}

const struct firmware *
rtw89_early_fw_feature_recognize(struct device *device,
     const struct rtw89_chip_info *chip,
     struct rtw89_fw_info *early_fw,
     int *used_fw_format)
{
 const struct firmware *firmware;
 char fw_name[64];
 int fw_format;
 u32 ver_code;
 int ret;

 for (fw_format = chip->fw_format_max; fw_format >= 0; fw_format--) {
  rtw89_fw_get_filename(fw_name, sizeof(fw_name),
          chip->fw_basename, fw_format);

  ret = request_firmware(&firmware, fw_name, device);
  if (!ret) {
   dev_info(device, "loaded firmware %s\n", fw_name);
   *used_fw_format = fw_format;
   break;
  }
 }

 if (ret) {
  dev_err(device, "failed to early request firmware: %d\n", ret);
  return NULL;
 }

 ver_code = rtw89_compat_fw_hdr_ver_code(firmware->data);

 if (!ver_code)
  goto out;

 rtw89_fw_iterate_feature_cfg(early_fw, chip, ver_code);

out:
 return firmware;
}

static int rtw89_fw_validate_ver_required(struct rtw89_dev *rtwdev)
{
 const struct rtw89_chip_variant *variant = rtwdev->variant;
 const struct rtw89_fw_suit *fw_suit;
 u32 suit_ver_code;

 if (!variant)
  return 0;

 fw_suit = rtw89_fw_suit_get(rtwdev, RTW89_FW_NORMAL);
 suit_ver_code = RTW89_FW_SUIT_VER_CODE(fw_suit);

 if (variant->fw_min_ver_code > suit_ver_code) {
  rtw89_err(rtwdev, "minimum required firmware version is 0x%x\n",
     variant->fw_min_ver_code);
  return -ENOENT;
 }

 return 0;
}

int rtw89_fw_recognize(struct rtw89_dev *rtwdev)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;
 int ret;

 if (chip->try_ce_fw) {
  ret = __rtw89_fw_recognize(rtwdev, RTW89_FW_NORMAL_CE, true);
  if (!ret)
   goto normal_done;
 }

 ret = __rtw89_fw_recognize(rtwdev, RTW89_FW_NORMAL, false);
 if (ret)
  return ret;

normal_done:
 ret = rtw89_fw_validate_ver_required(rtwdev);
 if (ret)
  return ret;

 /* It still works if wowlan firmware isn't existing. */
 __rtw89_fw_recognize(rtwdev, RTW89_FW_WOWLAN, false);

 /* It still works if log format file isn't existing. */
 __rtw89_fw_recognize(rtwdev, RTW89_FW_LOGFMT, true);

 rtw89_fw_recognize_features(rtwdev);

 rtw89_coex_recognize_ver(rtwdev);

 return 0;
}

static
int rtw89_build_phy_tbl_from_elm(struct rtw89_dev *rtwdev,
     const struct rtw89_fw_element_hdr *elm,
     const union rtw89_fw_element_arg arg)
{
 struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info;
 struct rtw89_phy_table *tbl;
 struct rtw89_reg2_def *regs;
 enum rtw89_rf_path rf_path;
 u32 n_regs, i;
 u8 idx;

 tbl = kzalloc(sizeof(*tbl), GFP_KERNEL);
 if (!tbl)
  return -ENOMEM;

 switch (le32_to_cpu(elm->id)) {
 case RTW89_FW_ELEMENT_ID_BB_REG:
  elm_info->bb_tbl = tbl;
  break;
 case RTW89_FW_ELEMENT_ID_BB_GAIN:
  elm_info->bb_gain = tbl;
  break;
 case RTW89_FW_ELEMENT_ID_RADIO_A:
 case RTW89_FW_ELEMENT_ID_RADIO_B:
 case RTW89_FW_ELEMENT_ID_RADIO_C:
 case RTW89_FW_ELEMENT_ID_RADIO_D:
  rf_path = arg.rf_path;
  idx = elm->u.reg2.idx;

  elm_info->rf_radio[idx] = tbl;
  tbl->rf_path = rf_path;
  tbl->config = rtw89_phy_config_rf_reg_v1;
  break;
 case RTW89_FW_ELEMENT_ID_RF_NCTL:
  elm_info->rf_nctl = tbl;
  break;
 default:
  kfree(tbl);
  return -ENOENT;
 }

 n_regs = le32_to_cpu(elm->size) / sizeof(tbl->regs[0]);
 regs = kcalloc(n_regs, sizeof(*regs), GFP_KERNEL);
 if (!regs)
  goto out;

 for (i = 0; i < n_regs; i++) {
  regs[i].addr = le32_to_cpu(elm->u.reg2.regs[i].addr);
  regs[i].data = le32_to_cpu(elm->u.reg2.regs[i].data);
 }

 tbl->n_regs = n_regs;
 tbl->regs = regs;

 return 0;

out:
 kfree(tbl);
 return -ENOMEM;
}

static
int rtw89_fw_recognize_txpwr_from_elm(struct rtw89_dev *rtwdev,
          const struct rtw89_fw_element_hdr *elm,
          const union rtw89_fw_element_arg arg)
{
 const struct __rtw89_fw_txpwr_element *txpwr_elm = &elm->u.txpwr;
 const unsigned long offset = arg.offset;
 struct rtw89_efuse *efuse = &rtwdev->efuse;
 struct rtw89_txpwr_conf *conf;

 if (!rtwdev->rfe_data) {
  rtwdev->rfe_data = kzalloc(sizeof(*rtwdev->rfe_data), GFP_KERNEL);
  if (!rtwdev->rfe_data)
   return -ENOMEM;
 }

 conf = (void *)rtwdev->rfe_data + offset;

 /* if multiple matched, take the last eventually */
 if (txpwr_elm->rfe_type == efuse->rfe_type)
  goto setup;

 /* without one is matched, accept default */
 if (txpwr_elm->rfe_type == RTW89_TXPWR_CONF_DFLT_RFE_TYPE &&
     (!rtw89_txpwr_conf_valid(conf) ||
      conf->rfe_type == RTW89_TXPWR_CONF_DFLT_RFE_TYPE))
  goto setup;

 rtw89_debug(rtwdev, RTW89_DBG_FW, "skip txpwr element ID %u RFE %u\n",
      elm->id, txpwr_elm->rfe_type);
 return 0;

setup:
 rtw89_debug(rtwdev, RTW89_DBG_FW, "take txpwr element ID %u RFE %u\n",
      elm->id, txpwr_elm->rfe_type);

 conf->rfe_type = txpwr_elm->rfe_type;
 conf->ent_sz = txpwr_elm->ent_sz;
 conf->num_ents = le32_to_cpu(txpwr_elm->num_ents);
 conf->data = txpwr_elm->content;
 return 0;
}

static
int rtw89_build_txpwr_trk_tbl_from_elm(struct rtw89_dev *rtwdev,
           const struct rtw89_fw_element_hdr *elm,
           const union rtw89_fw_element_arg arg)
{
 struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info;
 const struct rtw89_chip_info *chip = rtwdev->chip;
 u32 needed_bitmap = 0;
 u32 offset = 0;
 int subband;
 u32 bitmap;
 int type;

 if (chip->support_bands & BIT(NL80211_BAND_6GHZ))
  needed_bitmap |= RTW89_DEFAULT_NEEDED_FW_TXPWR_TRK_6GHZ;
 if (chip->support_bands & BIT(NL80211_BAND_5GHZ))
  needed_bitmap |= RTW89_DEFAULT_NEEDED_FW_TXPWR_TRK_5GHZ;
 if (chip->support_bands & BIT(NL80211_BAND_2GHZ))
  needed_bitmap |= RTW89_DEFAULT_NEEDED_FW_TXPWR_TRK_2GHZ;

 bitmap = le32_to_cpu(elm->u.txpwr_trk.bitmap);

 if ((bitmap & needed_bitmap) != needed_bitmap) {
  rtw89_warn(rtwdev, "needed txpwr trk bitmap %08x but %08x\n",
      needed_bitmap, bitmap);
  return -ENOENT;
 }

 elm_info->txpwr_trk = kzalloc(sizeof(*elm_info->txpwr_trk), GFP_KERNEL);
 if (!elm_info->txpwr_trk)
  return -ENOMEM;

 for (type = 0; bitmap; type++, bitmap >>= 1) {
  if (!(bitmap & BIT(0)))
   continue;

  if (type >= __RTW89_FW_TXPWR_TRK_TYPE_6GHZ_START &&
      type <= __RTW89_FW_TXPWR_TRK_TYPE_6GHZ_MAX)
   subband = 4;
  else if (type >= __RTW89_FW_TXPWR_TRK_TYPE_5GHZ_START &&
    type <= __RTW89_FW_TXPWR_TRK_TYPE_5GHZ_MAX)
   subband = 3;
  else if (type >= __RTW89_FW_TXPWR_TRK_TYPE_2GHZ_START &&
    type <= __RTW89_FW_TXPWR_TRK_TYPE_2GHZ_MAX)
   subband = 1;
  else
   break;

  elm_info->txpwr_trk->delta[type] = &elm->u.txpwr_trk.contents[offset];

  offset += subband;
  if (offset * DELTA_SWINGIDX_SIZE > le32_to_cpu(elm->size))
   goto err;
 }

 return 0;

err:
 rtw89_warn(rtwdev, "unexpected txpwr trk offset %d over size %d\n",
     offset, le32_to_cpu(elm->size));
 kfree(elm_info->txpwr_trk);
 elm_info->txpwr_trk = NULL;

 return -EFAULT;
}

static
int rtw89_build_rfk_log_fmt_from_elm(struct rtw89_dev *rtwdev,
         const struct rtw89_fw_element_hdr *elm,
         const union rtw89_fw_element_arg arg)
{
 struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info;
 u8 rfk_id;

 if (elm_info->rfk_log_fmt)
  goto allocated;

 elm_info->rfk_log_fmt = kzalloc(sizeof(*elm_info->rfk_log_fmt), GFP_KERNEL);
 if (!elm_info->rfk_log_fmt)
  return 1; /* this is an optional element, so just ignore this */

allocated:
 rfk_id = elm->u.rfk_log_fmt.rfk_id;
 if (rfk_id >= RTW89_PHY_C2H_RFK_LOG_FUNC_NUM)
  return 1;

 elm_info->rfk_log_fmt->elm[rfk_id] = elm;

 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,
  .fmap = cpu_to_le32(0x0),
 };
 u8 valid_size = min_t(u8, sizeof(entry), cursor_size);
 unsigned int i;
 u32 fmap;

 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;

 BUILD_BUG_ON(sizeof(fmap) != sizeof(entry.fmap));
 BUILD_BUG_ON(sizeof(fmap) * 8 < NUM_OF_RTW89_REGD_FUNC);

 fmap = le32_to_cpu(entry.fmap);
 for (i = 0; i < NUM_OF_RTW89_REGD_FUNC; i++) {
  if (fmap & BIT(i))
   set_bit(i, regd->func_bitmap);
 }

 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},
 [RTW89_FW_ELEMENT_ID_BBMCU1] = {__rtw89_fw_recognize_from_elm,
     { .fw_type = RTW89_FW_BBMCU1 }, NULL},
 [RTW89_FW_ELEMENT_ID_BB_REG] = {rtw89_build_phy_tbl_from_elm, {}, "BB"},
 [RTW89_FW_ELEMENT_ID_BB_GAIN] = {rtw89_build_phy_tbl_from_elm, {}, NULL},
 [RTW89_FW_ELEMENT_ID_RADIO_A] = {rtw89_build_phy_tbl_from_elm,
      { .rf_path =  RF_PATH_A }, "radio A"},
 [RTW89_FW_ELEMENT_ID_RADIO_B] = {rtw89_build_phy_tbl_from_elm,
      { .rf_path =  RF_PATH_B }, NULL},
 [RTW89_FW_ELEMENT_ID_RADIO_C] = {rtw89_build_phy_tbl_from_elm,
      { .rf_path =  RF_PATH_C }, NULL},
 [RTW89_FW_ELEMENT_ID_RADIO_D] = {rtw89_build_phy_tbl_from_elm,
      { .rf_path =  RF_PATH_D }, NULL},
 [RTW89_FW_ELEMENT_ID_RF_NCTL] = {rtw89_build_phy_tbl_from_elm, {}, "NCTL"},
 [RTW89_FW_ELEMENT_ID_TXPWR_BYRATE] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, byrate.conf) }, "TXPWR",
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_LMT_2GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, lmt_2ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_LMT_5GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, lmt_5ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_LMT_6GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, lmt_6ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_2GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, da_lmt_2ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_5GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, da_lmt_5ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_6GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, da_lmt_6ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_2GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, lmt_ru_2ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_5GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, lmt_ru_5ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_6GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, lmt_ru_6ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_RU_2GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, da_lmt_ru_2ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_RU_5GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, da_lmt_ru_5ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_RU_6GHZ] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, da_lmt_ru_6ghz.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, tx_shape_lmt.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT_RU] = {
  rtw89_fw_recognize_txpwr_from_elm,
  { .offset = offsetof(struct rtw89_rfe_data, tx_shape_lmt_ru.conf) }, NULL,
 },
 [RTW89_FW_ELEMENT_ID_TXPWR_TRK] = {
  rtw89_build_txpwr_trk_tbl_from_elm, {}, "PWR_TRK",
 },
 [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)
{
 struct rtw89_fw_info *fw_info = &rtwdev->fw;
 const struct firmware *firmware = fw_info->req.firmware;
 const struct rtw89_chip_info *chip = rtwdev->chip;
 u32 unrecognized_elements = chip->needed_fw_elms;
 const struct rtw89_fw_element_handler *handler;
 const struct rtw89_fw_element_hdr *hdr;
 u32 elm_size;
 u32 elem_id;
 u32 offset;
 int ret;

 BUILD_BUG_ON(sizeof(chip->needed_fw_elms) * 8 < RTW89_FW_ELEMENT_ID_NUM);

 offset = rtw89_mfw_get_size(rtwdev);
 offset = ALIGN(offset, RTW89_FW_ELEMENT_ALIGN);
 if (offset == 0)
  return -EINVAL;

 while (offset + sizeof(*hdr) < firmware->size) {
  hdr = (const struct rtw89_fw_element_hdr *)(firmware->data + offset);

  elm_size = le32_to_cpu(hdr->size);
  if (offset + elm_size >= firmware->size) {
   rtw89_warn(rtwdev, "firmware element size exceeds\n");
   break;
  }

  elem_id = le32_to_cpu(hdr->id);
  if (elem_id >= ARRAY_SIZE(__fw_element_handlers))
   goto next;

  handler = &__fw_element_handlers[elem_id];
  if (!handler->fn)
   goto next;

  ret = handler->fn(rtwdev, hdr, handler->arg);
  if (ret == 1) /* ignore this element */
   goto next;
  if (ret)
   return ret;

  if (handler->name)
   rtw89_info(rtwdev, "Firmware element %s version: %4ph\n",
       handler->name, hdr->ver);

  unrecognized_elements &= ~BIT(elem_id);
next:
  offset += sizeof(*hdr) + elm_size;
  offset = ALIGN(offset, RTW89_FW_ELEMENT_ALIGN);
 }

 if (unrecognized_elements) {
  rtw89_err(rtwdev, "Firmware elements 0x%08x are unrecognized\n",
     unrecognized_elements);
  return -ENOENT;
 }

 return 0;
}

void rtw89_h2c_pkt_set_hdr(struct rtw89_dev *rtwdev, struct sk_buff *skb,
      u8 type, u8 cat, u8 class, u8 func,
      bool rack, bool dack, u32 len)
{
 struct fwcmd_hdr *hdr;

 hdr = (struct fwcmd_hdr *)skb_push(skb, 8);

 if (!(rtwdev->fw.h2c_seq % 4))
  rack = true;
 hdr->hdr0 = cpu_to_le32(FIELD_PREP(H2C_HDR_DEL_TYPE, type) |
    FIELD_PREP(H2C_HDR_CAT, cat) |
    FIELD_PREP(H2C_HDR_CLASS, class) |
    FIELD_PREP(H2C_HDR_FUNC, func) |
    FIELD_PREP(H2C_HDR_H2C_SEQ, rtwdev->fw.h2c_seq));

 hdr->hdr1 = cpu_to_le32(FIELD_PREP(H2C_HDR_TOTAL_LEN,
        len + H2C_HEADER_LEN) |
    (rack ? H2C_HDR_REC_ACK : 0) |
    (dack ? H2C_HDR_DONE_ACK : 0));

 rtwdev->fw.h2c_seq++;
}

static void rtw89_h2c_pkt_set_hdr_fwdl(struct rtw89_dev *rtwdev,
           struct sk_buff *skb,
           u8 type, u8 cat, u8 class, u8 func,
           u32 len)
{
 struct fwcmd_hdr *hdr;

 hdr = (struct fwcmd_hdr *)skb_push(skb, 8);

 hdr->hdr0 = cpu_to_le32(FIELD_PREP(H2C_HDR_DEL_TYPE, type) |
    FIELD_PREP(H2C_HDR_CAT, cat) |
    FIELD_PREP(H2C_HDR_CLASS, class) |
    FIELD_PREP(H2C_HDR_FUNC, func) |
    FIELD_PREP(H2C_HDR_H2C_SEQ, rtwdev->fw.h2c_seq));

 hdr->hdr1 = cpu_to_le32(FIELD_PREP(H2C_HDR_TOTAL_LEN,
        len + H2C_HEADER_LEN));
}

static u32 __rtw89_fw_download_tweak_hdr_v0(struct rtw89_dev *rtwdev,
         struct rtw89_fw_bin_info *info,
         struct rtw89_fw_hdr *fw_hdr)
{
 struct rtw89_fw_hdr_section_info *section_info;
 struct rtw89_fw_hdr_section *section;
 int i;

 le32p_replace_bits(&fw_hdr->w7, FWDL_SECTION_PER_PKT_LEN,
      FW_HDR_W7_PART_SIZE);

 for (i = 0; i < info->section_num; i++) {
  section_info = &info->section_info[i];

  if (!section_info->len_override)
   continue;

  section = &fw_hdr->sections[i];
  le32p_replace_bits(§ion->w1, section_info->len_override,
       FWSECTION_HDR_W1_SEC_SIZE);
 }

 return 0;
}

static u32 __rtw89_fw_download_tweak_hdr_v1(struct rtw89_dev *rtwdev,
         struct rtw89_fw_bin_info *info,
         struct rtw89_fw_hdr_v1 *fw_hdr)
{
 struct rtw89_fw_hdr_section_info *section_info;
 struct rtw89_fw_hdr_section_v1 *section;
 u8 dst_sec_idx = 0;
 u8 sec_idx;

 le32p_replace_bits(&fw_hdr->w7, FWDL_SECTION_PER_PKT_LEN,
      FW_HDR_V1_W7_PART_SIZE);

 for (sec_idx = 0; sec_idx < info->section_num; sec_idx++) {
  section_info = &info->section_info[sec_idx];
  section = &fw_hdr->sections[sec_idx];

  if (section_info->ignore)
   continue;

  if (dst_sec_idx != sec_idx)
   fw_hdr->sections[dst_sec_idx] = *section;

  dst_sec_idx++;
 }

 le32p_replace_bits(&fw_hdr->w6, dst_sec_idx, FW_HDR_V1_W6_SEC_NUM);

 return (info->section_num - dst_sec_idx) * sizeof(*section);
}

static int __rtw89_fw_download_hdr(struct rtw89_dev *rtwdev,
       const struct rtw89_fw_suit *fw_suit,
       struct rtw89_fw_bin_info *info)
{
 u32 len = info->hdr_len - info->dynamic_hdr_len;
 struct rtw89_fw_hdr_v1 *fw_hdr_v1;
 const u8 *fw = fw_suit->data;
 struct rtw89_fw_hdr *fw_hdr;
 struct sk_buff *skb;
 u32 truncated;
 u32 ret = 0;

 skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
 if (!skb) {
  rtw89_err(rtwdev, "failed to alloc skb for fw hdr dl\n");
  return -ENOMEM;
 }

 skb_put_data(skb, fw, len);

 switch (fw_suit->hdr_ver) {
 case 0:
  fw_hdr = (struct rtw89_fw_hdr *)skb->data;
  truncated = __rtw89_fw_download_tweak_hdr_v0(rtwdev, info, fw_hdr);
  break;
 case 1:
  fw_hdr_v1 = (struct rtw89_fw_hdr_v1 *)skb->data;
  truncated = __rtw89_fw_download_tweak_hdr_v1(rtwdev, info, fw_hdr_v1);
  break;
 default:
  ret = -EOPNOTSUPP;
  goto fail;
 }

 if (truncated) {
  len -= truncated;
  skb_trim(skb, len);
 }

 rtw89_h2c_pkt_set_hdr_fwdl(rtwdev, skb, FWCMD_TYPE_H2C,
       H2C_CAT_MAC, H2C_CL_MAC_FWDL,
       H2C_FUNC_MAC_FWHDR_DL, len);

 ret = rtw89_h2c_tx(rtwdev, skb, false);
 if (ret) {
  rtw89_err(rtwdev, "failed to send h2c\n");
  goto fail;
 }

 return 0;
fail:
 dev_kfree_skb_any(skb);

 return ret;
}

static int rtw89_fw_download_hdr(struct rtw89_dev *rtwdev,
     const struct rtw89_fw_suit *fw_suit,
     struct rtw89_fw_bin_info *info)
{
 const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def;
 int ret;

 ret = __rtw89_fw_download_hdr(rtwdev, fw_suit, info);
 if (ret) {
  rtw89_err(rtwdev, "[ERR]FW header download\n");
  return ret;
 }

 ret = mac->fwdl_check_path_ready(rtwdev, false);
 if (ret) {
  rtw89_err(rtwdev, "[ERR]FWDL path ready\n");
  return ret;
 }

 rtw89_write32(rtwdev, R_AX_HALT_H2C_CTRL, 0);
 rtw89_write32(rtwdev, R_AX_HALT_C2H_CTRL, 0);

 return 0;
}

static int __rtw89_fw_download_main(struct rtw89_dev *rtwdev,
        struct rtw89_fw_hdr_section_info *info)
{
 struct sk_buff *skb;
 const u8 *section = info->addr;
 u32 residue_len = info->len;
 bool copy_key = false;
 u32 pkt_len;
 int ret;

 if (info->ignore)
  return 0;

 if (info->len_override) {
  if (info->len_override > info->len)
   rtw89_warn(rtwdev, "override length %u larger than original %u\n",
       info->len_override, info->len);
  else
   residue_len = info->len_override;
 }

 if (info->key_addr && info->key_len) {
  if (residue_len > FWDL_SECTION_PER_PKT_LEN || info->len < info->key_len)
   rtw89_warn(rtwdev,
       "ignore to copy key data because of len %d, %d, %d, %d\n",
       info->len, FWDL_SECTION_PER_PKT_LEN,
       info->key_len, residue_len);
  else
   copy_key = true;
 }

 while (residue_len) {
  if (residue_len >= FWDL_SECTION_PER_PKT_LEN)
   pkt_len = FWDL_SECTION_PER_PKT_LEN;
  else
   pkt_len = residue_len;

  skb = rtw89_fw_h2c_alloc_skb_no_hdr(rtwdev, pkt_len);
  if (!skb) {
   rtw89_err(rtwdev, "failed to alloc skb for fw dl\n");
   return -ENOMEM;
  }
  skb_put_data(skb, section, pkt_len);

  if (copy_key)
   memcpy(skb->data + pkt_len - info->key_len,
          info->key_addr, info->key_len);

  ret = rtw89_h2c_tx(rtwdev, skb, true);
  if (ret) {
   rtw89_err(rtwdev, "failed to send h2c\n");
   goto fail;
  }

  section += pkt_len;
  residue_len -= pkt_len;
 }

 return 0;
fail:
 dev_kfree_skb_any(skb);

 return ret;
}

static enum rtw89_fwdl_check_type
rtw89_fw_get_fwdl_chk_type_from_suit(struct rtw89_dev *rtwdev,
         const struct rtw89_fw_suit *fw_suit)
{
 switch (fw_suit->type) {
 case RTW89_FW_BBMCU0:
  return RTW89_FWDL_CHECK_BB0_FWDL_DONE;
 case RTW89_FW_BBMCU1:
  return RTW89_FWDL_CHECK_BB1_FWDL_DONE;
 default:
  return RTW89_FWDL_CHECK_WCPU_FWDL_DONE;
 }
}

static int rtw89_fw_download_main(struct rtw89_dev *rtwdev,
      const struct rtw89_fw_suit *fw_suit,
      struct rtw89_fw_bin_info *info)
{
 struct rtw89_fw_hdr_section_info *section_info = info->section_info;
 const struct rtw89_chip_info *chip = rtwdev->chip;
 enum rtw89_fwdl_check_type chk_type;
 u8 section_num = info->section_num;
 int ret;

 while (section_num--) {
  ret = __rtw89_fw_download_main(rtwdev, section_info);
  if (ret)
   return ret;
  section_info++;
 }

 if (chip->chip_gen == RTW89_CHIP_AX)
  return 0;

 chk_type = rtw89_fw_get_fwdl_chk_type_from_suit(rtwdev, fw_suit);
 ret = rtw89_fw_check_rdy(rtwdev, chk_type);
 if (ret) {
  rtw89_warn(rtwdev, "failed to download firmware type %u\n",
      fw_suit->type);
  return ret;
 }

 return 0;
}

static void rtw89_fw_prog_cnt_dump(struct rtw89_dev *rtwdev)
{
 enum rtw89_chip_gen chip_gen = rtwdev->chip->chip_gen;
 u32 addr = R_AX_DBG_PORT_SEL;
 u32 val32;
 u16 index;

 if (chip_gen == RTW89_CHIP_BE) {
  addr = R_BE_WLCPU_PORT_PC;
  goto dump;
 }

 rtw89_write32(rtwdev, R_AX_DBG_CTRL,
        FIELD_PREP(B_AX_DBG_SEL0, FW_PROG_CNTR_DBG_SEL) |
        FIELD_PREP(B_AX_DBG_SEL1, FW_PROG_CNTR_DBG_SEL));
 rtw89_write32_mask(rtwdev, R_AX_SYS_STATUS1, B_AX_SEL_0XC0_MASK, MAC_DBG_SEL);

dump:
 for (index = 0; index < 15; index++) {
  val32 = rtw89_read32(rtwdev, addr);
  rtw89_err(rtwdev, "[ERR]fw PC = 0x%x\n", val32);
  fsleep(10);
 }
}

static void rtw89_fw_dl_fail_dump(struct rtw89_dev *rtwdev)
{
 u32 val32;

 val32 = rtw89_read32(rtwdev, R_AX_WCPU_FW_CTRL);
 rtw89_err(rtwdev, "[ERR]fwdl 0x1E0 = 0x%x\n", val32);

 val32 = rtw89_read32(rtwdev, R_AX_BOOT_DBG);
 rtw89_err(rtwdev, "[ERR]fwdl 0x83F0 = 0x%x\n", val32);

 rtw89_fw_prog_cnt_dump(rtwdev);
}

static int rtw89_fw_download_suit(struct rtw89_dev *rtwdev,
      struct rtw89_fw_suit *fw_suit)
{
 const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def;
 struct rtw89_fw_bin_info info = {};
 int ret;

 ret = rtw89_fw_hdr_parser(rtwdev, fw_suit, &info);
 if (ret) {
  rtw89_err(rtwdev, "parse fw header fail\n");
  return ret;
 }

 rtw89_fwdl_secure_idmem_share_mode(rtwdev, info.idmem_share_mode);

 if (rtwdev->chip->chip_id == RTL8922A &&
     (fw_suit->type == RTW89_FW_NORMAL || fw_suit->type == RTW89_FW_WOWLAN))
  rtw89_write32(rtwdev, R_BE_SECURE_BOOT_MALLOC_INFO, 0x20248000);

 ret = mac->fwdl_check_path_ready(rtwdev, true);
 if (ret) {
  rtw89_err(rtwdev, "[ERR]H2C path ready\n");
  return ret;
 }

 ret = rtw89_fw_download_hdr(rtwdev, fw_suit, &info);
 if (ret)
  return ret;

 ret = rtw89_fw_download_main(rtwdev, fw_suit, &info);
 if (ret)
  return ret;

 return 0;
}

static
int __rtw89_fw_download(struct rtw89_dev *rtwdev, enum rtw89_fw_type type,
   bool include_bb)
{
 const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def;
 struct rtw89_fw_info *fw_info = &rtwdev->fw;
 struct rtw89_fw_suit *fw_suit = rtw89_fw_suit_get(rtwdev, type);
 u8 bbmcu_nr = rtwdev->chip->bbmcu_nr;
 int ret;
 int i;

 mac->disable_cpu(rtwdev);
 ret = mac->fwdl_enable_wcpu(rtwdev, 0, true, include_bb);
 if (ret)
  return ret;

 ret = rtw89_fw_download_suit(rtwdev, fw_suit);
 if (ret)
  goto fwdl_err;

 for (i = 0; i < bbmcu_nr && include_bb; i++) {
  fw_suit = rtw89_fw_suit_get(rtwdev, RTW89_FW_BBMCU0 + i);

  ret = rtw89_fw_download_suit(rtwdev, fw_suit);
  if (ret)
   goto fwdl_err;
 }

 fw_info->h2c_seq = 0;
 fw_info->rec_seq = 0;
 fw_info->h2c_counter = 0;
 fw_info->c2h_counter = 0;
 rtwdev->mac.rpwm_seq_num = RPWM_SEQ_NUM_MAX;
 rtwdev->mac.cpwm_seq_num = CPWM_SEQ_NUM_MAX;

 mdelay(5);

 ret = rtw89_fw_check_rdy(rtwdev, RTW89_FWDL_CHECK_FREERTOS_DONE);
 if (ret) {
  rtw89_warn(rtwdev, "download firmware fail\n");
  goto fwdl_err;
 }

 return ret;

fwdl_err:
 rtw89_fw_dl_fail_dump(rtwdev);
 return ret;
}

int rtw89_fw_download(struct rtw89_dev *rtwdev, enum rtw89_fw_type type,
        bool include_bb)
{
 int retry;
 int ret;

 for (retry = 0; retry < 5; retry++) {
  ret = __rtw89_fw_download(rtwdev, type, include_bb);
  if (!ret)
   return 0;
 }

 return ret;
}

int rtw89_wait_firmware_completion(struct rtw89_dev *rtwdev)
{
 struct rtw89_fw_info *fw = &rtwdev->fw;

 wait_for_completion(&fw->req.completion);
 if (!fw->req.firmware)
  return -EINVAL;

 return 0;
}

static int rtw89_load_firmware_req(struct rtw89_dev *rtwdev,
       struct rtw89_fw_req_info *req,
       const char *fw_name, bool nowarn)
{
 int ret;

 if (req->firmware) {
  rtw89_debug(rtwdev, RTW89_DBG_FW,
       "full firmware has been early requested\n");
  complete_all(&req->completion);
  return 0;
 }

 if (nowarn)
  ret = firmware_request_nowarn(&req->firmware, fw_name, rtwdev->dev);
 else
  ret = request_firmware(&req->firmware, fw_name, rtwdev->dev);

 complete_all(&req->completion);

 return ret;
}

void rtw89_load_firmware_work(struct work_struct *work)
{
 struct rtw89_dev *rtwdev =
  container_of(work, struct rtw89_dev, load_firmware_work);
 const struct rtw89_chip_info *chip = rtwdev->chip;
 char fw_name[64];

 rtw89_fw_get_filename(fw_name, sizeof(fw_name),
         chip->fw_basename, rtwdev->fw.fw_format);

 rtw89_load_firmware_req(rtwdev, &rtwdev->fw.req, fw_name, false);
}

static void rtw89_free_phy_tbl_from_elm(struct rtw89_phy_table *tbl)
{
 if (!tbl)
  return;

 kfree(tbl->regs);
 kfree(tbl);
}

static void rtw89_unload_firmware_elements(struct rtw89_dev *rtwdev)
{
 struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info;
 int i;

 rtw89_free_phy_tbl_from_elm(elm_info->bb_tbl);
 rtw89_free_phy_tbl_from_elm(elm_info->bb_gain);
 for (i = 0; i < ARRAY_SIZE(elm_info->rf_radio); i++)
  rtw89_free_phy_tbl_from_elm(elm_info->rf_radio[i]);
 rtw89_free_phy_tbl_from_elm(elm_info->rf_nctl);

 kfree(elm_info->txpwr_trk);
 kfree(elm_info->rfk_log_fmt);
}

void rtw89_unload_firmware(struct rtw89_dev *rtwdev)
{
 struct rtw89_fw_info *fw = &rtwdev->fw;

 cancel_work_sync(&rtwdev->load_firmware_work);

 if (fw->req.firmware) {
  release_firmware(fw->req.firmware);

  /* assign NULL back in case rtw89_free_ieee80211_hw()
 * try to release the same one again.
 */

  fw->req.firmware = NULL;
 }

 kfree(fw->log.fmts);
 rtw89_unload_firmware_elements(rtwdev);
}

static u32 rtw89_fw_log_get_fmt_idx(struct rtw89_dev *rtwdev, u32 fmt_id)
{
 struct rtw89_fw_log *fw_log = &rtwdev->fw.log;
 u32 i;

 if (fmt_id > fw_log->last_fmt_id)
  return 0;

 for (i = 0; i < fw_log->fmt_count; i++) {
  if (le32_to_cpu(fw_log->fmt_ids[i]) == fmt_id)
   return i;
 }
 return 0;
}

static int rtw89_fw_log_create_fmts_dict(struct rtw89_dev *rtwdev)
{
 struct rtw89_fw_log *log = &rtwdev->fw.log;
 const struct rtw89_fw_logsuit_hdr *suit_hdr;
 struct rtw89_fw_suit *suit = &log->suit;
 const void *fmts_ptr, *fmts_end_ptr;
 u32 fmt_count;
 int i;

 suit_hdr = (const struct rtw89_fw_logsuit_hdr *)suit->data;
 fmt_count = le32_to_cpu(suit_hdr->count);
 log->fmt_ids = suit_hdr->ids;
 fmts_ptr = &suit_hdr->ids[fmt_count];
 fmts_end_ptr = suit->data + suit->size;
 log->fmts = kcalloc(fmt_count, sizeof(char *), GFP_KERNEL);
 if (!log->fmts)
  return -ENOMEM;

 for (i = 0; i < fmt_count; i++) {
  fmts_ptr = memchr_inv(fmts_ptr, 0, fmts_end_ptr - fmts_ptr);
  if (!fmts_ptr)
   break;

  (*log->fmts)[i] = fmts_ptr;
  log->last_fmt_id = le32_to_cpu(log->fmt_ids[i]);
  log->fmt_count++;
  fmts_ptr += strlen(fmts_ptr);
 }

 return 0;
}

int rtw89_fw_log_prepare(struct rtw89_dev *rtwdev)
{
 struct rtw89_fw_log *log = &rtwdev->fw.log;
 struct rtw89_fw_suit *suit = &log->suit;

 if (!suit || !suit->data) {
  rtw89_debug(rtwdev, RTW89_DBG_FW, "no log format file\n");
  return -EINVAL;
 }
 if (log->fmts)
  return 0;

 return rtw89_fw_log_create_fmts_dict(rtwdev);
}

static void rtw89_fw_log_dump_data(struct rtw89_dev *rtwdev,
       const struct rtw89_fw_c2h_log_fmt *log_fmt,
       u32 fmt_idx, u8 para_int, bool raw_data)
{
 const char *(*fmts)[] = rtwdev->fw.log.fmts;
 char str_buf[RTW89_C2H_FW_LOG_STR_BUF_SIZE];
 u32 args[RTW89_C2H_FW_LOG_MAX_PARA_NUM] = {0};
 int i;

 if (log_fmt->argc > RTW89_C2H_FW_LOG_MAX_PARA_NUM) {
  rtw89_warn(rtwdev, "C2H log: Arg count is unexpected %d\n",
      log_fmt->argc);
  return;
 }

 if (para_int)
  for (i = 0 ; i < log_fmt->argc; i++)
   args[i] = le32_to_cpu(log_fmt->u.argv[i]);

 if (raw_data) {
  if (para_int)
   snprintf(str_buf, RTW89_C2H_FW_LOG_STR_BUF_SIZE,
     "fw_enc(%d, %d, %d) %*ph", le32_to_cpu(log_fmt->fmt_id),
     para_int, log_fmt->argc, (int)sizeof(args), args);
  else
   snprintf(str_buf, RTW89_C2H_FW_LOG_STR_BUF_SIZE,
     "fw_enc(%d, %d, %d, %s)", le32_to_cpu(log_fmt->fmt_id),
     para_int, log_fmt->argc, log_fmt->u.raw);
 } else {
  snprintf(str_buf, RTW89_C2H_FW_LOG_STR_BUF_SIZE, (*fmts)[fmt_idx],
    args[0x0], args[0x1], args[0x2], args[0x3], args[0x4],
    args[0x5], args[0x6], args[0x7], args[0x8], args[0x9],
    args[0xa], args[0xb], args[0xc], args[0xd], args[0xe],
    args[0xf]);
 }

 rtw89_info(rtwdev, "C2H log: %s", str_buf);
}

void rtw89_fw_log_dump(struct rtw89_dev *rtwdev, u8 *buf, u32 len)
{
 const struct rtw89_fw_c2h_log_fmt *log_fmt;
 u8 para_int;
 u32 fmt_idx;

 if (len < RTW89_C2H_HEADER_LEN) {
  rtw89_err(rtwdev, "c2h log length is wrong!\n");
  return;
 }

 buf += RTW89_C2H_HEADER_LEN;
 len -= RTW89_C2H_HEADER_LEN;
 log_fmt = (const struct rtw89_fw_c2h_log_fmt *)buf;

 if (len < RTW89_C2H_FW_FORMATTED_LOG_MIN_LEN)
  goto plain_log;

 if (log_fmt->signature != cpu_to_le16(RTW89_C2H_FW_LOG_SIGNATURE))
  goto plain_log;

 if (!rtwdev->fw.log.fmts)
  return;

 para_int = u8_get_bits(log_fmt->feature, RTW89_C2H_FW_LOG_FEATURE_PARA_INT);
 fmt_idx = rtw89_fw_log_get_fmt_idx(rtwdev, le32_to_cpu(log_fmt->fmt_id));

 if (!para_int && log_fmt->argc != 0 && fmt_idx != 0)
  rtw89_info(rtwdev, "C2H log: %s%s",
      (*rtwdev->fw.log.fmts)[fmt_idx], log_fmt->u.raw);
 else if (fmt_idx != 0 && para_int)
  rtw89_fw_log_dump_data(rtwdev, log_fmt, fmt_idx, para_int, false);
 else
  rtw89_fw_log_dump_data(rtwdev, log_fmt, fmt_idx, para_int, true);
 return;

plain_log:
 rtw89_info(rtwdev, "C2H log: %.*s", len, buf);

}

#define H2C_CAM_LEN 60
int rtw89_fw_h2c_cam(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link,
       struct rtw89_sta_link *rtwsta_link, const u8 *scan_mac_addr)
{
 struct sk_buff *skb;
 int ret;

 skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_CAM_LEN);
 if (!skb) {
  rtw89_err(rtwdev, "failed to alloc skb for fw dl\n");
  return -ENOMEM;
 }
 skb_put(skb, H2C_CAM_LEN);
 rtw89_cam_fill_addr_cam_info(rtwdev, rtwvif_link, rtwsta_link, scan_mac_addr,
         skb->data);
 rtw89_cam_fill_bssid_cam_info(rtwdev, rtwvif_link, rtwsta_link, skb->data);

 rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
         H2C_CAT_MAC,
         H2C_CL_MAC_ADDR_CAM_UPDATE,
         H2C_FUNC_MAC_ADDR_CAM_UPD, 0, 1,
         H2C_CAM_LEN);

 ret = rtw89_h2c_tx(rtwdev, skb, false);
 if (ret) {
  rtw89_err(rtwdev, "failed to send h2c\n");
  goto fail;
 }

 return 0;
fail:
 dev_kfree_skb_any(skb);

 return ret;
}

int rtw89_fw_h2c_dctl_sec_cam_v1(struct rtw89_dev *rtwdev,
     struct rtw89_vif_link *rtwvif_link,
     struct rtw89_sta_link *rtwsta_link)
{
 struct rtw89_h2c_dctlinfo_ud_v1 *h2c;
 u32 len = sizeof(*h2c);
 struct sk_buff *skb;
 int ret;

 skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
 if (!skb) {
  rtw89_err(rtwdev, "failed to alloc skb for dctl sec cam\n");
  return -ENOMEM;
 }
 skb_put(skb, len);
 h2c = (struct rtw89_h2c_dctlinfo_ud_v1 *)skb->data;

 rtw89_cam_fill_dctl_sec_cam_info_v1(rtwdev, rtwvif_link, rtwsta_link, h2c);

 rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
         H2C_CAT_MAC,
         H2C_CL_MAC_FR_EXCHG,
         H2C_FUNC_MAC_DCTLINFO_UD_V1, 0, 0,
         len);

 ret = rtw89_h2c_tx(rtwdev, skb, false);
 if (ret) {
  rtw89_err(rtwdev, "failed to send h2c\n");
  goto fail;
 }

 return 0;
fail:
 dev_kfree_skb_any(skb);

 return ret;
}
EXPORT_SYMBOL(rtw89_fw_h2c_dctl_sec_cam_v1);

int rtw89_fw_h2c_dctl_sec_cam_v2(struct rtw89_dev *rtwdev,
     struct rtw89_vif_link *rtwvif_link,
     struct rtw89_sta_link *rtwsta_link)
{
 struct rtw89_h2c_dctlinfo_ud_v2 *h2c;
 u32 len = sizeof(*h2c);
 struct sk_buff *skb;
 int ret;

 skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
 if (!skb) {
  rtw89_err(rtwdev, "failed to alloc skb for dctl sec cam\n");
  return -ENOMEM;
 }
 skb_put(skb, len);
 h2c = (struct rtw89_h2c_dctlinfo_ud_v2 *)skb->data;

 rtw89_cam_fill_dctl_sec_cam_info_v2(rtwdev, rtwvif_link, rtwsta_link, h2c);

 rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
         H2C_CAT_MAC,
         H2C_CL_MAC_FR_EXCHG,
         H2C_FUNC_MAC_DCTLINFO_UD_V2, 0, 0,
         len);

 ret = rtw89_h2c_tx(rtwdev, skb, false);
 if (ret) {
  rtw89_err(rtwdev, "failed to send h2c\n");
  goto fail;
 }

 return 0;
fail:
 dev_kfree_skb_any(skb);

 return ret;
}
EXPORT_SYMBOL(rtw89_fw_h2c_dctl_sec_cam_v2);

int rtw89_fw_h2c_default_dmac_tbl_v2(struct rtw89_dev *rtwdev,
         struct rtw89_vif_link *rtwvif_link,
         struct rtw89_sta_link *rtwsta_link)
{
 u8 mac_id = rtwsta_link ? rtwsta_link->mac_id : rtwvif_link->mac_id;
 struct rtw89_h2c_dctlinfo_ud_v2 *h2c;
 u32 len = sizeof(*h2c);
 struct sk_buff *skb;
 int ret;

 skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
 if (!skb) {
  rtw89_err(rtwdev, "failed to alloc skb for dctl v2\n");
  return -ENOMEM;
 }
 skb_put(skb, len);
 h2c = (struct rtw89_h2c_dctlinfo_ud_v2 *)skb->data;

 h2c->c0 = le32_encode_bits(mac_id, DCTLINFO_V2_C0_MACID) |
    le32_encode_bits(1, DCTLINFO_V2_C0_OP);

 h2c->m0 = cpu_to_le32(DCTLINFO_V2_W0_ALL);
 h2c->m1 = cpu_to_le32(DCTLINFO_V2_W1_ALL);
 h2c->m2 = cpu_to_le32(DCTLINFO_V2_W2_ALL);
 h2c->m3 = cpu_to_le32(DCTLINFO_V2_W3_ALL);
 h2c->m4 = cpu_to_le32(DCTLINFO_V2_W4_ALL);
 h2c->m5 = cpu_to_le32(DCTLINFO_V2_W5_ALL);
 h2c->m6 = cpu_to_le32(DCTLINFO_V2_W6_ALL);
 h2c->m7 = cpu_to_le32(DCTLINFO_V2_W7_ALL);
 h2c->m8 = cpu_to_le32(DCTLINFO_V2_W8_ALL);
 h2c->m9 = cpu_to_le32(DCTLINFO_V2_W9_ALL);
 h2c->m10 = cpu_to_le32(DCTLINFO_V2_W10_ALL);
 h2c->m11 = cpu_to_le32(DCTLINFO_V2_W11_ALL);
 h2c->m12 = cpu_to_le32(DCTLINFO_V2_W12_ALL);

 rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
         H2C_CAT_MAC,
         H2C_CL_MAC_FR_EXCHG,
         H2C_FUNC_MAC_DCTLINFO_UD_V2, 0, 0,
         len);

 ret = rtw89_h2c_tx(rtwdev, skb, false);
 if (ret) {
  rtw89_err(rtwdev, "failed to send h2c\n");
  goto fail;
 }

 return 0;
fail:
 dev_kfree_skb_any(skb);

 return ret;
}
EXPORT_SYMBOL(rtw89_fw_h2c_default_dmac_tbl_v2);

int rtw89_fw_h2c_ba_cam(struct rtw89_dev *rtwdev,
   struct rtw89_vif_link *rtwvif_link,
   struct rtw89_sta_link *rtwsta_link,
   bool valid, struct ieee80211_ampdu_params *params)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;
 struct rtw89_h2c_ba_cam *h2c;
 u8 macid = rtwsta_link->mac_id;
 u32 len = sizeof(*h2c);
 struct sk_buff *skb;
 u8 entry_idx;
 int ret;

 ret = valid ?
       rtw89_core_acquire_sta_ba_entry(rtwdev, rtwsta_link, params->tid,
           &entry_idx) :
       rtw89_core_release_sta_ba_entry(rtwdev, rtwsta_link, params->tid,
           &entry_idx);
 if (ret) {
  /* it still works even if we don't have static BA CAM, because
 * hardware can create dynamic BA CAM automatically.
 */

  rtw89_debug(rtwdev, RTW89_DBG_TXRX,
       "failed to %s entry tid=%d for h2c ba cam\n",
       valid ? "alloc" : "free", params->tid);
  return 0;
 }

 skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
 if (!skb) {
  rtw89_err(rtwdev, "failed to alloc skb for h2c ba cam\n");
  return -ENOMEM;
 }
 skb_put(skb, len);
 h2c = (struct rtw89_h2c_ba_cam *)skb->data;

 h2c->w0 = le32_encode_bits(macid, RTW89_H2C_BA_CAM_W0_MACID);
 if (chip->bacam_ver == RTW89_BACAM_V0_EXT)
  h2c->w1 |= le32_encode_bits(entry_idx, RTW89_H2C_BA_CAM_W1_ENTRY_IDX_V1);
 else
  h2c->w0 |= le32_encode_bits(entry_idx, RTW89_H2C_BA_CAM_W0_ENTRY_IDX);
 if (!valid)
  goto end;
 h2c->w0 |= le32_encode_bits(valid, RTW89_H2C_BA_CAM_W0_VALID) |
     le32_encode_bits(params->tid, RTW89_H2C_BA_CAM_W0_TID);
 if (params->buf_size > 64)
  h2c->w0 |= le32_encode_bits(4, RTW89_H2C_BA_CAM_W0_BMAP_SIZE);
 else
  h2c->w0 |= le32_encode_bits(0, RTW89_H2C_BA_CAM_W0_BMAP_SIZE);
 /* If init req is set, hw will set the ssn */
 h2c->w0 |= le32_encode_bits(1, RTW89_H2C_BA_CAM_W0_INIT_REQ) |
     le32_encode_bits(params->ssn, RTW89_H2C_BA_CAM_W0_SSN);

 if (chip->bacam_ver == RTW89_BACAM_V0_EXT) {
  h2c->w1 |= le32_encode_bits(1, RTW89_H2C_BA_CAM_W1_STD_EN) |
      le32_encode_bits(rtwvif_link->mac_idx,
         RTW89_H2C_BA_CAM_W1_BAND);
 }

end:
 rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
         H2C_CAT_MAC,
         H2C_CL_BA_CAM,
         H2C_FUNC_MAC_BA_CAM, 0, 1,
         len);

 ret = rtw89_h2c_tx(rtwdev, skb, false);
 if (ret) {
  rtw89_err(rtwdev, "failed to send h2c\n");
  goto fail;
 }

 return 0;
fail:
 dev_kfree_skb_any(skb);

 return ret;
}
EXPORT_SYMBOL(rtw89_fw_h2c_ba_cam);

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=99 H=95 G=96

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.