Merge "soc: qcom: Add snapshot of ramdump driver"

This commit is contained in:
qctecmdr 2022-09-15 16:17:01 -07:00 committed by Gerrit - the friendly Code Review server
commit fa073dcc43
5 changed files with 293 additions and 2 deletions

View File

@ -83,12 +83,14 @@ CONFIG_QCOM_MEM_BUF_DEV=m
# CONFIG_QCOM_MSM_IPCC is not set
CONFIG_QCOM_PANIC_ON_NOTIF_TIMEOUT=y
CONFIG_QCOM_PDC=m
CONFIG_QCOM_PDR_HELPERS=m
CONFIG_QCOM_PIL_INFO=m
# CONFIG_QCOM_Q6V5_ADSP is not set
CONFIG_QCOM_Q6V5_COMMON=m
# CONFIG_QCOM_Q6V5_MSS is not set
CONFIG_QCOM_Q6V5_PAS=m
# CONFIG_QCOM_Q6V5_WCSS is not set
CONFIG_QCOM_QMI_HELPERS=m
CONFIG_QCOM_RPMH=m
CONFIG_QCOM_RPROC_COMMON=m
CONFIG_QCOM_RUN_QUEUE_STATS=m

View File

@ -125,8 +125,14 @@ config QCOM_OCMEM
audio components on some Snapdragon SoCs.
config QCOM_PDR_HELPERS
tristate
tristate "Qualcomm Technologies, Inc. Protection Domain Restart(PDR) driver"
select QCOM_QMI_HELPERS
help
Qualcomm Technologies, Inc. SoCs allow for multiple protection domains (PDs)
to run on the same Q6 sub-system. This allows for services
like AVS AUDIO to have their own separate address space and
crash/recover without disrupting the other PDs running on
the same Q6 ADSP.
config QCOM_QMI_HELPERS
tristate
@ -639,6 +645,15 @@ config GH_TLMM_VM_MEM_ACCESS
Say Y here to compile the driver as a part of kernel or M to compile
as a module.
config QCOM_RAMDUMP
tristate "Qualcomm Technologies, Inc. Ramdump driver"
default n
help
This option enables the QTI ramdump driver. The ramdump driver
provides APIs to collect ramdumps which can be extracted from
userspace. Say 'Y' here to enable this driver. It's safe to say
'N' here if you don't plan on collecting ramdumps.
config QCOM_ICC_BWMON
tristate "QCOM Interconnect Bandwidth Monitor driver"
depends on ARCH_QCOM || COMPILE_TEST

View File

@ -57,4 +57,5 @@ obj-$(CONFIG_QCOM_VA_MINIDUMP) += qcom_va_minidump.o
obj-$(CONFIG_QCOM_LOGBUF_VENDOR_HOOKS) += qcom_logbuf_vh.o
obj-$(CONFIG_QCOM_HUNG_TASK_ENH) += hung_task_enh.o
obj-$(CONFIG_GH_TLMM_VM_MEM_ACCESS) += gh_tlmm_vm_mem_access.o
obj-$(CONFIG_QCOM_ICC_BWMON) += icc-bwmon.o
obj-$(CONFIG_QCOM_RAMDUMP) += qcom_ramdump.o
obj-$(CONFIG_QCOM_ICC_BWMON) += icc-bwmon.o

View File

@ -0,0 +1,229 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/elf.h>
#include <linux/wait.h>
#include <linux/cdev.h>
#include <linux/atomic.h>
#include <soc/qcom/qcom_ramdump.h>
#include <linux/devcoredump.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/devcoredump.h>
#include <linux/soc/qcom/mdt_loader.h>
#define RAMDUMP_TIMEOUT 120000
struct qcom_ramdump_desc {
void *data;
struct completion dump_done;
};
static int enable_dump_collection;
module_param(enable_dump_collection, int, 0644);
bool dump_enabled(void)
{
return enable_dump_collection;
}
EXPORT_SYMBOL(dump_enabled);
static ssize_t qcom_devcd_readv(char *buffer, loff_t offset, size_t count,
void *data, size_t datalen)
{
struct qcom_ramdump_desc *desc = data;
return memory_read_from_buffer(buffer, count, &offset, desc->data, datalen);
}
static void qcom_devcd_freev(void *data)
{
struct qcom_ramdump_desc *desc = data;
vfree(desc->data);
complete(&desc->dump_done);
}
static int qcom_devcd_dump(struct device *dev, void *data, size_t datalen, gfp_t gfp)
{
struct qcom_ramdump_desc desc;
int ret;
desc.data = data;
init_completion(&desc.dump_done);
dev_coredumpm(dev, NULL, &desc, datalen, gfp, qcom_devcd_readv, qcom_devcd_freev);
ret = wait_for_completion_timeout(&desc.dump_done, msecs_to_jiffies(RAMDUMP_TIMEOUT));
if (!ret)
dev_err(dev, "ramdump collection timed out\n");
return ret ? 0 : -ETIMEDOUT;
}
int qcom_dump(struct list_head *segs, struct device *dev)
{
struct qcom_dump_segment *segment;
void *data;
void __iomem *ptr;
size_t data_size = 0;
size_t offset = 0;
if (!segs || list_empty(segs))
return -EINVAL;
list_for_each_entry(segment, segs, node) {
pr_info("Got segment size %d\n", segment->size);
data_size += segment->size;
}
data = vmalloc(data_size);
if (!data)
return -ENOMEM;
list_for_each_entry(segment, segs, node) {
if (segment->va)
memcpy(data + offset, segment->va, segment->size);
else {
ptr = devm_ioremap(dev, segment->da, segment->size);
if (!ptr) {
dev_err(dev,
"invalid coredump segment (%pad, %zu)\n",
&segment->da, segment->size);
memset(data + offset, 0xff, segment->size);
} else
memcpy_fromio(data + offset, ptr,
segment->size);
}
offset += segment->size;
}
return qcom_devcd_dump(dev, data, data_size, GFP_KERNEL);
}
EXPORT_SYMBOL(qcom_dump);
int qcom_elf_dump(struct list_head *segs, struct device *dev)
{
struct qcom_dump_segment *segment;
struct elf32_phdr *phdr;
struct elf32_hdr *ehdr;
size_t data_size;
size_t offset;
int phnum = 0;
void *data;
void __iomem *ptr;
if (!segs || list_empty(segs))
return -EINVAL;
data_size = sizeof(*ehdr);
list_for_each_entry(segment, segs, node) {
data_size += sizeof(*phdr) + segment->size;
phnum++;
}
data = vmalloc(data_size);
if (!data)
return -ENOMEM;
pr_debug("Creating elf with size %d\n", data_size);
ehdr = data;
memset(ehdr, 0, sizeof(*ehdr));
memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
ehdr->e_ident[EI_CLASS] = ELFCLASS32;
ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
ehdr->e_ident[EI_VERSION] = EV_CURRENT;
ehdr->e_ident[EI_OSABI] = ELFOSABI_NONE;
ehdr->e_type = ET_CORE;
ehdr->e_machine = EM_NONE;
ehdr->e_version = EV_CURRENT;
ehdr->e_entry = 0;
ehdr->e_phoff = sizeof(*ehdr);
ehdr->e_ehsize = sizeof(*ehdr);
ehdr->e_phentsize = sizeof(*phdr);
ehdr->e_phnum = phnum;
phdr = data + ehdr->e_phoff;
offset = ehdr->e_phoff + sizeof(*phdr) * ehdr->e_phnum;
list_for_each_entry(segment, segs, node) {
memset(phdr, 0, sizeof(*phdr));
phdr->p_type = PT_LOAD;
phdr->p_offset = offset;
phdr->p_vaddr = segment->da;
phdr->p_paddr = segment->da;
phdr->p_filesz = segment->size;
phdr->p_memsz = segment->size;
phdr->p_flags = PF_R | PF_W | PF_X;
phdr->p_align = 0;
if (segment->va)
memcpy(data + offset, segment->va, segment->size);
else {
ptr = devm_ioremap(dev, segment->da, segment->size);
if (!ptr) {
dev_err(dev,
"invalid coredump segment (%pad, %zu)\n",
&segment->da, segment->size);
memset(data + offset, 0xff, segment->size);
} else
memcpy_fromio(data + offset, ptr,
segment->size);
}
offset += phdr->p_filesz;
phdr++;
}
return qcom_devcd_dump(dev, data, data_size, GFP_KERNEL);
}
EXPORT_SYMBOL(qcom_elf_dump);
int qcom_fw_elf_dump(struct firmware *fw, struct device *dev)
{
const struct elf32_phdr *phdrs, *phdr;
const struct elf32_hdr *ehdr;
struct qcom_dump_segment *segment;
struct list_head head;
int i;
ehdr = (struct elf32_hdr *)fw->data;
phdrs = (struct elf32_phdr *)(ehdr + 1);
INIT_LIST_HEAD(&head);
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if (phdr->p_type != PT_LOAD)
continue;
if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
continue;
if (!phdr->p_memsz)
continue;
segment = kzalloc(sizeof(*segment), GFP_KERNEL);
if (!segment)
return -ENOMEM;
segment->da = phdr->p_paddr;
segment->size = phdr->p_memsz;
list_add_tail(&segment->node, &head);
}
qcom_elf_dump(&head, dev);
return 0;
}
EXPORT_SYMBOL(qcom_fw_elf_dump);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Ramdump driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#ifndef _QCOM_RAMDUMP_HEADER
#define _QCOM_RAMDUMP_HEADER
#include <linux/kernel.h>
#include <linux/firmware.h>
struct device;
struct qcom_dump_segment {
struct list_head node;
dma_addr_t da;
void *va;
size_t size;
};
#if IS_ENABLED(CONFIG_QCOM_RAMDUMP)
extern int qcom_elf_dump(struct list_head *segs, struct device *dev);
extern int qcom_dump(struct list_head *head, struct device *dev);
extern int qcom_fw_elf_dump(struct firmware *fw, struct device *dev);
extern bool dump_enabled(void);
#else
static inline int qcom_elf_dump(struct list_head *segs, struct device *dev)
{
return -ENODEV;
}
static inline int qcom_dump(struct list_head *head, struct device *dev)
{
return -ENODEV;
}
static inline int qcom_fw_elf_dump(struct firmware *fw, struct device *dev)
{
return -ENODEV;
}
static inline bool dump_enabled(void)
{
return false;
}
#endif /* CONFIG_QCOM_RAMDUMP */
#endif