diff --git a/drivers/soc/qcom/ramdump.c b/drivers/soc/qcom/ramdump.c index c86a59e3ec80..2731c7828576 100644 --- a/drivers/soc/qcom/ramdump.c +++ b/drivers/soc/qcom/ramdump.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2011-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2020, The Linux Foundation. All rights reserved. */ #include @@ -644,3 +644,163 @@ do_elf_ramdump(void *handle, struct ramdump_segment *segments, int nsegments) return _do_ramdump(handle, segments, nsegments, true); } EXPORT_SYMBOL(do_elf_ramdump); + +int do_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; + } + + dev_coredumpv(dev, data, data_size, GFP_KERNEL); + return 0; +} +EXPORT_SYMBOL(do_dump); + +int do_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++; + } + dev_coredumpv(dev, data, data_size, GFP_KERNEL); + return 0; +} +EXPORT_SYMBOL(do_elf_dump); + +int do_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); + } + do_elf_dump(&head, dev); + return 0; +} +EXPORT_SYMBOL(do_fw_elf_dump); diff --git a/include/soc/qcom/ramdump.h b/include/soc/qcom/ramdump.h index 7b3829333291..ae03e9f091e3 100644 --- a/include/soc/qcom/ramdump.h +++ b/include/soc/qcom/ramdump.h @@ -1,10 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2011-2014, 2017-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2014, 2017-2020, The Linux Foundation. All rights reserved. */ #ifndef _RAMDUMP_HEADER #define _RAMDUMP_HEADER +#include +#include +#include +#include struct device; @@ -15,6 +19,17 @@ struct ramdump_segment { unsigned long size; }; +struct qcom_dump_segment { + struct list_head node; + dma_addr_t da; + void *va; + size_t size; +}; + +extern int do_elf_dump(struct list_head *segs, struct device *dev); +extern int do_dump(struct list_head *head, struct device *dev); +extern int do_fw_elf_dump(struct firmware *fw, struct device *dev); + #if IS_ENABLED(CONFIG_MSM_SUBSYSTEM_RESTART) extern void *create_ramdump_device(const char *dev_name, struct device *parent); extern void destroy_ramdump_device(void *dev);