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