linux/drivers/net/wireless/intel/iwlwifi/mld/fw.c
Johannes Berg 563abc938f wifi: iwlwifi: use PNVM data embedded in .ucode files
Given compatibility issues with external PNVM data that doesn't match
the firmware it was designed with/for, future firmware releases will
include the PNVM data in the firmware files directly, avoiding those
mismatch issues. Make the driver load and use that embedded PNVM data
in preference of external files, falling back to the external file if
it isn't present.

Co-developed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20250709081300.c843f77aa2d3.I7200f8dd40ef82aff1f5574fdd3966913cda592c@changeid
2025-07-09 11:39:28 +03:00

554 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
*/
#include "mld.h"
#include "fw/api/alive.h"
#include "fw/api/scan.h"
#include "fw/api/rx.h"
#include "phy.h"
#include "fw/dbg.h"
#include "fw/pnvm.h"
#include "hcmd.h"
#include "power.h"
#include "mcc.h"
#include "led.h"
#include "coex.h"
#include "regulatory.h"
#include "thermal.h"
static int iwl_mld_send_tx_ant_cfg(struct iwl_mld *mld)
{
struct iwl_tx_ant_cfg_cmd cmd;
lockdep_assert_wiphy(mld->wiphy);
cmd.valid = cpu_to_le32(iwl_mld_get_valid_tx_ant(mld));
IWL_DEBUG_FW(mld, "select valid tx ant: %u\n", cmd.valid);
return iwl_mld_send_cmd_pdu(mld, TX_ANT_CONFIGURATION_CMD, &cmd);
}
static int iwl_mld_send_rss_cfg_cmd(struct iwl_mld *mld)
{
struct iwl_rss_config_cmd cmd = {
.flags = cpu_to_le32(IWL_RSS_ENABLE),
.hash_mask = BIT(IWL_RSS_HASH_TYPE_IPV4_TCP) |
BIT(IWL_RSS_HASH_TYPE_IPV4_UDP) |
BIT(IWL_RSS_HASH_TYPE_IPV4_PAYLOAD) |
BIT(IWL_RSS_HASH_TYPE_IPV6_TCP) |
BIT(IWL_RSS_HASH_TYPE_IPV6_UDP) |
BIT(IWL_RSS_HASH_TYPE_IPV6_PAYLOAD),
};
lockdep_assert_wiphy(mld->wiphy);
/* Do not direct RSS traffic to Q 0 which is our fallback queue */
for (int i = 0; i < ARRAY_SIZE(cmd.indirection_table); i++)
cmd.indirection_table[i] =
1 + (i % (mld->trans->info.num_rxqs - 1));
netdev_rss_key_fill(cmd.secret_key, sizeof(cmd.secret_key));
return iwl_mld_send_cmd_pdu(mld, RSS_CONFIG_CMD, &cmd);
}
static int iwl_mld_config_scan(struct iwl_mld *mld)
{
struct iwl_scan_config cmd = {
.tx_chains = cpu_to_le32(iwl_mld_get_valid_tx_ant(mld)),
.rx_chains = cpu_to_le32(iwl_mld_get_valid_rx_ant(mld))
};
return iwl_mld_send_cmd_pdu(mld, WIDE_ID(LONG_GROUP, SCAN_CFG_CMD),
&cmd);
}
static void iwl_mld_alive_imr_data(struct iwl_trans *trans,
const struct iwl_imr_alive_info *imr_info)
{
struct iwl_imr_data *imr_data = &trans->dbg.imr_data;
imr_data->imr_enable = le32_to_cpu(imr_info->enabled);
imr_data->imr_size = le32_to_cpu(imr_info->size);
imr_data->imr2sram_remainbyte = imr_data->imr_size;
imr_data->imr_base_addr = imr_info->base_addr;
imr_data->imr_curr_addr = le64_to_cpu(imr_data->imr_base_addr);
if (imr_data->imr_enable)
return;
for (int i = 0; i < ARRAY_SIZE(trans->dbg.active_regions); i++) {
struct iwl_fw_ini_region_tlv *reg;
if (!trans->dbg.active_regions[i])
continue;
reg = (void *)trans->dbg.active_regions[i]->data;
/* We have only one DRAM IMR region, so we
* can break as soon as we find the first
* one.
*/
if (reg->type == IWL_FW_INI_REGION_DRAM_IMR) {
trans->dbg.unsupported_region_msk |= BIT(i);
break;
}
}
}
struct iwl_mld_alive_data {
__le32 sku_id[3];
bool valid;
};
static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait,
struct iwl_rx_packet *pkt, void *data)
{
unsigned int pkt_len = iwl_rx_packet_payload_len(pkt);
unsigned int expected_sz;
struct iwl_mld *mld =
container_of(notif_wait, struct iwl_mld, notif_wait);
struct iwl_trans *trans = mld->trans;
u32 version = iwl_fw_lookup_notif_ver(mld->fw, LEGACY_GROUP,
UCODE_ALIVE_NTFY, 0);
struct iwl_mld_alive_data *alive_data = data;
struct iwl_alive_ntf *palive;
struct iwl_umac_alive *umac;
struct iwl_lmac_alive *lmac1;
struct iwl_lmac_alive *lmac2 = NULL;
u32 lmac_error_event_table;
u32 umac_error_table;
u16 status;
switch (version) {
case 6:
case 7:
expected_sz = sizeof(struct iwl_alive_ntf_v6);
break;
case 8:
expected_sz = sizeof(struct iwl_alive_ntf);
break;
default:
return false;
}
if (pkt_len != expected_sz)
return false;
palive = (void *)pkt->data;
iwl_mld_alive_imr_data(trans, &palive->imr);
umac = &palive->umac_data;
lmac1 = &palive->lmac_data[0];
lmac2 = &palive->lmac_data[1];
status = le16_to_cpu(palive->status);
BUILD_BUG_ON(sizeof(alive_data->sku_id) !=
sizeof(palive->sku_id.data));
memcpy(alive_data->sku_id, palive->sku_id.data,
sizeof(palive->sku_id.data));
IWL_DEBUG_FW(mld, "Got sku_id: 0x0%x 0x0%x 0x0%x\n",
le32_to_cpu(alive_data->sku_id[0]),
le32_to_cpu(alive_data->sku_id[1]),
le32_to_cpu(alive_data->sku_id[2]));
lmac_error_event_table =
le32_to_cpu(lmac1->dbg_ptrs.error_event_table_ptr);
iwl_fw_lmac1_set_alive_err_table(trans, lmac_error_event_table);
if (lmac2)
trans->dbg.lmac_error_event_table[1] =
le32_to_cpu(lmac2->dbg_ptrs.error_event_table_ptr);
umac_error_table = le32_to_cpu(umac->dbg_ptrs.error_info_addr) &
~FW_ADDR_CACHE_CONTROL;
if (umac_error_table >= trans->mac_cfg->base->min_umac_error_event_table)
iwl_fw_umac_set_alive_err_table(trans, umac_error_table);
else
IWL_ERR(mld, "Not valid error log pointer 0x%08X\n",
umac_error_table);
alive_data->valid = status == IWL_ALIVE_STATUS_OK;
IWL_DEBUG_FW(mld,
"Alive ucode status 0x%04x revision 0x%01X 0x%01X\n",
status, lmac1->ver_type, lmac1->ver_subtype);
if (lmac2)
IWL_DEBUG_FW(mld, "Alive ucode CDB\n");
IWL_DEBUG_FW(mld,
"UMAC version: Major - 0x%x, Minor - 0x%x\n",
le32_to_cpu(umac->umac_major),
le32_to_cpu(umac->umac_minor));
if (version >= 7)
IWL_DEBUG_FW(mld, "FW alive flags 0x%x\n",
le16_to_cpu(palive->flags));
if (version >= 8)
IWL_DEBUG_FW(mld, "platform_id 0x%llx\n",
le64_to_cpu(palive->platform_id));
iwl_fwrt_update_fw_versions(&mld->fwrt, lmac1, umac);
return true;
}
#define MLD_ALIVE_TIMEOUT (2 * HZ)
#define MLD_INIT_COMPLETE_TIMEOUT (2 * HZ)
static void iwl_mld_print_alive_notif_timeout(struct iwl_mld *mld)
{
struct iwl_trans *trans = mld->trans;
struct iwl_pc_data *pc_data;
u8 count;
IWL_ERR(mld,
"SecBoot CPU1 Status: 0x%x, CPU2 Status: 0x%x\n",
iwl_read_umac_prph(trans, UMAG_SB_CPU_1_STATUS),
iwl_read_umac_prph(trans,
UMAG_SB_CPU_2_STATUS));
#define IWL_FW_PRINT_REG_INFO(reg_name) \
IWL_ERR(mld, #reg_name ": 0x%x\n", iwl_read_umac_prph(trans, reg_name))
IWL_FW_PRINT_REG_INFO(WFPM_LMAC1_PD_NOTIFICATION);
IWL_FW_PRINT_REG_INFO(HPM_SECONDARY_DEVICE_STATE);
/* print OTP info */
IWL_FW_PRINT_REG_INFO(WFPM_MAC_OTP_CFG7_ADDR);
IWL_FW_PRINT_REG_INFO(WFPM_MAC_OTP_CFG7_DATA);
#undef IWL_FW_PRINT_REG_INFO
pc_data = trans->dbg.pc_data;
for (count = 0; count < trans->dbg.num_pc; count++, pc_data++)
IWL_ERR(mld, "%s: 0x%x\n", pc_data->pc_name,
pc_data->pc_address);
}
static int iwl_mld_load_fw_wait_alive(struct iwl_mld *mld,
struct iwl_mld_alive_data *alive_data)
{
static const u16 alive_cmd[] = { UCODE_ALIVE_NTFY };
struct iwl_notification_wait alive_wait;
int ret;
lockdep_assert_wiphy(mld->wiphy);
iwl_init_notification_wait(&mld->notif_wait, &alive_wait,
alive_cmd, ARRAY_SIZE(alive_cmd),
iwl_alive_fn, alive_data);
iwl_dbg_tlv_time_point(&mld->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL);
ret = iwl_trans_start_fw(mld->trans, mld->fw, IWL_UCODE_REGULAR, true);
if (ret) {
iwl_remove_notification(&mld->notif_wait, &alive_wait);
return ret;
}
ret = iwl_wait_notification(&mld->notif_wait, &alive_wait,
MLD_ALIVE_TIMEOUT);
if (ret) {
if (ret == -ETIMEDOUT)
iwl_fw_dbg_error_collect(&mld->fwrt,
FW_DBG_TRIGGER_ALIVE_TIMEOUT);
iwl_mld_print_alive_notif_timeout(mld);
return ret;
}
if (!alive_data->valid) {
IWL_ERR(mld, "Loaded firmware is not valid!\n");
return -EIO;
}
iwl_trans_fw_alive(mld->trans);
return 0;
}
static int iwl_mld_run_fw_init_sequence(struct iwl_mld *mld)
{
struct iwl_notification_wait init_wait;
struct iwl_init_extended_cfg_cmd init_cfg = {
.init_flags = cpu_to_le32(BIT(IWL_INIT_PHY)),
};
struct iwl_mld_alive_data alive_data = {};
static const u16 init_complete[] = {
INIT_COMPLETE_NOTIF,
};
int ret;
lockdep_assert_wiphy(mld->wiphy);
ret = iwl_mld_load_fw_wait_alive(mld, &alive_data);
if (ret)
return ret;
ret = iwl_pnvm_load(mld->trans, &mld->notif_wait,
mld->fw, alive_data.sku_id);
if (ret) {
IWL_ERR(mld, "Timeout waiting for PNVM load %d\n", ret);
return ret;
}
iwl_dbg_tlv_time_point(&mld->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE,
NULL);
iwl_init_notification_wait(&mld->notif_wait,
&init_wait,
init_complete,
ARRAY_SIZE(init_complete),
NULL, NULL);
ret = iwl_mld_send_cmd_pdu(mld,
WIDE_ID(SYSTEM_GROUP, INIT_EXTENDED_CFG_CMD),
&init_cfg);
if (ret) {
IWL_ERR(mld, "Failed to send init config command: %d\n", ret);
iwl_remove_notification(&mld->notif_wait, &init_wait);
return ret;
}
ret = iwl_mld_send_phy_cfg_cmd(mld);
if (ret) {
IWL_ERR(mld, "Failed to send PHY config command: %d\n", ret);
iwl_remove_notification(&mld->notif_wait, &init_wait);
return ret;
}
ret = iwl_wait_notification(&mld->notif_wait, &init_wait,
MLD_INIT_COMPLETE_TIMEOUT);
if (ret) {
IWL_ERR(mld, "Failed to get INIT_COMPLETE %d\n", ret);
return ret;
}
return 0;
}
int iwl_mld_load_fw(struct iwl_mld *mld)
{
int ret;
lockdep_assert_wiphy(mld->wiphy);
ret = iwl_trans_start_hw(mld->trans);
if (ret)
return ret;
ret = iwl_mld_run_fw_init_sequence(mld);
if (ret)
goto err;
mld->fw_status.running = true;
return 0;
err:
iwl_mld_stop_fw(mld);
return ret;
}
void iwl_mld_stop_fw(struct iwl_mld *mld)
{
lockdep_assert_wiphy(mld->wiphy);
iwl_abort_notification_waits(&mld->notif_wait);
iwl_fw_dbg_stop_sync(&mld->fwrt);
iwl_trans_stop_device(mld->trans);
/* HW is stopped, no more coming RX. Cancel all notifications in
* case they were sent just before stopping the HW.
*/
iwl_mld_cancel_async_notifications(mld);
mld->fw_status.running = false;
}
static void iwl_mld_restart_disconnect_iter(void *data, u8 *mac,
struct ieee80211_vif *vif)
{
if (vif->type == NL80211_IFTYPE_STATION)
ieee80211_hw_restart_disconnect(vif);
}
void iwl_mld_send_recovery_cmd(struct iwl_mld *mld, u32 flags)
{
u32 error_log_size = mld->fw->ucode_capa.error_log_size;
struct iwl_fw_error_recovery_cmd recovery_cmd = {
.flags = cpu_to_le32(flags),
};
struct iwl_host_cmd cmd = {
.id = WIDE_ID(SYSTEM_GROUP, FW_ERROR_RECOVERY_CMD),
.flags = CMD_WANT_SKB,
.data = {&recovery_cmd, },
.len = {sizeof(recovery_cmd), },
};
int ret;
/* no error log was defined in TLV */
if (!error_log_size)
return;
if (flags & ERROR_RECOVERY_UPDATE_DB) {
/* no buf was allocated upon NIC error */
if (!mld->error_recovery_buf)
return;
cmd.data[1] = mld->error_recovery_buf;
cmd.len[1] = error_log_size;
cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY;
recovery_cmd.buf_size = cpu_to_le32(error_log_size);
}
ret = iwl_mld_send_cmd(mld, &cmd);
/* we no longer need the recovery buffer */
kfree(mld->error_recovery_buf);
mld->error_recovery_buf = NULL;
if (ret) {
IWL_ERR(mld, "Failed to send recovery cmd %d\n", ret);
return;
}
if (flags & ERROR_RECOVERY_UPDATE_DB) {
struct iwl_rx_packet *pkt = cmd.resp_pkt;
u32 pkt_len = iwl_rx_packet_payload_len(pkt);
u32 resp;
if (IWL_FW_CHECK(mld, pkt_len != sizeof(resp),
"Unexpected recovery cmd response size %u (expected %zu)\n",
pkt_len, sizeof(resp)))
goto out;
resp = le32_to_cpup((__le32 *)cmd.resp_pkt->data);
if (!resp)
goto out;
IWL_ERR(mld,
"Failed to send recovery cmd blob was invalid %d\n",
resp);
ieee80211_iterate_interfaces(mld->hw, 0,
iwl_mld_restart_disconnect_iter,
NULL);
}
out:
iwl_free_resp(&cmd);
}
static int iwl_mld_config_fw(struct iwl_mld *mld)
{
int ret;
lockdep_assert_wiphy(mld->wiphy);
iwl_fw_disable_dbg_asserts(&mld->fwrt);
iwl_get_shared_mem_conf(&mld->fwrt);
ret = iwl_mld_send_tx_ant_cfg(mld);
if (ret)
return ret;
ret = iwl_mld_send_bt_init_conf(mld);
if (ret)
return ret;
ret = iwl_set_soc_latency(&mld->fwrt);
if (ret)
return ret;
iwl_mld_configure_lari(mld);
ret = iwl_mld_config_temp_report_ths(mld);
if (ret)
return ret;
#ifdef CONFIG_THERMAL
ret = iwl_mld_config_ctdp(mld, mld->cooling_dev.cur_state,
CTDP_CMD_OPERATION_START);
if (ret)
return ret;
#endif
ret = iwl_configure_rxq(&mld->fwrt);
if (ret)
return ret;
ret = iwl_mld_send_rss_cfg_cmd(mld);
if (ret)
return ret;
ret = iwl_mld_config_scan(mld);
if (ret)
return ret;
ret = iwl_mld_update_device_power(mld, false);
if (ret)
return ret;
if (mld->fw_status.in_hw_restart) {
iwl_mld_send_recovery_cmd(mld, ERROR_RECOVERY_UPDATE_DB);
iwl_mld_time_sync_fw_config(mld);
}
iwl_mld_led_config_fw(mld);
ret = iwl_mld_init_ppag(mld);
if (ret)
return ret;
ret = iwl_mld_init_sar(mld);
if (ret)
return ret;
ret = iwl_mld_init_sgom(mld);
if (ret)
return ret;
iwl_mld_init_tas(mld);
iwl_mld_init_uats(mld);
return 0;
}
int iwl_mld_start_fw(struct iwl_mld *mld)
{
int ret;
lockdep_assert_wiphy(mld->wiphy);
ret = iwl_mld_load_fw(mld);
if (IWL_FW_CHECK(mld, ret, "Failed to start firmware %d\n", ret)) {
iwl_fw_dbg_error_collect(&mld->fwrt, FW_DBG_TRIGGER_DRIVER);
return ret;
}
IWL_DEBUG_INFO(mld, "uCode started.\n");
ret = iwl_mld_config_fw(mld);
if (ret)
goto error;
ret = iwl_mld_init_mcc(mld);
if (ret)
goto error;
return 0;
error:
iwl_mld_stop_fw(mld);
return ret;
}