Merge tag 'xtensa-20190607' of git://github.com/jcmvbkbc/linux-xtensa
[linux-2.6-block.git] / drivers / soc / qcom / mdt_loader.c
CommitLineData
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
20static 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 40ssize_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 67EXPORT_SYMBOL_GPL(qcom_mdt_get_size);
051fb70f 68
0e622e80
S
69static 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 184out:
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 */
203int 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
211EXPORT_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 */
226int 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}
234EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init);
235
051fb70f
BA
236MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");
237MODULE_LICENSE("GPL v2");