Add shared memory based minidump backend driver and hook it with minidump core (qcom_minidump) by registering SMEM as backend device. Signed-off-by: Mukesh Ojha <quic_mojha@xxxxxxxxxxx> --- drivers/soc/qcom/Kconfig | 8 +- drivers/soc/qcom/qcom_minidump_smem.c | 231 +++++++++++++++++++++++++++++++++- drivers/soc/qcom/smem.c | 9 ++ 3 files changed, 240 insertions(+), 8 deletions(-) diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 874ee8c3efe0..1834213fd652 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -298,8 +298,12 @@ config QCOM_MINIDUMP_SMEM tristate "QCOM Minidump SMEM (as backend) Support" depends on ARCH_QCOM depends on QCOM_SMEM + select QCOM_MINIDUMP help Enablement of core minidump feature is controlled from boot firmware - side, and this config allow linux to query minidump segments associated - with the remote processor and check its validity. + side, and this config allow linux to query and manages minidump + table for remote processors as well as APSS. + + This config should be enabled if the low level minidump is implemented + as part of SMEM. endmenu diff --git a/drivers/soc/qcom/qcom_minidump_smem.c b/drivers/soc/qcom/qcom_minidump_smem.c index b588833ea26e..bdc75aa2f518 100644 --- a/drivers/soc/qcom/qcom_minidump_smem.c +++ b/drivers/soc/qcom/qcom_minidump_smem.c @@ -2,18 +2,34 @@ /* * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. */ +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/init.h> +#include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/string.h> #include <linux/soc/qcom/smem.h> -#include <soc/qcom/qcom_minidump.h> + +#include "qcom_minidump_internal.h" #define MAX_NUM_OF_SS 10 -#define MAX_REGION_NAME_LENGTH 16 #define SBL_MINIDUMP_SMEM_ID 602 -#define MINIDUMP_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0) -#define MINIDUMP_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0) -#define MINIDUMP_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0) + +#define MINIDUMP_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0) +#define MINIDUMP_REGION_INVALID ('I' << 24 | 'N' << 16 | 'V' << 8 | 'A' << 0) +#define MINIDUMP_REGION_INIT ('I' << 24 | 'N' << 16 | 'I' << 8 | 'T' << 0) +#define MINIDUMP_REGION_NOINIT 0 + +#define MINIDUMP_SS_ENCR_REQ (0 << 24 | 'Y' << 16 | 'E' << 8 | 'S' << 0) +#define MINIDUMP_SS_ENCR_NOTREQ (0 << 24 | 0 << 16 | 'N' << 8 | 'R' << 0) +#define MINIDUMP_SS_ENCR_START ('S' << 24 | 'T' << 16 | 'R' << 8 | 'T' << 0) +#define MINIDUMP_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0) +#define MINIDUMP_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0) + +#define MINIDUMP_APSS_DESC 0 /** * struct minidump_region - Minidump region @@ -64,6 +80,16 @@ struct minidump_global_toc { }; /** + * struct minidump_ss_data - Minidump subsystem private data + * @md_ss_toc : Application Subsystem TOC pointer + * @md_regions : Application Subsystem region base pointer + */ +struct minidump_ss_data { + struct minidump_subsystem *md_ss_toc; + struct minidump_region *md_regions; +}; + +/** * qcom_ss_md_mapped_base() - For getting subsystem iomapped base segment address * for given minidump index. * @@ -143,5 +169,198 @@ int qcom_ss_valid_segment_info(void *ptr, int i, char **name, dma_addr_t *da, si } EXPORT_SYMBOL_GPL(qcom_ss_valid_segment_info); +static int smem_md_get_region_index(struct minidump_ss_data *mdss_data, + const struct qcom_minidump_region *region) +{ + struct minidump_subsystem *mdss_toc = mdss_data->md_ss_toc; + struct minidump_region *mdr; + unsigned int i; + unsigned int count; + + count = le32_to_cpu(mdss_toc->region_count); + for (i = 0; i < count; i++) { + mdr = &mdss_data->md_regions[i]; + if (!strcmp(mdr->name, region->name)) + return i; + } + + return -ENOENT; +} + +static void smem_md_add_region(struct minidump_ss_data *mdss_data, + const struct qcom_minidump_region *region) +{ + struct minidump_subsystem *mdss_toc = mdss_data->md_ss_toc; + struct minidump_region *mdr; + unsigned int region_cnt; + + region_cnt = le32_to_cpu(mdss_toc->region_count); + mdr = &mdss_data->md_regions[region_cnt]; + strscpy(mdr->name, region->name, sizeof(mdr->name)); + mdr->address = cpu_to_le64(region->phys_addr); + mdr->size = cpu_to_le64(region->size); + mdr->valid = cpu_to_le32(MINIDUMP_REGION_VALID); + region_cnt++; + mdss_toc->region_count = cpu_to_le32(region_cnt); +} + +static int smem_md_region_register(struct minidump *md, + const struct qcom_minidump_region *region) +{ + struct minidump_ss_data *mdss_data = md->apss_data; + struct minidump_subsystem *mdss_toc = mdss_data->md_ss_toc; + unsigned int num_region; + int ret; + + ret = smem_md_get_region_index(mdss_data, region); + if (ret >= 0) { + dev_info(md->dev, "%s region is already registered\n", region->name); + return -EEXIST; + } + + /* Check if there is a room for a new entry */ + num_region = le32_to_cpu(mdss_toc->region_count); + if (num_region >= md->max_region_limit) { + dev_err(md->dev, "maximum region limit %u reached\n", num_region); + return -ENOSPC; + } + + smem_md_add_region(mdss_data, region); + + return 0; +} + +static int smem_md_region_unregister(struct minidump *md, + const struct qcom_minidump_region *region) +{ + struct minidump_ss_data *mdss_data = md->apss_data; + struct minidump_subsystem *mdss_toc = mdss_data->md_ss_toc; + struct minidump_region *mdr; + unsigned int region_cnt; + unsigned int idx; + int ret; + + ret = smem_md_get_region_index(mdss_data, region); + if (ret < 0) { + dev_err(md->dev, "%s region is not present\n", region->name); + return ret; + } + + idx = ret; + mdr = &mdss_data->md_regions[0]; + region_cnt = le32_to_cpu(mdss_toc->region_count); + /* + * Left shift all the regions exist after this removed region + * index by 1 to fill the gap and zero out the last region + * present at the end. + */ + memmove(&mdr[idx], &mdr[idx + 1], + (region_cnt - idx - 1) * sizeof(struct minidump_region)); + memset(&mdr[region_cnt - 1], 0, sizeof(struct minidump_region)); + region_cnt--; + mdss_toc->region_count = cpu_to_le32(region_cnt); + + return 0; +} + +static int smem_md_table_init(struct minidump *md) +{ + struct minidump_global_toc *mdgtoc; + struct minidump_ss_data *mdss_data; + struct minidump_subsystem *mdss_toc; + size_t size; + int ret; + + mdgtoc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, &size); + if (IS_ERR(mdgtoc)) { + ret = PTR_ERR(mdgtoc); + dev_err(md->dev, "Couldn't find minidump smem item: %d\n", ret); + return ret; + } + + if (size < sizeof(mdgtoc) || !mdgtoc->status) { + ret = -EINVAL; + dev_err(md->dev, "minidump table is not initialized: %d\n", ret); + return ret; + } + + mdss_data = devm_kzalloc(md->dev, sizeof(*mdss_data), GFP_KERNEL); + if (!mdss_data) + return -ENOMEM; + + mdss_data->md_ss_toc = &mdgtoc->subsystems[MINIDUMP_APSS_DESC]; + mdss_data->md_regions = devm_kcalloc(md->dev, md->max_region_limit, + sizeof(struct minidump_region), GFP_KERNEL); + if (!mdss_data->md_regions) + return -ENOMEM; + + mdss_toc = mdss_data->md_ss_toc; + mdss_toc->regions_baseptr = cpu_to_le64(virt_to_phys(mdss_data->md_regions)); + mdss_toc->enabled = cpu_to_le32(MINIDUMP_SS_ENABLED); + mdss_toc->status = cpu_to_le32(1); + mdss_toc->region_count = cpu_to_le32(0); + + /* Tell bootloader not to encrypt the regions of this subsystem */ + mdss_toc->encryption_status = cpu_to_le32(MINIDUMP_SS_ENCR_DONE); + mdss_toc->encryption_required = cpu_to_le32(MINIDUMP_SS_ENCR_NOTREQ); + md->apss_data = mdss_data; + + return 0; +} + +static int smem_md_table_exit(struct minidump *md) +{ + struct minidump_ss_data *mdss_data; + + mdss_data = md->apss_data; + memset(mdss_data->md_ss_toc, + cpu_to_le32(0), sizeof(struct minidump_subsystem)); + + return 0; +} + +static struct minidump_ops smem_md_ops = { + .md_table_init = smem_md_table_init, + .md_table_exit = smem_md_table_exit, + .md_region_register = smem_md_region_register, + .md_region_unregister = smem_md_region_unregister, +}; + +static int qcom_minidump_smem_probe(struct platform_device *pdev) +{ + struct minidump *md; + + md = devm_kzalloc(&pdev->dev, sizeof(*md), GFP_KERNEL); + if (!md) + return -ENOMEM; + + md->dev = &pdev->dev; + md->ops = &smem_md_ops; + md->name = "smem"; + platform_set_drvdata(pdev, md); + + return qcom_minidump_backend_register(md); +} + +static int qcom_minidump_smem_remove(struct platform_device *pdev) +{ + struct minidump *md = platform_get_drvdata(pdev); + + qcom_minidump_backend_unregister(md); + + return 0; +} + +static struct platform_driver qcom_minidump_smem_driver = { + .probe = qcom_minidump_smem_probe, + .remove = qcom_minidump_smem_remove, + .driver = { + .name = "qcom-minidump-smem", + }, +}; + +module_platform_driver(qcom_minidump_smem_driver); + MODULE_DESCRIPTION("Qualcomm Minidump SMEM as backend driver"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:qcom-minidump-smem"); diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 6be7ea93c78c..af582aa7f9a3 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -269,6 +269,7 @@ struct smem_region { * @partitions: list of partitions of current processor/host * @item_count: max accepted item number * @socinfo: platform device pointer + * @minidump: minidump platform device pointer * @num_regions: number of @regions * @regions: list of the memory regions defining the shared memory */ @@ -279,6 +280,7 @@ struct qcom_smem { u32 item_count; struct platform_device *socinfo; + struct platform_device *minidump; struct smem_ptable *ptable; struct smem_partition global_partition; struct smem_partition partitions[SMEM_HOST_COUNT]; @@ -1151,11 +1153,18 @@ static int qcom_smem_probe(struct platform_device *pdev) if (IS_ERR(smem->socinfo)) dev_dbg(&pdev->dev, "failed to register socinfo device\n"); + smem->minidump = platform_device_register_data(&pdev->dev, "qcom-minidump-smem", + PLATFORM_DEVID_NONE, NULL, + 0); + if (IS_ERR(smem->minidump)) + dev_dbg(&pdev->dev, "failed to register minidump device\n"); + return 0; } static int qcom_smem_remove(struct platform_device *pdev) { + platform_device_unregister(__smem->minidump); platform_device_unregister(__smem->socinfo); hwspin_lock_free(__smem->hwlock); -- 2.7.4