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