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