xref: /linux/drivers/soc/qcom/mdt_loader.c (revision 0f46f50845ce75bfaba62df0421084d23bb6a72f)
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