brintos

brintos / linux-shallow public Read only

0
0
Text · 11.9 KiB · b2c0fb5 Raw
448 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Qualcomm Peripheral Image Loader4 *5 * Copyright (C) 2016 Linaro Ltd6 * Copyright (C) 2015 Sony Mobile Communications Inc7 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.8 */9 10#include <linux/cleanup.h>11#include <linux/device.h>12#include <linux/elf.h>13#include <linux/firmware.h>14#include <linux/kernel.h>15#include <linux/module.h>16#include <linux/firmware/qcom/qcom_scm.h>17#include <linux/sizes.h>18#include <linux/slab.h>19#include <linux/soc/qcom/mdt_loader.h>20 21static bool mdt_phdr_valid(const struct elf32_phdr *phdr)22{23	if (phdr->p_type != PT_LOAD)24		return false;25 26	if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)27		return false;28 29	if (!phdr->p_memsz)30		return false;31 32	return true;33}34 35static ssize_t mdt_load_split_segment(void *ptr, const struct elf32_phdr *phdrs,36				      unsigned int segment, const char *fw_name,37				      struct device *dev)38{39	const struct elf32_phdr *phdr = &phdrs[segment];40	const struct firmware *seg_fw;41	ssize_t ret;42 43	if (strlen(fw_name) < 4)44		return -EINVAL;45 46	char *seg_name __free(kfree) = kstrdup(fw_name, GFP_KERNEL);47	if (!seg_name)48		return -ENOMEM;49 50	sprintf(seg_name + strlen(fw_name) - 3, "b%02d", segment);51	ret = request_firmware_into_buf(&seg_fw, seg_name, dev,52					ptr, phdr->p_filesz);53	if (ret) {54		dev_err(dev, "error %zd loading %s\n", ret, seg_name);55		return ret;56	}57 58	if (seg_fw->size != phdr->p_filesz) {59		dev_err(dev,60			"failed to load segment %d from truncated file %s\n",61			segment, seg_name);62		ret = -EINVAL;63	}64 65	release_firmware(seg_fw);66 67	return ret;68}69 70/**71 * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt72 * @fw:		firmware object for the mdt file73 *74 * Returns size of the loaded firmware blob, or -EINVAL on failure.75 */76ssize_t qcom_mdt_get_size(const struct firmware *fw)77{78	const struct elf32_phdr *phdrs;79	const struct elf32_phdr *phdr;80	const struct elf32_hdr *ehdr;81	phys_addr_t min_addr = PHYS_ADDR_MAX;82	phys_addr_t max_addr = 0;83	int i;84 85	ehdr = (struct elf32_hdr *)fw->data;86	phdrs = (struct elf32_phdr *)(ehdr + 1);87 88	for (i = 0; i < ehdr->e_phnum; i++) {89		phdr = &phdrs[i];90 91		if (!mdt_phdr_valid(phdr))92			continue;93 94		if (phdr->p_paddr < min_addr)95			min_addr = phdr->p_paddr;96 97		if (phdr->p_paddr + phdr->p_memsz > max_addr)98			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);99	}100 101	return min_addr < max_addr ? max_addr - min_addr : -EINVAL;102}103EXPORT_SYMBOL_GPL(qcom_mdt_get_size);104 105/**106 * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn107 * @fw:		firmware of mdt header or mbn108 * @data_len:	length of the read metadata blob109 * @fw_name:	name of the firmware, for construction of segment file names110 * @dev:	device handle to associate resources with111 *112 * The mechanism that performs the authentication of the loading firmware113 * expects an ELF header directly followed by the segment of hashes, with no114 * padding inbetween. This function allocates a chunk of memory for this pair115 * and copy the two pieces into the buffer.116 *117 * In the case of split firmware the hash is found directly following the ELF118 * header, rather than at p_offset described by the second program header.119 *120 * The caller is responsible to free (kfree()) the returned pointer.121 *122 * Return: pointer to data, or ERR_PTR()123 */124void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len,125			     const char *fw_name, struct device *dev)126{127	const struct elf32_phdr *phdrs;128	const struct elf32_hdr *ehdr;129	unsigned int hash_segment = 0;130	size_t hash_offset;131	size_t hash_size;132	size_t ehdr_size;133	unsigned int i;134	ssize_t ret;135	void *data;136 137	ehdr = (struct elf32_hdr *)fw->data;138	phdrs = (struct elf32_phdr *)(ehdr + 1);139 140	if (ehdr->e_phnum < 2)141		return ERR_PTR(-EINVAL);142 143	if (phdrs[0].p_type == PT_LOAD)144		return ERR_PTR(-EINVAL);145 146	for (i = 1; i < ehdr->e_phnum; i++) {147		if ((phdrs[i].p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) {148			hash_segment = i;149			break;150		}151	}152 153	if (!hash_segment) {154		dev_err(dev, "no hash segment found in %s\n", fw_name);155		return ERR_PTR(-EINVAL);156	}157 158	ehdr_size = phdrs[0].p_filesz;159	hash_size = phdrs[hash_segment].p_filesz;160 161	data = kmalloc(ehdr_size + hash_size, GFP_KERNEL);162	if (!data)163		return ERR_PTR(-ENOMEM);164 165	/* Copy ELF header */166	memcpy(data, fw->data, ehdr_size);167 168	if (ehdr_size + hash_size == fw->size) {169		/* Firmware is split and hash is packed following the ELF header */170		hash_offset = phdrs[0].p_filesz;171		memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);172	} else if (phdrs[hash_segment].p_offset + hash_size <= fw->size) {173		/* Hash is in its own segment, but within the loaded file */174		hash_offset = phdrs[hash_segment].p_offset;175		memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);176	} else {177		/* Hash is in its own segment, beyond the loaded file */178		ret = mdt_load_split_segment(data + ehdr_size, phdrs, hash_segment, fw_name, dev);179		if (ret) {180			kfree(data);181			return ERR_PTR(ret);182		}183	}184 185	*data_len = ehdr_size + hash_size;186 187	return data;188}189EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata);190 191/**192 * qcom_mdt_pas_init() - initialize PAS region for firmware loading193 * @dev:	device handle to associate resources with194 * @fw:		firmware object for the mdt file195 * @fw_name:	name of the firmware, for construction of segment file names196 * @pas_id:	PAS identifier197 * @mem_phys:	physical address of allocated memory region198 * @ctx:	PAS metadata context, to be released by caller199 *200 * Returns 0 on success, negative errno otherwise.201 */202int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,203		      const char *fw_name, int pas_id, phys_addr_t mem_phys,204		      struct qcom_scm_pas_metadata *ctx)205{206	const struct elf32_phdr *phdrs;207	const struct elf32_phdr *phdr;208	const struct elf32_hdr *ehdr;209	phys_addr_t min_addr = PHYS_ADDR_MAX;210	phys_addr_t max_addr = 0;211	bool relocate = false;212	size_t metadata_len;213	void *metadata;214	int ret;215	int i;216 217	ehdr = (struct elf32_hdr *)fw->data;218	phdrs = (struct elf32_phdr *)(ehdr + 1);219 220	for (i = 0; i < ehdr->e_phnum; i++) {221		phdr = &phdrs[i];222 223		if (!mdt_phdr_valid(phdr))224			continue;225 226		if (phdr->p_flags & QCOM_MDT_RELOCATABLE)227			relocate = true;228 229		if (phdr->p_paddr < min_addr)230			min_addr = phdr->p_paddr;231 232		if (phdr->p_paddr + phdr->p_memsz > max_addr)233			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);234	}235 236	metadata = qcom_mdt_read_metadata(fw, &metadata_len, fw_name, dev);237	if (IS_ERR(metadata)) {238		ret = PTR_ERR(metadata);239		dev_err(dev, "error %d reading firmware %s metadata\n", ret, fw_name);240		goto out;241	}242 243	ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx);244	kfree(metadata);245	if (ret) {246		/* Invalid firmware metadata */247		dev_err(dev, "error %d initializing firmware %s\n", ret, fw_name);248		goto out;249	}250 251	if (relocate) {252		ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);253		if (ret) {254			/* Unable to set up relocation */255			dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name);256			goto out;257		}258	}259 260out:261	return ret;262}263EXPORT_SYMBOL_GPL(qcom_mdt_pas_init);264 265static bool qcom_mdt_bins_are_split(const struct firmware *fw, const char *fw_name)266{267	const struct elf32_phdr *phdrs;268	const struct elf32_hdr *ehdr;269	uint64_t seg_start, seg_end;270	int i;271 272	ehdr = (struct elf32_hdr *)fw->data;273	phdrs = (struct elf32_phdr *)(ehdr + 1);274 275	for (i = 0; i < ehdr->e_phnum; i++) {276		/*277		 * The size of the MDT file is not padded to include any278		 * zero-sized segments at the end. Ignore these, as they should279		 * not affect the decision about image being split or not.280		 */281		if (!phdrs[i].p_filesz)282			continue;283 284		seg_start = phdrs[i].p_offset;285		seg_end = phdrs[i].p_offset + phdrs[i].p_filesz;286		if (seg_start > fw->size || seg_end > fw->size)287			return true;288	}289 290	return false;291}292 293static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,294			   const char *fw_name, int pas_id, void *mem_region,295			   phys_addr_t mem_phys, size_t mem_size,296			   phys_addr_t *reloc_base, bool pas_init)297{298	const struct elf32_phdr *phdrs;299	const struct elf32_phdr *phdr;300	const struct elf32_hdr *ehdr;301	phys_addr_t mem_reloc;302	phys_addr_t min_addr = PHYS_ADDR_MAX;303	ssize_t offset;304	bool relocate = false;305	bool is_split;306	void *ptr;307	int ret = 0;308	int i;309 310	if (!fw || !mem_region || !mem_phys || !mem_size)311		return -EINVAL;312 313	is_split = qcom_mdt_bins_are_split(fw, fw_name);314	ehdr = (struct elf32_hdr *)fw->data;315	phdrs = (struct elf32_phdr *)(ehdr + 1);316 317	for (i = 0; i < ehdr->e_phnum; i++) {318		phdr = &phdrs[i];319 320		if (!mdt_phdr_valid(phdr))321			continue;322 323		if (phdr->p_flags & QCOM_MDT_RELOCATABLE)324			relocate = true;325 326		if (phdr->p_paddr < min_addr)327			min_addr = phdr->p_paddr;328	}329 330	if (relocate) {331		/*332		 * The image is relocatable, so offset each segment based on333		 * the lowest segment address.334		 */335		mem_reloc = min_addr;336	} else {337		/*338		 * Image is not relocatable, so offset each segment based on339		 * the allocated physical chunk of memory.340		 */341		mem_reloc = mem_phys;342	}343 344	for (i = 0; i < ehdr->e_phnum; i++) {345		phdr = &phdrs[i];346 347		if (!mdt_phdr_valid(phdr))348			continue;349 350		offset = phdr->p_paddr - mem_reloc;351		if (offset < 0 || offset + phdr->p_memsz > mem_size) {352			dev_err(dev, "segment outside memory range\n");353			ret = -EINVAL;354			break;355		}356 357		if (phdr->p_filesz > phdr->p_memsz) {358			dev_err(dev,359				"refusing to load segment %d with p_filesz > p_memsz\n",360				i);361			ret = -EINVAL;362			break;363		}364 365		ptr = mem_region + offset;366 367		if (phdr->p_filesz && !is_split) {368			/* Firmware is large enough to be non-split */369			if (phdr->p_offset + phdr->p_filesz > fw->size) {370				dev_err(dev, "file %s segment %d would be truncated\n",371					fw_name, i);372				ret = -EINVAL;373				break;374			}375 376			memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);377		} else if (phdr->p_filesz) {378			/* Firmware not large enough, load split-out segments */379			ret = mdt_load_split_segment(ptr, phdrs, i, fw_name, dev);380			if (ret)381				break;382		}383 384		if (phdr->p_memsz > phdr->p_filesz)385			memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);386	}387 388	if (reloc_base)389		*reloc_base = mem_reloc;390 391	return ret;392}393 394/**395 * qcom_mdt_load() - load the firmware which header is loaded as fw396 * @dev:	device handle to associate resources with397 * @fw:		firmware object for the mdt file398 * @firmware:	name of the firmware, for construction of segment file names399 * @pas_id:	PAS identifier400 * @mem_region:	allocated memory region to load firmware into401 * @mem_phys:	physical address of allocated memory region402 * @mem_size:	size of the allocated memory region403 * @reloc_base:	adjusted physical address after relocation404 *405 * Returns 0 on success, negative errno otherwise.406 */407int qcom_mdt_load(struct device *dev, const struct firmware *fw,408		  const char *firmware, int pas_id, void *mem_region,409		  phys_addr_t mem_phys, size_t mem_size,410		  phys_addr_t *reloc_base)411{412	int ret;413 414	ret = qcom_mdt_pas_init(dev, fw, firmware, pas_id, mem_phys, NULL);415	if (ret)416		return ret;417 418	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,419			       mem_size, reloc_base, true);420}421EXPORT_SYMBOL_GPL(qcom_mdt_load);422 423/**424 * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw425 * @dev:	device handle to associate resources with426 * @fw:		firmware object for the mdt file427 * @firmware:	name of the firmware, for construction of segment file names428 * @pas_id:	PAS identifier429 * @mem_region:	allocated memory region to load firmware into430 * @mem_phys:	physical address of allocated memory region431 * @mem_size:	size of the allocated memory region432 * @reloc_base:	adjusted physical address after relocation433 *434 * Returns 0 on success, negative errno otherwise.435 */436int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,437			  const char *firmware, int pas_id,438			  void *mem_region, phys_addr_t mem_phys,439			  size_t mem_size, phys_addr_t *reloc_base)440{441	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,442			       mem_size, reloc_base, false);443}444EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init);445 446MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");447MODULE_LICENSE("GPL v2");448