diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath11k/pci.c')
-rw-r--r-- | drivers/net/wireless/ath/ath11k/pci.c | 245 |
1 files changed, 233 insertions, 12 deletions
diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c index b93f04973ad7..78444f8ea153 100644 --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -1,13 +1,15 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. */ #include <linux/module.h> #include <linux/msi.h> #include <linux/pci.h> #include <linux/of.h> +#include <linux/time.h> +#include <linux/vmalloc.h> #include "pci.h" #include "core.h" @@ -610,6 +612,187 @@ static void ath11k_pci_aspm_restore(struct ath11k_pci *ab_pci) PCI_EXP_LNKCTL_ASPMC); } +#ifdef CONFIG_DEV_COREDUMP +static int ath11k_pci_coredump_calculate_size(struct ath11k_base *ab, u32 *dump_seg_sz) +{ + struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); + struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl; + struct image_info *rddm_img, *fw_img; + struct ath11k_tlv_dump_data *dump_tlv; + enum ath11k_fw_crash_dump_type mem_type; + u32 len = 0, rddm_tlv_sz = 0, paging_tlv_sz = 0; + struct ath11k_dump_file_data *file_data; + int i; + + rddm_img = mhi_ctrl->rddm_image; + if (!rddm_img) { + ath11k_err(ab, "No RDDM dump found\n"); + return 0; + } + + fw_img = mhi_ctrl->fbc_image; + + for (i = 0; i < fw_img->entries ; i++) { + if (!fw_img->mhi_buf[i].buf) + continue; + + paging_tlv_sz += fw_img->mhi_buf[i].len; + } + dump_seg_sz[FW_CRASH_DUMP_PAGING_DATA] = paging_tlv_sz; + + for (i = 0; i < rddm_img->entries; i++) { + if (!rddm_img->mhi_buf[i].buf) + continue; + + rddm_tlv_sz += rddm_img->mhi_buf[i].len; + } + dump_seg_sz[FW_CRASH_DUMP_RDDM_DATA] = rddm_tlv_sz; + + for (i = 0; i < ab->qmi.mem_seg_count; i++) { + mem_type = ath11k_coredump_get_dump_type(ab->qmi.target_mem[i].type); + + if (mem_type == FW_CRASH_DUMP_NONE) + continue; + + if (mem_type == FW_CRASH_DUMP_TYPE_MAX) { + ath11k_dbg(ab, ATH11K_DBG_PCI, + "target mem region type %d not supported", + ab->qmi.target_mem[i].type); + continue; + } + + if (!ab->qmi.target_mem[i].anyaddr) + continue; + + dump_seg_sz[mem_type] += ab->qmi.target_mem[i].size; + } + + for (i = 0; i < FW_CRASH_DUMP_TYPE_MAX; i++) { + if (!dump_seg_sz[i]) + continue; + + len += sizeof(*dump_tlv) + dump_seg_sz[i]; + } + + if (len) + len += sizeof(*file_data); + + return len; +} + +static void ath11k_pci_coredump_download(struct ath11k_base *ab) +{ + struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); + struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl; + struct image_info *rddm_img, *fw_img; + struct timespec64 timestamp; + int i, len, mem_idx; + enum ath11k_fw_crash_dump_type mem_type; + struct ath11k_dump_file_data *file_data; + struct ath11k_tlv_dump_data *dump_tlv; + size_t hdr_len = sizeof(*file_data); + void *buf; + u32 dump_seg_sz[FW_CRASH_DUMP_TYPE_MAX] = { 0 }; + + ath11k_mhi_coredump(mhi_ctrl, false); + + len = ath11k_pci_coredump_calculate_size(ab, dump_seg_sz); + if (!len) { + ath11k_warn(ab, "No crash dump data found for devcoredump"); + return; + } + + rddm_img = mhi_ctrl->rddm_image; + fw_img = mhi_ctrl->fbc_image; + + /* dev_coredumpv() requires vmalloc data */ + buf = vzalloc(len); + if (!buf) + return; + + ab->dump_data = buf; + ab->ath11k_coredump_len = len; + file_data = ab->dump_data; + strscpy(file_data->df_magic, "ATH11K-FW-DUMP", sizeof(file_data->df_magic)); + file_data->len = cpu_to_le32(len); + file_data->version = cpu_to_le32(ATH11K_FW_CRASH_DUMP_V2); + file_data->chip_id = cpu_to_le32(ab_pci->dev_id); + file_data->qrtr_id = cpu_to_le32(ab_pci->ab->qmi.service_ins_id); + file_data->bus_id = cpu_to_le32(pci_domain_nr(ab_pci->pdev->bus)); + guid_gen(&file_data->guid); + ktime_get_real_ts64(×tamp); + file_data->tv_sec = cpu_to_le64(timestamp.tv_sec); + file_data->tv_nsec = cpu_to_le64(timestamp.tv_nsec); + buf += hdr_len; + dump_tlv = buf; + dump_tlv->type = cpu_to_le32(FW_CRASH_DUMP_PAGING_DATA); + dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[FW_CRASH_DUMP_PAGING_DATA]); + buf += COREDUMP_TLV_HDR_SIZE; + + /* append all segments together as they are all part of a single contiguous + * block of memory + */ + for (i = 0; i < fw_img->entries ; i++) { + if (!fw_img->mhi_buf[i].buf) + continue; + + memcpy_fromio(buf, (void const __iomem *)fw_img->mhi_buf[i].buf, + fw_img->mhi_buf[i].len); + buf += fw_img->mhi_buf[i].len; + } + + dump_tlv = buf; + dump_tlv->type = cpu_to_le32(FW_CRASH_DUMP_RDDM_DATA); + dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[FW_CRASH_DUMP_RDDM_DATA]); + buf += COREDUMP_TLV_HDR_SIZE; + + /* append all segments together as they are all part of a single contiguous + * block of memory + */ + for (i = 0; i < rddm_img->entries; i++) { + if (!rddm_img->mhi_buf[i].buf) + continue; + + memcpy_fromio(buf, (void const __iomem *)rddm_img->mhi_buf[i].buf, + rddm_img->mhi_buf[i].len); + buf += rddm_img->mhi_buf[i].len; + } + + mem_idx = FW_CRASH_DUMP_REMOTE_MEM_DATA; + for (; mem_idx < FW_CRASH_DUMP_TYPE_MAX; mem_idx++) { + if (mem_idx == FW_CRASH_DUMP_NONE) + continue; + + for (i = 0; i < ab->qmi.mem_seg_count; i++) { + mem_type = ath11k_coredump_get_dump_type + (ab->qmi.target_mem[i].type); + + if (mem_type != mem_idx) + continue; + + if (!ab->qmi.target_mem[i].anyaddr) { + ath11k_dbg(ab, ATH11K_DBG_PCI, + "Skipping mem region type %d", + ab->qmi.target_mem[i].type); + continue; + } + + dump_tlv = buf; + dump_tlv->type = cpu_to_le32(mem_idx); + dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[mem_idx]); + buf += COREDUMP_TLV_HDR_SIZE; + + memcpy_fromio(buf, ab->qmi.target_mem[i].iaddr, + ab->qmi.target_mem[i].size); + + buf += ab->qmi.target_mem[i].size; + } + } + + queue_work(ab->workqueue, &ab->dump_work); +} +#endif + static int ath11k_pci_power_up(struct ath11k_base *ab) { struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); @@ -638,7 +821,7 @@ static int ath11k_pci_power_up(struct ath11k_base *ab) return 0; } -static void ath11k_pci_power_down(struct ath11k_base *ab) +static void ath11k_pci_power_down(struct ath11k_base *ab, bool is_suspend) { struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); @@ -649,7 +832,7 @@ static void ath11k_pci_power_down(struct ath11k_base *ab) ath11k_pci_msi_disable(ab_pci); - ath11k_mhi_stop(ab_pci); + ath11k_mhi_stop(ab_pci, is_suspend); clear_bit(ATH11K_FLAG_DEVICE_INIT_DONE, &ab->dev_flags); ath11k_pci_sw_reset(ab_pci->ab, false); } @@ -713,6 +896,9 @@ static const struct ath11k_hif_ops ath11k_pci_hif_ops = { .ce_irq_enable = ath11k_pci_hif_ce_irq_enable, .ce_irq_disable = ath11k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath11k_pcic_get_ce_msi_idx, +#ifdef CONFIG_DEV_COREDUMP + .coredump_download = ath11k_pci_coredump_download, +#endif }; static void ath11k_pci_read_hw_version(struct ath11k_base *ab, u32 *major, u32 *minor) @@ -735,7 +921,7 @@ static int ath11k_pci_set_irq_affinity_hint(struct ath11k_pci *ab_pci, if (test_bit(ATH11K_FLAG_MULTI_MSI_VECTORS, &ab_pci->ab->dev_flags)) return 0; - return irq_set_affinity_hint(ab_pci->pdev->irq, m); + return irq_set_affinity_and_hint(ab_pci->pdev->irq, m); } static int ath11k_pci_probe(struct pci_dev *pdev, @@ -743,7 +929,7 @@ static int ath11k_pci_probe(struct pci_dev *pdev, { struct ath11k_base *ab; struct ath11k_pci *ab_pci; - u32 soc_hw_version_major, soc_hw_version_minor, addr; + u32 soc_hw_version_major, soc_hw_version_minor; int ret; u32 sub_version; @@ -769,8 +955,7 @@ static int ath11k_pci_probe(struct pci_dev *pdev, * from DT. If memory is reserved from DT for FW, ath11k driver need not * allocate memory. */ - ret = of_property_read_u32(ab->dev->of_node, "memory-region", &addr); - if (!ret) + if (of_property_present(ab->dev->of_node, "memory-region")) set_bit(ATH11K_FLAG_FIXED_MEM_RGN, &ab->dev_flags); ret = ath11k_pci_claim(ab_pci, pdev); @@ -939,6 +1124,8 @@ unsupported_wcn6855_soc: return 0; err_free_irq: + /* __free_irq() expects the caller to have cleared the affinity hint */ + ath11k_pci_set_irq_affinity_hint(ab_pci, NULL); ath11k_pcic_free_irq(ab); err_ce_free: @@ -973,17 +1160,21 @@ static void ath11k_pci_remove(struct pci_dev *pdev) ath11k_pci_set_irq_affinity_hint(ab_pci, NULL); if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags)) { - ath11k_pci_power_down(ab); + ath11k_pci_power_down(ab, false); ath11k_debugfs_soc_destroy(ab); ath11k_qmi_deinit_service(ab); + ath11k_core_pm_notifier_unregister(ab); goto qmi_fail; } set_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags); + cancel_work_sync(&ab->reset_work); + cancel_work_sync(&ab->dump_work); ath11k_core_deinit(ab); qmi_fail: + ath11k_fw_destroy(ab); ath11k_mhi_unregister(ab_pci); ath11k_pcic_free_irq(ab); @@ -1001,7 +1192,7 @@ static void ath11k_pci_shutdown(struct pci_dev *pdev) struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); ath11k_pci_set_irq_affinity_hint(ab_pci, NULL); - ath11k_pci_power_down(ab); + ath11k_pci_power_down(ab, false); } static __maybe_unused int ath11k_pci_pm_suspend(struct device *dev) @@ -1038,9 +1229,39 @@ static __maybe_unused int ath11k_pci_pm_resume(struct device *dev) return ret; } -static SIMPLE_DEV_PM_OPS(ath11k_pci_pm_ops, - ath11k_pci_pm_suspend, - ath11k_pci_pm_resume); +static __maybe_unused int ath11k_pci_pm_suspend_late(struct device *dev) +{ + struct ath11k_base *ab = dev_get_drvdata(dev); + int ret; + + ret = ath11k_core_suspend_late(ab); + if (ret) + ath11k_warn(ab, "failed to late suspend core: %d\n", ret); + + /* Similar to ath11k_pci_pm_suspend(), we return success here + * even error happens, to allow system suspend/hibernation survive. + */ + return 0; +} + +static __maybe_unused int ath11k_pci_pm_resume_early(struct device *dev) +{ + struct ath11k_base *ab = dev_get_drvdata(dev); + int ret; + + ret = ath11k_core_resume_early(ab); + if (ret) + ath11k_warn(ab, "failed to early resume core: %d\n", ret); + + return ret; +} + +static const struct dev_pm_ops __maybe_unused ath11k_pci_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ath11k_pci_pm_suspend, + ath11k_pci_pm_resume) + SET_LATE_SYSTEM_SLEEP_PM_OPS(ath11k_pci_pm_suspend_late, + ath11k_pci_pm_resume_early) +}; static struct pci_driver ath11k_pci_driver = { .name = "ath11k_pci", |