blob: c00f96fa86ef177b910390e3883b95db478e02b2 [file] [log] [blame]
Jeff Hugo5ba15fe2013-05-06 14:24:24 -06001/* Copyright (c) 2013, The Linux Foundation. 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/export.h>
14#include <linux/init.h>
15#include <linux/kernel.h>
16#include <linux/moduleparam.h>
17#include <linux/printk.h>
18
19#include <mach/board.h>
20#include <mach/msm_iomap.h>
21#include <mach/msm_smem.h>
22#include <mach/ramdump.h>
23#include <mach/subsystem_notif.h>
24
25#include "smem_private.h"
26
27/**
28 * OVERFLOW_ADD_UNSIGNED() - check for unsigned overflow
29 *
30 * @type: type to check for overflow
31 * @a: left value to use
32 * @b: right value to use
33 * @returns: true if a + b will result in overflow; false otherwise
34 */
35#define OVERFLOW_ADD_UNSIGNED(type, a, b) \
36 (((type)~0 - (a)) < (b) ? true : false)
37
38enum {
39 MSM_SMEM_DEBUG = 1U << 0,
40 MSM_SMEM_INFO = 1U << 1,
41};
42
43static int msm_smem_debug_mask;
44module_param_named(debug_mask, msm_smem_debug_mask,
45 int, S_IRUGO | S_IWUSR | S_IWGRP);
46
47#define SMEM_DBG(x...) do { \
48 if (msm_smem_debug_mask & MSM_SMEM_DEBUG) \
49 pr_debug(x); \
50 } while (0)
51
52remote_spinlock_t remote_spinlock;
53int spinlocks_initialized;
54uint32_t num_smem_areas;
55struct smem_area *smem_areas;
56struct ramdump_segment *smem_ramdump_segments;
57
58static void *smem_ramdump_dev;
59
60struct restart_notifier_block {
61 unsigned processor;
62 char *name;
63 struct notifier_block nb;
64};
65
66static int restart_notifier_cb(struct notifier_block *this,
67 unsigned long code,
68 void *data);
69
70static struct restart_notifier_block restart_notifiers[] = {
71 {SMEM_MODEM, "modem", .nb.notifier_call = restart_notifier_cb},
72 {SMEM_Q6, "lpass", .nb.notifier_call = restart_notifier_cb},
73 {SMEM_WCNSS, "wcnss", .nb.notifier_call = restart_notifier_cb},
74 {SMEM_DSPS, "dsps", .nb.notifier_call = restart_notifier_cb},
75 {SMEM_MODEM, "gss", .nb.notifier_call = restart_notifier_cb},
76 {SMEM_Q6, "adsp", .nb.notifier_call = restart_notifier_cb},
77};
78
79/**
80 * smem_phys_to_virt() - Convert a physical base and offset to virtual address
81 *
82 * @base: physical base address to check
83 * @offset: offset from the base to get the final address
84 * @returns: virtual SMEM address; NULL for failure
85 *
86 * Takes a physical address and an offset and checks if the resulting physical
87 * address would fit into one of the smem regions. If so, returns the
88 * corresponding virtual address. Otherwise returns NULL.
89 */
90static void *smem_phys_to_virt(phys_addr_t base, unsigned offset)
91{
92 int i;
93 phys_addr_t phys_addr;
94 resource_size_t size;
95
96 if (OVERFLOW_ADD_UNSIGNED(phys_addr_t, base, offset))
97 return NULL;
98
99 if (!smem_areas) {
100 /*
101 * Early boot - no area configuration yet, so default
102 * to using the main memory region.
103 *
104 * To remove the MSM_SHARED_RAM_BASE and the static
105 * mapping of SMEM in the future, add dump_stack()
106 * to identify the early callers of smem_get_entry()
107 * (which calls this function) and replace those calls
108 * with a new function that knows how to lookup the
109 * SMEM base address before SMEM has been probed.
110 */
111 phys_addr = msm_shared_ram_phys;
112 size = MSM_SHARED_RAM_SIZE;
113
114 if (base >= phys_addr && base + offset < phys_addr + size) {
115 if (OVERFLOW_ADD_UNSIGNED(uintptr_t,
116 (uintptr_t)MSM_SHARED_RAM_BASE, offset)) {
117 pr_err("%s: overflow %p %x\n", __func__,
118 MSM_SHARED_RAM_BASE, offset);
119 return NULL;
120 }
121
122 return MSM_SHARED_RAM_BASE + offset;
123 } else {
124 return NULL;
125 }
126 }
127 for (i = 0; i < num_smem_areas; ++i) {
128 phys_addr = smem_areas[i].phys_addr;
129 size = smem_areas[i].size;
130
131 if (base < phys_addr || base + offset >= phys_addr + size)
132 continue;
133
134 if (OVERFLOW_ADD_UNSIGNED(uintptr_t,
135 (uintptr_t)smem_areas[i].virt_addr, offset)) {
136 pr_err("%s: overflow %p %x\n", __func__,
137 smem_areas[i].virt_addr, offset);
138 return NULL;
139 }
140
141 return smem_areas[i].virt_addr + offset;
142 }
143
144 return NULL;
145}
146
147/**
148 * smem_virt_to_phys() - Convert SMEM address to physical address.
149 *
150 * @smem_address: Address of SMEM item (returned by smem_alloc(), etc)
151 * @returns: Physical address (or NULL if there is a failure)
152 *
153 * This function should only be used if an SMEM item needs to be handed
154 * off to a DMA engine.
155 */
156phys_addr_t smem_virt_to_phys(void *smem_address)
157{
158 phys_addr_t phys_addr = 0;
159 int i;
160 void *vend;
161
162 if (!smem_areas)
163 return phys_addr;
164
165 for (i = 0; i < num_smem_areas; ++i) {
166 vend = (void *)(smem_areas[i].virt_addr + smem_areas[i].size);
167
168 if (smem_address >= smem_areas[i].virt_addr &&
169 smem_address < vend) {
170 phys_addr = smem_address - smem_areas[i].virt_addr;
171 phys_addr += smem_areas[i].phys_addr;
172 break;
173 }
174 }
175
176 return phys_addr;
177}
178EXPORT_SYMBOL(smem_virt_to_phys);
179
180/* smem_alloc returns the pointer to smem item if it is already allocated.
181 * Otherwise, it returns NULL.
182 */
183void *smem_alloc(unsigned id, unsigned size)
184{
185 return smem_find(id, size);
186}
187EXPORT_SYMBOL(smem_alloc);
188
189void *smem_find(unsigned id, unsigned size_in)
190{
191 unsigned size;
192 void *ptr;
193
194 ptr = smem_get_entry(id, &size);
195 if (!ptr)
196 return 0;
197
198 size_in = ALIGN(size_in, 8);
199 if (size_in != size) {
200 pr_err("smem_find(%d, %d): wrong size %d\n",
201 id, size_in, size);
202 return 0;
203 }
204
205 return ptr;
206}
207EXPORT_SYMBOL(smem_find);
208
209/* smem_alloc2 returns the pointer to smem item. If it is not allocated,
210 * it allocates it and then returns the pointer to it.
211 */
212void *smem_alloc2(unsigned id, unsigned size_in)
213{
214 struct smem_shared *shared = (void *) MSM_SHARED_RAM_BASE;
215 struct smem_heap_entry *toc = shared->heap_toc;
216 unsigned long flags;
217 void *ret = NULL;
218
219 if (!shared->heap_info.initialized) {
220 pr_err("%s: smem heap info not initialized\n", __func__);
221 return NULL;
222 }
223
224 if (id >= SMEM_NUM_ITEMS)
225 return NULL;
226
227 size_in = ALIGN(size_in, 8);
228 remote_spin_lock_irqsave(&remote_spinlock, flags);
229 if (toc[id].allocated) {
230 SMEM_DBG("%s: %u already allocated\n", __func__, id);
231 if (size_in != toc[id].size)
232 pr_err("%s: wrong size %u (expected %u)\n",
233 __func__, toc[id].size, size_in);
234 else
235 ret = (void *)(MSM_SHARED_RAM_BASE + toc[id].offset);
236 } else if (id > SMEM_FIXED_ITEM_LAST) {
237 SMEM_DBG("%s: allocating %u\n", __func__, id);
238 if (shared->heap_info.heap_remaining >= size_in) {
239 toc[id].offset = shared->heap_info.free_offset;
240 toc[id].size = size_in;
241 wmb();
242 toc[id].allocated = 1;
243
244 shared->heap_info.free_offset += size_in;
245 shared->heap_info.heap_remaining -= size_in;
246 ret = (void *)(MSM_SHARED_RAM_BASE + toc[id].offset);
247 } else
248 pr_err("%s: not enough memory %u (required %u)\n",
249 __func__, shared->heap_info.heap_remaining,
250 size_in);
251 }
252 wmb();
253 remote_spin_unlock_irqrestore(&remote_spinlock, flags);
254 return ret;
255}
256EXPORT_SYMBOL(smem_alloc2);
257
258void *smem_get_entry(unsigned id, unsigned *size)
259{
260 struct smem_shared *shared = (void *) MSM_SHARED_RAM_BASE;
261 struct smem_heap_entry *toc = shared->heap_toc;
262 int use_spinlocks = spinlocks_initialized;
263 void *ret = 0;
264 unsigned long flags = 0;
265
266 if (id >= SMEM_NUM_ITEMS)
267 return ret;
268
269 if (use_spinlocks)
270 remote_spin_lock_irqsave(&remote_spinlock, flags);
271 /* toc is in device memory and cannot be speculatively accessed */
272 if (toc[id].allocated) {
273 phys_addr_t phys_base;
274
275 *size = toc[id].size;
276 barrier();
277
278 phys_base = toc[id].reserved & BASE_ADDR_MASK;
279 if (!phys_base)
280 phys_base = (phys_addr_t)msm_shared_ram_phys;
281 ret = smem_phys_to_virt(phys_base, toc[id].offset);
282 } else {
283 *size = 0;
284 }
285 if (use_spinlocks)
286 remote_spin_unlock_irqrestore(&remote_spinlock, flags);
287
288 return ret;
289}
290EXPORT_SYMBOL(smem_get_entry);
291
292
293/**
294 * smem_get_remote_spinlock - Remote spinlock pointer for unit testing.
295 *
296 * @returns: pointer to SMEM remote spinlock
297 */
298remote_spinlock_t *smem_get_remote_spinlock(void)
299{
300 return &remote_spinlock;
301}
302EXPORT_SYMBOL(smem_get_remote_spinlock);
303
304static int restart_notifier_cb(struct notifier_block *this,
305 unsigned long code,
306 void *data)
307{
308 if (code == SUBSYS_AFTER_SHUTDOWN) {
309 struct restart_notifier_block *notifier;
310
311 notifier = container_of(this,
312 struct restart_notifier_block, nb);
313 SMEM_DBG("%s: ssrestart for processor %d ('%s')\n",
314 __func__, notifier->processor,
315 notifier->name);
316
317 remote_spin_release(&remote_spinlock, notifier->processor);
318 remote_spin_release_all(notifier->processor);
319
320 if (smem_ramdump_dev) {
321 int ret;
322
323 SMEM_DBG("%s: saving ramdump\n", __func__);
324 /*
325 * XPU protection does not currently allow the
326 * auxiliary memory regions to be dumped. If this
327 * changes, then num_smem_areas + 1 should be passed
328 * into do_elf_ramdump() to dump all regions.
329 */
330 ret = do_elf_ramdump(smem_ramdump_dev,
331 smem_ramdump_segments, 1);
332 if (ret < 0)
333 pr_err("%s: unable to dump smem %d\n", __func__,
334 ret);
335 }
336 }
337
338 return NOTIFY_DONE;
339}
340
341static __init int modem_restart_late_init(void)
342{
343 int i;
344 void *handle;
345 struct restart_notifier_block *nb;
346
347 smem_ramdump_dev = create_ramdump_device("smem", NULL);
348 if (IS_ERR_OR_NULL(smem_ramdump_dev)) {
349 pr_err("%s: Unable to create smem ramdump device.\n",
350 __func__);
351 smem_ramdump_dev = NULL;
352 }
353
354 for (i = 0; i < ARRAY_SIZE(restart_notifiers); i++) {
355 nb = &restart_notifiers[i];
356 handle = subsys_notif_register_notifier(nb->name, &nb->nb);
357 SMEM_DBG("%s: registering notif for '%s', handle=%p\n",
358 __func__, nb->name, handle);
359 }
360
361 return 0;
362}
363late_initcall(modem_restart_late_init);