blob: 0886975c9f44234d22b97b34e12f4f2d1f71b366 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#include <linux/module.h>
14#include <linux/string.h>
Stephen Boyd3f4da322011-08-30 01:03:23 -070015#include <linux/device.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070016#include <linux/firmware.h>
17#include <linux/io.h>
18#include <linux/debugfs.h>
19#include <linux/elf.h>
20#include <linux/mutex.h>
21#include <linux/memblock.h>
Stephen Boyd3f4da322011-08-30 01:03:23 -070022#include <linux/slab.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070023
24#include <asm/uaccess.h>
25#include <asm/setup.h>
26
27#include "peripheral-loader.h"
28
Stephen Boyd3f4da322011-08-30 01:03:23 -070029struct pil_device {
30 struct pil_desc *desc;
31 int count;
32 struct mutex lock;
33 struct list_head list;
34};
35
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070036static DEFINE_MUTEX(pil_list_lock);
37static LIST_HEAD(pil_list);
38
39static struct pil_device *__find_peripheral(const char *str)
40{
41 struct pil_device *dev;
42
43 list_for_each_entry(dev, &pil_list, list)
Stephen Boyd3f4da322011-08-30 01:03:23 -070044 if (!strcmp(dev->desc->name, str))
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070045 return dev;
46 return NULL;
47}
48
49static struct pil_device *find_peripheral(const char *str)
50{
51 struct pil_device *dev;
52
53 if (!str)
54 return NULL;
55
56 mutex_lock(&pil_list_lock);
57 dev = __find_peripheral(str);
58 mutex_unlock(&pil_list_lock);
59
60 return dev;
61}
62
63#define IOMAP_SIZE SZ_4M
64
65static int load_segment(const struct elf32_phdr *phdr, unsigned num,
66 struct pil_device *pil)
67{
68 int ret, count, paddr;
69 char fw_name[30];
70 const struct firmware *fw = NULL;
71 const u8 *data;
72
73 if (memblock_is_region_memory(phdr->p_paddr, phdr->p_memsz)) {
Stephen Boyd3f4da322011-08-30 01:03:23 -070074 dev_err(pil->desc->dev, "Kernel memory would be overwritten");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070075 return -EPERM;
76 }
77
78 if (phdr->p_filesz) {
Stephen Boyd3f4da322011-08-30 01:03:23 -070079 snprintf(fw_name, ARRAY_SIZE(fw_name), "%s.b%02d",
80 pil->desc->name, num);
81 ret = request_firmware(&fw, fw_name, pil->desc->dev);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070082 if (ret) {
Stephen Boyd3f4da322011-08-30 01:03:23 -070083 dev_err(pil->desc->dev, "Failed to locate blob %s\n",
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070084 fw_name);
85 return ret;
86 }
87
88 if (fw->size != phdr->p_filesz) {
Stephen Boyd3f4da322011-08-30 01:03:23 -070089 dev_err(pil->desc->dev,
90 "Blob size %u doesn't match %u\n", fw->size,
91 phdr->p_filesz);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070092 ret = -EPERM;
93 goto release_fw;
94 }
95 }
96
97 /* Load the segment into memory */
98 count = phdr->p_filesz;
99 paddr = phdr->p_paddr;
100 data = fw ? fw->data : NULL;
101 while (count > 0) {
102 int size;
103 u8 __iomem *buf;
104
105 size = min_t(size_t, IOMAP_SIZE, count);
106 buf = ioremap(paddr, size);
107 if (!buf) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700108 dev_err(pil->desc->dev, "Failed to map memory\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700109 ret = -ENOMEM;
110 goto release_fw;
111 }
112 memcpy(buf, data, size);
113 iounmap(buf);
114
115 count -= size;
116 paddr += size;
117 data += size;
118 }
119
120 /* Zero out trailing memory */
121 count = phdr->p_memsz - phdr->p_filesz;
122 while (count > 0) {
123 int size;
124 u8 __iomem *buf;
125
126 size = min_t(size_t, IOMAP_SIZE, count);
127 buf = ioremap(paddr, size);
128 if (!buf) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700129 dev_err(pil->desc->dev, "Failed to map memory\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700130 ret = -ENOMEM;
131 goto release_fw;
132 }
133 memset(buf, 0, size);
134 iounmap(buf);
135
136 count -= size;
137 paddr += size;
138 }
139
Stephen Boyd3f4da322011-08-30 01:03:23 -0700140 ret = pil->desc->ops->verify_blob(pil->desc, phdr->p_paddr,
141 phdr->p_memsz);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700142 if (ret)
Stephen Boyd3f4da322011-08-30 01:03:23 -0700143 dev_err(pil->desc->dev, "Blob %u failed verification\n", num);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700144
145release_fw:
146 release_firmware(fw);
147 return ret;
148}
149
150#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24))
151
152static int segment_is_loadable(const struct elf32_phdr *p)
153{
154 return (p->p_type & PT_LOAD) && !segment_is_hash(p->p_flags);
155}
156
157static int load_image(struct pil_device *pil)
158{
159 int i, ret;
160 char fw_name[30];
161 struct elf32_hdr *ehdr;
162 const struct elf32_phdr *phdr;
163 const struct firmware *fw;
164
Stephen Boyd3f4da322011-08-30 01:03:23 -0700165 snprintf(fw_name, sizeof(fw_name), "%s.mdt", pil->desc->name);
166 ret = request_firmware(&fw, fw_name, pil->desc->dev);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700167 if (ret) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700168 dev_err(pil->desc->dev, "Failed to locate %s\n", fw_name);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700169 goto out;
170 }
171
172 if (fw->size < sizeof(*ehdr)) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700173 dev_err(pil->desc->dev, "Not big enough to be an elf header\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700174 ret = -EIO;
175 goto release_fw;
176 }
177
178 ehdr = (struct elf32_hdr *)fw->data;
179 if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700180 dev_err(pil->desc->dev, "Not an elf header\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700181 ret = -EIO;
182 goto release_fw;
183 }
184
185 if (ehdr->e_phnum == 0) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700186 dev_err(pil->desc->dev, "No loadable segments\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700187 ret = -EIO;
188 goto release_fw;
189 }
Stephen Boyd96a9f902011-07-18 18:43:00 -0700190 if (sizeof(struct elf32_phdr) * ehdr->e_phnum +
191 sizeof(struct elf32_hdr) > fw->size) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700192 dev_err(pil->desc->dev, "Program headers not within mdt\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700193 ret = -EIO;
194 goto release_fw;
195 }
196
Stephen Boyd3f4da322011-08-30 01:03:23 -0700197 ret = pil->desc->ops->init_image(pil->desc, fw->data, fw->size);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700198 if (ret) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700199 dev_err(pil->desc->dev, "Invalid firmware metadata\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700200 goto release_fw;
201 }
202
Stephen Boydc9753e12011-07-13 17:58:48 -0700203 phdr = (const struct elf32_phdr *)(fw->data + sizeof(struct elf32_hdr));
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700204 for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
205 if (!segment_is_loadable(phdr))
206 continue;
207
208 ret = load_segment(phdr, i, pil);
209 if (ret) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700210 dev_err(pil->desc->dev, "Failed to load segment %d\n",
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700211 i);
212 goto release_fw;
213 }
214 }
215
Stephen Boyd3f4da322011-08-30 01:03:23 -0700216 ret = pil->desc->ops->auth_and_reset(pil->desc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700217 if (ret) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700218 dev_err(pil->desc->dev, "Failed to bring out of reset\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700219 goto release_fw;
220 }
Stephen Boyde4389ad2011-12-08 10:20:27 -0800221 dev_info(pil->desc->dev, "brought out of reset\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700222
223release_fw:
224 release_firmware(fw);
225out:
226 return ret;
227}
228
229/**
230 * pil_get() - Load a peripheral into memory and take it out of reset
231 * @name: pointer to a string containing the name of the peripheral to load
232 *
233 * This function returns a pointer if it succeeds. If an error occurs an
234 * ERR_PTR is returned.
235 *
236 * If PIL is not enabled in the kernel, the value %NULL will be returned.
237 */
238void *pil_get(const char *name)
239{
240 int ret;
241 struct pil_device *pil;
242 struct pil_device *pil_d;
243 void *retval;
244
245 pil = retval = find_peripheral(name);
246 if (!pil)
247 return ERR_PTR(-ENODEV);
248
Stephen Boyd3f4da322011-08-30 01:03:23 -0700249 pil_d = find_peripheral(pil->desc->depends_on);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700250 if (pil_d) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700251 void *p = pil_get(pil_d->desc->name);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700252 if (IS_ERR(p))
253 return p;
254 }
255
256 mutex_lock(&pil->lock);
257 if (pil->count) {
258 pil->count++;
259 goto unlock;
260 }
261
262 ret = load_image(pil);
263 if (ret) {
264 retval = ERR_PTR(ret);
265 goto unlock;
266 }
267
268 pil->count++;
269unlock:
270 mutex_unlock(&pil->lock);
271 return retval;
272}
273EXPORT_SYMBOL(pil_get);
274
275/**
276 * pil_put() - Inform PIL the peripheral no longer needs to be active
277 * @peripheral_handle: pointer from a previous call to pil_get()
278 *
279 * This doesn't imply that a peripheral is shutdown or in reset since another
280 * driver could be using the peripheral.
281 */
282void pil_put(void *peripheral_handle)
283{
284 struct pil_device *pil_d;
285 struct pil_device *pil = peripheral_handle;
Stephen Boyd545e8362011-09-13 15:04:38 -0700286 if (!pil || IS_ERR(pil))
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700287 return;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700288
289 mutex_lock(&pil->lock);
290 WARN(!pil->count, "%s: Reference count mismatch\n", __func__);
291 /* TODO: Peripheral shutdown support */
292 if (pil->count == 1)
293 goto unlock;
294 if (pil->count)
295 pil->count--;
296 if (pil->count == 0)
Stephen Boyd3f4da322011-08-30 01:03:23 -0700297 pil->desc->ops->shutdown(pil->desc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700298unlock:
299 mutex_unlock(&pil->lock);
300
Stephen Boyd3f4da322011-08-30 01:03:23 -0700301 pil_d = find_peripheral(pil->desc->depends_on);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700302 if (pil_d)
303 pil_put(pil_d);
304}
305EXPORT_SYMBOL(pil_put);
306
307void pil_force_shutdown(const char *name)
308{
309 struct pil_device *pil;
310
311 pil = find_peripheral(name);
312 if (!pil)
313 return;
314
315 mutex_lock(&pil->lock);
316 if (!WARN(!pil->count, "%s: Reference count mismatch\n", __func__))
Stephen Boyd3f4da322011-08-30 01:03:23 -0700317 pil->desc->ops->shutdown(pil->desc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700318 mutex_unlock(&pil->lock);
319}
320EXPORT_SYMBOL(pil_force_shutdown);
321
322int pil_force_boot(const char *name)
323{
324 int ret = -EINVAL;
325 struct pil_device *pil;
326
327 pil = find_peripheral(name);
328 if (!pil)
329 return -EINVAL;
330
331 mutex_lock(&pil->lock);
332 if (!WARN(!pil->count, "%s: Reference count mismatch\n", __func__))
333 ret = load_image(pil);
334 mutex_unlock(&pil->lock);
335
336 return ret;
337}
338EXPORT_SYMBOL(pil_force_boot);
339
340#ifdef CONFIG_DEBUG_FS
341int msm_pil_debugfs_open(struct inode *inode, struct file *filp)
342{
343 filp->private_data = inode->i_private;
344 return 0;
345}
346
347static ssize_t msm_pil_debugfs_read(struct file *filp, char __user *ubuf,
348 size_t cnt, loff_t *ppos)
349{
350 int r;
351 char buf[40];
352 struct pil_device *pil = filp->private_data;
353
354 mutex_lock(&pil->lock);
355 r = snprintf(buf, sizeof(buf), "%d\n", pil->count);
356 mutex_unlock(&pil->lock);
357 return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
358}
359
360static ssize_t msm_pil_debugfs_write(struct file *filp,
361 const char __user *ubuf, size_t cnt, loff_t *ppos)
362{
363 struct pil_device *pil = filp->private_data;
364 char buf[4];
365
366 if (cnt > sizeof(buf))
367 return -EINVAL;
368
369 if (copy_from_user(&buf, ubuf, cnt))
370 return -EFAULT;
371
372 if (!strncmp(buf, "get", 3)) {
Stephen Boyd3f4da322011-08-30 01:03:23 -0700373 if (IS_ERR(pil_get(pil->desc->name)))
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700374 return -EIO;
375 } else if (!strncmp(buf, "put", 3))
376 pil_put(pil);
377 else
378 return -EINVAL;
379
380 return cnt;
381}
382
383static const struct file_operations msm_pil_debugfs_fops = {
384 .open = msm_pil_debugfs_open,
385 .read = msm_pil_debugfs_read,
386 .write = msm_pil_debugfs_write,
387};
388
389static struct dentry *pil_base_dir;
390
391static int msm_pil_debugfs_init(void)
392{
393 pil_base_dir = debugfs_create_dir("pil", NULL);
394 if (!pil_base_dir) {
395 pil_base_dir = NULL;
396 return -ENOMEM;
397 }
398
399 return 0;
400}
401arch_initcall(msm_pil_debugfs_init);
402
403static int msm_pil_debugfs_add(struct pil_device *pil)
404{
405 if (!pil_base_dir)
406 return -ENOMEM;
407
Stephen Boyd3f4da322011-08-30 01:03:23 -0700408 if (!debugfs_create_file(pil->desc->name, S_IRUGO | S_IWUSR,
409 pil_base_dir, pil, &msm_pil_debugfs_fops))
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700410 return -ENOMEM;
411 return 0;
412}
413#else
414static int msm_pil_debugfs_add(struct pil_device *pil) { return 0; }
415#endif
416
417static int msm_pil_shutdown_at_boot(void)
418{
419 struct pil_device *pil;
420
421 mutex_lock(&pil_list_lock);
422 list_for_each_entry(pil, &pil_list, list)
Stephen Boyd3f4da322011-08-30 01:03:23 -0700423 pil->desc->ops->shutdown(pil->desc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700424 mutex_unlock(&pil_list_lock);
425
426 return 0;
427}
428late_initcall(msm_pil_shutdown_at_boot);
429
Stephen Boyd3f4da322011-08-30 01:03:23 -0700430int msm_pil_register(struct pil_desc *desc)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700431{
Stephen Boyd3f4da322011-08-30 01:03:23 -0700432 struct pil_device *pil = kzalloc(sizeof(*pil), GFP_KERNEL);
433 if (!pil)
434 return -ENOMEM;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700435
436 mutex_init(&pil->lock);
Stephen Boyd3f4da322011-08-30 01:03:23 -0700437 INIT_LIST_HEAD(&pil->list);
438 pil->desc = desc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700439
440 mutex_lock(&pil_list_lock);
441 list_add(&pil->list, &pil_list);
442 mutex_unlock(&pil_list_lock);
443
Stephen Boyd3f4da322011-08-30 01:03:23 -0700444 return msm_pil_debugfs_add(pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700445}
Stephen Boyd3f4da322011-08-30 01:03:23 -0700446EXPORT_SYMBOL(msm_pil_register);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700447
448MODULE_LICENSE("GPL v2");
449MODULE_DESCRIPTION("Load peripheral images and bring peripherals out of reset");