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