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