Merge tag 'gpio-fixes-for-v5.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6-microblaze.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/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");