1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Qualcomm Peripheral Image Loader 4 * 5 * Copyright (C) 2016 Linaro Ltd 6 * Copyright (C) 2015 Sony Mobile Communications Inc 7 * 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 21 static bool mdt_header_valid(const struct firmware *fw) 22 { 23 const struct elf32_hdr *ehdr; 24 size_t phend; 25 size_t shend; 26 27 if (fw->size < sizeof(*ehdr)) 28 return false; 29 30 ehdr = (struct elf32_hdr *)fw->data; 31 32 if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) 33 return false; 34 35 if (ehdr->e_phentsize != sizeof(struct elf32_phdr)) 36 return false; 37 38 phend = size_add(size_mul(sizeof(struct elf32_phdr), ehdr->e_phnum), ehdr->e_phoff); 39 if (phend > fw->size) 40 return false; 41 42 if (ehdr->e_shentsize || ehdr->e_shnum) { 43 if (ehdr->e_shentsize != sizeof(struct elf32_shdr)) 44 return false; 45 46 shend = size_add(size_mul(sizeof(struct elf32_shdr), ehdr->e_shnum), ehdr->e_shoff); 47 if (shend > fw->size) 48 return false; 49 } 50 51 return true; 52 } 53 54 static bool mdt_phdr_loadable(const struct elf32_phdr *phdr) 55 { 56 if (phdr->p_type != PT_LOAD) 57 return false; 58 59 if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) 60 return false; 61 62 if (!phdr->p_memsz) 63 return false; 64 65 return true; 66 } 67 68 static ssize_t mdt_load_split_segment(void *ptr, const struct elf32_phdr *phdrs, 69 unsigned int segment, const char *fw_name, 70 struct device *dev) 71 { 72 const struct elf32_phdr *phdr = &phdrs[segment]; 73 const struct firmware *seg_fw; 74 ssize_t ret; 75 76 if (strlen(fw_name) < 4) 77 return -EINVAL; 78 79 char *seg_name __free(kfree) = kstrdup(fw_name, GFP_KERNEL); 80 if (!seg_name) 81 return -ENOMEM; 82 83 sprintf(seg_name + strlen(fw_name) - 3, "b%02d", segment); 84 ret = request_firmware_into_buf(&seg_fw, seg_name, dev, 85 ptr, phdr->p_filesz); 86 if (ret) { 87 dev_err(dev, "error %zd loading %s\n", ret, seg_name); 88 return ret; 89 } 90 91 if (seg_fw->size != phdr->p_filesz) { 92 dev_err(dev, 93 "failed to load segment %d from truncated file %s\n", 94 segment, seg_name); 95 ret = -EINVAL; 96 } 97 98 release_firmware(seg_fw); 99 100 return ret; 101 } 102 103 /** 104 * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt 105 * @fw: firmware object for the mdt file 106 * 107 * Returns size of the loaded firmware blob, or -EINVAL on failure. 108 */ 109 ssize_t qcom_mdt_get_size(const struct firmware *fw) 110 { 111 const struct elf32_phdr *phdrs; 112 const struct elf32_phdr *phdr; 113 const struct elf32_hdr *ehdr; 114 phys_addr_t min_addr = PHYS_ADDR_MAX; 115 phys_addr_t max_addr = 0; 116 int i; 117 118 if (!mdt_header_valid(fw)) 119 return -EINVAL; 120 121 ehdr = (struct elf32_hdr *)fw->data; 122 phdrs = (struct elf32_phdr *)(fw->data + ehdr->e_phoff); 123 124 for (i = 0; i < ehdr->e_phnum; i++) { 125 phdr = &phdrs[i]; 126 127 if (!mdt_phdr_loadable(phdr)) 128 continue; 129 130 if (phdr->p_paddr < min_addr) 131 min_addr = phdr->p_paddr; 132 133 if (phdr->p_paddr + phdr->p_memsz > max_addr) 134 max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); 135 } 136 137 return min_addr < max_addr ? max_addr - min_addr : -EINVAL; 138 } 139 EXPORT_SYMBOL_GPL(qcom_mdt_get_size); 140 141 /** 142 * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn 143 * @fw: firmware of mdt header or mbn 144 * @data_len: length of the read metadata blob 145 * @fw_name: name of the firmware, for construction of segment file names 146 * @dev: device handle to associate resources with 147 * 148 * The mechanism that performs the authentication of the loading firmware 149 * expects an ELF header directly followed by the segment of hashes, with no 150 * padding inbetween. This function allocates a chunk of memory for this pair 151 * and copy the two pieces into the buffer. 152 * 153 * In the case of split firmware the hash is found directly following the ELF 154 * header, rather than at p_offset described by the second program header. 155 * 156 * The caller is responsible to free (kfree()) the returned pointer. 157 * 158 * Return: pointer to data, or ERR_PTR() 159 */ 160 void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len, 161 const char *fw_name, struct device *dev) 162 { 163 const struct elf32_phdr *phdrs; 164 const struct elf32_hdr *ehdr; 165 unsigned int hash_segment = 0; 166 size_t hash_offset; 167 size_t hash_size; 168 size_t ehdr_size; 169 unsigned int i; 170 ssize_t ret; 171 void *data; 172 173 if (!mdt_header_valid(fw)) 174 return ERR_PTR(-EINVAL); 175 176 ehdr = (struct elf32_hdr *)fw->data; 177 phdrs = (struct elf32_phdr *)(fw->data + ehdr->e_phoff); 178 179 if (ehdr->e_phnum < 2) 180 return ERR_PTR(-EINVAL); 181 182 if (phdrs[0].p_type == PT_LOAD) 183 return ERR_PTR(-EINVAL); 184 185 for (i = 1; i < ehdr->e_phnum; i++) { 186 if ((phdrs[i].p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) { 187 hash_segment = i; 188 break; 189 } 190 } 191 192 if (!hash_segment) { 193 dev_err(dev, "no hash segment found in %s\n", fw_name); 194 return ERR_PTR(-EINVAL); 195 } 196 197 ehdr_size = phdrs[0].p_filesz; 198 hash_size = phdrs[hash_segment].p_filesz; 199 200 data = kmalloc(ehdr_size + hash_size, GFP_KERNEL); 201 if (!data) 202 return ERR_PTR(-ENOMEM); 203 204 /* Copy ELF header */ 205 memcpy(data, fw->data, ehdr_size); 206 207 if (ehdr_size + hash_size == fw->size) { 208 /* Firmware is split and hash is packed following the ELF header */ 209 hash_offset = phdrs[0].p_filesz; 210 memcpy(data + ehdr_size, fw->data + hash_offset, hash_size); 211 } else if (phdrs[hash_segment].p_offset + hash_size <= fw->size) { 212 /* Hash is in its own segment, but within the loaded file */ 213 hash_offset = phdrs[hash_segment].p_offset; 214 memcpy(data + ehdr_size, fw->data + hash_offset, hash_size); 215 } else { 216 /* Hash is in its own segment, beyond the loaded file */ 217 ret = mdt_load_split_segment(data + ehdr_size, phdrs, hash_segment, fw_name, dev); 218 if (ret) { 219 kfree(data); 220 return ERR_PTR(ret); 221 } 222 } 223 224 *data_len = ehdr_size + hash_size; 225 226 return data; 227 } 228 EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata); 229 230 static int __qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, 231 const char *fw_name, int pas_id, phys_addr_t mem_phys, 232 struct qcom_scm_pas_context *ctx) 233 { 234 const struct elf32_phdr *phdrs; 235 const struct elf32_phdr *phdr; 236 const struct elf32_hdr *ehdr; 237 phys_addr_t min_addr = PHYS_ADDR_MAX; 238 phys_addr_t max_addr = 0; 239 bool relocate = false; 240 size_t metadata_len; 241 void *metadata; 242 int ret; 243 int i; 244 245 if (!mdt_header_valid(fw)) 246 return -EINVAL; 247 248 ehdr = (struct elf32_hdr *)fw->data; 249 phdrs = (struct elf32_phdr *)(fw->data + ehdr->e_phoff); 250 251 for (i = 0; i < ehdr->e_phnum; i++) { 252 phdr = &phdrs[i]; 253 254 if (!mdt_phdr_loadable(phdr)) 255 continue; 256 257 if (phdr->p_flags & QCOM_MDT_RELOCATABLE) 258 relocate = true; 259 260 if (phdr->p_paddr < min_addr) 261 min_addr = phdr->p_paddr; 262 263 if (phdr->p_paddr + phdr->p_memsz > max_addr) 264 max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); 265 } 266 267 metadata = qcom_mdt_read_metadata(fw, &metadata_len, fw_name, dev); 268 if (IS_ERR(metadata)) { 269 ret = PTR_ERR(metadata); 270 dev_err(dev, "error %d reading firmware %s metadata\n", ret, fw_name); 271 goto out; 272 } 273 274 ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx); 275 kfree(metadata); 276 if (ret) { 277 /* Invalid firmware metadata */ 278 dev_err(dev, "error %d initializing firmware %s\n", ret, fw_name); 279 goto out; 280 } 281 282 if (relocate) { 283 ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr); 284 if (ret) { 285 /* Unable to set up relocation */ 286 dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name); 287 goto out; 288 } 289 } 290 291 out: 292 return ret; 293 } 294 295 static bool qcom_mdt_bins_are_split(const struct firmware *fw) 296 { 297 const struct elf32_phdr *phdrs; 298 const struct elf32_hdr *ehdr; 299 uint64_t seg_start, seg_end; 300 int i; 301 302 ehdr = (struct elf32_hdr *)fw->data; 303 phdrs = (struct elf32_phdr *)(fw->data + ehdr->e_phoff); 304 305 for (i = 0; i < ehdr->e_phnum; i++) { 306 /* 307 * The size of the MDT file is not padded to include any 308 * zero-sized segments at the end. Ignore these, as they should 309 * not affect the decision about image being split or not. 310 */ 311 if (!phdrs[i].p_filesz) 312 continue; 313 314 seg_start = phdrs[i].p_offset; 315 seg_end = phdrs[i].p_offset + phdrs[i].p_filesz; 316 if (seg_start > fw->size || seg_end > fw->size) 317 return true; 318 } 319 320 return false; 321 } 322 323 /** 324 * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw 325 * @dev: device handle to associate resources with 326 * @fw: firmware object for the mdt file 327 * @fw_name: name of the firmware, for construction of segment file names 328 * @mem_region: allocated memory region to load firmware into 329 * @mem_phys: physical address of allocated memory region 330 * @mem_size: size of the allocated memory region 331 * @reloc_base: adjusted physical address after relocation 332 * 333 * Returns 0 on success, negative errno otherwise. 334 */ 335 int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw, 336 const char *fw_name, void *mem_region, 337 phys_addr_t mem_phys, size_t mem_size, 338 phys_addr_t *reloc_base) 339 { 340 const struct elf32_phdr *phdrs; 341 const struct elf32_phdr *phdr; 342 const struct elf32_hdr *ehdr; 343 phys_addr_t mem_reloc; 344 phys_addr_t min_addr = PHYS_ADDR_MAX; 345 ssize_t offset; 346 bool relocate = false; 347 bool is_split; 348 void *ptr; 349 int ret = 0; 350 int i; 351 352 if (!fw || !mem_region || !mem_phys || !mem_size) 353 return -EINVAL; 354 355 if (!mdt_header_valid(fw)) 356 return -EINVAL; 357 358 is_split = qcom_mdt_bins_are_split(fw); 359 ehdr = (struct elf32_hdr *)fw->data; 360 phdrs = (struct elf32_phdr *)(fw->data + ehdr->e_phoff); 361 362 for (i = 0; i < ehdr->e_phnum; i++) { 363 phdr = &phdrs[i]; 364 365 if (!mdt_phdr_loadable(phdr)) 366 continue; 367 368 if (phdr->p_flags & QCOM_MDT_RELOCATABLE) 369 relocate = true; 370 371 if (phdr->p_paddr < min_addr) 372 min_addr = phdr->p_paddr; 373 } 374 375 if (relocate) { 376 /* 377 * The image is relocatable, so offset each segment based on 378 * the lowest segment address. 379 */ 380 mem_reloc = min_addr; 381 } else { 382 /* 383 * Image is not relocatable, so offset each segment based on 384 * the allocated physical chunk of memory. 385 */ 386 mem_reloc = mem_phys; 387 } 388 389 for (i = 0; i < ehdr->e_phnum; i++) { 390 phdr = &phdrs[i]; 391 392 if (!mdt_phdr_loadable(phdr)) 393 continue; 394 395 offset = phdr->p_paddr - mem_reloc; 396 if (offset < 0 || offset + phdr->p_memsz > mem_size) { 397 dev_err(dev, "segment outside memory range\n"); 398 ret = -EINVAL; 399 break; 400 } 401 402 if (phdr->p_filesz > phdr->p_memsz) { 403 dev_err(dev, 404 "refusing to load segment %d with p_filesz > p_memsz\n", 405 i); 406 ret = -EINVAL; 407 break; 408 } 409 410 ptr = mem_region + offset; 411 412 if (phdr->p_filesz && !is_split) { 413 /* Firmware is large enough to be non-split */ 414 if (phdr->p_offset + phdr->p_filesz > fw->size) { 415 dev_err(dev, "file %s segment %d would be truncated\n", 416 fw_name, i); 417 ret = -EINVAL; 418 break; 419 } 420 421 memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz); 422 } else if (phdr->p_filesz) { 423 /* Firmware not large enough, load split-out segments */ 424 ret = mdt_load_split_segment(ptr, phdrs, i, fw_name, dev); 425 if (ret) 426 break; 427 } 428 429 if (phdr->p_memsz > phdr->p_filesz) 430 memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); 431 } 432 433 if (reloc_base) 434 *reloc_base = mem_reloc; 435 436 return ret; 437 } 438 EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init); 439 440 /** 441 * qcom_mdt_load() - load the firmware which header is loaded as fw 442 * @dev: device handle to associate resources with 443 * @fw: firmware object for the mdt file 444 * @fw_name: name of the firmware, for construction of segment file names 445 * @pas_id: PAS identifier 446 * @mem_region: allocated memory region to load firmware into 447 * @mem_phys: physical address of allocated memory region 448 * @mem_size: size of the allocated memory region 449 * @reloc_base: adjusted physical address after relocation 450 * 451 * Returns 0 on success, negative errno otherwise. 452 */ 453 int qcom_mdt_load(struct device *dev, const struct firmware *fw, 454 const char *fw_name, int pas_id, void *mem_region, 455 phys_addr_t mem_phys, size_t mem_size, 456 phys_addr_t *reloc_base) 457 { 458 int ret; 459 460 ret = __qcom_mdt_pas_init(dev, fw, fw_name, pas_id, mem_phys, NULL); 461 if (ret) 462 return ret; 463 464 return qcom_mdt_load_no_init(dev, fw, fw_name, mem_region, mem_phys, 465 mem_size, reloc_base); 466 } 467 EXPORT_SYMBOL_GPL(qcom_mdt_load); 468 469 /** 470 * qcom_mdt_pas_load - Loads and authenticates the metadata of the firmware 471 * (typically contained in the .mdt file), followed by loading the actual 472 * firmware segments (e.g., .bXX files). Authentication of the segments done 473 * by a separate call. 474 * 475 * The PAS context must be initialized using qcom_scm_pas_context_init() 476 * prior to invoking this function. 477 * 478 * @ctx: Pointer to the PAS (Peripheral Authentication Service) context 479 * @fw: Firmware object representing the .mdt file 480 * @firmware: Name of the firmware used to construct segment file names 481 * @mem_region: Memory region allocated for loading the firmware 482 * @reloc_base: Physical address adjusted after relocation 483 * 484 * Return: 0 on success or a negative error code on failure. 485 */ 486 int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx, const struct firmware *fw, 487 const char *firmware, void *mem_region, phys_addr_t *reloc_base) 488 { 489 int ret; 490 491 ret = __qcom_mdt_pas_init(ctx->dev, fw, firmware, ctx->pas_id, ctx->mem_phys, ctx); 492 if (ret) 493 return ret; 494 495 return qcom_mdt_load_no_init(ctx->dev, fw, firmware, mem_region, ctx->mem_phys, 496 ctx->mem_size, reloc_base); 497 } 498 EXPORT_SYMBOL_GPL(qcom_mdt_pas_load); 499 500 MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); 501 MODULE_LICENSE("GPL v2"); 502