blob: ee91d61ed83a5a8c633eab5356bc97d58dcd4163 [file] [log] [blame]
// SPDX-License-Identifier: BSD-3-Clause-Clear
/**
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
*/
#include <linux/devcoredump.h>
#include <linux/platform_device.h>
#include <linux/dma-direction.h>
#include <linux/mhi.h>
#include <linux/pci.h>
#include <linux/mm.h>
#include <linux/uuid.h>
#include <linux/time.h>
#include "core.h"
#include "coredump.h"
#include "pci.h"
#include "mhi.h"
#include "debug.h"
struct ath11k_coredump_segment_info ath11k_coredump_seg_info;
EXPORT_SYMBOL(ath11k_coredump_seg_info);
static void ath11k_coredump_update_hdr(struct ath11k_base *ab,
struct ath11k_dump_file_data *file_data,
size_t header_size)
{
struct timespec64 timestamp;
strscpy(file_data->df_magic, "ATH11K-FW-DUMP",
sizeof(file_data->df_magic));
file_data->len = cpu_to_le32(header_size);
file_data->version = cpu_to_le32(ATH11K_FW_CRASH_DUMP_VERSION);
guid_gen(&file_data->guid);
ktime_get_real_ts64(&timestamp);
file_data->tv_sec = cpu_to_le64(timestamp.tv_sec);
file_data->tv_nsec = cpu_to_le64(timestamp.tv_nsec);
}
static void *ath11k_coredump_find_segment(loff_t user_offset,
struct ath11k_dump_segment *segment,
int phnum, size_t *data_left)
{
int i;
for (i = 0; i < phnum; i++, segment++) {
if (user_offset < segment->len) {
*data_left = user_offset;
return segment;
}
user_offset -= segment->len;
}
*data_left = 0;
return NULL;
}
static ssize_t ath11k_coredump_read_q6dump(char *buffer, loff_t offset, size_t count,
void *data, size_t header_size)
{
struct ath11k_coredump_state *dump_state = data;
struct ath11k_dump_segment *segments = dump_state->segments;
struct ath11k_dump_segment *seg;
void *elfcore = dump_state->header;
size_t data_left, copy_size, bytes_left = count;
void __iomem *addr;
/* Copy the header first */
if (offset < header_size) {
copy_size = header_size - offset;
copy_size = min(copy_size, bytes_left);
memcpy(buffer, elfcore + offset, copy_size);
offset += copy_size;
bytes_left -= copy_size;
buffer += copy_size;
return copy_size;
}
while (bytes_left) {
seg = ath11k_coredump_find_segment(offset - header_size,
segments, dump_state->num_seg,
&data_left);
/* End of segments check */
if (!seg) {
pr_info("Ramdump complete %lld bytes read\n", offset);
return 0;
}
if (data_left)
copy_size = min_t(size_t, bytes_left, data_left);
else
copy_size = bytes_left;
addr = seg->vaddr;
addr += data_left;
memcpy_fromio(buffer, addr, copy_size);
offset += copy_size;
buffer += copy_size;
bytes_left -= copy_size;
}
return count - bytes_left;
}
static void ath11k_coredump_free_q6dump(void *data)
{
struct ath11k_coredump_state *dump_state = data;
complete(&dump_state->dump_done);
}
void ath11k_coredump_build_inline(struct ath11k_base *ab,
struct ath11k_dump_segment *segments, int num_seg)
{
struct ath11k_coredump_state *dump_state;
struct timespec64 timestamp;
struct ath11k_dump_file_data *file_data;
size_t header_size;
struct ath11k_pci *ar_pci = (struct ath11k_pci *)ab->drv_priv;
struct device dev;
u8 *buf;
header_size = sizeof(struct ath11k_dump_file_data);
header_size += num_seg * sizeof(*segments);
header_size = PAGE_ALIGN(header_size);
buf = vzalloc(header_size);
if (!buf)
return;
ATH11K_MEMORY_STATS_INC(ab, malloc_size, header_size);
file_data = (struct ath11k_dump_file_data *)buf;
strlcpy(file_data->df_magic, "ATH11K-FW-DUMP",
sizeof(file_data->df_magic));
file_data->len = cpu_to_le32(header_size);
file_data->version = cpu_to_le32(ATH11K_FW_CRASH_DUMP_VERSION);
if (ab->hw_rev == ATH11K_HW_QCN6122) {
file_data->chip_id = ab->qmi.target.chip_id;
file_data->qrtr_id = ab->qmi.service_ins_id;
file_data->bus_id = ab->userpd_id;
dev = ab->pdev->dev;
} else {
file_data->chip_id = ar_pci->dev_id;
file_data->qrtr_id = ar_pci->ab->qmi.service_ins_id;
file_data->bus_id = pci_domain_nr(ar_pci->pdev->bus);
dev = ar_pci->pdev->dev;
}
if (file_data->bus_id > ATH11K_MAX_PCI_DOMAINS)
file_data->bus_id = ATH11K_MAX_PCI_DOMAINS;
guid_gen(&file_data->guid);
ktime_get_real_ts64(&timestamp);
file_data->tv_sec = cpu_to_le64(timestamp.tv_sec);
file_data->tv_nsec = cpu_to_le64(timestamp.tv_nsec);
file_data->num_seg = num_seg;
file_data->seg_size = sizeof(*segments);
/* copy segment details to file */
buf += offsetof(struct ath11k_dump_file_data, seg);
file_data->seg =(struct ath11k_dump_segment *)buf;
memcpy(file_data->seg, segments, num_seg * sizeof(*segments));
dump_state = vzalloc(sizeof(*dump_state));
if(!dump_state) {
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, header_size);
return;
}
dump_state->header = file_data;
dump_state->num_seg = num_seg;
dump_state->segments = segments;
init_completion(&dump_state->dump_done);
dev_coredumpm(&dev, NULL, dump_state, header_size, GFP_KERNEL,
ath11k_coredump_read_q6dump, ath11k_coredump_free_q6dump);
/* Wait until the dump is read and free is called */
wait_for_completion(&dump_state->dump_done);
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(*dump_state));
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, header_size);
vfree(dump_state);
vfree(file_data);
}
void ath11k_coredump_download_rddm(struct ath11k_base *ab)
{
struct ath11k_pci *ar_pci = (struct ath11k_pci *)ab->drv_priv;
struct mhi_controller *mhi_ctrl = ar_pci->mhi_ctrl;
struct image_info *rddm_img, *fw_img;
struct ath11k_dump_segment *segment, *seg_info;
int i, rem_seg_cnt = 0, len, num_seg, seg_sz, qdss_seg_cnt = 1;
ath11k_mhi_coredump(mhi_ctrl, false);
rddm_img = mhi_ctrl->rddm_image;
fw_img = mhi_ctrl->fbc_image;
for (i = 0; i < ab->qmi.mem_seg_count; i++) {
if (ab->qmi.target_mem[i].type == HOST_DDR_REGION_TYPE ||
(ab->qmi.target_mem[i].type == CALDB_MEM_REGION_TYPE && ab->enable_cold_boot_cal && ab->hw_params.cold_boot_calib) ||
ab->qmi.target_mem[i].type == M3_DUMP_REGION_TYPE)
rem_seg_cnt++;
}
num_seg = fw_img->entries + rddm_img->entries + rem_seg_cnt;
if (ab->is_qdss_tracing)
num_seg += qdss_seg_cnt;
len = num_seg * sizeof(*segment);
seg_info = segment = (struct ath11k_dump_segment *)vzalloc(len);
if (!seg_info)
ath11k_warn(ab, "fail to alloc memory for rddm\n");
for (i = 0; i < fw_img->entries ; i++) {
seg_sz = fw_img->mhi_buf[i].len;
seg_info->len = PAGE_ALIGN(seg_sz);
seg_info->addr = fw_img->mhi_buf[i].dma_addr;
seg_info->vaddr = fw_img->mhi_buf[i].buf;
seg_info->type = ATH11K_FW_CRASH_PAGING_DATA;
ath11k_info(ab, "seg vaddr is %px len is 0x%x type %d\n",
seg_info->vaddr, seg_info->len, seg_info->type);
seg_info++;
}
for (i = 0; i < rddm_img->entries; i++) {
seg_sz = rddm_img->mhi_buf[i].len;
seg_info->len = PAGE_ALIGN(seg_sz);
seg_info->addr = rddm_img->mhi_buf[i].dma_addr;
seg_info->vaddr = rddm_img->mhi_buf[i].buf;
ath11k_info(ab, "seg vaddr is %px len is 0x%x type %d\n",
seg_info->vaddr, seg_info->len, seg_info->type);
seg_info->type = ATH11K_FW_CRASH_RDDM_DATA;
ath11k_info(ab, "seg vaddr is %px len is 0x%x type %d\n",
seg_info->vaddr, seg_info->len, seg_info->type);
seg_info++;
}
for (i = 0; i < ab->qmi.mem_seg_count; i++) {
if (ab->qmi.target_mem[i].type == HOST_DDR_REGION_TYPE ||
ab->qmi.target_mem[i].type == M3_DUMP_REGION_TYPE) {
seg_info->len = ab->qmi.target_mem[i].size;
seg_info->addr = ab->qmi.target_mem[i].paddr;
seg_info->vaddr = ab->qmi.target_mem[i].vaddr;
seg_info->type = ATH11K_FW_REMOTE_MEM_DATA;
ath11k_info(ab, "seg vaddr is %px len is 0x%x type %d\n",
seg_info->vaddr, seg_info->len, seg_info->type);
seg_info++;
}
}
if (ab->is_qdss_tracing) {
seg_info->len = ab->qmi.qdss_mem[0].size;
seg_info->addr = ab->qmi.qdss_mem[0].paddr;
seg_info->vaddr = ab->qmi.qdss_mem[0].vaddr;
seg_info->type = ATH11K_FW_REMOTE_MEM_DATA;
ath11k_info(ab, "seg vaddr is %px len is 0x%x type %d\n",
seg_info->vaddr, seg_info->len, seg_info->type);
seg_info++;
}
for (i = 0; i < ab->qmi.mem_seg_count; i++) {
if (ab->qmi.target_mem[i].type == CALDB_MEM_REGION_TYPE && ab->enable_cold_boot_cal && ab->hw_params.cold_boot_calib) {
seg_info->len = ab->qmi.target_mem[i].size;
seg_info->addr = ab->qmi.target_mem[i].paddr;
seg_info->vaddr = ab->qmi.target_mem[i].vaddr;
seg_info->type = ATH11K_FW_REMOTE_MEM_DATA;
ath11k_info(ab, "seg vaddr is %px len is 0x%x type %d\n",
seg_info->vaddr, seg_info->len, seg_info->type);
seg_info++;
}
}
/* Crash the system once all the stats are dumped */
if(!ab->fw_recovery_support) {
ath11k_core_dump_bp_stats(ab);
ath11k_hal_dump_srng_stats(ab);
ath11k_coredump_seg_info.chip_id = ar_pci->dev_id;
ath11k_coredump_seg_info.qrtr_id = ar_pci->ab->qmi.service_ins_id;
ath11k_coredump_seg_info.bus_id = pci_domain_nr(ar_pci->pdev->bus);
ath11k_coredump_seg_info.num_seg = num_seg;
ath11k_coredump_seg_info.seg = segment;
BUG_ON(1);
} else {
ath11k_coredump_build_inline(ab, segment, num_seg);
}
vfree(segment);
}
void ath11k_coredump_qdss_dump(struct ath11k_base *ab,
struct ath11k_qmi_event_qdss_trace_save_data *event_data)
{
struct ath11k_dump_segment *segment;
struct ath11k_dump_file_data *file_data;
size_t header_size;
int i, buf_sz, num_seg;
u8 *dump;
num_seg = event_data->mem_seg_len;
header_size = sizeof(*file_data);
header_size += num_seg * sizeof(*segment);
header_size = PAGE_ALIGN(header_size);
buf_sz = header_size + ab->qmi.qdss_mem[0].size;
dump = vzalloc(buf_sz);
if (!dump) {
ath11k_warn(ab, "Failed to allocate memory for qdss dump\n");
return;
}
file_data = (struct ath11k_dump_file_data *)dump;
ath11k_coredump_update_hdr(ab, file_data, header_size);
file_data->num_seg = cpu_to_le32(num_seg);
file_data->seg_size = cpu_to_le32(sizeof(*segment));
segment = (struct ath11k_dump_segment *)dump +
offsetof(struct ath11k_dump_file_data, data);
for (i = 0; i < num_seg; i++) {
segment->len = event_data->mem_seg[i].size;
segment->vaddr = ab->qmi.qdss_mem[0].vaddr;
segment->addr = ab->qmi.qdss_mem[0].paddr;
segment->type = ATH11K_FW_QDSS_DATA;
segment++;
}
memcpy(dump + header_size, ab->qmi.qdss_mem[0].vaddr,
event_data->mem_seg[0].size);
dev_coredumpv(ab->dev, dump, buf_sz, GFP_KERNEL);
}