162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Qualcomm Peripheral Image Loader
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/device.h>
1162306a36Sopenharmony_ci#include <linux/elf.h>
1262306a36Sopenharmony_ci#include <linux/firmware.h>
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/firmware/qcom/qcom_scm.h>
1662306a36Sopenharmony_ci#include <linux/sizes.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/soc/qcom/mdt_loader.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic bool mdt_phdr_valid(const struct elf32_phdr *phdr)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	if (phdr->p_type != PT_LOAD)
2362306a36Sopenharmony_ci		return false;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
2662306a36Sopenharmony_ci		return false;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (!phdr->p_memsz)
2962306a36Sopenharmony_ci		return false;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	return true;
3262306a36Sopenharmony_ci}
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic ssize_t mdt_load_split_segment(void *ptr, const struct elf32_phdr *phdrs,
3562306a36Sopenharmony_ci				      unsigned int segment, const char *fw_name,
3662306a36Sopenharmony_ci				      struct device *dev)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	const struct elf32_phdr *phdr = &phdrs[segment];
3962306a36Sopenharmony_ci	const struct firmware *seg_fw;
4062306a36Sopenharmony_ci	char *seg_name;
4162306a36Sopenharmony_ci	ssize_t ret;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (strlen(fw_name) < 4)
4462306a36Sopenharmony_ci		return -EINVAL;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	seg_name = kstrdup(fw_name, GFP_KERNEL);
4762306a36Sopenharmony_ci	if (!seg_name)
4862306a36Sopenharmony_ci		return -ENOMEM;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	sprintf(seg_name + strlen(fw_name) - 3, "b%02d", segment);
5162306a36Sopenharmony_ci	ret = request_firmware_into_buf(&seg_fw, seg_name, dev,
5262306a36Sopenharmony_ci					ptr, phdr->p_filesz);
5362306a36Sopenharmony_ci	if (ret) {
5462306a36Sopenharmony_ci		dev_err(dev, "error %zd loading %s\n", ret, seg_name);
5562306a36Sopenharmony_ci		kfree(seg_name);
5662306a36Sopenharmony_ci		return ret;
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (seg_fw->size != phdr->p_filesz) {
6062306a36Sopenharmony_ci		dev_err(dev,
6162306a36Sopenharmony_ci			"failed to load segment %d from truncated file %s\n",
6262306a36Sopenharmony_ci			segment, seg_name);
6362306a36Sopenharmony_ci		ret = -EINVAL;
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	release_firmware(seg_fw);
6762306a36Sopenharmony_ci	kfree(seg_name);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	return ret;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci/**
7362306a36Sopenharmony_ci * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt
7462306a36Sopenharmony_ci * @fw:		firmware object for the mdt file
7562306a36Sopenharmony_ci *
7662306a36Sopenharmony_ci * Returns size of the loaded firmware blob, or -EINVAL on failure.
7762306a36Sopenharmony_ci */
7862306a36Sopenharmony_cissize_t qcom_mdt_get_size(const struct firmware *fw)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	const struct elf32_phdr *phdrs;
8162306a36Sopenharmony_ci	const struct elf32_phdr *phdr;
8262306a36Sopenharmony_ci	const struct elf32_hdr *ehdr;
8362306a36Sopenharmony_ci	phys_addr_t min_addr = PHYS_ADDR_MAX;
8462306a36Sopenharmony_ci	phys_addr_t max_addr = 0;
8562306a36Sopenharmony_ci	int i;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	ehdr = (struct elf32_hdr *)fw->data;
8862306a36Sopenharmony_ci	phdrs = (struct elf32_phdr *)(ehdr + 1);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	for (i = 0; i < ehdr->e_phnum; i++) {
9162306a36Sopenharmony_ci		phdr = &phdrs[i];
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		if (!mdt_phdr_valid(phdr))
9462306a36Sopenharmony_ci			continue;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		if (phdr->p_paddr < min_addr)
9762306a36Sopenharmony_ci			min_addr = phdr->p_paddr;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		if (phdr->p_paddr + phdr->p_memsz > max_addr)
10062306a36Sopenharmony_ci			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	return min_addr < max_addr ? max_addr - min_addr : -EINVAL;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_mdt_get_size);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci/**
10862306a36Sopenharmony_ci * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn
10962306a36Sopenharmony_ci * @fw:		firmware of mdt header or mbn
11062306a36Sopenharmony_ci * @data_len:	length of the read metadata blob
11162306a36Sopenharmony_ci * @fw_name:	name of the firmware, for construction of segment file names
11262306a36Sopenharmony_ci * @dev:	device handle to associate resources with
11362306a36Sopenharmony_ci *
11462306a36Sopenharmony_ci * The mechanism that performs the authentication of the loading firmware
11562306a36Sopenharmony_ci * expects an ELF header directly followed by the segment of hashes, with no
11662306a36Sopenharmony_ci * padding inbetween. This function allocates a chunk of memory for this pair
11762306a36Sopenharmony_ci * and copy the two pieces into the buffer.
11862306a36Sopenharmony_ci *
11962306a36Sopenharmony_ci * In the case of split firmware the hash is found directly following the ELF
12062306a36Sopenharmony_ci * header, rather than at p_offset described by the second program header.
12162306a36Sopenharmony_ci *
12262306a36Sopenharmony_ci * The caller is responsible to free (kfree()) the returned pointer.
12362306a36Sopenharmony_ci *
12462306a36Sopenharmony_ci * Return: pointer to data, or ERR_PTR()
12562306a36Sopenharmony_ci */
12662306a36Sopenharmony_civoid *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len,
12762306a36Sopenharmony_ci			     const char *fw_name, struct device *dev)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	const struct elf32_phdr *phdrs;
13062306a36Sopenharmony_ci	const struct elf32_hdr *ehdr;
13162306a36Sopenharmony_ci	unsigned int hash_segment = 0;
13262306a36Sopenharmony_ci	size_t hash_offset;
13362306a36Sopenharmony_ci	size_t hash_size;
13462306a36Sopenharmony_ci	size_t ehdr_size;
13562306a36Sopenharmony_ci	unsigned int i;
13662306a36Sopenharmony_ci	ssize_t ret;
13762306a36Sopenharmony_ci	void *data;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	ehdr = (struct elf32_hdr *)fw->data;
14062306a36Sopenharmony_ci	phdrs = (struct elf32_phdr *)(ehdr + 1);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (ehdr->e_phnum < 2)
14362306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (phdrs[0].p_type == PT_LOAD)
14662306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	for (i = 1; i < ehdr->e_phnum; i++) {
14962306a36Sopenharmony_ci		if ((phdrs[i].p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) {
15062306a36Sopenharmony_ci			hash_segment = i;
15162306a36Sopenharmony_ci			break;
15262306a36Sopenharmony_ci		}
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (!hash_segment) {
15662306a36Sopenharmony_ci		dev_err(dev, "no hash segment found in %s\n", fw_name);
15762306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	ehdr_size = phdrs[0].p_filesz;
16162306a36Sopenharmony_ci	hash_size = phdrs[hash_segment].p_filesz;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	data = kmalloc(ehdr_size + hash_size, GFP_KERNEL);
16462306a36Sopenharmony_ci	if (!data)
16562306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/* Copy ELF header */
16862306a36Sopenharmony_ci	memcpy(data, fw->data, ehdr_size);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (ehdr_size + hash_size == fw->size) {
17162306a36Sopenharmony_ci		/* Firmware is split and hash is packed following the ELF header */
17262306a36Sopenharmony_ci		hash_offset = phdrs[0].p_filesz;
17362306a36Sopenharmony_ci		memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);
17462306a36Sopenharmony_ci	} else if (phdrs[hash_segment].p_offset + hash_size <= fw->size) {
17562306a36Sopenharmony_ci		/* Hash is in its own segment, but within the loaded file */
17662306a36Sopenharmony_ci		hash_offset = phdrs[hash_segment].p_offset;
17762306a36Sopenharmony_ci		memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);
17862306a36Sopenharmony_ci	} else {
17962306a36Sopenharmony_ci		/* Hash is in its own segment, beyond the loaded file */
18062306a36Sopenharmony_ci		ret = mdt_load_split_segment(data + ehdr_size, phdrs, hash_segment, fw_name, dev);
18162306a36Sopenharmony_ci		if (ret) {
18262306a36Sopenharmony_ci			kfree(data);
18362306a36Sopenharmony_ci			return ERR_PTR(ret);
18462306a36Sopenharmony_ci		}
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	*data_len = ehdr_size + hash_size;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return data;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_mdt_read_metadata);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci/**
19462306a36Sopenharmony_ci * qcom_mdt_pas_init() - initialize PAS region for firmware loading
19562306a36Sopenharmony_ci * @dev:	device handle to associate resources with
19662306a36Sopenharmony_ci * @fw:		firmware object for the mdt file
19762306a36Sopenharmony_ci * @fw_name:	name of the firmware, for construction of segment file names
19862306a36Sopenharmony_ci * @pas_id:	PAS identifier
19962306a36Sopenharmony_ci * @mem_phys:	physical address of allocated memory region
20062306a36Sopenharmony_ci * @ctx:	PAS metadata context, to be released by caller
20162306a36Sopenharmony_ci *
20262306a36Sopenharmony_ci * Returns 0 on success, negative errno otherwise.
20362306a36Sopenharmony_ci */
20462306a36Sopenharmony_ciint qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,
20562306a36Sopenharmony_ci		      const char *fw_name, int pas_id, phys_addr_t mem_phys,
20662306a36Sopenharmony_ci		      struct qcom_scm_pas_metadata *ctx)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	const struct elf32_phdr *phdrs;
20962306a36Sopenharmony_ci	const struct elf32_phdr *phdr;
21062306a36Sopenharmony_ci	const struct elf32_hdr *ehdr;
21162306a36Sopenharmony_ci	phys_addr_t min_addr = PHYS_ADDR_MAX;
21262306a36Sopenharmony_ci	phys_addr_t max_addr = 0;
21362306a36Sopenharmony_ci	bool relocate = false;
21462306a36Sopenharmony_ci	size_t metadata_len;
21562306a36Sopenharmony_ci	void *metadata;
21662306a36Sopenharmony_ci	int ret;
21762306a36Sopenharmony_ci	int i;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	ehdr = (struct elf32_hdr *)fw->data;
22062306a36Sopenharmony_ci	phdrs = (struct elf32_phdr *)(ehdr + 1);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	for (i = 0; i < ehdr->e_phnum; i++) {
22362306a36Sopenharmony_ci		phdr = &phdrs[i];
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		if (!mdt_phdr_valid(phdr))
22662306a36Sopenharmony_ci			continue;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
22962306a36Sopenharmony_ci			relocate = true;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		if (phdr->p_paddr < min_addr)
23262306a36Sopenharmony_ci			min_addr = phdr->p_paddr;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci		if (phdr->p_paddr + phdr->p_memsz > max_addr)
23562306a36Sopenharmony_ci			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
23662306a36Sopenharmony_ci	}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	metadata = qcom_mdt_read_metadata(fw, &metadata_len, fw_name, dev);
23962306a36Sopenharmony_ci	if (IS_ERR(metadata)) {
24062306a36Sopenharmony_ci		ret = PTR_ERR(metadata);
24162306a36Sopenharmony_ci		dev_err(dev, "error %d reading firmware %s metadata\n", ret, fw_name);
24262306a36Sopenharmony_ci		goto out;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx);
24662306a36Sopenharmony_ci	kfree(metadata);
24762306a36Sopenharmony_ci	if (ret) {
24862306a36Sopenharmony_ci		/* Invalid firmware metadata */
24962306a36Sopenharmony_ci		dev_err(dev, "error %d initializing firmware %s\n", ret, fw_name);
25062306a36Sopenharmony_ci		goto out;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (relocate) {
25462306a36Sopenharmony_ci		ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
25562306a36Sopenharmony_ci		if (ret) {
25662306a36Sopenharmony_ci			/* Unable to set up relocation */
25762306a36Sopenharmony_ci			dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name);
25862306a36Sopenharmony_ci			goto out;
25962306a36Sopenharmony_ci		}
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ciout:
26362306a36Sopenharmony_ci	return ret;
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_mdt_pas_init);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic bool qcom_mdt_bins_are_split(const struct firmware *fw, const char *fw_name)
26862306a36Sopenharmony_ci{
26962306a36Sopenharmony_ci	const struct elf32_phdr *phdrs;
27062306a36Sopenharmony_ci	const struct elf32_hdr *ehdr;
27162306a36Sopenharmony_ci	uint64_t seg_start, seg_end;
27262306a36Sopenharmony_ci	int i;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	ehdr = (struct elf32_hdr *)fw->data;
27562306a36Sopenharmony_ci	phdrs = (struct elf32_phdr *)(ehdr + 1);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	for (i = 0; i < ehdr->e_phnum; i++) {
27862306a36Sopenharmony_ci		/*
27962306a36Sopenharmony_ci		 * The size of the MDT file is not padded to include any
28062306a36Sopenharmony_ci		 * zero-sized segments at the end. Ignore these, as they should
28162306a36Sopenharmony_ci		 * not affect the decision about image being split or not.
28262306a36Sopenharmony_ci		 */
28362306a36Sopenharmony_ci		if (!phdrs[i].p_filesz)
28462306a36Sopenharmony_ci			continue;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci		seg_start = phdrs[i].p_offset;
28762306a36Sopenharmony_ci		seg_end = phdrs[i].p_offset + phdrs[i].p_filesz;
28862306a36Sopenharmony_ci		if (seg_start > fw->size || seg_end > fw->size)
28962306a36Sopenharmony_ci			return true;
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	return false;
29362306a36Sopenharmony_ci}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_cistatic int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
29662306a36Sopenharmony_ci			   const char *fw_name, int pas_id, void *mem_region,
29762306a36Sopenharmony_ci			   phys_addr_t mem_phys, size_t mem_size,
29862306a36Sopenharmony_ci			   phys_addr_t *reloc_base, bool pas_init)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	const struct elf32_phdr *phdrs;
30162306a36Sopenharmony_ci	const struct elf32_phdr *phdr;
30262306a36Sopenharmony_ci	const struct elf32_hdr *ehdr;
30362306a36Sopenharmony_ci	phys_addr_t mem_reloc;
30462306a36Sopenharmony_ci	phys_addr_t min_addr = PHYS_ADDR_MAX;
30562306a36Sopenharmony_ci	ssize_t offset;
30662306a36Sopenharmony_ci	bool relocate = false;
30762306a36Sopenharmony_ci	bool is_split;
30862306a36Sopenharmony_ci	void *ptr;
30962306a36Sopenharmony_ci	int ret = 0;
31062306a36Sopenharmony_ci	int i;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (!fw || !mem_region || !mem_phys || !mem_size)
31362306a36Sopenharmony_ci		return -EINVAL;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	is_split = qcom_mdt_bins_are_split(fw, fw_name);
31662306a36Sopenharmony_ci	ehdr = (struct elf32_hdr *)fw->data;
31762306a36Sopenharmony_ci	phdrs = (struct elf32_phdr *)(ehdr + 1);
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	for (i = 0; i < ehdr->e_phnum; i++) {
32062306a36Sopenharmony_ci		phdr = &phdrs[i];
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci		if (!mdt_phdr_valid(phdr))
32362306a36Sopenharmony_ci			continue;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci		if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
32662306a36Sopenharmony_ci			relocate = true;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		if (phdr->p_paddr < min_addr)
32962306a36Sopenharmony_ci			min_addr = phdr->p_paddr;
33062306a36Sopenharmony_ci	}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	if (relocate) {
33362306a36Sopenharmony_ci		/*
33462306a36Sopenharmony_ci		 * The image is relocatable, so offset each segment based on
33562306a36Sopenharmony_ci		 * the lowest segment address.
33662306a36Sopenharmony_ci		 */
33762306a36Sopenharmony_ci		mem_reloc = min_addr;
33862306a36Sopenharmony_ci	} else {
33962306a36Sopenharmony_ci		/*
34062306a36Sopenharmony_ci		 * Image is not relocatable, so offset each segment based on
34162306a36Sopenharmony_ci		 * the allocated physical chunk of memory.
34262306a36Sopenharmony_ci		 */
34362306a36Sopenharmony_ci		mem_reloc = mem_phys;
34462306a36Sopenharmony_ci	}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	for (i = 0; i < ehdr->e_phnum; i++) {
34762306a36Sopenharmony_ci		phdr = &phdrs[i];
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci		if (!mdt_phdr_valid(phdr))
35062306a36Sopenharmony_ci			continue;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci		offset = phdr->p_paddr - mem_reloc;
35362306a36Sopenharmony_ci		if (offset < 0 || offset + phdr->p_memsz > mem_size) {
35462306a36Sopenharmony_ci			dev_err(dev, "segment outside memory range\n");
35562306a36Sopenharmony_ci			ret = -EINVAL;
35662306a36Sopenharmony_ci			break;
35762306a36Sopenharmony_ci		}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci		if (phdr->p_filesz > phdr->p_memsz) {
36062306a36Sopenharmony_ci			dev_err(dev,
36162306a36Sopenharmony_ci				"refusing to load segment %d with p_filesz > p_memsz\n",
36262306a36Sopenharmony_ci				i);
36362306a36Sopenharmony_ci			ret = -EINVAL;
36462306a36Sopenharmony_ci			break;
36562306a36Sopenharmony_ci		}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci		ptr = mem_region + offset;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci		if (phdr->p_filesz && !is_split) {
37062306a36Sopenharmony_ci			/* Firmware is large enough to be non-split */
37162306a36Sopenharmony_ci			if (phdr->p_offset + phdr->p_filesz > fw->size) {
37262306a36Sopenharmony_ci				dev_err(dev, "file %s segment %d would be truncated\n",
37362306a36Sopenharmony_ci					fw_name, i);
37462306a36Sopenharmony_ci				ret = -EINVAL;
37562306a36Sopenharmony_ci				break;
37662306a36Sopenharmony_ci			}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci			memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);
37962306a36Sopenharmony_ci		} else if (phdr->p_filesz) {
38062306a36Sopenharmony_ci			/* Firmware not large enough, load split-out segments */
38162306a36Sopenharmony_ci			ret = mdt_load_split_segment(ptr, phdrs, i, fw_name, dev);
38262306a36Sopenharmony_ci			if (ret)
38362306a36Sopenharmony_ci				break;
38462306a36Sopenharmony_ci		}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci		if (phdr->p_memsz > phdr->p_filesz)
38762306a36Sopenharmony_ci			memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	if (reloc_base)
39162306a36Sopenharmony_ci		*reloc_base = mem_reloc;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	return ret;
39462306a36Sopenharmony_ci}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci/**
39762306a36Sopenharmony_ci * qcom_mdt_load() - load the firmware which header is loaded as fw
39862306a36Sopenharmony_ci * @dev:	device handle to associate resources with
39962306a36Sopenharmony_ci * @fw:		firmware object for the mdt file
40062306a36Sopenharmony_ci * @firmware:	name of the firmware, for construction of segment file names
40162306a36Sopenharmony_ci * @pas_id:	PAS identifier
40262306a36Sopenharmony_ci * @mem_region:	allocated memory region to load firmware into
40362306a36Sopenharmony_ci * @mem_phys:	physical address of allocated memory region
40462306a36Sopenharmony_ci * @mem_size:	size of the allocated memory region
40562306a36Sopenharmony_ci * @reloc_base:	adjusted physical address after relocation
40662306a36Sopenharmony_ci *
40762306a36Sopenharmony_ci * Returns 0 on success, negative errno otherwise.
40862306a36Sopenharmony_ci */
40962306a36Sopenharmony_ciint qcom_mdt_load(struct device *dev, const struct firmware *fw,
41062306a36Sopenharmony_ci		  const char *firmware, int pas_id, void *mem_region,
41162306a36Sopenharmony_ci		  phys_addr_t mem_phys, size_t mem_size,
41262306a36Sopenharmony_ci		  phys_addr_t *reloc_base)
41362306a36Sopenharmony_ci{
41462306a36Sopenharmony_ci	int ret;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	ret = qcom_mdt_pas_init(dev, fw, firmware, pas_id, mem_phys, NULL);
41762306a36Sopenharmony_ci	if (ret)
41862306a36Sopenharmony_ci		return ret;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
42162306a36Sopenharmony_ci			       mem_size, reloc_base, true);
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_mdt_load);
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci/**
42662306a36Sopenharmony_ci * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw
42762306a36Sopenharmony_ci * @dev:	device handle to associate resources with
42862306a36Sopenharmony_ci * @fw:		firmware object for the mdt file
42962306a36Sopenharmony_ci * @firmware:	name of the firmware, for construction of segment file names
43062306a36Sopenharmony_ci * @pas_id:	PAS identifier
43162306a36Sopenharmony_ci * @mem_region:	allocated memory region to load firmware into
43262306a36Sopenharmony_ci * @mem_phys:	physical address of allocated memory region
43362306a36Sopenharmony_ci * @mem_size:	size of the allocated memory region
43462306a36Sopenharmony_ci * @reloc_base:	adjusted physical address after relocation
43562306a36Sopenharmony_ci *
43662306a36Sopenharmony_ci * Returns 0 on success, negative errno otherwise.
43762306a36Sopenharmony_ci */
43862306a36Sopenharmony_ciint qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,
43962306a36Sopenharmony_ci			  const char *firmware, int pas_id,
44062306a36Sopenharmony_ci			  void *mem_region, phys_addr_t mem_phys,
44162306a36Sopenharmony_ci			  size_t mem_size, phys_addr_t *reloc_base)
44262306a36Sopenharmony_ci{
44362306a36Sopenharmony_ci	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
44462306a36Sopenharmony_ci			       mem_size, reloc_base, false);
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_mdt_load_no_init);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ciMODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");
44962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
450