162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2019-2020 Linaro Ltd.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#include <linux/kernel.h>
662306a36Sopenharmony_ci#include <linux/module.h>
762306a36Sopenharmony_ci#include <linux/mutex.h>
862306a36Sopenharmony_ci#include <linux/of_address.h>
962306a36Sopenharmony_ci#include "qcom_pil_info.h"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci/*
1262306a36Sopenharmony_ci * The PIL relocation information region is used to communicate memory regions
1362306a36Sopenharmony_ci * occupied by co-processor firmware for post mortem crash analysis.
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * It consists of an array of entries with an 8 byte textual identifier of the
1662306a36Sopenharmony_ci * region followed by a 64 bit base address and 32 bit size, both little
1762306a36Sopenharmony_ci * endian.
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_ci#define PIL_RELOC_NAME_LEN	8
2062306a36Sopenharmony_ci#define PIL_RELOC_ENTRY_SIZE	(PIL_RELOC_NAME_LEN + sizeof(__le64) + sizeof(__le32))
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct pil_reloc {
2362306a36Sopenharmony_ci	void __iomem *base;
2462306a36Sopenharmony_ci	size_t num_entries;
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic struct pil_reloc _reloc __read_mostly;
2862306a36Sopenharmony_cistatic DEFINE_MUTEX(pil_reloc_lock);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic int qcom_pil_info_init(void)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct device_node *np;
3362306a36Sopenharmony_ci	struct resource imem;
3462306a36Sopenharmony_ci	void __iomem *base;
3562306a36Sopenharmony_ci	int ret;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	/* Already initialized? */
3862306a36Sopenharmony_ci	if (_reloc.base)
3962306a36Sopenharmony_ci		return 0;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	np = of_find_compatible_node(NULL, NULL, "qcom,pil-reloc-info");
4262306a36Sopenharmony_ci	if (!np)
4362306a36Sopenharmony_ci		return -ENOENT;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	ret = of_address_to_resource(np, 0, &imem);
4662306a36Sopenharmony_ci	of_node_put(np);
4762306a36Sopenharmony_ci	if (ret < 0)
4862306a36Sopenharmony_ci		return ret;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	base = ioremap(imem.start, resource_size(&imem));
5162306a36Sopenharmony_ci	if (!base) {
5262306a36Sopenharmony_ci		pr_err("failed to map PIL relocation info region\n");
5362306a36Sopenharmony_ci		return -ENOMEM;
5462306a36Sopenharmony_ci	}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	memset_io(base, 0, resource_size(&imem));
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	_reloc.base = base;
5962306a36Sopenharmony_ci	_reloc.num_entries = (u32)resource_size(&imem) / PIL_RELOC_ENTRY_SIZE;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	return 0;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci/**
6562306a36Sopenharmony_ci * qcom_pil_info_store() - store PIL information of image in IMEM
6662306a36Sopenharmony_ci * @image:	name of the image
6762306a36Sopenharmony_ci * @base:	base address of the loaded image
6862306a36Sopenharmony_ci * @size:	size of the loaded image
6962306a36Sopenharmony_ci *
7062306a36Sopenharmony_ci * Return: 0 on success, negative errno on failure
7162306a36Sopenharmony_ci */
7262306a36Sopenharmony_ciint qcom_pil_info_store(const char *image, phys_addr_t base, size_t size)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	char buf[PIL_RELOC_NAME_LEN];
7562306a36Sopenharmony_ci	void __iomem *entry;
7662306a36Sopenharmony_ci	int ret;
7762306a36Sopenharmony_ci	int i;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	mutex_lock(&pil_reloc_lock);
8062306a36Sopenharmony_ci	ret = qcom_pil_info_init();
8162306a36Sopenharmony_ci	if (ret < 0) {
8262306a36Sopenharmony_ci		mutex_unlock(&pil_reloc_lock);
8362306a36Sopenharmony_ci		return ret;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	for (i = 0; i < _reloc.num_entries; i++) {
8762306a36Sopenharmony_ci		entry = _reloc.base + i * PIL_RELOC_ENTRY_SIZE;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		memcpy_fromio(buf, entry, PIL_RELOC_NAME_LEN);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci		/*
9262306a36Sopenharmony_ci		 * An empty record means we didn't find it, given that the
9362306a36Sopenharmony_ci		 * records are packed.
9462306a36Sopenharmony_ci		 */
9562306a36Sopenharmony_ci		if (!buf[0])
9662306a36Sopenharmony_ci			goto found_unused;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci		if (!strncmp(buf, image, PIL_RELOC_NAME_LEN))
9962306a36Sopenharmony_ci			goto found_existing;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	pr_warn("insufficient PIL info slots\n");
10362306a36Sopenharmony_ci	mutex_unlock(&pil_reloc_lock);
10462306a36Sopenharmony_ci	return -ENOMEM;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cifound_unused:
10762306a36Sopenharmony_ci	memcpy_toio(entry, image, strnlen(image, PIL_RELOC_NAME_LEN));
10862306a36Sopenharmony_cifound_existing:
10962306a36Sopenharmony_ci	/* Use two writel() as base is only aligned to 4 bytes on odd entries */
11062306a36Sopenharmony_ci	writel(base, entry + PIL_RELOC_NAME_LEN);
11162306a36Sopenharmony_ci	writel((u64)base >> 32, entry + PIL_RELOC_NAME_LEN + 4);
11262306a36Sopenharmony_ci	writel(size, entry + PIL_RELOC_NAME_LEN + sizeof(__le64));
11362306a36Sopenharmony_ci	mutex_unlock(&pil_reloc_lock);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return 0;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_pil_info_store);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic void __exit pil_reloc_exit(void)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	mutex_lock(&pil_reloc_lock);
12262306a36Sopenharmony_ci	iounmap(_reloc.base);
12362306a36Sopenharmony_ci	_reloc.base = NULL;
12462306a36Sopenharmony_ci	mutex_unlock(&pil_reloc_lock);
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_cimodule_exit(pil_reloc_exit);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm PIL relocation info");
12962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
130