soc: qcom: Add snapshot of minidump driver

This is a snapshot of the minidump driver as of msm-5.15
commit <908a517b050d> ("Merge "sched/walt: remove duplicate
definitions of rt_task_arrival_time"").

Change-Id: I47eba0f9aedb1732724b94f567404287378c6189
Signed-off-by: Huang Yiwei <quic_hyiwei@quicinc.com>
This commit is contained in:
Huang Yiwei 2022-04-06 18:59:57 +08:00
parent 686aa245d9
commit 1ce2d41f84
10 changed files with 4536 additions and 0 deletions

View File

@ -293,4 +293,75 @@ config QCOM_CPU_VENDOR_HOOKS
having scope to upstream.
If unsure, say N.
config QCOM_MINIDUMP
tristate "QCOM Minidump Support"
depends on QCOM_SMEM
help
This enables minidump feature. It allows various clients to
register to dump their state at system bad state (panic/WDT,etc.,).
Minidump would dump all registered entries, only when DLOAD mode
is enabled.
config QCOM_VA_MINIDUMP
tristate "QCOM VA Minidump Support"
depends on QCOM_MINIDUMP
help
This enables minidump feature for registering dynamic
data structures. It supports VA based registration
with minidump, which is made into an ELF. The region
for ELF is registered with legacy minidump.
config QCOM_DYN_MINIDUMP_STACK
bool "QTI Dynamic Minidump Stack Registration Support"
depends on QCOM_MINIDUMP
help
This enables minidump dynamic current stack registration feature.
It allows current task stack to be available in minidump, for cases
where CPU is unable to register it from IPI_CPU_STOP. The stack data
can be used to unwind stack frames.
config QCOM_MINIDUMP_FTRACE
bool "QCOM Minidump Support"
depends on QCOM_MINIDUMP
help
This enables ftrace buffer registration in minidump table.
On oops, ftrace content will be copied to that buffer.
This way ftrace buffer content becomes a part of minidump dump
collection.
config QCOM_MINIDUMP_PANIC_DUMP
bool "QCOM Minidump Panic dumps Support"
depends on QCOM_MINIDUMP
help
This enables collection of debug information like runqueue
statistics etc. on panic in minidump. It dumps current, CFS,
and RT runqueue tasks running on each cpu. This help in
knowing the tasks running, pending, hogging on cpu during
panic.
config QCOM_MINIDUMP_PANIC_CPU_CONTEXT
bool "QCOM Minidump Panic dumps Support"
depends on ARM64 && QCOM_MINIDUMP_PANIC_DUMP
help
This enables cpu context collection in minidump table,
on panic.
config QCOM_MINIDUMP_PSTORE
bool "QCOM Minidump Pstore dumps Support"
depends on ARM64 && QCOM_MINIDUMP
help
This enables pstore registration in minidump table.
Here, pstore framework dump logs like pmsg, dmesg,
console ftrace etc. in the given carve-out memory
(non-volatile memory) and during warm reboot these
dump will be copied outside the system on panic.
config MINIDUMP_MAX_ENTRIES
int "Minidump Maximum num of entries"
default 200
depends on QCOM_MINIDUMP
help
This defines maximum number of entries to be allocated for application
subsytem in Minidump table.
endmenu

View File

@ -33,4 +33,8 @@ obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o
obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o
obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o
obj-$(CONFIG_QCOM_CPU_VENDOR_HOOKS) += qcom_cpu_vendor_hooks.o
obj-$(CONFIG_QCOM_MINIDUMP) += minidump.o
minidump-y += msm_minidump.o minidump_log.o
minidump-$(CONFIG_QCOM_MINIDUMP_PANIC_DUMP) += minidump_memory.o
obj-$(CONFIG_QCOM_VA_MINIDUMP) += qcom_va_minidump.o
obj-$(CONFIG_QCOM_LOGBUF_VENDOR_HOOKS) += qcom_logbuf_vh.o

45
drivers/soc/qcom/elf.h Normal file
View File

@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __QCOM_ELF_COMMON_H
#define __QCOM_ELF_COMMON_H
#include <linux/elf.h>
/* Generic helpers for ELF use */
/* Return first section header */
static inline struct elf_shdr *elf_sheader(struct elfhdr *hdr)
{
return (struct elf_shdr *)((size_t)hdr + (size_t)hdr->e_shoff);
}
/* Return idx section header */
static inline struct elf_shdr *elf_section(struct elfhdr *hdr, int idx)
{
return &elf_sheader(hdr)[idx];
}
/* Return first program header */
static inline struct elf_phdr *elf_pheader(struct elfhdr *hdr)
{
return (struct elf_phdr *)((size_t)hdr + (size_t)hdr->e_phoff);
}
/* Return idx program header */
static inline struct elf_phdr *elf_program(struct elfhdr *hdr, int idx)
{
return &elf_pheader(hdr)[idx];
}
/* Return section's string table header */
static inline char *elf_str_table(struct elfhdr *hdr)
{
if (hdr->e_shstrndx == SHN_UNDEF)
return NULL;
return (char *)hdr + elf_section(hdr, hdr->e_shstrndx)->sh_offset;
}
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define MD_MEMINFO_PAGES 1
#define MD_SLABINFO_PAGES 8
#ifdef CONFIG_PAGE_OWNER
extern size_t md_pageowner_dump_size;
extern char *md_pageowner_dump_addr;
#endif
#ifdef CONFIG_SLUB_DEBUG
extern size_t md_slabowner_dump_size;
extern char *md_slabowner_dump_addr;
#endif
extern size_t md_dma_buf_info_size;
extern char *md_dma_buf_info_addr;
extern size_t md_dma_buf_procs_size;
extern char *md_dma_buf_procs_addr;
void md_dump_meminfo(struct seq_buf *m);
#ifdef CONFIG_SLUB_DEBUG
void md_dump_slabinfo(struct seq_buf *m);
#else
static inline void md_dump_slabinfo(struct seq_buf *m) {}
#endif
bool md_register_memory_dump(int size, char *name);
bool md_unregister_memory_dump(char *name);
#ifdef CONFIG_PAGE_OWNER
bool is_page_owner_enabled(void);
void md_dump_pageowner(char *addr, size_t dump_size);
void md_debugfs_pageowner(struct dentry *minidump_dir);
#else
static inline bool is_page_owner_enabled(void) { return false; }
static inline void md_dump_pageowner(char *addr, size_t dump_size) {}
static inline void md_debugfs_pageowner(struct dentry *minidump_dir) {}
#endif
#ifdef CONFIG_SLUB_DEBUG
bool is_slub_debug_enabled(void);
void md_dump_slabowner(char *addr, size_t dump_size);
void md_debugfs_slabowner(struct dentry *minidump_dir);
#else
static inline bool is_slub_debug_enabled(void) { return false; }
static inline void md_dump_slabowner(char *addr, size_t dump_size) {}
static inline void md_debugfs_slabowner(struct dentry *minidump_dir) {}
#endif
void md_dma_buf_info(char *m, size_t dump_size);
void md_debugfs_dmabufinfo(struct dentry *minidump_dir);
void md_dma_buf_procs(char *m, size_t dump_size);
void md_debugfs_dmabufprocs(struct dentry *minidump_dir);

View File

@ -0,0 +1,82 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2019, 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __MINIDUMP_PRIVATE_H
#define __MINIDUMP_PRIVATE_H
#define MD_REVISION 1
#define SBL_MINIDUMP_SMEM_ID 602
#define MAX_NUM_OF_SS 10
#define MD_SS_HLOS_ID 0
#define SMEM_ENTRY_SIZE 40
/* Bootloader has 16 byte support, 4 bytes reserved for itself */
#define MAX_REGION_NAME_LENGTH 16
#define MD_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0)
#define MD_REGION_INVALID ('I' << 24 | 'N' << 16 | 'V' << 8 | 'A' << 0)
#define MD_REGION_INIT ('I' << 24 | 'N' << 16 | 'I' << 8 | 'T' << 0)
#define MD_REGION_NOINIT 0
#define MD_SS_ENCR_REQ (0 << 24 | 'Y' << 16 | 'E' << 8 | 'S' << 0)
#define MD_SS_ENCR_NOTREQ (0 << 24 | 0 << 16 | 'N' << 8 | 'R' << 0)
#define MD_SS_ENCR_NONE ('N' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0)
#define MD_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0)
#define MD_SS_ENCR_START ('S' << 24 | 'T' << 16 | 'R' << 8 | 'T' << 0)
#define MD_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0)
#define MD_SS_DISABLED ('D' << 24 | 'S' << 16 | 'B' << 8 | 'L' << 0)
/**
* md_ss_region - Minidump region
* @name : Name of the region to be dumped
* @seq_num: : Use to differentiate regions with same name.
* @md_valid : This entry to be dumped (if set to 1)
* @region_base_address : Physical address of region to be dumped
* @region_size : Size of the region
*/
struct md_ss_region {
char name[MAX_REGION_NAME_LENGTH];
u32 seq_num;
u32 md_valid;
u64 region_base_address;
u64 region_size;
};
/**
* md_ss_toc: Sub system SMEM Table of content
* @md_ss_toc_init : SS toc init status
* @md_ss_enable_status : if set to 1, Bootloader would dump this SS regions
* @encryption_status: Encryption status for this subsystem
* @encryption_required : Decides to encrypt the SS regions or not
* @ss_region_count : Number of regions added in this SS toc
* @md_ss_smem_regions_baseptr : regions base pointer of the Subsystem
*/
struct md_ss_toc {
u32 md_ss_toc_init;
u32 md_ss_enable_status;
u32 encryption_status;
u32 encryption_required;
u32 ss_region_count;
u64 md_ss_smem_regions_baseptr;
};
/**
* md_global_toc: Global Table of Content
* @md_toc_init : Global Minidump init status
* @md_revision : Minidump revision
* @md_enable_status : Minidump enable status
* @md_ss_toc : Array of subsystems toc
*/
struct md_global_toc {
u32 md_toc_init;
u32 md_revision;
u32 md_enable_status;
struct md_ss_toc md_ss_toc[MAX_NUM_OF_SS];
};
int msm_minidump_log_init(void);
#endif

View File

@ -0,0 +1,661 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "Minidump: " fmt
#include <linux/init.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/elf.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/android_debug_symbols.h>
#include <linux/soc/qcom/smem.h>
#include <soc/qcom/minidump.h>
#include "minidump_private.h"
#include "elf.h"
#define MAX_NUM_ENTRIES (CONFIG_MINIDUMP_MAX_ENTRIES + 1)
#define MAX_STRTBL_SIZE (MAX_NUM_ENTRIES * MAX_REGION_NAME_LENGTH)
/**
* md_table : Local Minidump toc holder
* @num_regions : Number of regions requested
* @md_ss_toc : HLOS toc pointer
* @md_gbl_toc : Global toc pointer
* @md_regions : HLOS regions base pointer
* @entry : array of HLOS regions requested
*/
struct md_table {
u32 revision;
u32 num_regions;
struct md_ss_toc *md_ss_toc;
struct md_global_toc *md_gbl_toc;
struct md_ss_region *md_regions;
struct md_region entry[MAX_NUM_ENTRIES];
};
/**
* md_elfhdr: Minidump table elf header
* @ehdr: elf main header
* @shdr: Section header
* @phdr: Program header
* @elf_offset: section offset in elf
* @strtable_idx: string table current index position
*/
struct md_elfhdr {
struct elfhdr *ehdr;
struct elf_shdr *shdr;
struct elf_phdr *phdr;
u64 elf_offset;
u64 strtable_idx;
};
/* Protect elfheader and smem table from deferred calls contention */
static DEFINE_SPINLOCK(mdt_lock);
static DEFINE_RWLOCK(mdt_remove_lock);
static struct md_table minidump_table;
static struct md_elfhdr minidump_elfheader;
static int first_removed_entry = INT_MAX;
static bool md_init_done;
/* Number of pending entries to be added in ToC regions */
static unsigned int pendings;
static inline char *elf_lookup_string(struct elfhdr *hdr, int offset)
{
char *strtab = elf_str_table(hdr);
if ((strtab == NULL) || (minidump_elfheader.strtable_idx < offset))
return NULL;
return strtab + offset;
}
static inline unsigned int set_section_name(const char *name)
{
char *strtab = elf_str_table(minidump_elfheader.ehdr);
int idx = minidump_elfheader.strtable_idx;
int ret = 0;
if ((strtab == NULL) || (name == NULL))
return 0;
ret = idx;
idx += strscpy((strtab + idx), name, MAX_REGION_NAME_LENGTH);
minidump_elfheader.strtable_idx = idx + 1;
return ret;
}
struct md_region *md_get_region(char *name)
{
struct md_region *mdr;
int i, regno = minidump_table.num_regions;
for (i = 0; i < regno; i++) {
mdr = &minidump_table.entry[i];
if (!strcmp(mdr->name, name))
return mdr;
}
return NULL;
}
static inline int md_region_num(const char *name, int *seqno)
{
struct md_ss_region *mde = minidump_table.md_regions;
int i, regno = minidump_table.md_ss_toc->ss_region_count;
int ret = -EINVAL;
for (i = 0; i < regno; i++, mde++) {
if (!strcmp(mde->name, name)) {
ret = i;
if (mde->seq_num > *seqno)
*seqno = mde->seq_num;
}
}
return ret;
}
static inline int md_entry_num(const struct md_region *entry)
{
struct md_region *mdr;
int i, regno = minidump_table.num_regions;
for (i = 0; i < regno; i++) {
mdr = &minidump_table.entry[i];
if (!strcmp(mdr->name, entry->name))
return i;
}
return -ENOENT;
}
/* Update Mini dump table in SMEM */
static void md_update_ss_toc(const struct md_region *entry)
{
struct md_ss_region *mdr;
struct elfhdr *hdr = minidump_elfheader.ehdr;
struct elf_shdr *shdr = elf_section(hdr, hdr->e_shnum++);
struct elf_phdr *phdr = elf_program(hdr, hdr->e_phnum++);
int seq = 0, reg_cnt = minidump_table.md_ss_toc->ss_region_count;
mdr = &minidump_table.md_regions[reg_cnt];
strscpy(mdr->name, entry->name, sizeof(mdr->name));
mdr->region_base_address = entry->phys_addr;
mdr->region_size = entry->size;
if (md_region_num(entry->name, &seq) >= 0)
mdr->seq_num = seq + 1;
/* Update elf header */
shdr->sh_type = SHT_PROGBITS;
shdr->sh_name = set_section_name(mdr->name);
shdr->sh_addr = (elf_addr_t)entry->virt_addr;
shdr->sh_size = mdr->region_size;
shdr->sh_flags = SHF_WRITE;
shdr->sh_offset = minidump_elfheader.elf_offset;
shdr->sh_entsize = 0;
phdr->p_type = PT_LOAD;
phdr->p_offset = minidump_elfheader.elf_offset;
phdr->p_vaddr = entry->virt_addr;
phdr->p_paddr = entry->phys_addr;
phdr->p_filesz = phdr->p_memsz = mdr->region_size;
phdr->p_flags = PF_R | PF_W;
minidump_elfheader.elf_offset += shdr->sh_size;
mdr->md_valid = MD_REGION_VALID;
minidump_table.md_ss_toc->ss_region_count++;
}
bool msm_minidump_enabled(void)
{
bool ret = false;
unsigned long flags;
spin_lock_irqsave(&mdt_lock, flags);
if (minidump_table.md_ss_toc &&
(minidump_table.md_ss_toc->md_ss_enable_status ==
MD_SS_ENABLED))
ret = true;
spin_unlock_irqrestore(&mdt_lock, flags);
return ret;
}
EXPORT_SYMBOL(msm_minidump_enabled);
int msm_minidump_get_available_region(void)
{
int res;
unsigned long flags;
spin_lock_irqsave(&mdt_lock, flags);
res = MAX_NUM_ENTRIES - minidump_table.num_regions;
spin_unlock_irqrestore(&mdt_lock, flags);
return res;
}
EXPORT_SYMBOL(msm_minidump_get_available_region);
static inline int validate_region(const struct md_region *entry)
{
if (!entry)
return -EINVAL;
if ((strlen(entry->name) > MAX_NAME_LENGTH) || !entry->virt_addr ||
(!IS_ALIGNED(entry->size, 4))) {
printk_deferred("Invalid entry details\n");
return -EINVAL;
}
return 0;
}
int msm_minidump_update_region(int regno, const struct md_region *entry)
{
int ret = 0;
struct md_region *mdr;
struct md_ss_region *mdssr;
struct elfhdr *hdr = minidump_elfheader.ehdr;
struct elf_shdr *shdr;
struct elf_phdr *phdr;
unsigned long flags;
/* Ensure that init completes before we update regions */
if (!smp_load_acquire(&md_init_done))
return -EINVAL;
if (validate_region(entry) || (regno >= MAX_NUM_ENTRIES))
return -EINVAL;
read_lock_irqsave(&mdt_remove_lock, flags);
if (regno >= first_removed_entry) {
printk_deferred("Region:[%s] was moved\n", entry->name);
ret = -EINVAL;
goto err_unlock;
}
ret = md_entry_num(entry);
if (ret < 0) {
printk_deferred("Region:[%s] does not exist to update.\n", entry->name);
goto err_unlock;
}
mdr = &minidump_table.entry[regno];
mdr->virt_addr = entry->virt_addr;
mdr->phys_addr = entry->phys_addr;
mdssr = &minidump_table.md_regions[regno + 1];
mdssr->region_base_address = entry->phys_addr;
shdr = elf_section(hdr, regno + 4);
phdr = elf_program(hdr, regno + 1);
shdr->sh_addr = (elf_addr_t)entry->virt_addr;
phdr->p_vaddr = entry->virt_addr;
phdr->p_paddr = entry->phys_addr;
err_unlock:
read_unlock_irqrestore(&mdt_remove_lock, flags);
return ret;
}
EXPORT_SYMBOL(msm_minidump_update_region);
int msm_minidump_add_region(const struct md_region *entry)
{
u32 entries;
u32 toc_init;
struct md_region *mdr;
unsigned long flags;
if (validate_region(entry))
return -EINVAL;
spin_lock_irqsave(&mdt_lock, flags);
if (md_entry_num(entry) >= 0) {
spin_unlock_irqrestore(&mdt_lock, flags);
printk_deferred("Entry name already exist.\n");
return -EEXIST;
}
entries = minidump_table.num_regions;
if (entries >= MAX_NUM_ENTRIES) {
printk_deferred("Maximum entries reached.\n");
spin_unlock_irqrestore(&mdt_lock, flags);
return -ENOMEM;
}
toc_init = 0;
if (minidump_table.md_ss_toc &&
(minidump_table.md_ss_toc->md_ss_enable_status ==
MD_SS_ENABLED)) {
toc_init = 1;
if (minidump_table.md_ss_toc->ss_region_count >= MAX_NUM_ENTRIES) {
spin_unlock_irqrestore(&mdt_lock, flags);
printk_deferred("Maximum regions in minidump table reached.\n");
return -ENOMEM;
}
}
mdr = &minidump_table.entry[entries];
strscpy(mdr->name, entry->name, sizeof(mdr->name));
mdr->virt_addr = entry->virt_addr;
mdr->phys_addr = entry->phys_addr;
mdr->size = entry->size;
mdr->id = entry->id;
minidump_table.num_regions = entries + 1;
if (toc_init)
md_update_ss_toc(entry);
else
pendings++;
spin_unlock_irqrestore(&mdt_lock, flags);
return entries;
}
EXPORT_SYMBOL(msm_minidump_add_region);
int msm_minidump_clear_headers(const struct md_region *entry)
{
struct elfhdr *hdr = minidump_elfheader.ehdr;
struct elf_shdr *shdr = NULL, *tshdr = NULL;
struct elf_phdr *phdr = NULL, *tphdr = NULL;
int pidx, shidx, strln, i;
char *shname;
u64 esize;
esize = entry->size;
for (i = 0; i < hdr->e_phnum; i++) {
phdr = elf_program(hdr, i);
if ((phdr->p_paddr == entry->phys_addr) &&
(phdr->p_memsz == entry->size))
break;
}
if (i == hdr->e_phnum) {
printk_deferred("Cannot find entry in elf\n");
return -EINVAL;
}
pidx = i;
for (i = 0; i < hdr->e_shnum; i++) {
shdr = elf_section(hdr, i);
shname = elf_lookup_string(hdr, shdr->sh_name);
if (shname && !strcmp(shname, entry->name))
if ((shdr->sh_addr == entry->virt_addr) &&
(shdr->sh_size == entry->size))
break;
}
if (i == hdr->e_shnum) {
printk_deferred("Cannot find entry in elf\n");
return -EINVAL;
}
shidx = i;
if (shdr->sh_offset != phdr->p_offset) {
printk_deferred("Invalid entry details in elf, Minidump broken..\n");
return -EINVAL;
}
/* Clear name in string table */
strln = strlen(shname) + 1;
memmove(shname, shname + strln,
(minidump_elfheader.strtable_idx - shdr->sh_name));
minidump_elfheader.strtable_idx -= strln;
/* Clear program header */
tphdr = elf_program(hdr, pidx);
for (i = pidx; i < hdr->e_phnum - 1; i++) {
tphdr = elf_program(hdr, i + 1);
phdr = elf_program(hdr, i);
memcpy(phdr, tphdr, sizeof(struct elf_phdr));
phdr->p_offset = phdr->p_offset - esize;
}
memset(tphdr, 0, sizeof(struct elf_phdr));
hdr->e_phnum--;
/* Clear section header */
tshdr = elf_section(hdr, shidx);
for (i = shidx; i < hdr->e_shnum - 1; i++) {
tshdr = elf_section(hdr, i + 1);
shdr = elf_section(hdr, i);
memcpy(shdr, tshdr, sizeof(struct elf_shdr));
shdr->sh_offset -= esize;
shdr->sh_name -= strln;
}
memset(tshdr, 0, sizeof(struct elf_shdr));
hdr->e_shnum--;
minidump_elfheader.elf_offset -= esize;
return 0;
}
int msm_minidump_remove_region(const struct md_region *entry)
{
int rcount, ecount, seq = 0, rgno, entryno, ret;
unsigned long flags;
if (!entry || !minidump_table.md_ss_toc ||
(minidump_table.md_ss_toc->md_ss_enable_status !=
MD_SS_ENABLED))
return -EINVAL;
spin_lock_irqsave(&mdt_lock, flags);
write_lock(&mdt_remove_lock);
ret = md_entry_num(entry);
if (ret < 0) {
write_unlock(&mdt_remove_lock);
spin_unlock_irqrestore(&mdt_lock, flags);
printk_deferred("Not able to find the entry %s in table\n", entry->name);
return ret;
}
entryno = ret;
rgno = md_region_num(entry->name, &seq);
if (rgno < 0) {
write_unlock(&mdt_remove_lock);
spin_unlock_irqrestore(&mdt_lock, flags);
printk_deferred("Not able to find the region %s (%d,%d) in table\n",
entry->name, entryno, rgno);
return -EINVAL;
}
ecount = minidump_table.num_regions;
rcount = minidump_table.md_ss_toc->ss_region_count;
if (first_removed_entry > entryno)
first_removed_entry = entryno;
minidump_table.md_ss_toc->md_ss_toc_init = 0;
/* Remove entry from: entry list, ss region list and elf header */
memmove(&minidump_table.entry[entryno],
&minidump_table.entry[entryno + 1],
((ecount - entryno - 1) * sizeof(struct md_region)));
memset(&minidump_table.entry[ecount - 1], 0, sizeof(struct md_region));
memmove(&minidump_table.md_regions[rgno],
&minidump_table.md_regions[rgno + 1],
((rcount - rgno - 1) * sizeof(struct md_ss_region)));
memset(&minidump_table.md_regions[rcount - 1], 0,
sizeof(struct md_ss_region));
ret = msm_minidump_clear_headers(entry);
if (ret)
goto out;
minidump_table.md_ss_toc->ss_region_count--;
minidump_table.md_ss_toc->md_ss_toc_init = 1;
minidump_table.num_regions--;
out:
write_unlock(&mdt_remove_lock);
spin_unlock_irqrestore(&mdt_lock, flags);
if (ret)
printk_deferred("Minidump is broken..disable Minidump collection\n");
return ret;
}
EXPORT_SYMBOL(msm_minidump_remove_region);
static int msm_minidump_add_header(void)
{
struct md_ss_region *mdreg = &minidump_table.md_regions[0];
struct elfhdr *ehdr;
struct elf_shdr *shdr;
struct elf_phdr *phdr;
unsigned int strtbl_off, elfh_size, phdr_off;
char *banner, *linux_banner;
linux_banner = android_debug_symbol(ADS_LINUX_BANNER);
/* Header buffer contains:
* elf header, MAX_NUM_ENTRIES+4 of section and program elf headers,
* string table section and linux banner.
*/
elfh_size = sizeof(*ehdr) + MAX_STRTBL_SIZE +
(strlen(linux_banner) + 1) +
((sizeof(*shdr) + sizeof(*phdr))
* (MAX_NUM_ENTRIES + 4));
elfh_size = ALIGN(elfh_size, 4);
minidump_elfheader.ehdr = kzalloc(elfh_size, GFP_KERNEL);
if (!minidump_elfheader.ehdr)
return -ENOMEM;
strscpy(mdreg->name, "KELF_HEADER", sizeof(mdreg->name));
mdreg->region_base_address = virt_to_phys(minidump_elfheader.ehdr);
mdreg->region_size = elfh_size;
ehdr = minidump_elfheader.ehdr;
/* Assign section/program headers offset */
minidump_elfheader.shdr = shdr = (struct elf_shdr *)(ehdr + 1);
minidump_elfheader.phdr = phdr =
(struct elf_phdr *)(shdr + MAX_NUM_ENTRIES);
phdr_off = sizeof(*ehdr) + (sizeof(*shdr) * MAX_NUM_ENTRIES);
memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
ehdr->e_ident[EI_CLASS] = ELF_CLASS;
ehdr->e_ident[EI_DATA] = ELF_DATA;
ehdr->e_ident[EI_VERSION] = EV_CURRENT;
ehdr->e_ident[EI_OSABI] = ELF_OSABI;
ehdr->e_type = ET_CORE;
ehdr->e_machine = ELF_ARCH;
ehdr->e_version = EV_CURRENT;
ehdr->e_ehsize = sizeof(*ehdr);
ehdr->e_phoff = phdr_off;
ehdr->e_phentsize = sizeof(*phdr);
ehdr->e_shoff = sizeof(*ehdr);
ehdr->e_shentsize = sizeof(*shdr);
ehdr->e_shstrndx = 1;
minidump_elfheader.elf_offset = elfh_size;
/*
* First section header should be NULL,
* 2nd section is string table.
*/
minidump_elfheader.strtable_idx = 1;
strtbl_off = sizeof(*ehdr) +
((sizeof(*phdr) + sizeof(*shdr)) * MAX_NUM_ENTRIES);
shdr++;
shdr->sh_type = SHT_STRTAB;
shdr->sh_offset = (elf_addr_t)strtbl_off;
shdr->sh_size = MAX_STRTBL_SIZE;
shdr->sh_entsize = 0;
shdr->sh_flags = 0;
shdr->sh_name = set_section_name("STR_TBL");
shdr++;
/* 3rd section is for minidump_table VA, used by parsers */
shdr->sh_type = SHT_PROGBITS;
shdr->sh_entsize = 0;
shdr->sh_flags = 0;
shdr->sh_addr = (elf_addr_t)&minidump_table;
shdr->sh_name = set_section_name("minidump_table");
shdr++;
/* 4th section is linux banner */
banner = (char *)ehdr + strtbl_off + MAX_STRTBL_SIZE;
strscpy(banner, linux_banner, MAX_STRTBL_SIZE);
shdr->sh_type = SHT_PROGBITS;
shdr->sh_offset = (elf_addr_t)(strtbl_off + MAX_STRTBL_SIZE);
shdr->sh_size = strlen(linux_banner) + 1;
shdr->sh_addr = (elf_addr_t)linux_banner;
shdr->sh_entsize = 0;
shdr->sh_flags = SHF_WRITE;
shdr->sh_name = set_section_name("linux_banner");
phdr->p_type = PT_LOAD;
phdr->p_offset = (elf_addr_t)(strtbl_off + MAX_STRTBL_SIZE);
phdr->p_vaddr = (elf_addr_t)linux_banner;
phdr->p_paddr = virt_to_phys(linux_banner);
phdr->p_filesz = phdr->p_memsz = strlen(linux_banner) + 1;
phdr->p_flags = PF_R | PF_W;
/* Update headers count*/
ehdr->e_phnum = 1;
ehdr->e_shnum = 4;
mdreg->md_valid = MD_REGION_VALID;
return 0;
}
static int msm_minidump_driver_remove(struct platform_device *pdev)
{
/* TO-DO.
*Free the required resources and set the global
* variables as minidump is not initialized.
*/
return 0;
}
static int msm_minidump_driver_probe(struct platform_device *pdev)
{
unsigned int i;
size_t size;
struct md_region *mdr;
struct md_global_toc *md_global_toc;
struct md_ss_toc *md_ss_toc;
unsigned long flags;
/* Get Minidump table */
md_global_toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID,
&size);
if (IS_ERR_OR_NULL(md_global_toc)) {
pr_err("SMEM is not initialized.\n");
return PTR_ERR(md_global_toc);
}
/*Check global minidump support initialization */
if (!md_global_toc->md_toc_init) {
pr_err("System Minidump TOC not initialized\n");
return -ENODEV;
}
minidump_table.md_gbl_toc = md_global_toc;
minidump_table.revision = md_global_toc->md_revision;
md_ss_toc = &md_global_toc->md_ss_toc[MD_SS_HLOS_ID];
md_ss_toc->encryption_status = MD_SS_ENCR_DONE;
md_ss_toc->encryption_required = MD_SS_ENCR_NOTREQ;
minidump_table.md_ss_toc = md_ss_toc;
minidump_table.md_regions = devm_kzalloc(&pdev->dev, (MAX_NUM_ENTRIES *
sizeof(struct md_ss_region)), GFP_KERNEL);
if (!minidump_table.md_regions)
return -ENOMEM;
md_ss_toc->md_ss_smem_regions_baseptr =
virt_to_phys(minidump_table.md_regions);
/* First entry would be ELF header */
md_ss_toc->ss_region_count = 1;
msm_minidump_add_header();
/* Add pending entries to HLOS TOC */
spin_lock_irqsave(&mdt_lock, flags);
md_ss_toc->md_ss_toc_init = 1;
md_ss_toc->md_ss_enable_status = MD_SS_ENABLED;
for (i = 0; i < pendings; i++) {
mdr = &minidump_table.entry[i];
md_update_ss_toc(mdr);
}
pendings = 0;
spin_unlock_irqrestore(&mdt_lock, flags);
/* All updates above should be visible, before init completes */
smp_store_release(&md_init_done, true);
msm_minidump_log_init();
pr_info("Enabled with max number of regions %d\n",
CONFIG_MINIDUMP_MAX_ENTRIES);
return 0;
}
static const struct of_device_id msm_minidump_of_match[] = {
{ .compatible = "qcom,minidump" },
{ }
};
MODULE_DEVICE_TABLE(of, msm_minidump_of_match);
static struct platform_driver msm_minidump_driver = {
.driver = {
.name = "qcom-minidump",
.of_match_table = msm_minidump_of_match,
},
.probe = msm_minidump_driver_probe,
.remove = msm_minidump_driver_remove,
};
module_platform_driver(msm_minidump_driver);
MODULE_DESCRIPTION("MSM Mini Dump Driver");
MODULE_IMPORT_NS(MINIDUMP);
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,788 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "va-minidump: %s: " fmt, __func__
#include <linux/init.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/of_reserved_mem.h>
#include <linux/dma-mapping.h>
#include <linux/dma-direct.h>
#include <linux/elf.h>
#include <linux/slab.h>
#include <linux/panic_notifier.h>
#include <soc/qcom/minidump.h>
#include "elf.h"
struct va_md_tree_node {
struct va_md_entry entry;
int lindex;
int rindex;
};
struct va_md_elf_info {
unsigned long ehdr;
unsigned long shdr_cnt;
unsigned long phdr_cnt;
unsigned long pload_size;
unsigned long str_tbl_size;
};
#define VA_MD_VADDR_MARKER -1
#define VA_MD_CB_MARKER -2
#define MAX_ELF_SECTION 0xFFFFU
struct va_minidump_data {
phys_addr_t mem_phys_addr;
unsigned int total_mem_size;
unsigned long elf_mem;
unsigned int num_sections;
unsigned long str_tbl_idx;
struct va_md_elf_info elf;
struct md_region md_entry;
bool in_oops_handler;
bool va_md_minidump_reg;
bool va_md_init;
struct list_head va_md_list;
struct kset *va_md_kset;
};
/*
* Incase, client uses stack memory to allocate
* notifier block, we have to make a copy.
*/
struct notifier_block_list {
struct notifier_block nb;
struct list_head nb_list;
};
struct va_md_s_data {
struct kobject s_kobj;
struct atomic_notifier_head va_md_s_notif_list;
struct list_head va_md_s_list;
struct list_head va_md_s_nb_list;
bool enable;
};
struct va_minidump_data va_md_data;
static DEFINE_MUTEX(va_md_lock);
#define to_va_md_attr(_attr) container_of(_attr, struct va_md_attribute, attr)
#define to_va_md_s_data(obj) container_of(obj, struct va_md_s_data, s_kobj)
struct va_md_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count);
};
static ssize_t attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct va_md_attribute *va_md_attr = to_va_md_attr(attr);
ssize_t ret = -EIO;
if (va_md_attr->show)
ret = va_md_attr->show(kobj, attr, buf);
return ret;
}
static ssize_t attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct va_md_attribute *va_md_attr = to_va_md_attr(attr);
ssize_t ret = -EIO;
if (va_md_attr->store)
ret = va_md_attr->store(kobj, attr, buf, count);
return ret;
}
static const struct sysfs_ops va_md_sysfs_ops = {
.show = attr_show,
.store = attr_store,
};
static struct kobj_type va_md_kobj_type = {
.sysfs_ops = &va_md_sysfs_ops,
};
static ssize_t enable_show(struct kobject *kobj, struct attribute *this, char *buf)
{
struct va_md_s_data *vamd_sdata = to_va_md_s_data(kobj);
return scnprintf(buf, PAGE_SIZE, "enable: %u\n", vamd_sdata->enable);
}
static ssize_t enable_store(struct kobject *kobj, struct attribute *this,
const char *buf, size_t count)
{
struct va_md_s_data *vamd_sdata = to_va_md_s_data(kobj);
bool val;
int ret;
ret = kstrtobool(buf, &val);
if (ret) {
pr_err("Invalid value passed\n");
return ret;
}
vamd_sdata->enable = val;
return count;
}
static struct va_md_attribute va_md_s_attr = __ATTR_RW(enable);
static struct attribute *va_md_s_attrs[] = {
&va_md_s_attr.attr,
NULL
};
static struct attribute_group va_md_s_attr_group = {
.attrs = va_md_s_attrs,
};
bool qcom_va_md_enabled(void)
{
/*
* Ensure that minidump is enabled and va-minidump is initialized
* before we start registration.
*/
return msm_minidump_enabled() && smp_load_acquire(&va_md_data.va_md_init);
}
EXPORT_SYMBOL(qcom_va_md_enabled);
int qcom_va_md_register(char *name, struct notifier_block *nb)
{
int ret = 0;
struct va_md_s_data *va_md_s_data;
struct notifier_block_list *nbl, *temp_nbl;
struct kobject *kobj;
if (!qcom_va_md_enabled()) {
pr_err("qcom va minidump driver is not initialized\n");
return -ENODEV;
}
nbl = kzalloc(sizeof(struct notifier_block_list), GFP_KERNEL);
if (!nbl)
return -ENOMEM;
nbl->nb = *nb;
mutex_lock(&va_md_lock);
kobj = kset_find_obj(va_md_data.va_md_kset, name);
if (kobj) {
pr_warn("subsystem: %s is already registered\n", name);
kobject_put(kobj);
va_md_s_data = to_va_md_s_data(kobj);
goto register_notifier;
}
va_md_s_data = kzalloc(sizeof(*va_md_s_data), GFP_KERNEL);
if (!va_md_s_data) {
ret = -ENOMEM;
kfree(nbl);
goto out;
}
va_md_s_data->s_kobj.kset = va_md_data.va_md_kset;
ret = kobject_init_and_add(&va_md_s_data->s_kobj, &va_md_kobj_type,
&va_md_data.va_md_kset->kobj, name);
if (ret) {
pr_err("%s: Error in kobject creation\n", __func__);
kobject_put(&va_md_s_data->s_kobj);
kfree(nbl);
goto out;
}
kobject_uevent(&va_md_s_data->s_kobj, KOBJ_ADD);
ret = sysfs_create_group(&va_md_s_data->s_kobj, &va_md_s_attr_group);
if (ret) {
pr_err("%s: Error in creation sysfs_create_group\n", __func__);
kobject_put(&va_md_s_data->s_kobj);
kfree(nbl);
goto out;
}
ATOMIC_INIT_NOTIFIER_HEAD(&va_md_s_data->va_md_s_notif_list);
INIT_LIST_HEAD(&va_md_s_data->va_md_s_nb_list);
va_md_s_data->enable = false;
list_add_tail(&va_md_s_data->va_md_s_list, &va_md_data.va_md_list);
register_notifier:
list_for_each_entry(temp_nbl, &va_md_s_data->va_md_s_nb_list, nb_list) {
if (temp_nbl->nb.notifier_call == nbl->nb.notifier_call) {
pr_warn("subsystem:%s callback is already registered\n", name);
kfree(nbl);
ret = -EEXIST;
goto out;
}
}
atomic_notifier_chain_register(&va_md_s_data->va_md_s_notif_list, &nbl->nb);
list_add_tail(&nbl->nb_list, &va_md_s_data->va_md_s_nb_list);
out:
mutex_unlock(&va_md_lock);
return ret;
}
EXPORT_SYMBOL(qcom_va_md_register);
static void va_md_add_entry(struct va_md_entry *entry)
{
struct va_md_tree_node *dst = ((struct va_md_tree_node *)va_md_data.elf_mem) +
va_md_data.num_sections;
unsigned int len = strlen(entry->owner);
dst->entry = *entry;
WARN_ONCE(len > MAX_OWNER_STRING - 1,
"Client entry name %s (len = %u) is greater than expected %u\n",
entry->owner, len, MAX_OWNER_STRING - 1);
dst->entry.owner[MAX_OWNER_STRING - 1] = '\0';
if (entry->vaddr) {
dst->lindex = VA_MD_VADDR_MARKER;
dst->rindex = VA_MD_VADDR_MARKER;
} else {
dst->lindex = VA_MD_CB_MARKER;
dst->rindex = VA_MD_CB_MARKER;
}
va_md_data.num_sections++;
}
static bool va_md_check_overlap(struct va_md_entry *entry, unsigned int index)
{
unsigned long ent_start, ent_end;
unsigned long node_start, node_end;
struct va_md_tree_node *node = (struct va_md_tree_node *)va_md_data.elf_mem;
node_start = node[index].entry.vaddr;
node_end = node[index].entry.vaddr + node[index].entry.size - 1;
ent_start = entry->vaddr;
ent_end = entry->vaddr + entry->size - 1;
if (((node_start <= ent_start) && (ent_start <= node_end)) ||
((node_start <= ent_end) && (ent_end <= node_end)) ||
((ent_start <= node_start) && (node_end <= ent_end)))
return true;
return false;
}
static bool va_md_move_left(struct va_md_entry *entry, unsigned int index)
{
unsigned long ent_start, ent_end;
unsigned long node_start, node_end;
struct va_md_tree_node *node = (struct va_md_tree_node *)va_md_data.elf_mem;
node_start = node[index].entry.vaddr;
node_end = node[index].entry.vaddr + node[index].entry.size - 1;
ent_start = entry->vaddr;
ent_end = entry->vaddr + entry->size - 1;
if ((ent_start < node_start) && (ent_end < node_start))
return true;
return false;
}
static bool va_md_move_right(struct va_md_entry *entry, unsigned int index)
{
unsigned long ent_start, ent_end;
unsigned long node_start, node_end;
struct va_md_tree_node *node = (struct va_md_tree_node *)va_md_data.elf_mem;
node_start = node[index].entry.vaddr;
node_end = node[index].entry.vaddr + node[index].entry.size - 1;
ent_start = entry->vaddr;
ent_end = entry->vaddr + entry->size - 1;
if ((ent_start > node_end) && (ent_end > node_end))
return true;
return false;
}
static int va_md_tree_insert(struct va_md_entry *entry)
{
unsigned int baseindex = 0;
int ret = 0;
static int num_nodes;
struct va_md_tree_node *tree = (struct va_md_tree_node *)va_md_data.elf_mem;
if (!entry->vaddr || !va_md_data.num_sections) {
va_md_add_entry(entry);
goto out;
}
while (baseindex < va_md_data.num_sections) {
if ((tree[baseindex].lindex == VA_MD_CB_MARKER) &&
(tree[baseindex].rindex == VA_MD_CB_MARKER)) {
baseindex++;
continue;
}
if (va_md_check_overlap(entry, baseindex)) {
entry->owner[MAX_OWNER_STRING - 1] = '\0';
pr_err("Overlapping region owner:%s\n", entry->owner);
ret = -EINVAL;
goto out;
}
if (va_md_move_left(entry, baseindex)) {
if (tree[baseindex].lindex == VA_MD_VADDR_MARKER) {
tree[baseindex].lindex = va_md_data.num_sections;
va_md_add_entry(entry);
num_nodes++;
goto exit_loop;
} else {
baseindex = tree[baseindex].lindex;
continue;
}
} else if (va_md_move_right(entry, baseindex)) {
if (tree[baseindex].rindex == VA_MD_VADDR_MARKER) {
tree[baseindex].rindex = va_md_data.num_sections;
va_md_add_entry(entry);
num_nodes++;
goto exit_loop;
} else {
baseindex = tree[baseindex].rindex;
continue;
}
} else {
pr_err("Warning: Corrupted Binary Search Tree\n");
}
}
exit_loop:
if (!num_nodes) {
va_md_add_entry(entry);
num_nodes++;
}
out:
return ret;
}
static bool va_md_overflow_check(void)
{
unsigned long end_addr;
unsigned long start_addr = va_md_data.elf_mem;
start_addr += sizeof(struct va_md_tree_node) * va_md_data.num_sections;
end_addr = start_addr + sizeof(struct va_md_tree_node) - 1;
if (end_addr > va_md_data.elf_mem + va_md_data.total_mem_size - 1)
return true;
else
return false;
}
int qcom_va_md_add_region(struct va_md_entry *entry)
{
if (!va_md_data.in_oops_handler)
return -EINVAL;
if ((!entry->vaddr == !entry->cb) || (entry->size <= 0)) {
entry->owner[MAX_OWNER_STRING - 1] = '\0';
pr_err("Invalid entry from owner:%s\n", entry->owner);
return -EINVAL;
}
if (va_md_data.num_sections > MAX_ELF_SECTION) {
pr_err("MAX_ELF_SECTION reached\n");
return -ENOSPC;
}
if (va_md_overflow_check()) {
pr_err("Total CMA consumed for Qcom VA minidump\n");
return -ENOMEM;
}
return va_md_tree_insert(entry);
}
EXPORT_SYMBOL(qcom_va_md_add_region);
static void qcom_va_md_minidump_registration(void)
{
strscpy(va_md_data.md_entry.name, "KVA_DUMP", sizeof(va_md_data.md_entry.name));
va_md_data.md_entry.virt_addr = va_md_data.elf.ehdr;
va_md_data.md_entry.phys_addr = va_md_data.mem_phys_addr +
(sizeof(struct va_md_tree_node) * va_md_data.num_sections);
va_md_data.md_entry.size = sizeof(struct elfhdr) +
(sizeof(struct elf_shdr) * va_md_data.elf.shdr_cnt) +
(sizeof(struct elf_phdr) * va_md_data.elf.phdr_cnt) +
va_md_data.elf.pload_size + va_md_data.elf.str_tbl_size;
va_md_data.md_entry.size = ALIGN(va_md_data.md_entry.size, 4);
if (msm_minidump_add_region(&va_md_data.md_entry) < 0) {
pr_err("Failed to register VA driver CMA region with minidump\n");
va_md_data.va_md_minidump_reg = false;
return;
}
va_md_data.va_md_minidump_reg = true;
}
static inline unsigned long set_sec_name(struct elfhdr *ehdr, const char *name)
{
char *strtab = elf_str_table(ehdr);
unsigned long idx = va_md_data.str_tbl_idx;
unsigned long ret = 0;
if ((strtab == NULL) || (name == NULL))
return 0;
ret = idx;
idx += strscpy((strtab + idx), name, MAX_OWNER_STRING);
va_md_data.str_tbl_idx = idx + 1;
return ret;
}
static void qcom_va_add_elf_hdr(void)
{
struct elfhdr *ehdr = (struct elfhdr *)va_md_data.elf.ehdr;
unsigned long phdr_off;
phdr_off = sizeof(*ehdr) + (sizeof(struct elf_shdr) * va_md_data.elf.shdr_cnt);
memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
ehdr->e_ident[EI_CLASS] = ELF_CLASS;
ehdr->e_ident[EI_DATA] = ELF_DATA;
ehdr->e_ident[EI_VERSION] = EV_CURRENT;
ehdr->e_ident[EI_OSABI] = ELF_OSABI;
ehdr->e_type = ET_CORE;
ehdr->e_machine = ELF_ARCH;
ehdr->e_version = EV_CURRENT;
ehdr->e_ehsize = sizeof(*ehdr);
ehdr->e_shoff = sizeof(*ehdr);
ehdr->e_shentsize = sizeof(struct elf_shdr);
ehdr->e_shstrndx = 1;
ehdr->e_phentsize = sizeof(struct elf_phdr);
ehdr->e_phoff = phdr_off;
}
static void qcom_va_add_hdrs(void)
{
struct elf_shdr *shdr;
struct elf_phdr *phdr;
unsigned long strtbl_off, offset, i;
struct elfhdr *ehdr = (struct elfhdr *)va_md_data.elf.ehdr;
struct va_md_tree_node *arr = (struct va_md_tree_node *)va_md_data.elf_mem;
strtbl_off = ehdr->e_phoff + (sizeof(*phdr) * va_md_data.elf.phdr_cnt);
/* First section header is NULL */
shdr = elf_section(ehdr, ehdr->e_shnum);
ehdr->e_shnum++;
/* String table section */
va_md_data.str_tbl_idx = 1;
shdr = elf_section(ehdr, ehdr->e_shnum);
ehdr->e_shnum++;
shdr->sh_type = SHT_STRTAB;
shdr->sh_offset = strtbl_off;
shdr->sh_name = set_sec_name(ehdr, "STR_TBL");
shdr->sh_size = va_md_data.elf.str_tbl_size;
offset = strtbl_off + va_md_data.elf.str_tbl_size;
for (i = 0; i < (va_md_data.elf.shdr_cnt - 2); i++) {
/* section header */
shdr = elf_section(ehdr, ehdr->e_shnum);
shdr->sh_type = SHT_PROGBITS;
shdr->sh_name = set_sec_name(ehdr, arr[i].entry.owner);
shdr->sh_size = arr[i].entry.size;
shdr->sh_flags = SHF_WRITE;
shdr->sh_offset = offset;
/* program header */
phdr = elf_program(ehdr, ehdr->e_phnum);
phdr->p_type = PT_LOAD;
phdr->p_offset = offset;
phdr->p_filesz = phdr->p_memsz = arr[i].entry.size;
phdr->p_flags = PF_R | PF_W;
if (arr[i].entry.vaddr) {
shdr->sh_addr = phdr->p_vaddr = arr[i].entry.vaddr;
memcpy((void *)(va_md_data.elf.ehdr + offset),
(void *)shdr->sh_addr, shdr->sh_size);
} else {
shdr->sh_addr = phdr->p_vaddr = va_md_data.elf.ehdr + offset;
arr[i].entry.cb((void *)(va_md_data.elf.ehdr + offset),
shdr->sh_size);
}
offset += shdr->sh_size;
ehdr->e_shnum++;
ehdr->e_phnum++;
}
}
static int qcom_va_md_calc_size(unsigned int shdr_cnt)
{
unsigned int len, size = 0;
static unsigned long tot_size;
struct va_md_tree_node *arr = (struct va_md_tree_node *)va_md_data.elf_mem;
if (!shdr_cnt) {
tot_size = sizeof(struct va_md_tree_node) * va_md_data.num_sections;
size = (sizeof(struct elfhdr) + (2 * sizeof(struct elf_shdr)) +
strlen("STR_TBL") + 2);
}
len = strlen(arr[shdr_cnt].entry.owner);
size += (sizeof(struct elf_shdr) + sizeof(struct elf_phdr) +
arr[shdr_cnt].entry.size + len + 1);
tot_size += size;
if (tot_size > va_md_data.total_mem_size) {
pr_err("Total CMA consumed, no space left\n");
return -ENOSPC;
}
if (!shdr_cnt) {
va_md_data.elf.ehdr = va_md_data.elf_mem + (sizeof(struct va_md_tree_node)
* va_md_data.num_sections);
va_md_data.elf.shdr_cnt = 2;
va_md_data.elf.phdr_cnt = 0;
va_md_data.elf.pload_size = 0;
va_md_data.elf.str_tbl_size = strlen("STR_TBL") + 2;
}
va_md_data.elf.shdr_cnt++;
va_md_data.elf.phdr_cnt++;
va_md_data.elf.pload_size += arr[shdr_cnt].entry.size;
va_md_data.elf.str_tbl_size += (len + 1);
return 0;
}
static int qcom_va_md_calc_elf_size(void)
{
unsigned int i;
int ret = 0;
if (va_md_overflow_check()) {
pr_err("Total CMA consumed, no space to create ELF\n");
return -ENOSPC;
}
pr_debug("Num sections:%u\n", va_md_data.num_sections);
for (i = 0; i < va_md_data.num_sections; i++) {
ret = qcom_va_md_calc_size(i);
if (ret < 0)
break;
}
return ret;
}
static int qcom_va_md_panic_handler(struct notifier_block *this,
unsigned long event, void *ptr)
{
unsigned long size;
struct va_md_s_data *va_md_s_data;
if (va_md_data.in_oops_handler)
return NOTIFY_DONE;
va_md_data.in_oops_handler = true;
list_for_each_entry(va_md_s_data, &va_md_data.va_md_list, va_md_s_list) {
if (va_md_s_data->enable)
atomic_notifier_call_chain(&va_md_s_data->va_md_s_notif_list,
0, NULL);
}
if (!va_md_data.num_sections)
goto out;
if (qcom_va_md_calc_elf_size() < 0)
goto out;
size = sizeof(struct elfhdr) +
(sizeof(struct elf_shdr) * va_md_data.elf.shdr_cnt) +
(sizeof(struct elf_phdr) * va_md_data.elf.phdr_cnt) +
va_md_data.elf.pload_size + va_md_data.elf.str_tbl_size;
size = ALIGN(size, 4);
memset((void *)va_md_data.elf.ehdr, 0, size);
qcom_va_md_minidump_registration();
out:
return NOTIFY_DONE;
}
static int qcom_va_md_elf_panic_handler(struct notifier_block *this,
unsigned long event, void *ptr)
{
if (!va_md_data.num_sections || !va_md_data.va_md_minidump_reg)
goto out;
qcom_va_add_elf_hdr();
qcom_va_add_hdrs();
out:
va_md_data.in_oops_handler = false;
return NOTIFY_DONE;
}
static struct notifier_block qcom_va_md_panic_blk = {
.notifier_call = qcom_va_md_panic_handler,
.priority = INT_MAX - 3,
};
static struct notifier_block qcom_va_md_elf_panic_blk = {
.notifier_call = qcom_va_md_elf_panic_handler,
.priority = INT_MAX - 4,
};
static int qcom_va_md_reserve_mem(struct device *dev)
{
struct device_node *node;
unsigned int size[2];
int ret = 0;
node = of_parse_phandle(dev->of_node, "memory-region", 0);
if (node) {
ret = of_reserved_mem_device_init_by_idx(dev, dev->of_node, 0);
of_node_put(dev->of_node);
if (ret) {
pr_err("Failed to initialize CMA mem, ret %d\n",
ret);
goto out;
}
}
ret = of_property_read_u32_array(node, "size", size, 2);
if (ret) {
pr_err("Failed to get size of CMA, ret %d\n", ret);
goto out;
}
va_md_data.total_mem_size = size[1];
out:
return ret;
}
static int qcom_va_md_driver_remove(struct platform_device *pdev)
{
struct va_md_s_data *va_md_s_data, *tmp;
struct notifier_block_list *nbl, *tmpnbl;
mutex_lock(&va_md_lock);
list_for_each_entry_safe(va_md_s_data, tmp, &va_md_data.va_md_list, va_md_s_list) {
list_for_each_entry_safe(nbl, tmpnbl, &va_md_s_data->va_md_s_nb_list, nb_list) {
atomic_notifier_chain_unregister(&va_md_s_data->va_md_s_notif_list,
&nbl->nb);
list_del(&nbl->nb_list);
kfree(nbl);
}
list_del(&va_md_s_data->va_md_s_nb_list);
sysfs_remove_group(&va_md_s_data->s_kobj, &va_md_s_attr_group);
kobject_put(&va_md_s_data->s_kobj);
list_del(&va_md_s_data->va_md_s_list);
kfree(va_md_s_data);
}
mutex_unlock(&va_md_lock);
kset_unregister(va_md_data.va_md_kset);
atomic_notifier_chain_unregister(&panic_notifier_list, &qcom_va_md_elf_panic_blk);
atomic_notifier_chain_unregister(&panic_notifier_list, &qcom_va_md_panic_blk);
vunmap((void *)va_md_data.elf_mem);
return 0;
}
static int qcom_va_md_driver_probe(struct platform_device *pdev)
{
int ret = 0;
int i;
void *vaddr;
int count;
struct page **pages, *page;
dma_addr_t dma_handle;
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
return ret;
ret = qcom_va_md_reserve_mem(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "CMA for VA based minidump is not present\n");
goto out;
}
vaddr = dma_alloc_coherent(&pdev->dev, va_md_data.total_mem_size, &dma_handle,
GFP_KERNEL);
if (!vaddr) {
ret = -ENOMEM;
goto out;
}
dma_free_coherent(&pdev->dev, va_md_data.total_mem_size, vaddr, dma_handle);
page = phys_to_page(dma_to_phys(&pdev->dev, dma_handle));
count = PAGE_ALIGN(va_md_data.total_mem_size) >> PAGE_SHIFT;
pages = kmalloc_array(count, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return -ENOMEM;
for (i = 0; i < count; i++)
pages[i] = nth_page(page, i);
vaddr = vmap(pages, count, VM_DMA_COHERENT, pgprot_dmacoherent(PAGE_KERNEL));
kfree(pages);
va_md_data.mem_phys_addr = dma_to_phys(&pdev->dev, dma_handle);
va_md_data.elf_mem = (unsigned long)vaddr;
atomic_notifier_chain_register(&panic_notifier_list, &qcom_va_md_panic_blk);
atomic_notifier_chain_register(&panic_notifier_list, &qcom_va_md_elf_panic_blk);
INIT_LIST_HEAD(&va_md_data.va_md_list);
va_md_data.va_md_kset = kset_create_and_add("va-minidump", NULL, kernel_kobj);
if (!va_md_data.va_md_kset) {
dev_err(&pdev->dev, "Failed to create kset for va-minidump\n");
vunmap((void *)va_md_data.elf_mem);
ret = -ENOMEM;
goto out;
}
/* All updates above should be visible, before init completes */
smp_store_release(&va_md_data.va_md_init, true);
out:
return ret;
}
static const struct of_device_id qcom_va_md_of_match[] = {
{.compatible = "qcom,va-minidump"},
{}
};
MODULE_DEVICE_TABLE(of, qcom_va_md_of_match);
static struct platform_driver qcom_va_md_driver = {
.driver = {
.name = "qcom-va-minidump",
.of_match_table = qcom_va_md_of_match,
},
.probe = qcom_va_md_driver_probe,
.remove = qcom_va_md_driver_remove,
};
module_platform_driver(qcom_va_md_driver);
MODULE_DESCRIPTION("Qcom VA Minidump Driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,93 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
*/
#ifndef __MINIDUMP_H
#define __MINIDUMP_H
#include <linux/types.h>
#define MAX_NAME_LENGTH 12
/* md_region - Minidump table entry
* @name: Entry name, Minidump will dump binary with this name.
* @id: Entry ID, used only for SDI dumps.
* @virt_addr: Address of the entry.
* @phys_addr: Physical address of the entry to dump.
* @size: Number of byte to dump from @address location
* it should be 4 byte aligned.
*/
struct md_region {
char name[MAX_NAME_LENGTH];
u32 id;
u64 virt_addr;
u64 phys_addr;
u64 size;
};
/*
* Register an entry in Minidump table
* Returns:
* region number: entry position in minidump table.
* Negative error number on failures.
*/
#if IS_ENABLED(CONFIG_QCOM_MINIDUMP)
extern int msm_minidump_add_region(const struct md_region *entry);
extern int msm_minidump_remove_region(const struct md_region *entry);
/*
* Update registered region address in Minidump table.
* It does not hold any locks, so strictly serialize the region updates.
* Returns:
* Zero: on successfully update
* Negetive error number on failures.
*/
extern int msm_minidump_update_region(int regno, const struct md_region *entry);
extern bool msm_minidump_enabled(void);
extern struct md_region *md_get_region(char *name);
extern void dump_stack_minidump(u64 sp);
extern int msm_minidump_get_available_region(void);
extern void md_dump_process(void);
#else
static inline int msm_minidump_add_region(const struct md_region *entry)
{
/* Return quietly, if minidump is not supported */
return 0;
}
static inline int msm_minidump_remove_region(const struct md_region *entry)
{
return 0;
}
static inline bool msm_minidump_enabled(void) { return false; }
static inline struct md_region *md_get_region(char *name) { return NULL; }
static inline void dump_stack_minidump(u64 sp) {}
static inline void add_trace_event(char *buf, size_t size) {}
static inline void md_dump_process(void) {}
#endif
#define MAX_OWNER_STRING 32
struct va_md_entry {
unsigned long vaddr;
unsigned char owner[MAX_OWNER_STRING];
unsigned int size;
void (*cb)(void *dst, unsigned long size);
};
#if IS_ENABLED(CONFIG_QCOM_VA_MINIDUMP)
extern bool qcom_va_md_enabled(void);
extern int qcom_va_md_register(char *name, struct notifier_block *nb);
extern int qcom_va_md_add_region(struct va_md_entry *entry);
#else
static inline bool qcom_va_md_enabled(void) { return false; }
static inline int qcom_va_md_register(char *name, struct notifier_block *nb)
{
return -ENODEV;
}
static inline int qcom_va_md_add_region(struct va_md_entry *entry)
{
return -ENODEV;
}
#endif
#endif