soc: qcom: Add snapshot of ramdump driver

Add snapshot of ramdump driver as of msm-5.10 commit <3d260f03b417> ("Merge
"remoteproc: qcom: common: Panic if recovery fails"").

Change-Id: I4f70da93242911a18a3f64906dfab95c4ba89308
Signed-off-by: Yogesh Lal <ylal@codeaurora.org>
[quic_gurus@quicinc.com: Merge conflicts in Kconfig & Makefile, GPL]
Signed-off-by: Guru Das Srinagesh <quic_gurus@quicinc.com>
This commit is contained in:
Yogesh Lal 2021-08-31 19:05:56 +05:30 committed by Guru Das Srinagesh
parent a7aee12b9e
commit e834f9391e
4 changed files with 284 additions and 1 deletions

View File

@ -645,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