diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath12k/pci.c')
-rw-r--r-- | drivers/net/wireless/ath/ath12k/pci.c | 200 |
1 files changed, 193 insertions, 7 deletions
diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index bd269aa1740b..cf907550e6a4 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -7,6 +7,8 @@ #include <linux/module.h> #include <linux/msi.h> #include <linux/pci.h> +#include <linux/time.h> +#include <linux/vmalloc.h> #include "pci.h" #include "core.h" @@ -425,9 +427,9 @@ static void ath12k_pci_sync_ce_irqs(struct ath12k_base *ab) } } -static void ath12k_pci_ce_tasklet(struct tasklet_struct *t) +static void ath12k_pci_ce_workqueue(struct work_struct *work) { - struct ath12k_ce_pipe *ce_pipe = from_tasklet(ce_pipe, t, intr_tq); + struct ath12k_ce_pipe *ce_pipe = from_work(ce_pipe, work, intr_wq); int irq_idx = ATH12K_PCI_IRQ_CE0_OFFSET + ce_pipe->pipe_num; ath12k_ce_per_engine_service(ce_pipe->ab, ce_pipe->pipe_num); @@ -449,7 +451,7 @@ static irqreturn_t ath12k_pci_ce_interrupt_handler(int irq, void *arg) disable_irq_nosync(ab->irq_num[irq_idx]); - tasklet_schedule(&ce_pipe->intr_tq); + queue_work(system_bh_wq, &ce_pipe->intr_wq); return IRQ_HANDLED; } @@ -675,7 +677,7 @@ static int ath12k_pci_config_irq(struct ath12k_base *ab) irq_idx = ATH12K_PCI_IRQ_CE0_OFFSET + i; - tasklet_setup(&ce_pipe->intr_tq, ath12k_pci_ce_tasklet); + INIT_WORK(&ce_pipe->intr_wq, ath12k_pci_ce_workqueue); ret = request_irq(irq, ath12k_pci_ce_interrupt_handler, ab_pci->irq_flags, irq_name[irq_idx], @@ -962,7 +964,7 @@ static void ath12k_pci_aspm_restore(struct ath12k_pci *ab_pci) PCI_EXP_LNKCTL_ASPMC); } -static void ath12k_pci_kill_tasklets(struct ath12k_base *ab) +static void ath12k_pci_cancel_workqueue(struct ath12k_base *ab) { int i; @@ -972,7 +974,7 @@ static void ath12k_pci_kill_tasklets(struct ath12k_base *ab) if (ath12k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR) continue; - tasklet_kill(&ce_pipe->intr_tq); + cancel_work_sync(&ce_pipe->intr_wq); } } @@ -980,7 +982,7 @@ static void ath12k_pci_ce_irq_disable_sync(struct ath12k_base *ab) { ath12k_pci_ce_irqs_disable(ab); ath12k_pci_sync_ce_irqs(ab); - ath12k_pci_kill_tasklets(ab); + ath12k_pci_cancel_workqueue(ab); } int ath12k_pci_map_service_to_pipe(struct ath12k_base *ab, u16 service_id, @@ -1259,6 +1261,186 @@ void ath12k_pci_write32(struct ath12k_base *ab, u32 offset, u32 value) ab_pci->pci_ops->release(ab); } +#ifdef CONFIG_ATH12K_COREDUMP +static int ath12k_pci_coredump_calculate_size(struct ath12k_base *ab, u32 *dump_seg_sz) +{ + struct ath12k_pci *ab_pci = ath12k_pci_priv(ab); + struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl; + struct image_info *rddm_img, *fw_img; + struct ath12k_tlv_dump_data *dump_tlv; + enum ath12k_fw_crash_dump_type mem_type; + u32 len = 0, rddm_tlv_sz = 0, paging_tlv_sz = 0; + struct ath12k_dump_file_data *file_data; + int i; + + rddm_img = mhi_ctrl->rddm_image; + if (!rddm_img) { + ath12k_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 = ath12k_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) { + ath12k_dbg(ab, ATH12K_DBG_PCI, + "target mem region type %d not supported", + ab->qmi.target_mem[i].type); + continue; + } + + if (!ab->qmi.target_mem[i].paddr) + 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 ath12k_pci_coredump_download(struct ath12k_base *ab) +{ + struct ath12k_pci *ab_pci = ath12k_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 ath12k_fw_crash_dump_type mem_type; + struct ath12k_dump_file_data *file_data; + struct ath12k_tlv_dump_data *dump_tlv; + size_t hdr_len = sizeof(*file_data); + void *buf; + u32 dump_seg_sz[FW_CRASH_DUMP_TYPE_MAX] = { 0 }; + + ath12k_mhi_coredump(mhi_ctrl, false); + + len = ath12k_pci_coredump_calculate_size(ab, dump_seg_sz); + if (!len) { + ath12k_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->ath12k_coredump_len = len; + file_data = ab->dump_data; + strscpy(file_data->df_magic, "ATH12K-FW-DUMP", sizeof(file_data->df_magic)); + file_data->len = cpu_to_le32(len); + file_data->version = cpu_to_le32(ATH12K_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 (!dump_seg_sz[mem_idx] || mem_idx == FW_CRASH_DUMP_NONE) + 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; + + for (i = 0; i < ab->qmi.mem_seg_count; i++) { + mem_type = ath12k_coredump_get_dump_type + (ab->qmi.target_mem[i].type); + + if (mem_type != mem_idx) + continue; + + if (!ab->qmi.target_mem[i].paddr) { + ath12k_dbg(ab, ATH12K_DBG_PCI, + "Skipping mem region type %d", + ab->qmi.target_mem[i].type); + continue; + } + + memcpy_fromio(buf, ab->qmi.target_mem[i].v.ioaddr, + ab->qmi.target_mem[i].size); + buf += ab->qmi.target_mem[i].size; + } + } + + queue_work(ab->workqueue, &ab->dump_work); +} +#endif + int ath12k_pci_power_up(struct ath12k_base *ab) { struct ath12k_pci *ab_pci = ath12k_pci_priv(ab); @@ -1329,6 +1511,9 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .ce_irq_disable = ath12k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx, .panic_handler = ath12k_pci_panic_handler, +#ifdef CONFIG_ATH12K_COREDUMP + .coredump_download = ath12k_pci_coredump_download, +#endif }; static @@ -1538,6 +1723,7 @@ static void ath12k_pci_remove(struct pci_dev *pdev) set_bit(ATH12K_FLAG_UNREGISTERING, &ab->dev_flags); cancel_work_sync(&ab->reset_work); + cancel_work_sync(&ab->dump_work); ath12k_core_deinit(ab); qmi_fail: |