1c970d6dd532b89848961256e9a2d42117f522ed
[sfrench/cifs-2.6.git] / drivers / soc / qcom / mdt_loader.c
1 /*
2  * Qualcomm Peripheral Image Loader
3  *
4  * Copyright (C) 2016 Linaro Ltd
5  * Copyright (C) 2015 Sony Mobile Communications Inc
6  * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * version 2 as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  */
17
18 #include <linux/device.h>
19 #include <linux/elf.h>
20 #include <linux/firmware.h>
21 #include <linux/kernel.h>
22 #include <linux/module.h>
23 #include <linux/qcom_scm.h>
24 #include <linux/sizes.h>
25 #include <linux/slab.h>
26 #include <linux/soc/qcom/mdt_loader.h>
27
28 static bool mdt_phdr_valid(const struct elf32_phdr *phdr)
29 {
30         if (phdr->p_type != PT_LOAD)
31                 return false;
32
33         if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
34                 return false;
35
36         if (!phdr->p_memsz)
37                 return false;
38
39         return true;
40 }
41
42 /**
43  * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt
44  * @fw:         firmware object for the mdt file
45  *
46  * Returns size of the loaded firmware blob, or -EINVAL on failure.
47  */
48 ssize_t qcom_mdt_get_size(const struct firmware *fw)
49 {
50         const struct elf32_phdr *phdrs;
51         const struct elf32_phdr *phdr;
52         const struct elf32_hdr *ehdr;
53         phys_addr_t min_addr = PHYS_ADDR_MAX;
54         phys_addr_t max_addr = 0;
55         int i;
56
57         ehdr = (struct elf32_hdr *)fw->data;
58         phdrs = (struct elf32_phdr *)(ehdr + 1);
59
60         for (i = 0; i < ehdr->e_phnum; i++) {
61                 phdr = &phdrs[i];
62
63                 if (!mdt_phdr_valid(phdr))
64                         continue;
65
66                 if (phdr->p_paddr < min_addr)
67                         min_addr = phdr->p_paddr;
68
69                 if (phdr->p_paddr + phdr->p_memsz > max_addr)
70                         max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
71         }
72
73         return min_addr < max_addr ? max_addr - min_addr : -EINVAL;
74 }
75 EXPORT_SYMBOL_GPL(qcom_mdt_get_size);
76
77 /**
78  * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn
79  * @fw:         firmware of mdt header or mbn
80  * @data_len:   length of the read metadata blob
81  *
82  * The mechanism that performs the authentication of the loading firmware
83  * expects an ELF header directly followed by the segment of hashes, with no
84  * padding inbetween. This function allocates a chunk of memory for this pair
85  * and copy the two pieces into the buffer.
86  *
87  * In the case of split firmware the hash is found directly following the ELF
88  * header, rather than at p_offset described by the second program header.
89  *
90  * The caller is responsible to free (kfree()) the returned pointer.
91  *
92  * Return: pointer to data, or ERR_PTR()
93  */
94 void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len)
95 {
96         const struct elf32_phdr *phdrs;
97         const struct elf32_hdr *ehdr;
98         size_t hash_offset;
99         size_t hash_size;
100         size_t ehdr_size;
101         void *data;
102
103         ehdr = (struct elf32_hdr *)fw->data;
104         phdrs = (struct elf32_phdr *)(ehdr + 1);
105
106         if (ehdr->e_phnum < 2)
107                 return ERR_PTR(-EINVAL);
108
109         if (phdrs[0].p_type == PT_LOAD || phdrs[1].p_type == PT_LOAD)
110                 return ERR_PTR(-EINVAL);
111
112         if ((phdrs[1].p_flags & QCOM_MDT_TYPE_MASK) != QCOM_MDT_TYPE_HASH)
113                 return ERR_PTR(-EINVAL);
114
115         ehdr_size = phdrs[0].p_filesz;
116         hash_size = phdrs[1].p_filesz;
117
118         data = kmalloc(ehdr_size + hash_size, GFP_KERNEL);
119         if (!data)
120                 return ERR_PTR(-ENOMEM);
121
122         /* Is the header and hash already packed */
123         if (ehdr_size + hash_size == fw->size)
124                 hash_offset = phdrs[0].p_filesz;
125         else
126                 hash_offset = phdrs[1].p_offset;
127
128         memcpy(data, fw->data, ehdr_size);
129         memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);
130
131         *data_len = ehdr_size + hash_size;
132
133         return data;
134 }
135 EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata);
136
137 static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
138                            const char *firmware, int pas_id, void *mem_region,
139                            phys_addr_t mem_phys, size_t mem_size,
140                            phys_addr_t *reloc_base, bool pas_init)
141 {
142         const struct elf32_phdr *phdrs;
143         const struct elf32_phdr *phdr;
144         const struct elf32_hdr *ehdr;
145         const struct firmware *seg_fw;
146         phys_addr_t mem_reloc;
147         phys_addr_t min_addr = PHYS_ADDR_MAX;
148         phys_addr_t max_addr = 0;
149         size_t metadata_len;
150         size_t fw_name_len;
151         ssize_t offset;
152         void *metadata;
153         char *fw_name;
154         bool relocate = false;
155         void *ptr;
156         int ret = 0;
157         int i;
158
159         if (!fw || !mem_region || !mem_phys || !mem_size)
160                 return -EINVAL;
161
162         ehdr = (struct elf32_hdr *)fw->data;
163         phdrs = (struct elf32_phdr *)(ehdr + 1);
164
165         fw_name_len = strlen(firmware);
166         if (fw_name_len <= 4)
167                 return -EINVAL;
168
169         fw_name = kstrdup(firmware, GFP_KERNEL);
170         if (!fw_name)
171                 return -ENOMEM;
172
173         if (pas_init) {
174                 metadata = qcom_mdt_read_metadata(fw, &metadata_len);
175                 if (IS_ERR(metadata)) {
176                         ret = PTR_ERR(metadata);
177                         goto out;
178                 }
179
180                 ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len);
181
182                 kfree(metadata);
183                 if (ret) {
184                         dev_err(dev, "invalid firmware metadata\n");
185                         goto out;
186                 }
187         }
188
189         for (i = 0; i < ehdr->e_phnum; i++) {
190                 phdr = &phdrs[i];
191
192                 if (!mdt_phdr_valid(phdr))
193                         continue;
194
195                 if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
196                         relocate = true;
197
198                 if (phdr->p_paddr < min_addr)
199                         min_addr = phdr->p_paddr;
200
201                 if (phdr->p_paddr + phdr->p_memsz > max_addr)
202                         max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
203         }
204
205         if (relocate) {
206                 if (pas_init) {
207                         ret = qcom_scm_pas_mem_setup(pas_id, mem_phys,
208                                                      max_addr - min_addr);
209                         if (ret) {
210                                 dev_err(dev, "unable to setup relocation\n");
211                                 goto out;
212                         }
213                 }
214
215                 /*
216                  * The image is relocatable, so offset each segment based on
217                  * the lowest segment address.
218                  */
219                 mem_reloc = min_addr;
220         } else {
221                 /*
222                  * Image is not relocatable, so offset each segment based on
223                  * the allocated physical chunk of memory.
224                  */
225                 mem_reloc = mem_phys;
226         }
227
228         for (i = 0; i < ehdr->e_phnum; i++) {
229                 phdr = &phdrs[i];
230
231                 if (!mdt_phdr_valid(phdr))
232                         continue;
233
234                 offset = phdr->p_paddr - mem_reloc;
235                 if (offset < 0 || offset + phdr->p_memsz > mem_size) {
236                         dev_err(dev, "segment outside memory range\n");
237                         ret = -EINVAL;
238                         break;
239                 }
240
241                 ptr = mem_region + offset;
242
243                 if (phdr->p_filesz && phdr->p_offset < fw->size) {
244                         /* Firmware is large enough to be non-split */
245                         if (phdr->p_offset + phdr->p_filesz > fw->size) {
246                                 dev_err(dev,
247                                         "failed to load segment %d from truncated file %s\n",
248                                         i, firmware);
249                                 ret = -EINVAL;
250                                 break;
251                         }
252
253                         memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);
254                 } else if (phdr->p_filesz) {
255                         /* Firmware not large enough, load split-out segments */
256                         sprintf(fw_name + fw_name_len - 3, "b%02d", i);
257                         ret = request_firmware_into_buf(&seg_fw, fw_name, dev,
258                                                         ptr, phdr->p_filesz);
259                         if (ret) {
260                                 dev_err(dev, "failed to load %s\n", fw_name);
261                                 break;
262                         }
263
264                         release_firmware(seg_fw);
265                 }
266
267                 if (phdr->p_memsz > phdr->p_filesz)
268                         memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
269         }
270
271         if (reloc_base)
272                 *reloc_base = mem_reloc;
273
274 out:
275         kfree(fw_name);
276
277         return ret;
278 }
279
280 /**
281  * qcom_mdt_load() - load the firmware which header is loaded as fw
282  * @dev:        device handle to associate resources with
283  * @fw:         firmware object for the mdt file
284  * @firmware:   name of the firmware, for construction of segment file names
285  * @pas_id:     PAS identifier
286  * @mem_region: allocated memory region to load firmware into
287  * @mem_phys:   physical address of allocated memory region
288  * @mem_size:   size of the allocated memory region
289  * @reloc_base: adjusted physical address after relocation
290  *
291  * Returns 0 on success, negative errno otherwise.
292  */
293 int qcom_mdt_load(struct device *dev, const struct firmware *fw,
294                   const char *firmware, int pas_id, void *mem_region,
295                   phys_addr_t mem_phys, size_t mem_size,
296                   phys_addr_t *reloc_base)
297 {
298         return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
299                                mem_size, reloc_base, true);
300 }
301 EXPORT_SYMBOL_GPL(qcom_mdt_load);
302
303 /**
304  * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw
305  * @dev:        device handle to associate resources with
306  * @fw:         firmware object for the mdt file
307  * @firmware:   name of the firmware, for construction of segment file names
308  * @pas_id:     PAS identifier
309  * @mem_region: allocated memory region to load firmware into
310  * @mem_phys:   physical address of allocated memory region
311  * @mem_size:   size of the allocated memory region
312  * @reloc_base: adjusted physical address after relocation
313  *
314  * Returns 0 on success, negative errno otherwise.
315  */
316 int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,
317                           const char *firmware, int pas_id,
318                           void *mem_region, phys_addr_t mem_phys,
319                           size_t mem_size, phys_addr_t *reloc_base)
320 {
321         return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
322                                mem_size, reloc_base, false);
323 }
324 EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init);
325
326 MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");
327 MODULE_LICENSE("GPL v2");