]> Git Repo - J-linux.git/blob - drivers/soc/qcom/mdt_loader.c
Merge tag 'vfs-6.13-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
[J-linux.git] / drivers / soc / qcom / mdt_loader.c
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");
This page took 0.056686 seconds and 4 git commands to generate.