Commit | Line | Data |
---|---|---|
4b638df4 BA |
1 | /* |
2 | * Copyright (c) 2015, Sony Mobile Communications AB. | |
3 | * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 and | |
7 | * only version 2 as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | */ | |
14 | ||
15 | #include <linux/hwspinlock.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/of.h> | |
19 | #include <linux/of_address.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/slab.h> | |
22 | #include <linux/soc/qcom/smem.h> | |
23 | ||
24 | /* | |
25 | * The Qualcomm shared memory system is a allocate only heap structure that | |
26 | * consists of one of more memory areas that can be accessed by the processors | |
27 | * in the SoC. | |
28 | * | |
29 | * All systems contains a global heap, accessible by all processors in the SoC, | |
30 | * with a table of contents data structure (@smem_header) at the beginning of | |
31 | * the main shared memory block. | |
32 | * | |
33 | * The global header contains meta data for allocations as well as a fixed list | |
34 | * of 512 entries (@smem_global_entry) that can be initialized to reference | |
35 | * parts of the shared memory space. | |
36 | * | |
37 | * | |
38 | * In addition to this global heap a set of "private" heaps can be set up at | |
39 | * boot time with access restrictions so that only certain processor pairs can | |
40 | * access the data. | |
41 | * | |
42 | * These partitions are referenced from an optional partition table | |
43 | * (@smem_ptable), that is found 4kB from the end of the main smem region. The | |
44 | * partition table entries (@smem_ptable_entry) lists the involved processors | |
45 | * (or hosts) and their location in the main shared memory region. | |
46 | * | |
47 | * Each partition starts with a header (@smem_partition_header) that identifies | |
48 | * the partition and holds properties for the two internal memory regions. The | |
49 | * two regions are cached and non-cached memory respectively. Each region | |
50 | * contain a link list of allocation headers (@smem_private_entry) followed by | |
51 | * their data. | |
52 | * | |
53 | * Items in the non-cached region are allocated from the start of the partition | |
54 | * while items in the cached region are allocated from the end. The free area | |
55 | * is hence the region between the cached and non-cached offsets. | |
56 | * | |
57 | * | |
58 | * To synchronize allocations in the shared memory heaps a remote spinlock must | |
59 | * be held - currently lock number 3 of the sfpb or tcsr is used for this on all | |
60 | * platforms. | |
61 | * | |
62 | */ | |
63 | ||
64 | /* | |
65 | * Item 3 of the global heap contains an array of versions for the various | |
66 | * software components in the SoC. We verify that the boot loader version is | |
67 | * what the expected version (SMEM_EXPECTED_VERSION) as a sanity check. | |
68 | */ | |
69 | #define SMEM_ITEM_VERSION 3 | |
70 | #define SMEM_MASTER_SBL_VERSION_INDEX 7 | |
71 | #define SMEM_EXPECTED_VERSION 11 | |
72 | ||
73 | /* | |
74 | * The first 8 items are only to be allocated by the boot loader while | |
75 | * initializing the heap. | |
76 | */ | |
77 | #define SMEM_ITEM_LAST_FIXED 8 | |
78 | ||
79 | /* Highest accepted item number, for both global and private heaps */ | |
80 | #define SMEM_ITEM_COUNT 512 | |
81 | ||
82 | /* Processor/host identifier for the application processor */ | |
83 | #define SMEM_HOST_APPS 0 | |
84 | ||
85 | /* Max number of processors/hosts in a system */ | |
86 | #define SMEM_HOST_COUNT 9 | |
87 | ||
88 | /** | |
89 | * struct smem_proc_comm - proc_comm communication struct (legacy) | |
90 | * @command: current command to be executed | |
91 | * @status: status of the currently requested command | |
92 | * @params: parameters to the command | |
93 | */ | |
94 | struct smem_proc_comm { | |
9806884d SB |
95 | __le32 command; |
96 | __le32 status; | |
97 | __le32 params[2]; | |
4b638df4 BA |
98 | }; |
99 | ||
100 | /** | |
101 | * struct smem_global_entry - entry to reference smem items on the heap | |
102 | * @allocated: boolean to indicate if this entry is used | |
103 | * @offset: offset to the allocated space | |
104 | * @size: size of the allocated space, 8 byte aligned | |
105 | * @aux_base: base address for the memory region used by this unit, or 0 for | |
106 | * the default region. bits 0,1 are reserved | |
107 | */ | |
108 | struct smem_global_entry { | |
9806884d SB |
109 | __le32 allocated; |
110 | __le32 offset; | |
111 | __le32 size; | |
112 | __le32 aux_base; /* bits 1:0 reserved */ | |
4b638df4 BA |
113 | }; |
114 | #define AUX_BASE_MASK 0xfffffffc | |
115 | ||
116 | /** | |
117 | * struct smem_header - header found in beginning of primary smem region | |
118 | * @proc_comm: proc_comm communication interface (legacy) | |
119 | * @version: array of versions for the various subsystems | |
120 | * @initialized: boolean to indicate that smem is initialized | |
121 | * @free_offset: index of the first unallocated byte in smem | |
122 | * @available: number of bytes available for allocation | |
123 | * @reserved: reserved field, must be 0 | |
124 | * toc: array of references to items | |
125 | */ | |
126 | struct smem_header { | |
127 | struct smem_proc_comm proc_comm[4]; | |
9806884d SB |
128 | __le32 version[32]; |
129 | __le32 initialized; | |
130 | __le32 free_offset; | |
131 | __le32 available; | |
132 | __le32 reserved; | |
4b638df4 BA |
133 | struct smem_global_entry toc[SMEM_ITEM_COUNT]; |
134 | }; | |
135 | ||
136 | /** | |
137 | * struct smem_ptable_entry - one entry in the @smem_ptable list | |
138 | * @offset: offset, within the main shared memory region, of the partition | |
139 | * @size: size of the partition | |
140 | * @flags: flags for the partition (currently unused) | |
141 | * @host0: first processor/host with access to this partition | |
142 | * @host1: second processor/host with access to this partition | |
143 | * @reserved: reserved entries for later use | |
144 | */ | |
145 | struct smem_ptable_entry { | |
9806884d SB |
146 | __le32 offset; |
147 | __le32 size; | |
148 | __le32 flags; | |
149 | __le16 host0; | |
150 | __le16 host1; | |
151 | __le32 reserved[8]; | |
4b638df4 BA |
152 | }; |
153 | ||
154 | /** | |
155 | * struct smem_ptable - partition table for the private partitions | |
156 | * @magic: magic number, must be SMEM_PTABLE_MAGIC | |
157 | * @version: version of the partition table | |
158 | * @num_entries: number of partitions in the table | |
159 | * @reserved: for now reserved entries | |
160 | * @entry: list of @smem_ptable_entry for the @num_entries partitions | |
161 | */ | |
162 | struct smem_ptable { | |
9806884d SB |
163 | u8 magic[4]; |
164 | __le32 version; | |
165 | __le32 num_entries; | |
166 | __le32 reserved[5]; | |
4b638df4 BA |
167 | struct smem_ptable_entry entry[]; |
168 | }; | |
9806884d SB |
169 | |
170 | static const u8 SMEM_PTABLE_MAGIC[] = { 0x24, 0x54, 0x4f, 0x43 }; /* "$TOC" */ | |
4b638df4 BA |
171 | |
172 | /** | |
173 | * struct smem_partition_header - header of the partitions | |
174 | * @magic: magic number, must be SMEM_PART_MAGIC | |
175 | * @host0: first processor/host with access to this partition | |
176 | * @host1: second processor/host with access to this partition | |
177 | * @size: size of the partition | |
178 | * @offset_free_uncached: offset to the first free byte of uncached memory in | |
179 | * this partition | |
180 | * @offset_free_cached: offset to the first free byte of cached memory in this | |
181 | * partition | |
182 | * @reserved: for now reserved entries | |
183 | */ | |
184 | struct smem_partition_header { | |
9806884d SB |
185 | u8 magic[4]; |
186 | __le16 host0; | |
187 | __le16 host1; | |
188 | __le32 size; | |
189 | __le32 offset_free_uncached; | |
190 | __le32 offset_free_cached; | |
191 | __le32 reserved[3]; | |
4b638df4 | 192 | }; |
9806884d SB |
193 | |
194 | static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; | |
4b638df4 BA |
195 | |
196 | /** | |
197 | * struct smem_private_entry - header of each item in the private partition | |
198 | * @canary: magic number, must be SMEM_PRIVATE_CANARY | |
199 | * @item: identifying number of the smem item | |
200 | * @size: size of the data, including padding bytes | |
201 | * @padding_data: number of bytes of padding of data | |
202 | * @padding_hdr: number of bytes of padding between the header and the data | |
203 | * @reserved: for now reserved entry | |
204 | */ | |
205 | struct smem_private_entry { | |
9806884d SB |
206 | u16 canary; /* bytes are the same so no swapping needed */ |
207 | __le16 item; | |
208 | __le32 size; /* includes padding bytes */ | |
209 | __le16 padding_data; | |
210 | __le16 padding_hdr; | |
211 | __le32 reserved; | |
4b638df4 BA |
212 | }; |
213 | #define SMEM_PRIVATE_CANARY 0xa5a5 | |
214 | ||
215 | /** | |
216 | * struct smem_region - representation of a chunk of memory used for smem | |
217 | * @aux_base: identifier of aux_mem base | |
218 | * @virt_base: virtual base address of memory with this aux_mem identifier | |
219 | * @size: size of the memory region | |
220 | */ | |
221 | struct smem_region { | |
222 | u32 aux_base; | |
223 | void __iomem *virt_base; | |
224 | size_t size; | |
225 | }; | |
226 | ||
227 | /** | |
228 | * struct qcom_smem - device data for the smem device | |
229 | * @dev: device pointer | |
230 | * @hwlock: reference to a hwspinlock | |
231 | * @partitions: list of pointers to partitions affecting the current | |
232 | * processor/host | |
233 | * @num_regions: number of @regions | |
234 | * @regions: list of the memory regions defining the shared memory | |
235 | */ | |
236 | struct qcom_smem { | |
237 | struct device *dev; | |
238 | ||
239 | struct hwspinlock *hwlock; | |
240 | ||
241 | struct smem_partition_header *partitions[SMEM_HOST_COUNT]; | |
242 | ||
243 | unsigned num_regions; | |
244 | struct smem_region regions[0]; | |
245 | }; | |
246 | ||
9806884d SB |
247 | static struct smem_private_entry * |
248 | phdr_to_last_private_entry(struct smem_partition_header *phdr) | |
249 | { | |
250 | void *p = phdr; | |
251 | ||
252 | return p + le32_to_cpu(phdr->offset_free_uncached); | |
253 | } | |
254 | ||
255 | static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr) | |
256 | { | |
257 | void *p = phdr; | |
258 | ||
259 | return p + le32_to_cpu(phdr->offset_free_cached); | |
260 | } | |
261 | ||
262 | static struct smem_private_entry * | |
263 | phdr_to_first_private_entry(struct smem_partition_header *phdr) | |
264 | { | |
265 | void *p = phdr; | |
266 | ||
267 | return p + sizeof(*phdr); | |
268 | } | |
269 | ||
270 | static struct smem_private_entry * | |
271 | private_entry_next(struct smem_private_entry *e) | |
272 | { | |
273 | void *p = e; | |
274 | ||
275 | return p + sizeof(*e) + le16_to_cpu(e->padding_hdr) + | |
276 | le32_to_cpu(e->size); | |
277 | } | |
278 | ||
279 | static void *entry_to_item(struct smem_private_entry *e) | |
280 | { | |
281 | void *p = e; | |
282 | ||
283 | return p + sizeof(*e) + le16_to_cpu(e->padding_hdr); | |
284 | } | |
285 | ||
4b638df4 BA |
286 | /* Pointer to the one and only smem handle */ |
287 | static struct qcom_smem *__smem; | |
288 | ||
289 | /* Timeout (ms) for the trylock of remote spinlocks */ | |
290 | #define HWSPINLOCK_TIMEOUT 1000 | |
291 | ||
292 | static int qcom_smem_alloc_private(struct qcom_smem *smem, | |
293 | unsigned host, | |
294 | unsigned item, | |
295 | size_t size) | |
296 | { | |
297 | struct smem_partition_header *phdr; | |
9806884d | 298 | struct smem_private_entry *hdr, *end; |
4b638df4 | 299 | size_t alloc_size; |
9806884d | 300 | void *cached; |
4b638df4 | 301 | |
4b638df4 | 302 | phdr = smem->partitions[host]; |
9806884d SB |
303 | hdr = phdr_to_first_private_entry(phdr); |
304 | end = phdr_to_last_private_entry(phdr); | |
305 | cached = phdr_to_first_cached_entry(phdr); | |
4b638df4 | 306 | |
9806884d | 307 | while (hdr < end) { |
4b638df4 BA |
308 | if (hdr->canary != SMEM_PRIVATE_CANARY) { |
309 | dev_err(smem->dev, | |
310 | "Found invalid canary in host %d partition\n", | |
311 | host); | |
312 | return -EINVAL; | |
313 | } | |
314 | ||
9806884d | 315 | if (le16_to_cpu(hdr->item) == item) |
4b638df4 BA |
316 | return -EEXIST; |
317 | ||
9806884d | 318 | hdr = private_entry_next(hdr); |
4b638df4 BA |
319 | } |
320 | ||
321 | /* Check that we don't grow into the cached region */ | |
322 | alloc_size = sizeof(*hdr) + ALIGN(size, 8); | |
9806884d | 323 | if ((void *)hdr + alloc_size >= cached) { |
4b638df4 BA |
324 | dev_err(smem->dev, "Out of memory\n"); |
325 | return -ENOSPC; | |
326 | } | |
327 | ||
4b638df4 | 328 | hdr->canary = SMEM_PRIVATE_CANARY; |
9806884d SB |
329 | hdr->item = cpu_to_le16(item); |
330 | hdr->size = cpu_to_le32(ALIGN(size, 8)); | |
331 | hdr->padding_data = cpu_to_le16(le32_to_cpu(hdr->size) - size); | |
4b638df4 BA |
332 | hdr->padding_hdr = 0; |
333 | ||
334 | /* | |
335 | * Ensure the header is written before we advance the free offset, so | |
336 | * that remote processors that does not take the remote spinlock still | |
337 | * gets a consistent view of the linked list. | |
338 | */ | |
339 | wmb(); | |
9806884d | 340 | le32_add_cpu(&phdr->offset_free_uncached, alloc_size); |
4b638df4 BA |
341 | |
342 | return 0; | |
343 | } | |
344 | ||
345 | static int qcom_smem_alloc_global(struct qcom_smem *smem, | |
346 | unsigned item, | |
347 | size_t size) | |
348 | { | |
349 | struct smem_header *header; | |
350 | struct smem_global_entry *entry; | |
351 | ||
352 | if (WARN_ON(item >= SMEM_ITEM_COUNT)) | |
353 | return -EINVAL; | |
354 | ||
355 | header = smem->regions[0].virt_base; | |
356 | entry = &header->toc[item]; | |
357 | if (entry->allocated) | |
358 | return -EEXIST; | |
359 | ||
360 | size = ALIGN(size, 8); | |
9806884d | 361 | if (WARN_ON(size > le32_to_cpu(header->available))) |
4b638df4 BA |
362 | return -ENOMEM; |
363 | ||
364 | entry->offset = header->free_offset; | |
9806884d | 365 | entry->size = cpu_to_le32(size); |
4b638df4 BA |
366 | |
367 | /* | |
368 | * Ensure the header is consistent before we mark the item allocated, | |
369 | * so that remote processors will get a consistent view of the item | |
370 | * even though they do not take the spinlock on read. | |
371 | */ | |
372 | wmb(); | |
9806884d | 373 | entry->allocated = cpu_to_le32(1); |
4b638df4 | 374 | |
9806884d SB |
375 | le32_add_cpu(&header->free_offset, size); |
376 | le32_add_cpu(&header->available, -size); | |
4b638df4 BA |
377 | |
378 | return 0; | |
379 | } | |
380 | ||
381 | /** | |
382 | * qcom_smem_alloc() - allocate space for a smem item | |
383 | * @host: remote processor id, or -1 | |
384 | * @item: smem item handle | |
385 | * @size: number of bytes to be allocated | |
386 | * | |
387 | * Allocate space for a given smem item of size @size, given that the item is | |
388 | * not yet allocated. | |
389 | */ | |
390 | int qcom_smem_alloc(unsigned host, unsigned item, size_t size) | |
391 | { | |
392 | unsigned long flags; | |
393 | int ret; | |
394 | ||
395 | if (!__smem) | |
396 | return -EPROBE_DEFER; | |
397 | ||
398 | if (item < SMEM_ITEM_LAST_FIXED) { | |
399 | dev_err(__smem->dev, | |
400 | "Rejecting allocation of static entry %d\n", item); | |
401 | return -EINVAL; | |
402 | } | |
403 | ||
404 | ret = hwspin_lock_timeout_irqsave(__smem->hwlock, | |
405 | HWSPINLOCK_TIMEOUT, | |
406 | &flags); | |
407 | if (ret) | |
408 | return ret; | |
409 | ||
18912806 AG |
410 | if (host < SMEM_HOST_COUNT && __smem->partitions[host]) |
411 | ret = qcom_smem_alloc_private(__smem, host, item, size); | |
412 | else | |
4b638df4 BA |
413 | ret = qcom_smem_alloc_global(__smem, item, size); |
414 | ||
415 | hwspin_unlock_irqrestore(__smem->hwlock, &flags); | |
416 | ||
417 | return ret; | |
418 | } | |
419 | EXPORT_SYMBOL(qcom_smem_alloc); | |
420 | ||
1a03964d SB |
421 | static void *qcom_smem_get_global(struct qcom_smem *smem, |
422 | unsigned item, | |
423 | size_t *size) | |
4b638df4 BA |
424 | { |
425 | struct smem_header *header; | |
426 | struct smem_region *area; | |
427 | struct smem_global_entry *entry; | |
428 | u32 aux_base; | |
429 | unsigned i; | |
430 | ||
431 | if (WARN_ON(item >= SMEM_ITEM_COUNT)) | |
1a03964d | 432 | return ERR_PTR(-EINVAL); |
4b638df4 BA |
433 | |
434 | header = smem->regions[0].virt_base; | |
435 | entry = &header->toc[item]; | |
436 | if (!entry->allocated) | |
1a03964d | 437 | return ERR_PTR(-ENXIO); |
4b638df4 | 438 | |
9806884d | 439 | aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; |
4b638df4 | 440 | |
1a03964d SB |
441 | for (i = 0; i < smem->num_regions; i++) { |
442 | area = &smem->regions[i]; | |
4b638df4 | 443 | |
1a03964d SB |
444 | if (area->aux_base == aux_base || !aux_base) { |
445 | if (size != NULL) | |
9806884d SB |
446 | *size = le32_to_cpu(entry->size); |
447 | return area->virt_base + le32_to_cpu(entry->offset); | |
4b638df4 BA |
448 | } |
449 | } | |
4b638df4 | 450 | |
1a03964d | 451 | return ERR_PTR(-ENOENT); |
4b638df4 BA |
452 | } |
453 | ||
1a03964d SB |
454 | static void *qcom_smem_get_private(struct qcom_smem *smem, |
455 | unsigned host, | |
456 | unsigned item, | |
457 | size_t *size) | |
4b638df4 BA |
458 | { |
459 | struct smem_partition_header *phdr; | |
9806884d | 460 | struct smem_private_entry *e, *end; |
4b638df4 | 461 | |
4b638df4 | 462 | phdr = smem->partitions[host]; |
9806884d SB |
463 | e = phdr_to_first_private_entry(phdr); |
464 | end = phdr_to_last_private_entry(phdr); | |
4b638df4 | 465 | |
9806884d SB |
466 | while (e < end) { |
467 | if (e->canary != SMEM_PRIVATE_CANARY) { | |
4b638df4 BA |
468 | dev_err(smem->dev, |
469 | "Found invalid canary in host %d partition\n", | |
470 | host); | |
1a03964d | 471 | return ERR_PTR(-EINVAL); |
4b638df4 BA |
472 | } |
473 | ||
9806884d | 474 | if (le16_to_cpu(e->item) == item) { |
4b638df4 | 475 | if (size != NULL) |
9806884d SB |
476 | *size = le32_to_cpu(e->size) - |
477 | le16_to_cpu(e->padding_data); | |
4b638df4 | 478 | |
9806884d | 479 | return entry_to_item(e); |
4b638df4 BA |
480 | } |
481 | ||
9806884d | 482 | e = private_entry_next(e); |
4b638df4 BA |
483 | } |
484 | ||
1a03964d | 485 | return ERR_PTR(-ENOENT); |
4b638df4 BA |
486 | } |
487 | ||
488 | /** | |
489 | * qcom_smem_get() - resolve ptr of size of a smem item | |
490 | * @host: the remote processor, or -1 | |
491 | * @item: smem item handle | |
4b638df4 BA |
492 | * @size: pointer to be filled out with size of the item |
493 | * | |
1a03964d SB |
494 | * Looks up smem item and returns pointer to it. Size of smem |
495 | * item is returned in @size. | |
4b638df4 | 496 | */ |
1a03964d | 497 | void *qcom_smem_get(unsigned host, unsigned item, size_t *size) |
4b638df4 BA |
498 | { |
499 | unsigned long flags; | |
500 | int ret; | |
1a03964d | 501 | void *ptr = ERR_PTR(-EPROBE_DEFER); |
4b638df4 BA |
502 | |
503 | if (!__smem) | |
1a03964d | 504 | return ptr; |
4b638df4 BA |
505 | |
506 | ret = hwspin_lock_timeout_irqsave(__smem->hwlock, | |
507 | HWSPINLOCK_TIMEOUT, | |
508 | &flags); | |
509 | if (ret) | |
1a03964d | 510 | return ERR_PTR(ret); |
4b638df4 | 511 | |
18912806 | 512 | if (host < SMEM_HOST_COUNT && __smem->partitions[host]) |
1a03964d | 513 | ptr = qcom_smem_get_private(__smem, host, item, size); |
18912806 | 514 | else |
1a03964d | 515 | ptr = qcom_smem_get_global(__smem, item, size); |
4b638df4 BA |
516 | |
517 | hwspin_unlock_irqrestore(__smem->hwlock, &flags); | |
1a03964d SB |
518 | |
519 | return ptr; | |
4b638df4 BA |
520 | |
521 | } | |
522 | EXPORT_SYMBOL(qcom_smem_get); | |
523 | ||
524 | /** | |
525 | * qcom_smem_get_free_space() - retrieve amount of free space in a partition | |
526 | * @host: the remote processor identifying a partition, or -1 | |
527 | * | |
528 | * To be used by smem clients as a quick way to determine if any new | |
529 | * allocations has been made. | |
530 | */ | |
531 | int qcom_smem_get_free_space(unsigned host) | |
532 | { | |
533 | struct smem_partition_header *phdr; | |
534 | struct smem_header *header; | |
535 | unsigned ret; | |
536 | ||
537 | if (!__smem) | |
538 | return -EPROBE_DEFER; | |
539 | ||
540 | if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { | |
541 | phdr = __smem->partitions[host]; | |
9806884d SB |
542 | ret = le32_to_cpu(phdr->offset_free_cached) - |
543 | le32_to_cpu(phdr->offset_free_uncached); | |
4b638df4 BA |
544 | } else { |
545 | header = __smem->regions[0].virt_base; | |
9806884d | 546 | ret = le32_to_cpu(header->available); |
4b638df4 BA |
547 | } |
548 | ||
549 | return ret; | |
550 | } | |
551 | EXPORT_SYMBOL(qcom_smem_get_free_space); | |
552 | ||
553 | static int qcom_smem_get_sbl_version(struct qcom_smem *smem) | |
554 | { | |
9806884d | 555 | __le32 *versions; |
4b638df4 | 556 | size_t size; |
4b638df4 | 557 | |
1a03964d SB |
558 | versions = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, &size); |
559 | if (IS_ERR(versions)) { | |
4b638df4 BA |
560 | dev_err(smem->dev, "Unable to read the version item\n"); |
561 | return -ENOENT; | |
562 | } | |
563 | ||
564 | if (size < sizeof(unsigned) * SMEM_MASTER_SBL_VERSION_INDEX) { | |
565 | dev_err(smem->dev, "Version item is too small\n"); | |
566 | return -EINVAL; | |
567 | } | |
568 | ||
9806884d | 569 | return le32_to_cpu(versions[SMEM_MASTER_SBL_VERSION_INDEX]); |
4b638df4 BA |
570 | } |
571 | ||
572 | static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, | |
573 | unsigned local_host) | |
574 | { | |
575 | struct smem_partition_header *header; | |
576 | struct smem_ptable_entry *entry; | |
577 | struct smem_ptable *ptable; | |
578 | unsigned remote_host; | |
9806884d | 579 | u32 version, host0, host1; |
4b638df4 BA |
580 | int i; |
581 | ||
582 | ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; | |
9806884d | 583 | if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) |
4b638df4 BA |
584 | return 0; |
585 | ||
9806884d SB |
586 | version = le32_to_cpu(ptable->version); |
587 | if (version != 1) { | |
4b638df4 | 588 | dev_err(smem->dev, |
9806884d | 589 | "Unsupported partition header version %d\n", version); |
4b638df4 BA |
590 | return -EINVAL; |
591 | } | |
592 | ||
9806884d | 593 | for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { |
4b638df4 | 594 | entry = &ptable->entry[i]; |
9806884d SB |
595 | host0 = le16_to_cpu(entry->host0); |
596 | host1 = le16_to_cpu(entry->host1); | |
4b638df4 | 597 | |
9806884d | 598 | if (host0 != local_host && host1 != local_host) |
4b638df4 BA |
599 | continue; |
600 | ||
9806884d | 601 | if (!le32_to_cpu(entry->offset)) |
4b638df4 BA |
602 | continue; |
603 | ||
9806884d | 604 | if (!le32_to_cpu(entry->size)) |
4b638df4 BA |
605 | continue; |
606 | ||
9806884d SB |
607 | if (host0 == local_host) |
608 | remote_host = host1; | |
4b638df4 | 609 | else |
9806884d | 610 | remote_host = host0; |
4b638df4 BA |
611 | |
612 | if (remote_host >= SMEM_HOST_COUNT) { | |
613 | dev_err(smem->dev, | |
614 | "Invalid remote host %d\n", | |
615 | remote_host); | |
616 | return -EINVAL; | |
617 | } | |
618 | ||
619 | if (smem->partitions[remote_host]) { | |
620 | dev_err(smem->dev, | |
621 | "Already found a partition for host %d\n", | |
622 | remote_host); | |
623 | return -EINVAL; | |
624 | } | |
625 | ||
9806884d SB |
626 | header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); |
627 | host0 = le16_to_cpu(header->host0); | |
628 | host1 = le16_to_cpu(header->host1); | |
4b638df4 | 629 | |
9806884d SB |
630 | if (memcmp(header->magic, SMEM_PART_MAGIC, |
631 | sizeof(header->magic))) { | |
4b638df4 BA |
632 | dev_err(smem->dev, |
633 | "Partition %d has invalid magic\n", i); | |
634 | return -EINVAL; | |
635 | } | |
636 | ||
9806884d | 637 | if (host0 != local_host && host1 != local_host) { |
4b638df4 BA |
638 | dev_err(smem->dev, |
639 | "Partition %d hosts are invalid\n", i); | |
640 | return -EINVAL; | |
641 | } | |
642 | ||
9806884d | 643 | if (host0 != remote_host && host1 != remote_host) { |
4b638df4 BA |
644 | dev_err(smem->dev, |
645 | "Partition %d hosts are invalid\n", i); | |
646 | return -EINVAL; | |
647 | } | |
648 | ||
649 | if (header->size != entry->size) { | |
650 | dev_err(smem->dev, | |
651 | "Partition %d has invalid size\n", i); | |
652 | return -EINVAL; | |
653 | } | |
654 | ||
9806884d | 655 | if (le32_to_cpu(header->offset_free_uncached) > le32_to_cpu(header->size)) { |
4b638df4 BA |
656 | dev_err(smem->dev, |
657 | "Partition %d has invalid free pointer\n", i); | |
658 | return -EINVAL; | |
659 | } | |
660 | ||
661 | smem->partitions[remote_host] = header; | |
662 | } | |
663 | ||
664 | return 0; | |
665 | } | |
666 | ||
d0bfd7c9 SB |
667 | static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, |
668 | const char *name, int i) | |
4b638df4 | 669 | { |
d0bfd7c9 SB |
670 | struct device_node *np; |
671 | struct resource r; | |
672 | int ret; | |
4b638df4 | 673 | |
d0bfd7c9 SB |
674 | np = of_parse_phandle(dev->of_node, name, 0); |
675 | if (!np) { | |
676 | dev_err(dev, "No %s specified\n", name); | |
677 | return -EINVAL; | |
4b638df4 BA |
678 | } |
679 | ||
d0bfd7c9 SB |
680 | ret = of_address_to_resource(np, 0, &r); |
681 | of_node_put(np); | |
682 | if (ret) | |
683 | return ret; | |
684 | ||
685 | smem->regions[i].aux_base = (u32)r.start; | |
686 | smem->regions[i].size = resource_size(&r); | |
687 | smem->regions[i].virt_base = devm_ioremap_nocache(dev, r.start, | |
688 | resource_size(&r)); | |
689 | if (!smem->regions[i].virt_base) | |
690 | return -ENOMEM; | |
691 | ||
692 | return 0; | |
4b638df4 BA |
693 | } |
694 | ||
695 | static int qcom_smem_probe(struct platform_device *pdev) | |
696 | { | |
697 | struct smem_header *header; | |
4b638df4 | 698 | struct qcom_smem *smem; |
4b638df4 | 699 | size_t array_size; |
d0bfd7c9 | 700 | int num_regions; |
4b638df4 BA |
701 | int hwlock_id; |
702 | u32 version; | |
703 | int ret; | |
4b638df4 | 704 | |
d0bfd7c9 SB |
705 | num_regions = 1; |
706 | if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL)) | |
707 | num_regions++; | |
4b638df4 BA |
708 | |
709 | array_size = num_regions * sizeof(struct smem_region); | |
710 | smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); | |
711 | if (!smem) | |
712 | return -ENOMEM; | |
713 | ||
714 | smem->dev = &pdev->dev; | |
715 | smem->num_regions = num_regions; | |
716 | ||
d0bfd7c9 | 717 | ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); |
4b638df4 BA |
718 | if (ret) |
719 | return ret; | |
720 | ||
d0bfd7c9 SB |
721 | if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev, |
722 | "qcom,rpm-msg-ram", 1))) | |
723 | return ret; | |
4b638df4 BA |
724 | |
725 | header = smem->regions[0].virt_base; | |
9806884d SB |
726 | if (le32_to_cpu(header->initialized) != 1 || |
727 | le32_to_cpu(header->reserved)) { | |
4b638df4 BA |
728 | dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); |
729 | return -EINVAL; | |
730 | } | |
731 | ||
732 | version = qcom_smem_get_sbl_version(smem); | |
733 | if (version >> 16 != SMEM_EXPECTED_VERSION) { | |
734 | dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); | |
735 | return -EINVAL; | |
736 | } | |
737 | ||
738 | ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); | |
739 | if (ret < 0) | |
740 | return ret; | |
741 | ||
742 | hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); | |
743 | if (hwlock_id < 0) { | |
744 | dev_err(&pdev->dev, "failed to retrieve hwlock\n"); | |
745 | return hwlock_id; | |
746 | } | |
747 | ||
748 | smem->hwlock = hwspin_lock_request_specific(hwlock_id); | |
749 | if (!smem->hwlock) | |
750 | return -ENXIO; | |
751 | ||
752 | __smem = smem; | |
753 | ||
754 | return 0; | |
755 | } | |
756 | ||
757 | static int qcom_smem_remove(struct platform_device *pdev) | |
758 | { | |
4b638df4 | 759 | hwspin_lock_free(__smem->hwlock); |
f8c67df7 | 760 | __smem = NULL; |
4b638df4 BA |
761 | |
762 | return 0; | |
763 | } | |
764 | ||
765 | static const struct of_device_id qcom_smem_of_match[] = { | |
766 | { .compatible = "qcom,smem" }, | |
767 | {} | |
768 | }; | |
769 | MODULE_DEVICE_TABLE(of, qcom_smem_of_match); | |
770 | ||
771 | static struct platform_driver qcom_smem_driver = { | |
772 | .probe = qcom_smem_probe, | |
773 | .remove = qcom_smem_remove, | |
774 | .driver = { | |
775 | .name = "qcom-smem", | |
776 | .of_match_table = qcom_smem_of_match, | |
777 | .suppress_bind_attrs = true, | |
778 | }, | |
779 | }; | |
780 | ||
781 | static int __init qcom_smem_init(void) | |
782 | { | |
783 | return platform_driver_register(&qcom_smem_driver); | |
784 | } | |
785 | arch_initcall(qcom_smem_init); | |
786 | ||
787 | static void __exit qcom_smem_exit(void) | |
788 | { | |
789 | platform_driver_unregister(&qcom_smem_driver); | |
790 | } | |
791 | module_exit(qcom_smem_exit) | |
792 | ||
793 | MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); | |
794 | MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); | |
795 | MODULE_LICENSE("GPL v2"); |