// 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.10 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland