diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c index b8a6622db9c1..2bb72c8337d0 100644 --- a/drivers/remoteproc/qcom_common.c +++ b/drivers/remoteproc/qcom_common.c @@ -5,6 +5,7 @@ * Copyright (C) 2016 Linaro Ltd * Copyright (C) 2015 Sony Mobile Communications Inc * Copyright (c) 2012-2013, 2020-2021 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #include @@ -17,9 +18,11 @@ #include #include #include +#include #include #include +#include "remoteproc_elf_helpers.h" #include "remoteproc_internal.h" #include "qcom_common.h" @@ -171,7 +174,114 @@ static int qcom_add_minidump_segments(struct rproc *rproc, struct minidump_subsy return 0; } -void qcom_minidump(struct rproc *rproc, unsigned int minidump_id, rproc_dumpfn_t dumpfn) +static void qcom_rproc_minidump(struct rproc *rproc, struct device *md_dev) +{ + struct rproc_dump_segment *segment; + void *shdr; + void *ehdr; + size_t data_size; + size_t strtbl_size = 0; + size_t strtbl_index = 1; + size_t offset; + void *data; + u8 class = rproc->elf_class; + int shnum; + unsigned int dump_conf = rproc->dump_conf; + char *str_tbl = "STR_TBL"; + + if (list_empty(&rproc->dump_segments) || + dump_conf == RPROC_COREDUMP_DISABLED) + return; + + if (class == ELFCLASSNONE) { + dev_err(&rproc->dev, "Elf class is not set\n"); + return; + } + + /* + * We allocate two extra section headers. The first one is null. + * Second section header is for the string table. Also space is + * allocated for string table. + */ + data_size = elf_size_of_hdr(class) + 2 * elf_size_of_shdr(class); + shnum = 2; + + /* the extra byte is for the null character at index 0 */ + strtbl_size += strlen(str_tbl) + 2; + + list_for_each_entry(segment, &rproc->dump_segments, node) { + data_size += elf_size_of_shdr(class); + strtbl_size += strlen(segment->priv) + 1; + data_size += segment->size; + shnum++; + } + + data_size += strtbl_size; + + data = vmalloc(data_size); + if (!data) + return; + + ehdr = data; + memset(ehdr, 0, elf_size_of_hdr(class)); + /* e_ident field is common for both elf32 and elf64 */ + elf_hdr_init_ident(ehdr, class); + elf_hdr_set_e_type(class, ehdr, ET_CORE); + elf_hdr_set_e_machine(class, ehdr, rproc->elf_machine); + elf_hdr_set_e_version(class, ehdr, EV_CURRENT); + elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr); + elf_hdr_set_e_shoff(class, ehdr, elf_size_of_hdr(class)); + elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class)); + elf_hdr_set_e_shentsize(class, ehdr, elf_size_of_shdr(class)); + elf_hdr_set_e_shnum(class, ehdr, shnum); + elf_hdr_set_e_shstrndx(class, ehdr, 1); + + /* + * The zeroth index of the section header is reserved and is rarely used. + * Set the section header as null (SHN_UNDEF) and move to the next one. + */ + shdr = data + elf_hdr_get_e_shoff(class, ehdr); + memset(shdr, 0, elf_size_of_shdr(class)); + shdr += elf_size_of_shdr(class); + + /* Initialize the string table. */ + offset = elf_hdr_get_e_shoff(class, ehdr) + + elf_size_of_shdr(class) * elf_hdr_get_e_shnum(class, ehdr); + memset(data + offset, 0, strtbl_size); + + /* Fill in the string table section header. */ + memset(shdr, 0, elf_size_of_shdr(class)); + elf_shdr_set_sh_type(class, shdr, SHT_STRTAB); + elf_shdr_set_sh_offset(class, shdr, offset); + elf_shdr_set_sh_size(class, shdr, strtbl_size); + elf_shdr_set_sh_entsize(class, shdr, 0); + elf_shdr_set_sh_flags(class, shdr, 0); + elf_shdr_set_sh_name(class, shdr, elf_strtbl_add(str_tbl, ehdr, class, &strtbl_index)); + offset += elf_shdr_get_sh_size(class, shdr); + shdr += elf_size_of_shdr(class); + + list_for_each_entry(segment, &rproc->dump_segments, node) { + memset(shdr, 0, elf_size_of_shdr(class)); + elf_shdr_set_sh_type(class, shdr, SHT_PROGBITS); + elf_shdr_set_sh_offset(class, shdr, offset); + elf_shdr_set_sh_addr(class, shdr, segment->da); + elf_shdr_set_sh_size(class, shdr, segment->size); + elf_shdr_set_sh_entsize(class, shdr, 0); + elf_shdr_set_sh_flags(class, shdr, SHF_WRITE); + elf_shdr_set_sh_name(class, shdr, + elf_strtbl_add(segment->priv, ehdr, class, &strtbl_index)); + + /* No need to copy segments for inline dumps */ + segment->dump(rproc, segment, data + offset, 0, segment->size); + offset += elf_shdr_get_sh_size(class, shdr); + shdr += elf_size_of_shdr(class); + } + + dev_coredumpv(md_dev, data, data_size, GFP_KERNEL); +} + +void qcom_minidump(struct rproc *rproc, struct device *md_dev, + unsigned int minidump_id, rproc_dumpfn_t dumpfn) { int ret; struct minidump_subsystem *subsystem; @@ -213,9 +323,10 @@ void qcom_minidump(struct rproc *rproc, unsigned int minidump_id, rproc_dumpfn_t } if (rproc->elf_class == ELFCLASS64) - rproc_coredump_using_sections(rproc); + qcom_rproc_minidump(rproc, md_dev); else rproc_coredump(rproc); + clean_minidump: qcom_minidump_cleanup(rproc); } diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h index af55811b6fc8..13368967c3ab 100644 --- a/drivers/remoteproc/qcom_common.h +++ b/drivers/remoteproc/qcom_common.h @@ -61,7 +61,8 @@ extern bool qcom_device_shutdown_in_progress; typedef void (*rproc_dumpfn_t)(struct rproc *rproc, struct rproc_dump_segment *segment, void *dest, size_t offset, size_t size); -void qcom_minidump(struct rproc *rproc, unsigned int minidump_id, rproc_dumpfn_t dumpfn); +void qcom_minidump(struct rproc *rproc, struct device *md_dev, + unsigned int minidump_id, rproc_dumpfn_t dumpfn); void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink, const char *ssr_name); diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 5d9a9fa025d6..aac11dba804e 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -33,6 +33,7 @@ #define CREATE_TRACE_POINTS #include +#include #include "qcom_common.h" #include "qcom_pil_info.h" @@ -78,6 +79,7 @@ struct adsp_data { struct qcom_adsp { struct device *dev; + struct device *minidump_dev; struct rproc *rproc; struct qcom_q6v5 q6v5; @@ -168,7 +170,7 @@ static void adsp_minidump(struct rproc *rproc) if (rproc->dump_conf == RPROC_COREDUMP_DISABLED) goto exit; - qcom_minidump(rproc, adsp->minidump_id, adsp_segment_dump); + qcom_minidump(rproc, adsp->minidump_dev, adsp->minidump_id, adsp_segment_dump); exit: trace_rproc_qcom_event(dev_name(adsp->dev), "adsp_minidump", "exit"); @@ -999,6 +1001,7 @@ static int adsp_probe(struct platform_device *pdev) struct rproc *rproc; const char *fw_name; const struct rproc_ops *ops = &adsp_ops; + char md_dev_name[32]; int ret; bool signal_aop; @@ -1132,11 +1135,21 @@ static int adsp_probe(struct platform_device *pdev) if (ret) goto remove_subdevs; + snprintf(md_dev_name, ARRAY_SIZE(md_dev_name), "%s-md", pdev->dev.of_node->name); + adsp->minidump_dev = qcom_create_ramdump_device(md_dev_name, NULL); + if (!adsp->minidump_dev) { + dev_err(&pdev->dev, "Unable to create %s minidump device.\n", md_dev_name); + ret = -ENOMEM; + goto remove_attr_txn_id; + } + ret = rproc_add(rproc); if (ret) - goto remove_attr_txn_id; + goto destroy_minidump_dev; return 0; +destroy_minidump_dev: + qcom_destroy_ramdump_device(adsp->minidump_dev); remove_attr_txn_id: device_remove_file(adsp->dev, &dev_attr_txn_id); remove_subdevs: @@ -1158,6 +1171,7 @@ static int adsp_remove(struct platform_device *pdev) struct qcom_adsp *adsp = platform_get_drvdata(pdev); rproc_del(adsp->rproc); + qcom_destroy_ramdump_device(adsp->minidump_dev); device_remove_file(adsp->dev, &dev_attr_txn_id); qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev); qcom_remove_sysmon_subdev(adsp->sysmon);