new file mode 100644
@@ -0,0 +1,536 @@
+// 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 "fw/dbg.h"
+#include "fw/pnvm.h"
+#include "hcmd.h"
+#include "iwl-nvm-parse.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->num_rx_queues - 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;
+ }
+ }
+}
+
+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);
+ 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_alive_ntf_v6 *palive;
+ bool *alive_valid = data;
+ 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;
+
+ if (version < 6 || version > 7 || pkt_len != sizeof(*palive))
+ 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);
+
+ trans->sku_id[0] = le32_to_cpu(palive->sku_id.data[0]);
+ trans->sku_id[1] = le32_to_cpu(palive->sku_id.data[1]);
+ trans->sku_id[2] = le32_to_cpu(palive->sku_id.data[2]);
+
+ IWL_DEBUG_FW(mld, "Got sku_id: 0x0%x 0x0%x 0x0%x\n",
+ trans->sku_id[0], trans->sku_id[1], trans->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->cfg->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_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));
+
+ 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)
+{
+ const struct fw_img *fw =
+ iwl_get_ucode_image(mld->fw, IWL_UCODE_REGULAR);
+ static const u16 alive_cmd[] = { UCODE_ALIVE_NTFY };
+ struct iwl_notification_wait alive_wait;
+ bool alive_valid = false;
+ 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_valid);
+
+ iwl_dbg_tlv_time_point(&mld->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL);
+
+ ret = iwl_trans_start_fw(mld->trans, fw, 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);
+ goto alive_failure;
+ }
+
+ if (!alive_valid) {
+ IWL_ERR(mld, "Loaded firmware is not valid!\n");
+ ret = -EIO;
+ goto alive_failure;
+ }
+
+ iwl_trans_fw_alive(mld->trans, 0);
+
+ return 0;
+
+alive_failure:
+ iwl_trans_stop_device(mld->trans);
+ return ret;
+}
+
+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 = {};
+ static const u16 init_complete[] = {
+ INIT_COMPLETE_NOTIF,
+ };
+ int ret;
+
+ lockdep_assert_wiphy(mld->wiphy);
+
+ ret = iwl_mld_load_fw_wait_alive(mld);
+ if (ret)
+ return ret;
+
+ mld->trans->step_urm =
+ !!(iwl_read_umac_prph(mld->trans, CNVI_PMU_STEP_FLOW) &
+ CNVI_PMU_STEP_FLOW_FORCE_URM);
+
+ ret = iwl_pnvm_load(mld->trans, &mld->notif_wait,
+ &mld->fw->ucode_capa);
+ if (ret) {
+ IWL_ERR(mld, "Timeout waiting for PNVM load %d\n", ret);
+ goto init_failure;
+ }
+
+ 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);
+ goto init_failure;
+ }
+
+ 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);
+ goto init_failure;
+ }
+
+ if (!mld->nvm_data) {
+ mld->nvm_data = iwl_get_nvm(mld->trans, mld->fw, 0, 0);
+ if (IS_ERR(mld->nvm_data)) {
+ ret = PTR_ERR(mld->nvm_data);
+ mld->nvm_data = NULL;
+ IWL_ERR(mld, "Failed to read NVM: %d\n", ret);
+ goto init_failure;
+ }
+ }
+
+ return 0;
+
+init_failure:
+ iwl_trans_stop_device(mld->trans);
+ return ret;
+}
+
+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)
+ return ret;
+
+ mld->fw_status.running = true;
+
+ return 0;
+}
+
+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);
+
+ 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;
+
+ ret = iwl_mld_init_mcc(mld);
+ 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);
+ goto error;
+ }
+
+ IWL_DEBUG_INFO(mld, "uCode started.\n");
+
+ ret = iwl_mld_config_fw(mld);
+ if (ret)
+ goto error;
+
+ return 0;
+
+error:
+ iwl_mld_stop_fw(mld);
+ return ret;
+}
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com> --- drivers/net/wireless/intel/iwlwifi/mld/fw.c | 536 ++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 drivers/net/wireless/intel/iwlwifi/mld/fw.c