mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 03:27:03 +02:00
Merge "soc: qcom: Add snapshot of ramdump driver"
This commit is contained in:
commit
fa073dcc43
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
229
drivers/soc/qcom/qcom_ramdump.c
Normal file
229
drivers/soc/qcom/qcom_ramdump.c
Normal 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");
|
||||
|
||||
44
include/soc/qcom/qcom_ramdump.h
Normal file
44
include/soc/qcom/qcom_ramdump.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user