xref: /linux/drivers/soc/qcom/mdt_loader.c (revision 34f7c6e7d4396090692a09789db231e12cb4762b)
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/device.h>
11 #include <linux/elf.h>
12 #include <linux/firmware.h>
13 #include <linux/kernel.h>
14 #include <linux/module.h>
15 #include <linux/qcom_scm.h>
16 #include <linux/sizes.h>
17 #include <linux/slab.h>
18 #include <linux/soc/qcom/mdt_loader.h>
19 
20 static bool mdt_phdr_valid(const struct elf32_phdr *phdr)
21 {
22 	if (phdr->p_type != PT_LOAD)
23 		return false;
24 
25 	if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
26 		return false;
27 
28 	if (!phdr->p_memsz)
29 		return false;
30 
31 	return true;
32 }
33 
34 static ssize_t mdt_load_split_segment(void *ptr, const struct elf32_phdr *phdrs,
35 				      unsigned int segment, const char *fw_name,
36 				      struct device *dev)
37 {
38 	const struct elf32_phdr *phdr = &phdrs[segment];
39 	const struct firmware *seg_fw;
40 	char *seg_name;
41 	ssize_t ret;
42 
43 	if (strlen(fw_name) < 4)
44 		return -EINVAL;
45 
46 	seg_name = 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 		kfree(seg_name);
56 		return ret;
57 	}
58 
59 	if (seg_fw->size != phdr->p_filesz) {
60 		dev_err(dev,
61 			"failed to load segment %d from truncated file %s\n",
62 			segment, seg_name);
63 		ret = -EINVAL;
64 	}
65 
66 	release_firmware(seg_fw);
67 	kfree(seg_name);
68 
69 	return ret;
70 }
71 
72 /**
73  * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt
74  * @fw:		firmware object for the mdt file
75  *
76  * Returns size of the loaded firmware blob, or -EINVAL on failure.
77  */
78 ssize_t qcom_mdt_get_size(const struct firmware *fw)
79 {
80 	const struct elf32_phdr *phdrs;
81 	const struct elf32_phdr *phdr;
82 	const struct elf32_hdr *ehdr;
83 	phys_addr_t min_addr = PHYS_ADDR_MAX;
84 	phys_addr_t max_addr = 0;
85 	int i;
86 
87 	ehdr = (struct elf32_hdr *)fw->data;
88 	phdrs = (struct elf32_phdr *)(ehdr + 1);
89 
90 	for (i = 0; i < ehdr->e_phnum; i++) {
91 		phdr = &phdrs[i];
92 
93 		if (!mdt_phdr_valid(phdr))
94 			continue;
95 
96 		if (phdr->p_paddr < min_addr)
97 			min_addr = phdr->p_paddr;
98 
99 		if (phdr->p_paddr + phdr->p_memsz > max_addr)
100 			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
101 	}
102 
103 	return min_addr < max_addr ? max_addr - min_addr : -EINVAL;
104 }
105 EXPORT_SYMBOL_GPL(qcom_mdt_get_size);
106 
107 /**
108  * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn
109  * @fw:		firmware of mdt header or mbn
110  * @data_len:	length of the read metadata blob
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  * @firmware:	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 	size_t metadata_len;
212 	void *metadata;
213 	int ret;
214 	int i;
215 
216 	ehdr = (struct elf32_hdr *)fw->data;
217 	phdrs = (struct elf32_phdr *)(ehdr + 1);
218 
219 	for (i = 0; i < ehdr->e_phnum; i++) {
220 		phdr = &phdrs[i];
221 
222 		if (!mdt_phdr_valid(phdr))
223 			continue;
224 
225 		if (phdr->p_paddr < min_addr)
226 			min_addr = phdr->p_paddr;
227 
228 		if (phdr->p_paddr + phdr->p_memsz > max_addr)
229 			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
230 	}
231 
232 	metadata = qcom_mdt_read_metadata(fw, &metadata_len, fw_name, dev);
233 	if (IS_ERR(metadata)) {
234 		ret = PTR_ERR(metadata);
235 		dev_err(dev, "error %d reading firmware %s metadata\n", ret, fw_name);
236 		goto out;
237 	}
238 
239 	ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx);
240 	kfree(metadata);
241 	if (ret) {
242 		/* Invalid firmware metadata */
243 		dev_err(dev, "error %d initializing firmware %s\n", ret, fw_name);
244 		goto out;
245 	}
246 
247 	ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
248 	if (ret) {
249 		/* Unable to set up relocation */
250 		dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name);
251 		goto out;
252 	}
253 
254 out:
255 	return ret;
256 }
257 EXPORT_SYMBOL_GPL(qcom_mdt_pas_init);
258 
259 static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
260 			   const char *fw_name, int pas_id, void *mem_region,
261 			   phys_addr_t mem_phys, size_t mem_size,
262 			   phys_addr_t *reloc_base, bool pas_init)
263 {
264 	const struct elf32_phdr *phdrs;
265 	const struct elf32_phdr *phdr;
266 	const struct elf32_hdr *ehdr;
267 	phys_addr_t mem_reloc;
268 	phys_addr_t min_addr = PHYS_ADDR_MAX;
269 	ssize_t offset;
270 	bool relocate = false;
271 	void *ptr;
272 	int ret = 0;
273 	int i;
274 
275 	if (!fw || !mem_region || !mem_phys || !mem_size)
276 		return -EINVAL;
277 
278 	ehdr = (struct elf32_hdr *)fw->data;
279 	phdrs = (struct elf32_phdr *)(ehdr + 1);
280 
281 	for (i = 0; i < ehdr->e_phnum; i++) {
282 		phdr = &phdrs[i];
283 
284 		if (!mdt_phdr_valid(phdr))
285 			continue;
286 
287 		if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
288 			relocate = true;
289 
290 		if (phdr->p_paddr < min_addr)
291 			min_addr = phdr->p_paddr;
292 	}
293 
294 	if (relocate) {
295 		/*
296 		 * The image is relocatable, so offset each segment based on
297 		 * the lowest segment address.
298 		 */
299 		mem_reloc = min_addr;
300 	} else {
301 		/*
302 		 * Image is not relocatable, so offset each segment based on
303 		 * the allocated physical chunk of memory.
304 		 */
305 		mem_reloc = mem_phys;
306 	}
307 
308 	for (i = 0; i < ehdr->e_phnum; i++) {
309 		phdr = &phdrs[i];
310 
311 		if (!mdt_phdr_valid(phdr))
312 			continue;
313 
314 		offset = phdr->p_paddr - mem_reloc;
315 		if (offset < 0 || offset + phdr->p_memsz > mem_size) {
316 			dev_err(dev, "segment outside memory range\n");
317 			ret = -EINVAL;
318 			break;
319 		}
320 
321 		if (phdr->p_filesz > phdr->p_memsz) {
322 			dev_err(dev,
323 				"refusing to load segment %d with p_filesz > p_memsz\n",
324 				i);
325 			ret = -EINVAL;
326 			break;
327 		}
328 
329 		ptr = mem_region + offset;
330 
331 		if (phdr->p_filesz && phdr->p_offset < fw->size &&
332 		    phdr->p_offset + phdr->p_filesz <= fw->size) {
333 			/* Firmware is large enough to be non-split */
334 			if (phdr->p_offset + phdr->p_filesz > fw->size) {
335 				dev_err(dev, "file %s segment %d would be truncated\n",
336 					fw_name, i);
337 				ret = -EINVAL;
338 				break;
339 			}
340 
341 			memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);
342 		} else if (phdr->p_filesz) {
343 			/* Firmware not large enough, load split-out segments */
344 			ret = mdt_load_split_segment(ptr, phdrs, i, fw_name, dev);
345 			if (ret)
346 				break;
347 		}
348 
349 		if (phdr->p_memsz > phdr->p_filesz)
350 			memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
351 	}
352 
353 	if (reloc_base)
354 		*reloc_base = mem_reloc;
355 
356 	return ret;
357 }
358 
359 /**
360  * qcom_mdt_load() - load the firmware which header is loaded as fw
361  * @dev:	device handle to associate resources with
362  * @fw:		firmware object for the mdt file
363  * @firmware:	name of the firmware, for construction of segment file names
364  * @pas_id:	PAS identifier
365  * @mem_region:	allocated memory region to load firmware into
366  * @mem_phys:	physical address of allocated memory region
367  * @mem_size:	size of the allocated memory region
368  * @reloc_base:	adjusted physical address after relocation
369  *
370  * Returns 0 on success, negative errno otherwise.
371  */
372 int qcom_mdt_load(struct device *dev, const struct firmware *fw,
373 		  const char *firmware, int pas_id, void *mem_region,
374 		  phys_addr_t mem_phys, size_t mem_size,
375 		  phys_addr_t *reloc_base)
376 {
377 	int ret;
378 
379 	ret = qcom_mdt_pas_init(dev, fw, firmware, pas_id, mem_phys, NULL);
380 	if (ret)
381 		return ret;
382 
383 	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
384 			       mem_size, reloc_base, true);
385 }
386 EXPORT_SYMBOL_GPL(qcom_mdt_load);
387 
388 /**
389  * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw
390  * @dev:	device handle to associate resources with
391  * @fw:		firmware object for the mdt file
392  * @firmware:	name of the firmware, for construction of segment file names
393  * @pas_id:	PAS identifier
394  * @mem_region:	allocated memory region to load firmware into
395  * @mem_phys:	physical address of allocated memory region
396  * @mem_size:	size of the allocated memory region
397  * @reloc_base:	adjusted physical address after relocation
398  *
399  * Returns 0 on success, negative errno otherwise.
400  */
401 int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,
402 			  const char *firmware, int pas_id,
403 			  void *mem_region, phys_addr_t mem_phys,
404 			  size_t mem_size, phys_addr_t *reloc_base)
405 {
406 	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
407 			       mem_size, reloc_base, false);
408 }
409 EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init);
410 
411 MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");
412 MODULE_LICENSE("GPL v2");
413