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 || 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
mdt_phdr_loadable(const struct elf32_phdr * phdr)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
mdt_load_split_segment(void * ptr,const struct elf32_phdr * phdrs,unsigned int segment,const char * fw_name,struct device * dev)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 */
qcom_mdt_get_size(const struct firmware * fw)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 */
qcom_mdt_read_metadata(const struct firmware * fw,size_t * data_len,const char * fw_name,struct device * dev)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 /**
231 * qcom_mdt_pas_init() - initialize PAS region for firmware loading
232 * @dev: device handle to associate resources with
233 * @fw: firmware object for the mdt file
234 * @fw_name: name of the firmware, for construction of segment file names
235 * @pas_id: PAS identifier
236 * @mem_phys: physical address of allocated memory region
237 * @ctx: PAS metadata context, to be released by caller
238 *
239 * Returns 0 on success, negative errno otherwise.
240 */
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)241 int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,
242 const char *fw_name, int pas_id, phys_addr_t mem_phys,
243 struct qcom_scm_pas_metadata *ctx)
244 {
245 const struct elf32_phdr *phdrs;
246 const struct elf32_phdr *phdr;
247 const struct elf32_hdr *ehdr;
248 phys_addr_t min_addr = PHYS_ADDR_MAX;
249 phys_addr_t max_addr = 0;
250 bool relocate = false;
251 size_t metadata_len;
252 void *metadata;
253 int ret;
254 int i;
255
256 if (!mdt_header_valid(fw))
257 return -EINVAL;
258
259 ehdr = (struct elf32_hdr *)fw->data;
260 phdrs = (struct elf32_phdr *)(fw->data + ehdr->e_phoff);
261
262 for (i = 0; i < ehdr->e_phnum; i++) {
263 phdr = &phdrs[i];
264
265 if (!mdt_phdr_loadable(phdr))
266 continue;
267
268 if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
269 relocate = true;
270
271 if (phdr->p_paddr < min_addr)
272 min_addr = phdr->p_paddr;
273
274 if (phdr->p_paddr + phdr->p_memsz > max_addr)
275 max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
276 }
277
278 metadata = qcom_mdt_read_metadata(fw, &metadata_len, fw_name, dev);
279 if (IS_ERR(metadata)) {
280 ret = PTR_ERR(metadata);
281 dev_err(dev, "error %d reading firmware %s metadata\n", ret, fw_name);
282 goto out;
283 }
284
285 ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx);
286 kfree(metadata);
287 if (ret) {
288 /* Invalid firmware metadata */
289 dev_err(dev, "error %d initializing firmware %s\n", ret, fw_name);
290 goto out;
291 }
292
293 if (relocate) {
294 ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
295 if (ret) {
296 /* Unable to set up relocation */
297 dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name);
298 goto out;
299 }
300 }
301
302 out:
303 return ret;
304 }
305 EXPORT_SYMBOL_GPL(qcom_mdt_pas_init);
306
qcom_mdt_bins_are_split(const struct firmware * fw)307 static bool qcom_mdt_bins_are_split(const struct firmware *fw)
308 {
309 const struct elf32_phdr *phdrs;
310 const struct elf32_hdr *ehdr;
311 uint64_t seg_start, seg_end;
312 int i;
313
314 ehdr = (struct elf32_hdr *)fw->data;
315 phdrs = (struct elf32_phdr *)(fw->data + ehdr->e_phoff);
316
317 for (i = 0; i < ehdr->e_phnum; i++) {
318 /*
319 * The size of the MDT file is not padded to include any
320 * zero-sized segments at the end. Ignore these, as they should
321 * not affect the decision about image being split or not.
322 */
323 if (!phdrs[i].p_filesz)
324 continue;
325
326 seg_start = phdrs[i].p_offset;
327 seg_end = phdrs[i].p_offset + phdrs[i].p_filesz;
328 if (seg_start > fw->size || seg_end > fw->size)
329 return true;
330 }
331
332 return false;
333 }
334
__qcom_mdt_load(struct device * dev,const struct firmware * fw,const char * fw_name,void * mem_region,phys_addr_t mem_phys,size_t mem_size,phys_addr_t * reloc_base)335 static int __qcom_mdt_load(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
439 /**
440 * qcom_mdt_load() - load the firmware which header is loaded as fw
441 * @dev: device handle to associate resources with
442 * @fw: firmware object for the mdt file
443 * @firmware: name of the firmware, for construction of segment file names
444 * @pas_id: PAS identifier
445 * @mem_region: allocated memory region to load firmware into
446 * @mem_phys: physical address of allocated memory region
447 * @mem_size: size of the allocated memory region
448 * @reloc_base: adjusted physical address after relocation
449 *
450 * Returns 0 on success, negative errno otherwise.
451 */
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)452 int qcom_mdt_load(struct device *dev, const struct firmware *fw,
453 const char *firmware, int pas_id, void *mem_region,
454 phys_addr_t mem_phys, size_t mem_size,
455 phys_addr_t *reloc_base)
456 {
457 int ret;
458
459 ret = qcom_mdt_pas_init(dev, fw, firmware, pas_id, mem_phys, NULL);
460 if (ret)
461 return ret;
462
463 return __qcom_mdt_load(dev, fw, firmware, mem_region, mem_phys,
464 mem_size, reloc_base);
465 }
466 EXPORT_SYMBOL_GPL(qcom_mdt_load);
467
468 /**
469 * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw
470 * @dev: device handle to associate resources with
471 * @fw: firmware object for the mdt file
472 * @firmware: name of the firmware, for construction of segment file names
473 * @mem_region: allocated memory region to load firmware into
474 * @mem_phys: physical address of allocated memory region
475 * @mem_size: size of the allocated memory region
476 * @reloc_base: adjusted physical address after relocation
477 *
478 * Returns 0 on success, negative errno otherwise.
479 */
qcom_mdt_load_no_init(struct device * dev,const struct firmware * fw,const char * firmware,void * mem_region,phys_addr_t mem_phys,size_t mem_size,phys_addr_t * reloc_base)480 int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,
481 const char *firmware, 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, mem_region, mem_phys,
485 mem_size, reloc_base);
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