162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Qualcomm Peripheral Image Loader helpers
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2016 Linaro Ltd
662306a36Sopenharmony_ci * Copyright (C) 2015 Sony Mobile Communications Inc
762306a36Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/firmware.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/notifier.h>
1462306a36Sopenharmony_ci#include <linux/remoteproc.h>
1562306a36Sopenharmony_ci#include <linux/remoteproc/qcom_rproc.h>
1662306a36Sopenharmony_ci#include <linux/rpmsg/qcom_glink.h>
1762306a36Sopenharmony_ci#include <linux/rpmsg/qcom_smd.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <linux/soc/qcom/mdt_loader.h>
2062306a36Sopenharmony_ci#include <linux/soc/qcom/smem.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include "remoteproc_internal.h"
2362306a36Sopenharmony_ci#include "qcom_common.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define to_glink_subdev(d) container_of(d, struct qcom_rproc_glink, subdev)
2662306a36Sopenharmony_ci#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)
2762306a36Sopenharmony_ci#define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev)
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define MAX_NUM_OF_SS           10
3062306a36Sopenharmony_ci#define MAX_REGION_NAME_LENGTH  16
3162306a36Sopenharmony_ci#define SBL_MINIDUMP_SMEM_ID	602
3262306a36Sopenharmony_ci#define MINIDUMP_REGION_VALID		('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0)
3362306a36Sopenharmony_ci#define MINIDUMP_SS_ENCR_DONE		('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0)
3462306a36Sopenharmony_ci#define MINIDUMP_SS_ENABLED		('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0)
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/**
3762306a36Sopenharmony_ci * struct minidump_region - Minidump region
3862306a36Sopenharmony_ci * @name		: Name of the region to be dumped
3962306a36Sopenharmony_ci * @seq_num:		: Use to differentiate regions with same name.
4062306a36Sopenharmony_ci * @valid		: This entry to be dumped (if set to 1)
4162306a36Sopenharmony_ci * @address		: Physical address of region to be dumped
4262306a36Sopenharmony_ci * @size		: Size of the region
4362306a36Sopenharmony_ci */
4462306a36Sopenharmony_cistruct minidump_region {
4562306a36Sopenharmony_ci	char	name[MAX_REGION_NAME_LENGTH];
4662306a36Sopenharmony_ci	__le32	seq_num;
4762306a36Sopenharmony_ci	__le32	valid;
4862306a36Sopenharmony_ci	__le64	address;
4962306a36Sopenharmony_ci	__le64	size;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/**
5362306a36Sopenharmony_ci * struct minidump_subsystem - Subsystem's SMEM Table of content
5462306a36Sopenharmony_ci * @status : Subsystem toc init status
5562306a36Sopenharmony_ci * @enabled : if set to 1, this region would be copied during coredump
5662306a36Sopenharmony_ci * @encryption_status: Encryption status for this subsystem
5762306a36Sopenharmony_ci * @encryption_required : Decides to encrypt the subsystem regions or not
5862306a36Sopenharmony_ci * @region_count : Number of regions added in this subsystem toc
5962306a36Sopenharmony_ci * @regions_baseptr : regions base pointer of the subsystem
6062306a36Sopenharmony_ci */
6162306a36Sopenharmony_cistruct minidump_subsystem {
6262306a36Sopenharmony_ci	__le32	status;
6362306a36Sopenharmony_ci	__le32	enabled;
6462306a36Sopenharmony_ci	__le32	encryption_status;
6562306a36Sopenharmony_ci	__le32	encryption_required;
6662306a36Sopenharmony_ci	__le32	region_count;
6762306a36Sopenharmony_ci	__le64	regions_baseptr;
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/**
7162306a36Sopenharmony_ci * struct minidump_global_toc - Global Table of Content
7262306a36Sopenharmony_ci * @status : Global Minidump init status
7362306a36Sopenharmony_ci * @md_revision : Minidump revision
7462306a36Sopenharmony_ci * @enabled : Minidump enable status
7562306a36Sopenharmony_ci * @subsystems : Array of subsystems toc
7662306a36Sopenharmony_ci */
7762306a36Sopenharmony_cistruct minidump_global_toc {
7862306a36Sopenharmony_ci	__le32				status;
7962306a36Sopenharmony_ci	__le32				md_revision;
8062306a36Sopenharmony_ci	__le32				enabled;
8162306a36Sopenharmony_ci	struct minidump_subsystem	subsystems[MAX_NUM_OF_SS];
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistruct qcom_ssr_subsystem {
8562306a36Sopenharmony_ci	const char *name;
8662306a36Sopenharmony_ci	struct srcu_notifier_head notifier_list;
8762306a36Sopenharmony_ci	struct list_head list;
8862306a36Sopenharmony_ci};
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic LIST_HEAD(qcom_ssr_subsystem_list);
9162306a36Sopenharmony_cistatic DEFINE_MUTEX(qcom_ssr_subsys_lock);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic void qcom_minidump_cleanup(struct rproc *rproc)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	struct rproc_dump_segment *entry, *tmp;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) {
9862306a36Sopenharmony_ci		list_del(&entry->node);
9962306a36Sopenharmony_ci		kfree(entry->priv);
10062306a36Sopenharmony_ci		kfree(entry);
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic int qcom_add_minidump_segments(struct rproc *rproc, struct minidump_subsystem *subsystem,
10562306a36Sopenharmony_ci			void (*rproc_dumpfn_t)(struct rproc *rproc, struct rproc_dump_segment *segment,
10662306a36Sopenharmony_ci				void *dest, size_t offset, size_t size))
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct minidump_region __iomem *ptr;
10962306a36Sopenharmony_ci	struct minidump_region region;
11062306a36Sopenharmony_ci	int seg_cnt, i;
11162306a36Sopenharmony_ci	dma_addr_t da;
11262306a36Sopenharmony_ci	size_t size;
11362306a36Sopenharmony_ci	char *name;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (WARN_ON(!list_empty(&rproc->dump_segments))) {
11662306a36Sopenharmony_ci		dev_err(&rproc->dev, "dump segment list already populated\n");
11762306a36Sopenharmony_ci		return -EUCLEAN;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	seg_cnt = le32_to_cpu(subsystem->region_count);
12162306a36Sopenharmony_ci	ptr = ioremap((unsigned long)le64_to_cpu(subsystem->regions_baseptr),
12262306a36Sopenharmony_ci		      seg_cnt * sizeof(struct minidump_region));
12362306a36Sopenharmony_ci	if (!ptr)
12462306a36Sopenharmony_ci		return -EFAULT;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	for (i = 0; i < seg_cnt; i++) {
12762306a36Sopenharmony_ci		memcpy_fromio(&region, ptr + i, sizeof(region));
12862306a36Sopenharmony_ci		if (le32_to_cpu(region.valid) == MINIDUMP_REGION_VALID) {
12962306a36Sopenharmony_ci			name = kstrndup(region.name, MAX_REGION_NAME_LENGTH - 1, GFP_KERNEL);
13062306a36Sopenharmony_ci			if (!name) {
13162306a36Sopenharmony_ci				iounmap(ptr);
13262306a36Sopenharmony_ci				return -ENOMEM;
13362306a36Sopenharmony_ci			}
13462306a36Sopenharmony_ci			da = le64_to_cpu(region.address);
13562306a36Sopenharmony_ci			size = le64_to_cpu(region.size);
13662306a36Sopenharmony_ci			rproc_coredump_add_custom_segment(rproc, da, size, rproc_dumpfn_t, name);
13762306a36Sopenharmony_ci		}
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	iounmap(ptr);
14162306a36Sopenharmony_ci	return 0;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_civoid qcom_minidump(struct rproc *rproc, unsigned int minidump_id,
14562306a36Sopenharmony_ci		void (*rproc_dumpfn_t)(struct rproc *rproc,
14662306a36Sopenharmony_ci		struct rproc_dump_segment *segment, void *dest, size_t offset,
14762306a36Sopenharmony_ci		size_t size))
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	int ret;
15062306a36Sopenharmony_ci	struct minidump_subsystem *subsystem;
15162306a36Sopenharmony_ci	struct minidump_global_toc *toc;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/* Get Global minidump ToC*/
15462306a36Sopenharmony_ci	toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, NULL);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* check if global table pointer exists and init is set */
15762306a36Sopenharmony_ci	if (IS_ERR(toc) || !toc->status) {
15862306a36Sopenharmony_ci		dev_err(&rproc->dev, "Minidump TOC not found in SMEM\n");
15962306a36Sopenharmony_ci		return;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/* Get subsystem table of contents using the minidump id */
16362306a36Sopenharmony_ci	subsystem = &toc->subsystems[minidump_id];
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/**
16662306a36Sopenharmony_ci	 * Collect minidump if SS ToC is valid and segment table
16762306a36Sopenharmony_ci	 * is initialized in memory and encryption status is set.
16862306a36Sopenharmony_ci	 */
16962306a36Sopenharmony_ci	if (subsystem->regions_baseptr == 0 ||
17062306a36Sopenharmony_ci	    le32_to_cpu(subsystem->status) != 1 ||
17162306a36Sopenharmony_ci	    le32_to_cpu(subsystem->enabled) != MINIDUMP_SS_ENABLED) {
17262306a36Sopenharmony_ci		return rproc_coredump(rproc);
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	if (le32_to_cpu(subsystem->encryption_status) != MINIDUMP_SS_ENCR_DONE) {
17662306a36Sopenharmony_ci		dev_err(&rproc->dev, "Minidump not ready, skipping\n");
17762306a36Sopenharmony_ci		return;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/**
18162306a36Sopenharmony_ci	 * Clear out the dump segments populated by parse_fw before
18262306a36Sopenharmony_ci	 * re-populating them with minidump segments.
18362306a36Sopenharmony_ci	 */
18462306a36Sopenharmony_ci	rproc_coredump_cleanup(rproc);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	ret = qcom_add_minidump_segments(rproc, subsystem, rproc_dumpfn_t);
18762306a36Sopenharmony_ci	if (ret) {
18862306a36Sopenharmony_ci		dev_err(&rproc->dev, "Failed with error: %d while adding minidump entries\n", ret);
18962306a36Sopenharmony_ci		goto clean_minidump;
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci	rproc_coredump_using_sections(rproc);
19262306a36Sopenharmony_ciclean_minidump:
19362306a36Sopenharmony_ci	qcom_minidump_cleanup(rproc);
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_minidump);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int glink_subdev_start(struct rproc_subdev *subdev)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	glink->edge = qcom_glink_smem_register(glink->dev, glink->node);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(glink->edge);
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic void glink_subdev_stop(struct rproc_subdev *subdev, bool crashed)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	qcom_glink_smem_unregister(glink->edge);
21162306a36Sopenharmony_ci	glink->edge = NULL;
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic void glink_subdev_unprepare(struct rproc_subdev *subdev)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	qcom_glink_ssr_notify(glink->ssr_name);
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci/**
22262306a36Sopenharmony_ci * qcom_add_glink_subdev() - try to add a GLINK subdevice to rproc
22362306a36Sopenharmony_ci * @rproc:	rproc handle to parent the subdevice
22462306a36Sopenharmony_ci * @glink:	reference to a GLINK subdev context
22562306a36Sopenharmony_ci * @ssr_name:	identifier of the associated remoteproc for ssr notifications
22662306a36Sopenharmony_ci */
22762306a36Sopenharmony_civoid qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink,
22862306a36Sopenharmony_ci			   const char *ssr_name)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	struct device *dev = &rproc->dev;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	glink->node = of_get_child_by_name(dev->parent->of_node, "glink-edge");
23362306a36Sopenharmony_ci	if (!glink->node)
23462306a36Sopenharmony_ci		return;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	glink->ssr_name = kstrdup_const(ssr_name, GFP_KERNEL);
23762306a36Sopenharmony_ci	if (!glink->ssr_name)
23862306a36Sopenharmony_ci		return;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	glink->dev = dev;
24162306a36Sopenharmony_ci	glink->subdev.start = glink_subdev_start;
24262306a36Sopenharmony_ci	glink->subdev.stop = glink_subdev_stop;
24362306a36Sopenharmony_ci	glink->subdev.unprepare = glink_subdev_unprepare;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	rproc_add_subdev(rproc, &glink->subdev);
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_add_glink_subdev);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci/**
25062306a36Sopenharmony_ci * qcom_remove_glink_subdev() - remove a GLINK subdevice from rproc
25162306a36Sopenharmony_ci * @rproc:	rproc handle
25262306a36Sopenharmony_ci * @glink:	reference to a GLINK subdev context
25362306a36Sopenharmony_ci */
25462306a36Sopenharmony_civoid qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	if (!glink->node)
25762306a36Sopenharmony_ci		return;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	rproc_remove_subdev(rproc, &glink->subdev);
26062306a36Sopenharmony_ci	kfree_const(glink->ssr_name);
26162306a36Sopenharmony_ci	of_node_put(glink->node);
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_remove_glink_subdev);
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci/**
26662306a36Sopenharmony_ci * qcom_register_dump_segments() - register segments for coredump
26762306a36Sopenharmony_ci * @rproc:	remoteproc handle
26862306a36Sopenharmony_ci * @fw:		firmware header
26962306a36Sopenharmony_ci *
27062306a36Sopenharmony_ci * Register all segments of the ELF in the remoteproc coredump segment list
27162306a36Sopenharmony_ci *
27262306a36Sopenharmony_ci * Return: 0 on success, negative errno on failure.
27362306a36Sopenharmony_ci */
27462306a36Sopenharmony_ciint qcom_register_dump_segments(struct rproc *rproc,
27562306a36Sopenharmony_ci				const struct firmware *fw)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	const struct elf32_phdr *phdrs;
27862306a36Sopenharmony_ci	const struct elf32_phdr *phdr;
27962306a36Sopenharmony_ci	const struct elf32_hdr *ehdr;
28062306a36Sopenharmony_ci	int ret;
28162306a36Sopenharmony_ci	int i;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	ehdr = (struct elf32_hdr *)fw->data;
28462306a36Sopenharmony_ci	phdrs = (struct elf32_phdr *)(ehdr + 1);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	for (i = 0; i < ehdr->e_phnum; i++) {
28762306a36Sopenharmony_ci		phdr = &phdrs[i];
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		if (phdr->p_type != PT_LOAD)
29062306a36Sopenharmony_ci			continue;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci		if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
29362306a36Sopenharmony_ci			continue;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci		if (!phdr->p_memsz)
29662306a36Sopenharmony_ci			continue;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci		ret = rproc_coredump_add_segment(rproc, phdr->p_paddr,
29962306a36Sopenharmony_ci						 phdr->p_memsz);
30062306a36Sopenharmony_ci		if (ret)
30162306a36Sopenharmony_ci			return ret;
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	return 0;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_register_dump_segments);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic int smd_subdev_start(struct rproc_subdev *subdev)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	smd->edge = qcom_smd_register_edge(smd->dev, smd->node);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(smd->edge);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic void smd_subdev_stop(struct rproc_subdev *subdev, bool crashed)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	qcom_smd_unregister_edge(smd->edge);
32262306a36Sopenharmony_ci	smd->edge = NULL;
32362306a36Sopenharmony_ci}
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci/**
32662306a36Sopenharmony_ci * qcom_add_smd_subdev() - try to add a SMD subdevice to rproc
32762306a36Sopenharmony_ci * @rproc:	rproc handle to parent the subdevice
32862306a36Sopenharmony_ci * @smd:	reference to a Qualcomm subdev context
32962306a36Sopenharmony_ci */
33062306a36Sopenharmony_civoid qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
33162306a36Sopenharmony_ci{
33262306a36Sopenharmony_ci	struct device *dev = &rproc->dev;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	smd->node = of_get_child_by_name(dev->parent->of_node, "smd-edge");
33562306a36Sopenharmony_ci	if (!smd->node)
33662306a36Sopenharmony_ci		return;
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	smd->dev = dev;
33962306a36Sopenharmony_ci	smd->subdev.start = smd_subdev_start;
34062306a36Sopenharmony_ci	smd->subdev.stop = smd_subdev_stop;
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	rproc_add_subdev(rproc, &smd->subdev);
34362306a36Sopenharmony_ci}
34462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_add_smd_subdev);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci/**
34762306a36Sopenharmony_ci * qcom_remove_smd_subdev() - remove the smd subdevice from rproc
34862306a36Sopenharmony_ci * @rproc:	rproc handle
34962306a36Sopenharmony_ci * @smd:	the SMD subdevice to remove
35062306a36Sopenharmony_ci */
35162306a36Sopenharmony_civoid qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	if (!smd->node)
35462306a36Sopenharmony_ci		return;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	rproc_remove_subdev(rproc, &smd->subdev);
35762306a36Sopenharmony_ci	of_node_put(smd->node);
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_remove_smd_subdev);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic struct qcom_ssr_subsystem *qcom_ssr_get_subsys(const char *name)
36262306a36Sopenharmony_ci{
36362306a36Sopenharmony_ci	struct qcom_ssr_subsystem *info;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	mutex_lock(&qcom_ssr_subsys_lock);
36662306a36Sopenharmony_ci	/* Match in the global qcom_ssr_subsystem_list with name */
36762306a36Sopenharmony_ci	list_for_each_entry(info, &qcom_ssr_subsystem_list, list)
36862306a36Sopenharmony_ci		if (!strcmp(info->name, name))
36962306a36Sopenharmony_ci			goto out;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	info = kzalloc(sizeof(*info), GFP_KERNEL);
37262306a36Sopenharmony_ci	if (!info) {
37362306a36Sopenharmony_ci		info = ERR_PTR(-ENOMEM);
37462306a36Sopenharmony_ci		goto out;
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci	info->name = kstrdup_const(name, GFP_KERNEL);
37762306a36Sopenharmony_ci	srcu_init_notifier_head(&info->notifier_list);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	/* Add to global notification list */
38062306a36Sopenharmony_ci	list_add_tail(&info->list, &qcom_ssr_subsystem_list);
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ciout:
38362306a36Sopenharmony_ci	mutex_unlock(&qcom_ssr_subsys_lock);
38462306a36Sopenharmony_ci	return info;
38562306a36Sopenharmony_ci}
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci/**
38862306a36Sopenharmony_ci * qcom_register_ssr_notifier() - register SSR notification handler
38962306a36Sopenharmony_ci * @name:	Subsystem's SSR name
39062306a36Sopenharmony_ci * @nb:		notifier_block to be invoked upon subsystem's state change
39162306a36Sopenharmony_ci *
39262306a36Sopenharmony_ci * This registers the @nb notifier block as part the notifier chain for a
39362306a36Sopenharmony_ci * remoteproc associated with @name. The notifier block's callback
39462306a36Sopenharmony_ci * will be invoked when the remote processor's SSR events occur
39562306a36Sopenharmony_ci * (pre/post startup and pre/post shutdown).
39662306a36Sopenharmony_ci *
39762306a36Sopenharmony_ci * Return: a subsystem cookie on success, ERR_PTR on failure.
39862306a36Sopenharmony_ci */
39962306a36Sopenharmony_civoid *qcom_register_ssr_notifier(const char *name, struct notifier_block *nb)
40062306a36Sopenharmony_ci{
40162306a36Sopenharmony_ci	struct qcom_ssr_subsystem *info;
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	info = qcom_ssr_get_subsys(name);
40462306a36Sopenharmony_ci	if (IS_ERR(info))
40562306a36Sopenharmony_ci		return info;
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	srcu_notifier_chain_register(&info->notifier_list, nb);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	return &info->notifier_list;
41062306a36Sopenharmony_ci}
41162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_register_ssr_notifier);
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci/**
41462306a36Sopenharmony_ci * qcom_unregister_ssr_notifier() - unregister SSR notification handler
41562306a36Sopenharmony_ci * @notify:	subsystem cookie returned from qcom_register_ssr_notifier
41662306a36Sopenharmony_ci * @nb:		notifier_block to unregister
41762306a36Sopenharmony_ci *
41862306a36Sopenharmony_ci * This function will unregister the notifier from the particular notifier
41962306a36Sopenharmony_ci * chain.
42062306a36Sopenharmony_ci *
42162306a36Sopenharmony_ci * Return: 0 on success, %ENOENT otherwise.
42262306a36Sopenharmony_ci */
42362306a36Sopenharmony_ciint qcom_unregister_ssr_notifier(void *notify, struct notifier_block *nb)
42462306a36Sopenharmony_ci{
42562306a36Sopenharmony_ci	return srcu_notifier_chain_unregister(notify, nb);
42662306a36Sopenharmony_ci}
42762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_unregister_ssr_notifier);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_cistatic int ssr_notify_prepare(struct rproc_subdev *subdev)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
43262306a36Sopenharmony_ci	struct qcom_ssr_notify_data data = {
43362306a36Sopenharmony_ci		.name = ssr->info->name,
43462306a36Sopenharmony_ci		.crashed = false,
43562306a36Sopenharmony_ci	};
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	srcu_notifier_call_chain(&ssr->info->notifier_list,
43862306a36Sopenharmony_ci				 QCOM_SSR_BEFORE_POWERUP, &data);
43962306a36Sopenharmony_ci	return 0;
44062306a36Sopenharmony_ci}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_cistatic int ssr_notify_start(struct rproc_subdev *subdev)
44362306a36Sopenharmony_ci{
44462306a36Sopenharmony_ci	struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
44562306a36Sopenharmony_ci	struct qcom_ssr_notify_data data = {
44662306a36Sopenharmony_ci		.name = ssr->info->name,
44762306a36Sopenharmony_ci		.crashed = false,
44862306a36Sopenharmony_ci	};
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	srcu_notifier_call_chain(&ssr->info->notifier_list,
45162306a36Sopenharmony_ci				 QCOM_SSR_AFTER_POWERUP, &data);
45262306a36Sopenharmony_ci	return 0;
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_cistatic void ssr_notify_stop(struct rproc_subdev *subdev, bool crashed)
45662306a36Sopenharmony_ci{
45762306a36Sopenharmony_ci	struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
45862306a36Sopenharmony_ci	struct qcom_ssr_notify_data data = {
45962306a36Sopenharmony_ci		.name = ssr->info->name,
46062306a36Sopenharmony_ci		.crashed = crashed,
46162306a36Sopenharmony_ci	};
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	srcu_notifier_call_chain(&ssr->info->notifier_list,
46462306a36Sopenharmony_ci				 QCOM_SSR_BEFORE_SHUTDOWN, &data);
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cistatic void ssr_notify_unprepare(struct rproc_subdev *subdev)
46862306a36Sopenharmony_ci{
46962306a36Sopenharmony_ci	struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
47062306a36Sopenharmony_ci	struct qcom_ssr_notify_data data = {
47162306a36Sopenharmony_ci		.name = ssr->info->name,
47262306a36Sopenharmony_ci		.crashed = false,
47362306a36Sopenharmony_ci	};
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	srcu_notifier_call_chain(&ssr->info->notifier_list,
47662306a36Sopenharmony_ci				 QCOM_SSR_AFTER_SHUTDOWN, &data);
47762306a36Sopenharmony_ci}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci/**
48062306a36Sopenharmony_ci * qcom_add_ssr_subdev() - register subdevice as restart notification source
48162306a36Sopenharmony_ci * @rproc:	rproc handle
48262306a36Sopenharmony_ci * @ssr:	SSR subdevice handle
48362306a36Sopenharmony_ci * @ssr_name:	identifier to use for notifications originating from @rproc
48462306a36Sopenharmony_ci *
48562306a36Sopenharmony_ci * As the @ssr is registered with the @rproc SSR events will be sent to all
48662306a36Sopenharmony_ci * registered listeners for the remoteproc when it's SSR events occur
48762306a36Sopenharmony_ci * (pre/post startup and pre/post shutdown).
48862306a36Sopenharmony_ci */
48962306a36Sopenharmony_civoid qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr,
49062306a36Sopenharmony_ci			 const char *ssr_name)
49162306a36Sopenharmony_ci{
49262306a36Sopenharmony_ci	struct qcom_ssr_subsystem *info;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	info = qcom_ssr_get_subsys(ssr_name);
49562306a36Sopenharmony_ci	if (IS_ERR(info)) {
49662306a36Sopenharmony_ci		dev_err(&rproc->dev, "Failed to add ssr subdevice\n");
49762306a36Sopenharmony_ci		return;
49862306a36Sopenharmony_ci	}
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	ssr->info = info;
50162306a36Sopenharmony_ci	ssr->subdev.prepare = ssr_notify_prepare;
50262306a36Sopenharmony_ci	ssr->subdev.start = ssr_notify_start;
50362306a36Sopenharmony_ci	ssr->subdev.stop = ssr_notify_stop;
50462306a36Sopenharmony_ci	ssr->subdev.unprepare = ssr_notify_unprepare;
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	rproc_add_subdev(rproc, &ssr->subdev);
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_add_ssr_subdev);
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci/**
51162306a36Sopenharmony_ci * qcom_remove_ssr_subdev() - remove subdevice as restart notification source
51262306a36Sopenharmony_ci * @rproc:	rproc handle
51362306a36Sopenharmony_ci * @ssr:	SSR subdevice handle
51462306a36Sopenharmony_ci */
51562306a36Sopenharmony_civoid qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr)
51662306a36Sopenharmony_ci{
51762306a36Sopenharmony_ci	rproc_remove_subdev(rproc, &ssr->subdev);
51862306a36Sopenharmony_ci	ssr->info = NULL;
51962306a36Sopenharmony_ci}
52062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Remoteproc helper driver");
52362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
524