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..15f9a05b8d3a 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,12 +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 = rproc_add(rproc); if (ret) - goto remove_attr_txn_id; + goto destroy_minidump_dev; return 0; -remove_attr_txn_id: + +destroy_minidump_dev: + if (adsp->minidump_dev) + qcom_destroy_ramdump_device(adsp->minidump_dev); + device_remove_file(adsp->dev, &dev_attr_txn_id); remove_subdevs: qcom_remove_sysmon_subdev(adsp->sysmon); @@ -1158,6 +1170,8 @@ static int adsp_remove(struct platform_device *pdev) struct qcom_adsp *adsp = platform_get_drvdata(pdev); rproc_del(adsp->rproc); + if (adsp->minidump_dev) + 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); diff --git a/drivers/soc/qcom/qcom_ramdump.c b/drivers/soc/qcom/qcom_ramdump.c index 6379f3884412..4c18c8454b75 100644 --- a/drivers/soc/qcom/qcom_ramdump.c +++ b/drivers/soc/qcom/qcom_ramdump.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #include @@ -43,6 +44,21 @@ do { \ #define set_phdr_property(arg, class, member, value) \ set_xhdr_property(phdr, arg, class, member, value) +#define RAMDUMP_NUM_DEVICES 256 +#define RAMDUMP_NAME "ramdump" + +static struct class *ramdump_class; +static dev_t ramdump_dev; +static DEFINE_MUTEX(rd_minor_mutex); +static DEFINE_IDA(rd_minor_id); +static bool ramdump_devnode_inited; + +struct ramdump_device { + char name[256]; + struct cdev cdev; + struct device *dev; +}; + struct qcom_ramdump_desc { void *data; struct completion dump_done; @@ -252,6 +268,106 @@ int qcom_fw_elf_dump(struct firmware *fw, struct device *dev) } EXPORT_SYMBOL(qcom_fw_elf_dump); +static int ramdump_devnode_init(void) +{ + int ret; + + ramdump_class = class_create(THIS_MODULE, RAMDUMP_NAME); + ret = alloc_chrdev_region(&ramdump_dev, 0, RAMDUMP_NUM_DEVICES, + RAMDUMP_NAME); + if (ret) { + pr_err("%s: unable to allocate major\n", __func__); + return ret; + } + + ramdump_devnode_inited = true; + + return 0; +} + +void *qcom_create_ramdump_device(const char *dev_name, struct device *parent) +{ + int ret, minor; + struct ramdump_device *rd_dev; + + if (!dev_name) { + pr_err("%s: Invalid device name.\n", __func__); + return NULL; + } + + mutex_lock(&rd_minor_mutex); + if (!ramdump_devnode_inited) { + ret = ramdump_devnode_init(); + if (ret) { + mutex_unlock(&rd_minor_mutex); + return ERR_PTR(ret); + } + } + mutex_unlock(&rd_minor_mutex); + + rd_dev = kzalloc(sizeof(struct ramdump_device), GFP_KERNEL); + + if (!rd_dev) + return NULL; + + /* get a minor number */ + minor = ida_simple_get(&rd_minor_id, 0, RAMDUMP_NUM_DEVICES, + GFP_KERNEL); + if (minor < 0) { + pr_err("%s: No more minor numbers left! rc:%d\n", __func__, + minor); + ret = -ENODEV; + goto fail_out_of_minors; + } + + snprintf(rd_dev->name, ARRAY_SIZE(rd_dev->name), "%s", + dev_name); + + rd_dev->dev = device_create(ramdump_class, parent, + MKDEV(MAJOR(ramdump_dev), minor), + rd_dev, rd_dev->name); + if (IS_ERR(rd_dev->dev)) { + ret = PTR_ERR(rd_dev->dev); + pr_err("%s: device_create failed for %s (%d)\n", __func__, + dev_name, ret); + goto fail_return_minor; + } + + cdev_init(&rd_dev->cdev, NULL); + ret = cdev_add(&rd_dev->cdev, MKDEV(MAJOR(ramdump_dev), minor), 1); + if (ret) { + pr_err("%s: cdev_add failed for %s (%d)\n", __func__, + dev_name, ret); + goto fail_cdev_add; + } + + return (void *)rd_dev->dev; + +fail_cdev_add: + device_unregister(rd_dev->dev); +fail_return_minor: + ida_simple_remove(&rd_minor_id, minor); +fail_out_of_minors: + kfree(rd_dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(qcom_create_ramdump_device); + +void qcom_destroy_ramdump_device(void *dev) +{ + struct ramdump_device *rd_dev = container_of(dev, struct ramdump_device, dev); + int minor = MINOR(rd_dev->cdev.dev); + + if (IS_ERR_OR_NULL(rd_dev)) + return; + + cdev_del(&rd_dev->cdev); + device_unregister(rd_dev->dev); + ida_simple_remove(&rd_minor_id, minor); + kfree(rd_dev); +} +EXPORT_SYMBOL(qcom_destroy_ramdump_device); + MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Ramdump driver"); MODULE_LICENSE("GPL"); diff --git a/include/soc/qcom/qcom_ramdump.h b/include/soc/qcom/qcom_ramdump.h index 7a01228be771..ebdf57757b8f 100644 --- a/include/soc/qcom/qcom_ramdump.h +++ b/include/soc/qcom/qcom_ramdump.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2021, The Linux Foundation. All rights reserved. - * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #ifndef _QCOM_RAMDUMP_HEADER @@ -19,11 +19,21 @@ struct qcom_dump_segment { }; #if IS_ENABLED(CONFIG_QCOM_RAMDUMP) +extern void *qcom_create_ramdump_device(const char *dev_name, struct device *parent); +extern void qcom_destroy_ramdump_device(void *dev); extern int qcom_elf_dump(struct list_head *segs, struct device *dev, unsigned char class); 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 void *qcom_create_ramdump_device(const char *dev_name, + struct device *parent) +{ + return NULL; +} +static inline void qcom_destroy_ramdump_device(void *dev) +{ +} static inline int qcom_elf_dump(struct list_head *segs, struct device *dev, unsigned char class) { return -ENODEV;