| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| /* |
| * Copyright (C) 2005-2014, 2018-2024 Intel Corporation |
| * Copyright (C) 2015-2017 Intel Deutschland GmbH |
| */ |
| #include "iwl-trans.h" |
| #include "iwl-op-mode.h" |
| #include "fw/img.h" |
| #include "iwl-csr.h" |
| |
| #include "xvt.h" |
| #include "iwl-dnt-cfg.h" |
| #include "fw/dbg.h" |
| #include "fw/testmode.h" |
| #include "fw/api/power.h" |
| #include "fw/pnvm.h" |
| |
| #define XVT_UCODE_ALIVE_TIMEOUT (HZ * CPTCFG_IWL_TIMEOUT_FACTOR) |
| |
| struct iwl_xvt_alive_data { |
| bool valid; |
| u32 scd_base_addr; |
| }; |
| |
| static int iwl_xvt_send_dqa_cmd(struct iwl_xvt *xvt) |
| { |
| struct iwl_dqa_enable_cmd dqa_cmd = { |
| .cmd_queue = cpu_to_le32(IWL_MVM_DQA_CMD_QUEUE), |
| }; |
| u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, DQA_ENABLE_CMD); |
| int ret; |
| |
| ret = iwl_xvt_send_cmd_pdu(xvt, cmd_id, 0, sizeof(dqa_cmd), &dqa_cmd); |
| if (ret) |
| IWL_ERR(xvt, "Failed to send DQA enabling command: %d\n", ret); |
| else |
| IWL_DEBUG_FW(xvt, "Working in DQA mode\n"); |
| |
| return ret; |
| } |
| |
| static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, |
| struct iwl_rx_packet *pkt, |
| void *data) |
| { |
| struct iwl_xvt *xvt = |
| container_of(notif_wait, struct iwl_xvt, notif_wait); |
| struct iwl_xvt_alive_data *alive_data = data; |
| struct xvt_alive_resp_ver2 *palive2; |
| struct iwl_alive_ntf_v3 *palive3; |
| struct iwl_alive_ntf_v4 *palive4; |
| struct iwl_alive_ntf_v5 *palive5; |
| struct iwl_lmac_alive *lmac1, *lmac2; |
| struct iwl_umac_alive *umac; |
| u32 rx_packet_payload_size = iwl_rx_packet_payload_len(pkt); |
| u16 status, flags; |
| u32 lmac_error_event_table, umac_error_event_table; |
| u32 version = iwl_fw_lookup_notif_ver(xvt->fw, LEGACY_GROUP, |
| UCODE_ALIVE_NTFY, 0); |
| |
| if (rx_packet_payload_size == sizeof(*palive2)) { |
| |
| palive2 = (void *)pkt->data; |
| |
| lmac_error_event_table = |
| le32_to_cpu(palive2->error_event_table_ptr); |
| alive_data->scd_base_addr = le32_to_cpu(palive2->scd_base_ptr); |
| |
| alive_data->valid = le16_to_cpu(palive2->status) == |
| IWL_ALIVE_STATUS_OK; |
| iwl_tm_set_fw_ver(xvt->trans, palive2->ucode_major, |
| palive2->ucode_minor); |
| umac_error_event_table = |
| le32_to_cpu(palive2->error_info_addr); |
| |
| IWL_DEBUG_FW(xvt, |
| "Alive VER2 ucode status 0x%04x revision 0x%01X " |
| "0x%01X flags 0x%01X\n", |
| le16_to_cpu(palive2->status), palive2->ver_type, |
| palive2->ver_subtype, palive2->flags); |
| |
| IWL_DEBUG_FW(xvt, |
| "UMAC version: Major - 0x%x, Minor - 0x%x\n", |
| palive2->umac_major, palive2->umac_minor); |
| } else { |
| if (rx_packet_payload_size == sizeof(*palive3)) { |
| palive3 = (void *)pkt->data; |
| status = le16_to_cpu(palive3->status); |
| flags = le16_to_cpu(palive3->flags); |
| lmac1 = &palive3->lmac_data; |
| umac = &palive3->umac_data; |
| |
| IWL_DEBUG_FW(xvt, "Alive VER3\n"); |
| } else if (rx_packet_payload_size == sizeof(*palive4)) { |
| __le32 lmac2_err_ptr; |
| |
| palive4 = (void *)pkt->data; |
| status = le16_to_cpu(palive4->status); |
| flags = le16_to_cpu(palive4->flags); |
| lmac1 = &palive4->lmac_data[0]; |
| lmac2 = &palive4->lmac_data[1]; |
| umac = &palive4->umac_data; |
| lmac2_err_ptr = lmac2->dbg_ptrs.error_event_table_ptr; |
| xvt->trans->dbg.lmac_error_event_table[1] = |
| le32_to_cpu(lmac2_err_ptr); |
| |
| IWL_DEBUG_FW(xvt, "Alive VER4\n"); |
| } else if (version == 5 || version == 6) { |
| /* v5 and v6 are compatible (only IMR addition) */ |
| __le32 lmac2_err_ptr; |
| |
| palive5 = (void *)pkt->data; |
| status = le16_to_cpu(palive5->status); |
| flags = le16_to_cpu(palive5->flags); |
| lmac1 = &palive5->lmac_data[0]; |
| lmac2 = &palive5->lmac_data[1]; |
| umac = &palive5->umac_data; |
| lmac2_err_ptr = lmac2->dbg_ptrs.error_event_table_ptr; |
| xvt->trans->dbg.lmac_error_event_table[1] = |
| le32_to_cpu(lmac2_err_ptr); |
| |
| xvt->trans->sku_id[0] = le32_to_cpu(palive5->sku_id.data[0]); |
| xvt->trans->sku_id[1] = le32_to_cpu(palive5->sku_id.data[1]); |
| xvt->trans->sku_id[2] = le32_to_cpu(palive5->sku_id.data[2]); |
| |
| IWL_DEBUG_FW(xvt, |
| "Alive VER%d - Got sku_id: 0x0%x 0x0%x 0x0%x\n", |
| version, |
| xvt->trans->sku_id[0], |
| xvt->trans->sku_id[1], |
| xvt->trans->sku_id[2]); |
| } else { |
| IWL_ERR(xvt, "unrecognized alive notificatio\n"); |
| return false; |
| } |
| |
| alive_data->valid = status == IWL_ALIVE_STATUS_OK; |
| lmac_error_event_table = |
| le32_to_cpu(lmac1->dbg_ptrs.error_event_table_ptr); |
| alive_data->scd_base_addr = |
| le32_to_cpu(lmac1->dbg_ptrs.scd_base_ptr); |
| iwl_tm_set_fw_ver(xvt->trans, le32_to_cpu(lmac1->ucode_major), |
| le32_to_cpu(lmac1->ucode_minor)); |
| umac_error_event_table = |
| le32_to_cpu(umac->dbg_ptrs.error_info_addr); |
| |
| IWL_DEBUG_FW(xvt, |
| "status 0x%04x rev 0x%01X 0x%01X flags 0x%01X\n", |
| status, lmac1->ver_type, lmac1->ver_subtype, |
| flags); |
| IWL_DEBUG_FW(xvt, |
| "UMAC version: Major - 0x%x, Minor - 0x%x\n", |
| umac->umac_major, umac->umac_minor); |
| } |
| |
| iwl_fw_lmac1_set_alive_err_table(xvt->trans, lmac_error_event_table); |
| if (umac_error_event_table) |
| iwl_fw_umac_set_alive_err_table(xvt->trans, |
| umac_error_event_table); |
| |
| return true; |
| } |
| |
| static int iwl_xvt_load_ucode_wait_alive(struct iwl_xvt *xvt, |
| enum iwl_ucode_type ucode_type) |
| { |
| struct iwl_notification_wait alive_wait; |
| struct iwl_xvt_alive_data alive_data; |
| const struct fw_img *fw; |
| int ret; |
| enum iwl_ucode_type old_type = xvt->fwrt.cur_fw_img; |
| static const u16 alive_cmd[] = { UCODE_ALIVE_NTFY }; |
| struct iwl_scd_txq_cfg_cmd cmd = { |
| .scd_queue = IWL_XVT_DEFAULT_TX_QUEUE, |
| .action = SCD_CFG_ENABLE_QUEUE, |
| .window = IWL_FRAME_LIMIT, |
| .sta_id = IWL_XVT_TX_STA_ID_DEFAULT, |
| .ssn = 0, |
| .tx_fifo = IWL_XVT_DEFAULT_TX_FIFO, |
| .aggregate = false, |
| .tid = IWL_MAX_TID_COUNT, |
| }; |
| |
| iwl_fw_set_current_image(&xvt->fwrt, ucode_type); |
| fw = iwl_get_ucode_image(xvt->fw, ucode_type); |
| |
| if (!fw) |
| return -EINVAL; |
| |
| if (xvt->sw_stack_cfg.fw_dbg_flags & ~IWL_XVT_DBG_FLAGS_NO_DEFAULT_TXQ) |
| return -EOPNOTSUPP; |
| |
| iwl_init_notification_wait(&xvt->notif_wait, &alive_wait, |
| alive_cmd, ARRAY_SIZE(alive_cmd), |
| iwl_alive_fn, &alive_data); |
| |
| ret = iwl_trans_start_fw(xvt->trans, fw, |
| ucode_type == IWL_UCODE_INIT); |
| if (ret) { |
| iwl_fw_set_current_image(&xvt->fwrt, old_type); |
| iwl_remove_notification(&xvt->notif_wait, &alive_wait); |
| return ret; |
| } |
| |
| /* |
| * Some things may run in the background now, but we |
| * just wait for the ALIVE notification here. |
| */ |
| ret = iwl_wait_notification(&xvt->notif_wait, &alive_wait, |
| XVT_UCODE_ALIVE_TIMEOUT); |
| if (ret) { |
| IWL_ERR(xvt, "XVT: ret:%d\n", ret); |
| iwl_fw_set_current_image(&xvt->fwrt, old_type); |
| return ret; |
| } |
| |
| if (!alive_data.valid) { |
| IWL_ERR(xvt, "Loaded ucode is not valid!\n"); |
| iwl_fw_set_current_image(&xvt->fwrt, old_type); |
| return -EIO; |
| } |
| |
| /* fresh firmware was loaded */ |
| xvt->fw_error = false; |
| |
| ret = iwl_xvt_pnvm_load(xvt->trans, &xvt->notif_wait, |
| &xvt->fw->ucode_capa); |
| if (ret) { |
| IWL_ERR(xvt, "Timeout waiting for PNVM load!\n"); |
| iwl_fw_set_current_image(&xvt->fwrt, old_type); |
| return ret; |
| } |
| |
| iwl_trans_fw_alive(xvt->trans, alive_data.scd_base_addr); |
| |
| ret = iwl_init_paging(&xvt->fwrt, ucode_type); |
| if (ret) |
| return ret; |
| |
| if (ucode_type == IWL_UCODE_REGULAR && |
| fw_has_capa(&xvt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_DQA_SUPPORT)) { |
| ret = iwl_xvt_send_dqa_cmd(xvt); |
| if (ret) |
| return ret; |
| } |
| /* |
| * Starting from 22000 tx queue allocation must be done after add |
| * station, so it is not part of the init flow. |
| */ |
| if (!iwl_xvt_is_unified_fw(xvt) && |
| iwl_xvt_has_default_txq(xvt) && |
| ucode_type != IWL_UCODE_INIT) { |
| iwl_trans_txq_enable_cfg(xvt->trans, IWL_XVT_DEFAULT_TX_QUEUE, |
| 0, NULL, 0); |
| |
| WARN(iwl_xvt_send_cmd_pdu(xvt, SCD_QUEUE_CFG, 0, sizeof(cmd), |
| &cmd), |
| "Failed to configure queue %d on FIFO %d\n", |
| IWL_XVT_DEFAULT_TX_QUEUE, IWL_XVT_DEFAULT_TX_FIFO); |
| xvt->tx_meta_data[XVT_LMAC_0_ID].queue = |
| IWL_XVT_DEFAULT_TX_QUEUE; |
| } |
| |
| xvt->fw_running = true; |
| #ifdef CPTCFG_IWLWIFI_DEBUGFS |
| iwl_fw_set_dbg_rec_on(&xvt->fwrt); |
| #endif |
| |
| return 0; |
| } |
| |
| static int iwl_xvt_send_extended_config(struct iwl_xvt *xvt) |
| { |
| /* |
| * TODO: once WRT will be implemented in xVT, IWL_INIT_DEBUG_CFG |
| * flag will not always be set |
| */ |
| struct iwl_init_extended_cfg_cmd ext_cfg = { |
| .init_flags = cpu_to_le32(BIT(IWL_INIT_NVM) | |
| BIT(IWL_INIT_DEBUG_CFG)), |
| |
| }; |
| |
| if (xvt->sw_stack_cfg.load_mask & IWL_XVT_LOAD_MASK_RUNTIME) |
| ext_cfg.init_flags |= cpu_to_le32(BIT(IWL_INIT_PHY)); |
| |
| return iwl_xvt_send_cmd_pdu(xvt, WIDE_ID(SYSTEM_GROUP, |
| INIT_EXTENDED_CFG_CMD), 0, |
| sizeof(ext_cfg), &ext_cfg); |
| } |
| |
| static int iwl_xvt_config_ltr(struct iwl_xvt *xvt) |
| { |
| struct iwl_ltr_config_cmd cmd = { |
| .flags = cpu_to_le32(LTR_CFG_FLAG_FEATURE_ENABLE), |
| }; |
| |
| if (!xvt->trans->ltr_enabled) |
| return 0; |
| |
| return iwl_xvt_send_cmd_pdu(xvt, LTR_CONFIG, 0, sizeof(cmd), &cmd); |
| } |
| |
| int iwl_xvt_run_fw(struct iwl_xvt *xvt, u32 ucode_type) |
| { |
| int ret; |
| |
| if (ucode_type >= IWL_UCODE_TYPE_MAX) |
| return -EINVAL; |
| |
| lockdep_assert_held(&xvt->mutex); |
| |
| if (xvt->state != IWL_XVT_STATE_UNINITIALIZED) { |
| if (xvt->fw_running) { |
| xvt->fw_running = false; |
| if (xvt->fwrt.cur_fw_img == IWL_UCODE_REGULAR) |
| iwl_xvt_txq_disable(xvt); |
| } |
| iwl_fw_dbg_stop_sync(&xvt->fwrt); |
| iwl_trans_stop_device(xvt->trans); |
| } |
| |
| ret = iwl_trans_start_hw(xvt->trans); |
| if (ret) { |
| IWL_ERR(xvt, "Failed to start HW\n"); |
| return ret; |
| } |
| |
| iwl_trans_set_bits_mask(xvt->trans, |
| CSR_HW_IF_CONFIG_REG, |
| CSR_HW_IF_CONFIG_REG_BIT_MAC_SI, |
| CSR_HW_IF_CONFIG_REG_BIT_MAC_SI); |
| |
| iwl_dbg_tlv_time_point(&xvt->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL); |
| |
| /* Will also start the device */ |
| ret = iwl_xvt_load_ucode_wait_alive(xvt, ucode_type); |
| if (ret) { |
| IWL_ERR(xvt, "Failed to start ucode: %d\n", ret); |
| iwl_fw_dbg_stop_sync(&xvt->fwrt); |
| iwl_trans_stop_device(xvt->trans); |
| return ret; |
| } |
| |
| iwl_dbg_tlv_time_point(&xvt->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE, |
| NULL); |
| |
| iwl_fw_disable_dbg_asserts(&xvt->fwrt); |
| iwl_get_shared_mem_conf(&xvt->fwrt); |
| |
| if (iwl_xvt_is_unified_fw(xvt)) { |
| ret = iwl_xvt_send_extended_config(xvt); |
| if (ret) { |
| IWL_ERR(xvt, "Failed to send extended_config: %d\n", |
| ret); |
| iwl_fw_dbg_stop_sync(&xvt->fwrt); |
| iwl_trans_stop_device(xvt->trans); |
| return ret; |
| } |
| } |
| iwl_dnt_start(xvt->trans); |
| |
| if (xvt->fwrt.cur_fw_img == IWL_UCODE_REGULAR && |
| (!fw_has_capa(&xvt->fw->ucode_capa, |
| IWL_UCODE_TLV_CAPA_SET_LTR_GEN2))) |
| WARN_ON(iwl_xvt_config_ltr(xvt)); |
| |
| if (!iwl_trans_dbg_ini_valid(xvt->trans)) { |
| xvt->fwrt.dump.conf = FW_DBG_INVALID; |
| /* if we have a destination, assume EARLY START */ |
| if (xvt->fw->dbg.dest_tlv) |
| xvt->fwrt.dump.conf = FW_DBG_START_FROM_ALIVE; |
| iwl_fw_start_dbg_conf(&xvt->fwrt, FW_DBG_START_FROM_ALIVE); |
| } |
| |
| return ret; |
| } |