Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/tools/lib/perf/Documentation/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 1 kB image not shown  

Quelle  fw.c   Sprache: unbekannt

 
// 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

[ zur Elbe Produktseite wechseln0.15Quellennavigators  Analyse erneut starten  ]