Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
051fb70f BA |
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. | |
051fb70f BA |
8 | */ |
9 | ||
7f0dd07a | 10 | #include <linux/device.h> |
051fb70f BA |
11 | #include <linux/elf.h> |
12 | #include <linux/firmware.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/module.h> | |
7f0dd07a | 15 | #include <linux/qcom_scm.h> |
e67af182 | 16 | #include <linux/sizes.h> |
051fb70f | 17 | #include <linux/slab.h> |
2aad40d9 | 18 | #include <linux/soc/qcom/mdt_loader.h> |
051fb70f | 19 | |
7f0dd07a BA |
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 | ||
051fb70f | 34 | /** |
7f0dd07a BA |
35 | * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt |
36 | * @fw: firmware object for the mdt file | |
051fb70f | 37 | * |
7f0dd07a | 38 | * Returns size of the loaded firmware blob, or -EINVAL on failure. |
051fb70f | 39 | */ |
7f0dd07a | 40 | ssize_t qcom_mdt_get_size(const struct firmware *fw) |
051fb70f BA |
41 | { |
42 | const struct elf32_phdr *phdrs; | |
43 | const struct elf32_phdr *phdr; | |
44 | const struct elf32_hdr *ehdr; | |
d7dc899a | 45 | phys_addr_t min_addr = PHYS_ADDR_MAX; |
051fb70f | 46 | phys_addr_t max_addr = 0; |
051fb70f BA |
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 | ||
7f0dd07a | 55 | if (!mdt_phdr_valid(phdr)) |
051fb70f BA |
56 | continue; |
57 | ||
051fb70f BA |
58 | if (phdr->p_paddr < min_addr) |
59 | min_addr = phdr->p_paddr; | |
60 | ||
61 | if (phdr->p_paddr + phdr->p_memsz > max_addr) | |
62 | max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); | |
63 | } | |
64 | ||
7f0dd07a | 65 | return min_addr < max_addr ? max_addr - min_addr : -EINVAL; |
051fb70f | 66 | } |
7f0dd07a | 67 | EXPORT_SYMBOL_GPL(qcom_mdt_get_size); |
051fb70f | 68 | |
0e622e80 S |
69 | static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, |
70 | const char *firmware, int pas_id, void *mem_region, | |
71 | phys_addr_t mem_phys, size_t mem_size, | |
72 | phys_addr_t *reloc_base, bool pas_init) | |
051fb70f BA |
73 | { |
74 | const struct elf32_phdr *phdrs; | |
75 | const struct elf32_phdr *phdr; | |
76 | const struct elf32_hdr *ehdr; | |
3e8b571a | 77 | const struct firmware *seg_fw; |
7f0dd07a | 78 | phys_addr_t mem_reloc; |
d7dc899a | 79 | phys_addr_t min_addr = PHYS_ADDR_MAX; |
7f0dd07a | 80 | phys_addr_t max_addr = 0; |
051fb70f | 81 | size_t fw_name_len; |
01625cc5 | 82 | ssize_t offset; |
051fb70f | 83 | char *fw_name; |
7f0dd07a | 84 | bool relocate = false; |
051fb70f BA |
85 | void *ptr; |
86 | int ret; | |
87 | int i; | |
88 | ||
7f0dd07a BA |
89 | if (!fw || !mem_region || !mem_phys || !mem_size) |
90 | return -EINVAL; | |
91 | ||
051fb70f BA |
92 | ehdr = (struct elf32_hdr *)fw->data; |
93 | phdrs = (struct elf32_phdr *)(ehdr + 1); | |
94 | ||
95 | fw_name_len = strlen(firmware); | |
96 | if (fw_name_len <= 4) | |
97 | return -EINVAL; | |
98 | ||
99 | fw_name = kstrdup(firmware, GFP_KERNEL); | |
100 | if (!fw_name) | |
101 | return -ENOMEM; | |
102 | ||
0e622e80 S |
103 | if (pas_init) { |
104 | ret = qcom_scm_pas_init_image(pas_id, fw->data, fw->size); | |
105 | if (ret) { | |
106 | dev_err(dev, "invalid firmware metadata\n"); | |
107 | goto out; | |
108 | } | |
7f0dd07a BA |
109 | } |
110 | ||
051fb70f BA |
111 | for (i = 0; i < ehdr->e_phnum; i++) { |
112 | phdr = &phdrs[i]; | |
113 | ||
7f0dd07a | 114 | if (!mdt_phdr_valid(phdr)) |
051fb70f BA |
115 | continue; |
116 | ||
7f0dd07a BA |
117 | if (phdr->p_flags & QCOM_MDT_RELOCATABLE) |
118 | relocate = true; | |
119 | ||
120 | if (phdr->p_paddr < min_addr) | |
121 | min_addr = phdr->p_paddr; | |
122 | ||
123 | if (phdr->p_paddr + phdr->p_memsz > max_addr) | |
124 | max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); | |
125 | } | |
126 | ||
127 | if (relocate) { | |
0e622e80 S |
128 | if (pas_init) { |
129 | ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, | |
130 | max_addr - min_addr); | |
131 | if (ret) { | |
132 | dev_err(dev, "unable to setup relocation\n"); | |
133 | goto out; | |
134 | } | |
7f0dd07a BA |
135 | } |
136 | ||
137 | /* | |
138 | * The image is relocatable, so offset each segment based on | |
139 | * the lowest segment address. | |
140 | */ | |
141 | mem_reloc = min_addr; | |
142 | } else { | |
143 | /* | |
144 | * Image is not relocatable, so offset each segment based on | |
145 | * the allocated physical chunk of memory. | |
146 | */ | |
147 | mem_reloc = mem_phys; | |
148 | } | |
051fb70f | 149 | |
7f0dd07a BA |
150 | for (i = 0; i < ehdr->e_phnum; i++) { |
151 | phdr = &phdrs[i]; | |
152 | ||
153 | if (!mdt_phdr_valid(phdr)) | |
051fb70f BA |
154 | continue; |
155 | ||
7f0dd07a BA |
156 | offset = phdr->p_paddr - mem_reloc; |
157 | if (offset < 0 || offset + phdr->p_memsz > mem_size) { | |
158 | dev_err(dev, "segment outside memory range\n"); | |
051fb70f BA |
159 | ret = -EINVAL; |
160 | break; | |
161 | } | |
162 | ||
7f0dd07a BA |
163 | ptr = mem_region + offset; |
164 | ||
051fb70f BA |
165 | if (phdr->p_filesz) { |
166 | sprintf(fw_name + fw_name_len - 3, "b%02d", i); | |
445c2410 BA |
167 | ret = request_firmware_into_buf(&seg_fw, fw_name, dev, |
168 | ptr, phdr->p_filesz); | |
051fb70f | 169 | if (ret) { |
7f0dd07a | 170 | dev_err(dev, "failed to load %s\n", fw_name); |
051fb70f BA |
171 | break; |
172 | } | |
173 | ||
3e8b571a | 174 | release_firmware(seg_fw); |
051fb70f BA |
175 | } |
176 | ||
177 | if (phdr->p_memsz > phdr->p_filesz) | |
178 | memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); | |
179 | } | |
180 | ||
4dd27f54 BA |
181 | if (reloc_base) |
182 | *reloc_base = mem_reloc; | |
183 | ||
7f0dd07a | 184 | out: |
051fb70f BA |
185 | kfree(fw_name); |
186 | ||
187 | return ret; | |
188 | } | |
0e622e80 S |
189 | |
190 | /** | |
191 | * qcom_mdt_load() - load the firmware which header is loaded as fw | |
192 | * @dev: device handle to associate resources with | |
193 | * @fw: firmware object for the mdt file | |
194 | * @firmware: name of the firmware, for construction of segment file names | |
195 | * @pas_id: PAS identifier | |
196 | * @mem_region: allocated memory region to load firmware into | |
197 | * @mem_phys: physical address of allocated memory region | |
198 | * @mem_size: size of the allocated memory region | |
199 | * @reloc_base: adjusted physical address after relocation | |
200 | * | |
201 | * Returns 0 on success, negative errno otherwise. | |
202 | */ | |
203 | int qcom_mdt_load(struct device *dev, const struct firmware *fw, | |
204 | const char *firmware, int pas_id, void *mem_region, | |
205 | phys_addr_t mem_phys, size_t mem_size, | |
206 | phys_addr_t *reloc_base) | |
207 | { | |
208 | return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys, | |
209 | mem_size, reloc_base, true); | |
210 | } | |
051fb70f BA |
211 | EXPORT_SYMBOL_GPL(qcom_mdt_load); |
212 | ||
0e622e80 S |
213 | /** |
214 | * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw | |
215 | * @dev: device handle to associate resources with | |
216 | * @fw: firmware object for the mdt file | |
217 | * @firmware: name of the firmware, for construction of segment file names | |
218 | * @pas_id: PAS identifier | |
219 | * @mem_region: allocated memory region to load firmware into | |
220 | * @mem_phys: physical address of allocated memory region | |
221 | * @mem_size: size of the allocated memory region | |
222 | * @reloc_base: adjusted physical address after relocation | |
223 | * | |
224 | * Returns 0 on success, negative errno otherwise. | |
225 | */ | |
226 | int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw, | |
227 | const char *firmware, int pas_id, | |
228 | void *mem_region, phys_addr_t mem_phys, | |
229 | size_t mem_size, phys_addr_t *reloc_base) | |
230 | { | |
231 | return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys, | |
232 | mem_size, reloc_base, false); | |
233 | } | |
234 | EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init); | |
235 | ||
051fb70f BA |
236 | MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); |
237 | MODULE_LICENSE("GPL v2"); |