aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware
diff options
context:
space:
mode:
authorKumar Gala <galak@codeaurora.org>2015-02-26 16:49:09 -0500
committerKumar Gala <galak@codeaurora.org>2015-03-11 16:06:38 -0400
commit916f743da3546c28a2f350d197e3bea95d97ba15 (patch)
tree331acf806161e5db52de9c41305078217848675f /drivers/firmware
parent4de43476fc1baaf2bb7a520fc9e3b1797943b615 (diff)
firmware: qcom: scm: Move the scm driver to drivers/firmware
Architectural changes in the ARM Linux kernel tree mandate the eventual removal of the mach-* directories. Move the scm driver to drivers/firmware and the scm header to include/linux to support that removal. Signed-off-by: Kumar Gala <galak@codeaurora.org>
Diffstat (limited to 'drivers/firmware')
-rw-r--r--drivers/firmware/Kconfig4
-rw-r--r--drivers/firmware/Makefile2
-rw-r--r--drivers/firmware/qcom_scm.c344
3 files changed, 350 insertions, 0 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 41983883cef4..6517132e5d8b 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -132,6 +132,10 @@ config ISCSI_IBFT
132 detect iSCSI boot parameters dynamically during system boot, say Y. 132 detect iSCSI boot parameters dynamically during system boot, say Y.
133 Otherwise, say N. 133 Otherwise, say N.
134 134
135config QCOM_SCM
136 bool
137 depends on ARM || ARM64
138
135source "drivers/firmware/google/Kconfig" 139source "drivers/firmware/google/Kconfig"
136source "drivers/firmware/efi/Kconfig" 140source "drivers/firmware/efi/Kconfig"
137 141
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 5373dc5b6011..3fdd3912709a 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -11,6 +11,8 @@ obj-$(CONFIG_DMIID) += dmi-id.o
11obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o 11obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o
12obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o 12obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
13obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o 13obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
14obj-$(CONFIG_QCOM_SCM) += qcom_scm.o
15CFLAGS_qcom_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
14 16
15obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ 17obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
16obj-$(CONFIG_EFI) += efi/ 18obj-$(CONFIG_EFI) += efi/
diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c
new file mode 100644
index 000000000000..6e7a72bdb176
--- /dev/null
+++ b/drivers/firmware/qcom_scm.c
@@ -0,0 +1,344 @@
1/* Copyright (c) 2010, 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 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18#include <linux/slab.h>
19#include <linux/io.h>
20#include <linux/module.h>
21#include <linux/mutex.h>
22#include <linux/errno.h>
23#include <linux/err.h>
24#include <linux/qcom_scm.h>
25
26#include <asm/outercache.h>
27#include <asm/cacheflush.h>
28
29
30#define QCOM_SCM_ENOMEM -5
31#define QCOM_SCM_EOPNOTSUPP -4
32#define QCOM_SCM_EINVAL_ADDR -3
33#define QCOM_SCM_EINVAL_ARG -2
34#define QCOM_SCM_ERROR -1
35#define QCOM_SCM_INTERRUPTED 1
36
37static DEFINE_MUTEX(qcom_scm_lock);
38
39/**
40 * struct qcom_scm_command - one SCM command buffer
41 * @len: total available memory for command and response
42 * @buf_offset: start of command buffer
43 * @resp_hdr_offset: start of response buffer
44 * @id: command to be executed
45 * @buf: buffer returned from qcom_scm_get_command_buffer()
46 *
47 * An SCM command is laid out in memory as follows:
48 *
49 * ------------------- <--- struct qcom_scm_command
50 * | command header |
51 * ------------------- <--- qcom_scm_get_command_buffer()
52 * | command buffer |
53 * ------------------- <--- struct qcom_scm_response and
54 * | response header | qcom_scm_command_to_response()
55 * ------------------- <--- qcom_scm_get_response_buffer()
56 * | response buffer |
57 * -------------------
58 *
59 * There can be arbitrary padding between the headers and buffers so
60 * you should always use the appropriate qcom_scm_get_*_buffer() routines
61 * to access the buffers in a safe manner.
62 */
63struct qcom_scm_command {
64 __le32 len;
65 __le32 buf_offset;
66 __le32 resp_hdr_offset;
67 __le32 id;
68 __le32 buf[0];
69};
70
71/**
72 * struct qcom_scm_response - one SCM response buffer
73 * @len: total available memory for response
74 * @buf_offset: start of response data relative to start of qcom_scm_response
75 * @is_complete: indicates if the command has finished processing
76 */
77struct qcom_scm_response {
78 __le32 len;
79 __le32 buf_offset;
80 __le32 is_complete;
81};
82
83/**
84 * alloc_qcom_scm_command() - Allocate an SCM command
85 * @cmd_size: size of the command buffer
86 * @resp_size: size of the response buffer
87 *
88 * Allocate an SCM command, including enough room for the command
89 * and response headers as well as the command and response buffers.
90 *
91 * Returns a valid &qcom_scm_command on success or %NULL if the allocation fails.
92 */
93static struct qcom_scm_command *alloc_qcom_scm_command(size_t cmd_size, size_t resp_size)
94{
95 struct qcom_scm_command *cmd;
96 size_t len = sizeof(*cmd) + sizeof(struct qcom_scm_response) + cmd_size +
97 resp_size;
98 u32 offset;
99
100 cmd = kzalloc(PAGE_ALIGN(len), GFP_KERNEL);
101 if (cmd) {
102 cmd->len = cpu_to_le32(len);
103 offset = offsetof(struct qcom_scm_command, buf);
104 cmd->buf_offset = cpu_to_le32(offset);
105 cmd->resp_hdr_offset = cpu_to_le32(offset + cmd_size);
106 }
107 return cmd;
108}
109
110/**
111 * free_qcom_scm_command() - Free an SCM command
112 * @cmd: command to free
113 *
114 * Free an SCM command.
115 */
116static inline void free_qcom_scm_command(struct qcom_scm_command *cmd)
117{
118 kfree(cmd);
119}
120
121/**
122 * qcom_scm_command_to_response() - Get a pointer to a qcom_scm_response
123 * @cmd: command
124 *
125 * Returns a pointer to a response for a command.
126 */
127static inline struct qcom_scm_response *qcom_scm_command_to_response(
128 const struct qcom_scm_command *cmd)
129{
130 return (void *)cmd + le32_to_cpu(cmd->resp_hdr_offset);
131}
132
133/**
134 * qcom_scm_get_command_buffer() - Get a pointer to a command buffer
135 * @cmd: command
136 *
137 * Returns a pointer to the command buffer of a command.
138 */
139static inline void *qcom_scm_get_command_buffer(const struct qcom_scm_command *cmd)
140{
141 return (void *)cmd->buf;
142}
143
144/**
145 * qcom_scm_get_response_buffer() - Get a pointer to a response buffer
146 * @rsp: response
147 *
148 * Returns a pointer to a response buffer of a response.
149 */
150static inline void *qcom_scm_get_response_buffer(const struct qcom_scm_response *rsp)
151{
152 return (void *)rsp + le32_to_cpu(rsp->buf_offset);
153}
154
155static int qcom_scm_remap_error(int err)
156{
157 pr_err("qcom_scm_call failed with error code %d\n", err);
158 switch (err) {
159 case QCOM_SCM_ERROR:
160 return -EIO;
161 case QCOM_SCM_EINVAL_ADDR:
162 case QCOM_SCM_EINVAL_ARG:
163 return -EINVAL;
164 case QCOM_SCM_EOPNOTSUPP:
165 return -EOPNOTSUPP;
166 case QCOM_SCM_ENOMEM:
167 return -ENOMEM;
168 }
169 return -EINVAL;
170}
171
172static u32 smc(u32 cmd_addr)
173{
174 int context_id;
175 register u32 r0 asm("r0") = 1;
176 register u32 r1 asm("r1") = (u32)&context_id;
177 register u32 r2 asm("r2") = cmd_addr;
178 do {
179 asm volatile(
180 __asmeq("%0", "r0")
181 __asmeq("%1", "r0")
182 __asmeq("%2", "r1")
183 __asmeq("%3", "r2")
184#ifdef REQUIRES_SEC
185 ".arch_extension sec\n"
186#endif
187 "smc #0 @ switch to secure world\n"
188 : "=r" (r0)
189 : "r" (r0), "r" (r1), "r" (r2)
190 : "r3");
191 } while (r0 == QCOM_SCM_INTERRUPTED);
192
193 return r0;
194}
195
196static int __qcom_scm_call(const struct qcom_scm_command *cmd)
197{
198 int ret;
199 u32 cmd_addr = virt_to_phys(cmd);
200
201 /*
202 * Flush the command buffer so that the secure world sees
203 * the correct data.
204 */
205 __cpuc_flush_dcache_area((void *)cmd, cmd->len);
206 outer_flush_range(cmd_addr, cmd_addr + cmd->len);
207
208 ret = smc(cmd_addr);
209 if (ret < 0)
210 ret = qcom_scm_remap_error(ret);
211
212 return ret;
213}
214
215static void qcom_scm_inv_range(unsigned long start, unsigned long end)
216{
217 u32 cacheline_size, ctr;
218
219 asm volatile("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctr));
220 cacheline_size = 4 << ((ctr >> 16) & 0xf);
221
222 start = round_down(start, cacheline_size);
223 end = round_up(end, cacheline_size);
224 outer_inv_range(start, end);
225 while (start < end) {
226 asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start)
227 : "memory");
228 start += cacheline_size;
229 }
230 dsb();
231 isb();
232}
233
234/**
235 * qcom_scm_call() - Send an SCM command
236 * @svc_id: service identifier
237 * @cmd_id: command identifier
238 * @cmd_buf: command buffer
239 * @cmd_len: length of the command buffer
240 * @resp_buf: response buffer
241 * @resp_len: length of the response buffer
242 *
243 * Sends a command to the SCM and waits for the command to finish processing.
244 *
245 * A note on cache maintenance:
246 * Note that any buffers that are expected to be accessed by the secure world
247 * must be flushed before invoking qcom_scm_call and invalidated in the cache
248 * immediately after qcom_scm_call returns. Cache maintenance on the command
249 * and response buffers is taken care of by qcom_scm_call; however, callers are
250 * responsible for any other cached buffers passed over to the secure world.
251 */
252static int qcom_scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf,
253 size_t cmd_len, void *resp_buf, size_t resp_len)
254{
255 int ret;
256 struct qcom_scm_command *cmd;
257 struct qcom_scm_response *rsp;
258 unsigned long start, end;
259
260 cmd = alloc_qcom_scm_command(cmd_len, resp_len);
261 if (!cmd)
262 return -ENOMEM;
263
264 cmd->id = cpu_to_le32((svc_id << 10) | cmd_id);
265 if (cmd_buf)
266 memcpy(qcom_scm_get_command_buffer(cmd), cmd_buf, cmd_len);
267
268 mutex_lock(&qcom_scm_lock);
269 ret = __qcom_scm_call(cmd);
270 mutex_unlock(&qcom_scm_lock);
271 if (ret)
272 goto out;
273
274 rsp = qcom_scm_command_to_response(cmd);
275 start = (unsigned long)rsp;
276
277 do {
278 qcom_scm_inv_range(start, start + sizeof(*rsp));
279 } while (!rsp->is_complete);
280
281 end = (unsigned long)qcom_scm_get_response_buffer(rsp) + resp_len;
282 qcom_scm_inv_range(start, end);
283
284 if (resp_buf)
285 memcpy(resp_buf, qcom_scm_get_response_buffer(rsp), resp_len);
286out:
287 free_qcom_scm_command(cmd);
288 return ret;
289}
290
291u32 qcom_scm_get_version(void)
292{
293 int context_id;
294 static u32 version = -1;
295 register u32 r0 asm("r0");
296 register u32 r1 asm("r1");
297
298 if (version != -1)
299 return version;
300
301 mutex_lock(&qcom_scm_lock);
302
303 r0 = 0x1 << 8;
304 r1 = (u32)&context_id;
305 do {
306 asm volatile(
307 __asmeq("%0", "r0")
308 __asmeq("%1", "r1")
309 __asmeq("%2", "r0")
310 __asmeq("%3", "r1")
311#ifdef REQUIRES_SEC
312 ".arch_extension sec\n"
313#endif
314 "smc #0 @ switch to secure world\n"
315 : "=r" (r0), "=r" (r1)
316 : "r" (r0), "r" (r1)
317 : "r2", "r3");
318 } while (r0 == QCOM_SCM_INTERRUPTED);
319
320 version = r1;
321 mutex_unlock(&qcom_scm_lock);
322
323 return version;
324}
325EXPORT_SYMBOL(qcom_scm_get_version);
326
327#define QCOM_SCM_SVC_BOOT 0x1
328#define QCOM_SCM_BOOT_ADDR 0x1
329/*
330 * Set the cold/warm boot address for one of the CPU cores.
331 */
332int qcom_scm_set_boot_addr(u32 addr, int flags)
333{
334 struct {
335 __le32 flags;
336 __le32 addr;
337 } cmd;
338
339 cmd.addr = cpu_to_le32(addr);
340 cmd.flags = cpu_to_le32(flags);
341 return qcom_scm_call(QCOM_SCM_SVC_BOOT, QCOM_SCM_BOOT_ADDR,
342 &cmd, sizeof(cmd), NULL, 0);
343}
344EXPORT_SYMBOL(qcom_scm_set_boot_addr);