remoteproc: qcom: mdt_loader: Don't overwrite firmware object
[platform/kernel/linux-exynos.git] / drivers / remoteproc / 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/elf.h>
19 #include <linux/firmware.h>
20 #include <linux/kernel.h>
21 #include <linux/module.h>
22 #include <linux/remoteproc.h>
23 #include <linux/sizes.h>
24 #include <linux/slab.h>
25
26 #include "remoteproc_internal.h"
27 #include "qcom_mdt_loader.h"
28
29 /**
30  * qcom_mdt_parse() - extract useful parameters from the mdt header
31  * @fw:         firmware handle
32  * @fw_addr:    optional reference for base of the firmware's memory region
33  * @fw_size:    optional reference for size of the firmware's memory region
34  * @fw_relocate: optional reference for flagging if the firmware is relocatable
35  *
36  * Returns 0 on success, negative errno otherwise.
37  */
38 int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr,
39                    size_t *fw_size, bool *fw_relocate)
40 {
41         const struct elf32_phdr *phdrs;
42         const struct elf32_phdr *phdr;
43         const struct elf32_hdr *ehdr;
44         phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
45         phys_addr_t max_addr = 0;
46         bool relocate = false;
47         int i;
48
49         ehdr = (struct elf32_hdr *)fw->data;
50         phdrs = (struct elf32_phdr *)(ehdr + 1);
51
52         for (i = 0; i < ehdr->e_phnum; i++) {
53                 phdr = &phdrs[i];
54
55                 if (phdr->p_type != PT_LOAD)
56                         continue;
57
58                 if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
59                         continue;
60
61                 if (!phdr->p_memsz)
62                         continue;
63
64                 if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
65                         relocate = true;
66
67                 if (phdr->p_paddr < min_addr)
68                         min_addr = phdr->p_paddr;
69
70                 if (phdr->p_paddr + phdr->p_memsz > max_addr)
71                         max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
72         }
73
74         if (fw_addr)
75                 *fw_addr = min_addr;
76         if (fw_size)
77                 *fw_size = max_addr - min_addr;
78         if (fw_relocate)
79                 *fw_relocate = relocate;
80
81         return 0;
82 }
83 EXPORT_SYMBOL_GPL(qcom_mdt_parse);
84
85 /**
86  * qcom_mdt_load() - load the firmware which header is defined in fw
87  * @rproc:      rproc handle
88  * @fw:         frimware object for the header
89  * @firmware:   filename of the firmware, for building .bXX names
90  *
91  * Returns 0 on success, negative errno otherwise.
92  */
93 int qcom_mdt_load(struct rproc *rproc,
94                   const struct firmware *fw,
95                   const char *firmware)
96 {
97         const struct elf32_phdr *phdrs;
98         const struct elf32_phdr *phdr;
99         const struct elf32_hdr *ehdr;
100         const struct firmware *seg_fw;
101         size_t fw_name_len;
102         char *fw_name;
103         void *ptr;
104         int ret;
105         int i;
106
107         ehdr = (struct elf32_hdr *)fw->data;
108         phdrs = (struct elf32_phdr *)(ehdr + 1);
109
110         fw_name_len = strlen(firmware);
111         if (fw_name_len <= 4)
112                 return -EINVAL;
113
114         fw_name = kstrdup(firmware, GFP_KERNEL);
115         if (!fw_name)
116                 return -ENOMEM;
117
118         for (i = 0; i < ehdr->e_phnum; i++) {
119                 phdr = &phdrs[i];
120
121                 if (phdr->p_type != PT_LOAD)
122                         continue;
123
124                 if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
125                         continue;
126
127                 if (!phdr->p_memsz)
128                         continue;
129
130                 ptr = rproc_da_to_va(rproc, phdr->p_paddr, phdr->p_memsz);
131                 if (!ptr) {
132                         dev_err(&rproc->dev, "segment outside memory range\n");
133                         ret = -EINVAL;
134                         break;
135                 }
136
137                 if (phdr->p_filesz) {
138                         sprintf(fw_name + fw_name_len - 3, "b%02d", i);
139                         ret = request_firmware(&seg_fw, fw_name, &rproc->dev);
140                         if (ret) {
141                                 dev_err(&rproc->dev, "failed to load %s\n",
142                                         fw_name);
143                                 break;
144                         }
145
146                         memcpy(ptr, seg_fw->data, seg_fw->size);
147
148                         release_firmware(seg_fw);
149                 }
150
151                 if (phdr->p_memsz > phdr->p_filesz)
152                         memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
153         }
154
155         kfree(fw_name);
156
157         return ret;
158 }
159 EXPORT_SYMBOL_GPL(qcom_mdt_load);
160
161 MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");
162 MODULE_LICENSE("GPL v2");