[PATCH v4 05/21] soc: qcom: Add linux minidump smem backend driver support

From: Mukesh Ojha
Date: Wed Jun 28 2023 - 08:36:46 EST


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