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