1 Content-Type: text/plain; charset="utf-8"
3 Content-Transfer-Encoding: 7bit
4 Subject: [v2,2/2] soc: qcom: Add Shared Memory Manager driver
5 From: Bjorn Andersson <bjorn.andersson@sonymobile.com>
6 X-Patchwork-Id: 6202211
7 Message-Id: <1428795178-24312-2-git-send-email-bjorn.andersson@sonymobile.com>
8 To: Kumar Gala <galak@codeaurora.org>, Andy Gross <agross@codeaurora.org>,
9 David Brown <davidb@codeaurora.org>, Jeffrey Hugo <jhugo@codeaurora.org>
10 Cc: <linux-kernel@vger.kernel.org>, <linux-arm-msm@vger.kernel.org>,
11 <linux-soc@vger.kernel.org>
12 Date: Sat, 11 Apr 2015 16:32:58 -0700
14 The Shared Memory Manager driver implements an interface for allocating
15 and accessing items in the memory area shared among all of the
16 processors in a Qualcomm platform.
18 Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
19 Reviewed-by: Andy Gross <agross@codeaurora.org>
20 Tested-by: Andy Gross <agross@codeaurora.org>
24 - ioremapping the regions nocache
25 - improved documentation of the two regions of partitions
26 - corrected free space check in private allocator
28 drivers/soc/qcom/Kconfig | 7 +
29 drivers/soc/qcom/Makefile | 1 +
30 drivers/soc/qcom/smem.c | 768 ++++++++++++++++++++++++++++++++++++++++++
31 include/linux/soc/qcom/smem.h | 14 +
32 4 files changed, 790 insertions(+)
33 create mode 100644 drivers/soc/qcom/smem.c
34 create mode 100644 include/linux/soc/qcom/smem.h
36 --- a/drivers/soc/qcom/Kconfig
37 +++ b/drivers/soc/qcom/Kconfig
38 @@ -9,3 +9,10 @@ config QCOM_GSBI
39 functions for connecting the underlying serial UART, SPI, and I2C
40 devices to the output pins.
43 + tristate "Qualcomm Shared Memory Manager (SMEM)"
44 + depends on ARCH_QCOM
46 + Say y here to enable support for the Qualcomm Shared Memory Manager.
47 + The driver provides an interface to items in a heap shared among all
48 + processors in a Qualcomm platform.
49 --- a/drivers/soc/qcom/Makefile
50 +++ b/drivers/soc/qcom/Makefile
52 obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
53 +obj-$(CONFIG_QCOM_SMEM) += smem.o
55 +++ b/drivers/soc/qcom/smem.c
58 + * Copyright (c) 2015, Sony Mobile Communications AB.
59 + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
61 + * This program is free software; you can redistribute it and/or modify
62 + * it under the terms of the GNU General Public License version 2 and
63 + * only version 2 as published by the Free Software Foundation.
65 + * This program is distributed in the hope that it will be useful,
66 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
67 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
68 + * GNU General Public License for more details.
71 +#include <linux/hwspinlock.h>
72 +#include <linux/io.h>
73 +#include <linux/module.h>
74 +#include <linux/of.h>
75 +#include <linux/of_address.h>
76 +#include <linux/platform_device.h>
77 +#include <linux/slab.h>
78 +#include <linux/soc/qcom/smem.h>
81 + * The Qualcomm shared memory system is a allocate only heap structure that
82 + * consists of one of more memory areas that can be accessed by the processors
85 + * All systems contains a global heap, accessible by all processors in the SoC,
86 + * with a table of contents data structure (@smem_header) at the beginning of
87 + * the main shared memory block.
89 + * The global header contains metadata for allocations as well as a fixed list
90 + * of 512 entries (@smem_global_entry) that can be initialized to reference
91 + * parts of the shared memory space.
94 + * In addition to this global heap a set of "private" heaps can be set up at
95 + * boot time with access restrictions so that only certain processor pairs can
98 + * These partitions are referenced from an optional partition table
99 + * (@smem_ptable), that is found 4kB from the end of the main smem region. The
100 + * partition table entries (@smem_ptable_entry) lists the involved processors
101 + * (or hosts) and their location in the main shared memory region.
103 + * Each partition starts with a header (@smem_partition_header) that identifies
104 + * the partition and holds properties for the two internal memory regions. The
105 + * two regions are cached and non-cached memory respectively. Each region
106 + * contain a link list of allocation headers (@smem_private_entry) followed by
109 + * Items in the non-cached region are allocated from the start of the partition
110 + * while items in the cached region are allocated from the end. The free area
111 + * is hence the region between the cached and non-cached offsets.
114 + * To synchronize allocations in the shared memory heaps a remote spinlock must
115 + * be held - currently lock number 3 of the sfpb or tcsr is used for this on all
121 + * struct smem_proc_comm - proc_comm communication struct (legacy)
122 + * @command: current command to be executed
123 + * @status: status of the currently requested command
124 + * @params: parameters to the command
126 +struct smem_proc_comm {
133 + * struct smem_global_entry - entry to reference smem items on the heap
134 + * @allocated: boolean to indicate if this entry is used
135 + * @offset: offset to the allocated space
136 + * @size: size of the allocated space, 8 byte aligned
137 + * @aux_base: base address for the memory region used by this unit, or 0 for
138 + * the default region. bits 0,1 are reserved
140 +struct smem_global_entry {
144 + u32 aux_base; /* bits 1:0 reserved */
146 +#define AUX_BASE_MASK 0xfffffffc
149 + * struct smem_header - header found in beginning of primary smem region
150 + * @proc_comm: proc_comm communication interface (legacy)
151 + * @version: array of versions for the various subsystems
152 + * @initialized: boolean to indicate that smem is initialized
153 + * @free_offset: index of the first unallocated byte in smem
154 + * @available: number of bytes available for allocation
155 + * @reserved: reserved field, must be 0
156 + * toc: array of references to items
158 +struct smem_header {
159 + struct smem_proc_comm proc_comm[4];
165 + struct smem_global_entry toc[];
169 + * struct smem_ptable_entry - one entry in the @smem_ptable list
170 + * @offset: offset, within the main shared memory region, of the partition
171 + * @size: size of the partition
172 + * @flags: flags for the partition (currently unused)
173 + * @host0: first processor/host with access to this partition
174 + * @host1: second processor/host with access to this partition
175 + * @reserved: reserved entries for later use
177 +struct smem_ptable_entry {
187 + * struct smem_ptable - partition table for the private partitions
188 + * @magic: magic number, must be SMEM_PTABLE_MAGIC
189 + * @version: version of the partition table
190 + * @num_entries: number of partitions in the table
191 + * @reserved: for now reserved entries
192 + * @entry: list of @smem_ptable_entry for the @num_entries partitions
194 +struct smem_ptable {
199 + struct smem_ptable_entry entry[];
201 +#define SMEM_PTABLE_MAGIC 0x434f5424 /* "$TOC" */
204 + * struct smem_partition_header - header of the partitions
205 + * @magic: magic number, must be SMEM_PART_MAGIC
206 + * @host0: first processor/host with access to this partition
207 + * @host1: second processor/host with access to this partition
208 + * @size: size of the partition
209 + * @offset_free_uncached: offset to the first free byte of uncached memory in
211 + * @offset_free_cached: offset to the first free byte of cached memory in this
213 + * @reserved: for now reserved entries
215 +struct smem_partition_header {
220 + u32 offset_free_uncached;
221 + u32 offset_free_cached;
224 +#define SMEM_PART_MAGIC 0x54525024 /* "$PRT" */
227 + * struct smem_private_entry - header of each item in the private partition
228 + * @canary: magic number, must be SMEM_PRIVATE_CANARY
229 + * @item: identifying number of the smem item
230 + * @size: size of the data, including padding bytes
231 + * @padding_data: number of bytes of padding of data
232 + * @padding_hdr: number of bytes of padding between the header and the data
233 + * @reserved: for now reserved entry
235 +struct smem_private_entry {
238 + u32 size; /* includes padding bytes */
243 +#define SMEM_PRIVATE_CANARY 0xa5a5
246 + * Item 3 of the global heap contains an array of versions for the various
247 + * software components in the SoC. We verify that the boot loader version is
248 + * what the expected version (SMEM_EXPECTED_VERSION) as a sanity check.
250 +#define SMEM_ITEM_VERSION 3
251 +#define SMEM_MASTER_SBL_VERSION_INDEX 7
252 +#define SMEM_EXPECTED_VERSION 11
255 + * The first 8 items are only to be allocated by the boot loader while
256 + * initializing the heap.
258 +#define SMEM_ITEM_LAST_FIXED 8
260 +/* Highest accepted item number, for both global and private heaps */
261 +#define SMEM_ITEM_LAST 512
263 +/* Processor/host identifier for the application processor */
264 +#define SMEM_HOST_APPS 0
266 +/* Max number of processors/hosts in a system */
267 +#define SMEM_HOST_COUNT 7
270 + * struct smem_region - representation of a chunk of memory used for smem
271 + * @aux_base: identifier of aux_mem base
272 + * @virt_base: virtual base address of memory with this aux_mem identifier
273 + * @size: size of the memory region
275 +struct smem_region {
277 + void __iomem *virt_base;
282 + * struct qcom_smem - device data for the smem device
283 + * @dev: device pointer
284 + * @hwlock: reference to a hwspinlock
285 + * @partitions: list of pointers to partitions affecting the current
287 + * @num_regions: number of @regions
288 + * @regions: list of the memory regions defining the shared memory
291 + struct device *dev;
293 + struct hwspinlock *hwlock;
295 + struct smem_partition_header *partitions[SMEM_HOST_COUNT];
297 + unsigned num_regions;
298 + struct smem_region regions[0];
301 +/* Pointer to the one and only smem handle */
302 +static struct qcom_smem *__smem;
304 +/* Timeout (ms) for the trylock of remote spinlocks */
305 +#define HWSPINLOCK_TIMEOUT 1000
307 +static int qcom_smem_alloc_private(struct qcom_smem *smem,
312 + struct smem_partition_header *phdr;
313 + struct smem_private_entry *hdr;
317 + /* We're not going to find it if there's no matching partition */
318 + if (host >= SMEM_HOST_COUNT || !smem->partitions[host])
321 + phdr = smem->partitions[host];
323 + p = (void *)phdr + sizeof(*phdr);
324 + while (p < (void *)phdr + phdr->offset_free_uncached) {
327 + if (hdr->canary != SMEM_PRIVATE_CANARY) {
329 + "Found invalid canary in host %d partition\n",
334 + if (hdr->item == item)
337 + p += sizeof(*hdr) + hdr->padding_hdr + hdr->size;
340 + /* Check that we don't grow into the cached region */
341 + alloc_size = sizeof(*hdr) + ALIGN(size, 8);
342 + if (p + alloc_size >= (void *)phdr + phdr->offset_free_cached) {
343 + dev_err(smem->dev, "Out of memory\n");
348 + hdr->canary = SMEM_PRIVATE_CANARY;
350 + hdr->size = ALIGN(size, 8);
351 + hdr->padding_data = hdr->size - size;
352 + hdr->padding_hdr = 0;
355 + * Ensure the header is written before we advance the free offset, so
356 + * that remote processors that does not take the remote spinlock still
357 + * gets a consistent view of the linked list.
360 + phdr->offset_free_uncached += alloc_size;
365 +static int qcom_smem_alloc_global(struct qcom_smem *smem,
369 + struct smem_header *header;
370 + struct smem_global_entry *entry;
372 + if (WARN_ON(item >= SMEM_ITEM_LAST))
375 + header = smem->regions[0].virt_base;
376 + entry = &header->toc[item];
377 + if (entry->allocated)
380 + size = ALIGN(size, 8);
381 + if (WARN_ON(size > header->available))
384 + entry->offset = header->free_offset;
385 + entry->size = size;
388 + * Ensure the header is consistent before we mark the item allocated,
389 + * so that remote processors will get a consistent view of the item
390 + * even though they do not take the spinlock on read.
393 + entry->allocated = 1;
395 + header->free_offset += size;
396 + header->available -= size;
402 + * qcom_smem_alloc - allocate space for a smem item
403 + * @host: remote processor id, or -1
404 + * @item: smem item handle
405 + * @size: number of bytes to be allocated
407 + * Allocate space for a given smem item of size @size, given that the item is
408 + * not yet allocated.
410 +int qcom_smem_alloc(unsigned host, unsigned item, size_t size)
412 + unsigned long flags;
416 + return -EPROBE_DEFER;
418 + if (item < SMEM_ITEM_LAST_FIXED) {
419 + dev_err(__smem->dev,
420 + "Rejecting allocation of static entry %d\n", item);
424 + ret = hwspin_lock_timeout_irqsave(__smem->hwlock,
425 + HWSPINLOCK_TIMEOUT,
430 + ret = qcom_smem_alloc_private(__smem, host, item, size);
431 + if (ret == -ENOENT)
432 + ret = qcom_smem_alloc_global(__smem, item, size);
434 + hwspin_unlock_irqrestore(__smem->hwlock, &flags);
438 +EXPORT_SYMBOL(qcom_smem_alloc);
440 +static int qcom_smem_get_global(struct qcom_smem *smem,
445 + struct smem_header *header;
446 + struct smem_region *area;
447 + struct smem_global_entry *entry;
451 + if (WARN_ON(item >= SMEM_ITEM_LAST))
454 + header = smem->regions[0].virt_base;
455 + entry = &header->toc[item];
456 + if (!entry->allocated)
460 + aux_base = entry->aux_base & AUX_BASE_MASK;
462 + for (i = 0; i < smem->num_regions; i++) {
463 + area = &smem->regions[i];
465 + if (area->aux_base == aux_base || !aux_base) {
466 + *ptr = area->virt_base + entry->offset;
472 + *size = entry->size;
477 +static int qcom_smem_get_private(struct qcom_smem *smem,
483 + struct smem_partition_header *phdr;
484 + struct smem_private_entry *hdr;
487 + /* We're not going to find it if there's no matching partition */
488 + if (host >= SMEM_HOST_COUNT || !smem->partitions[host])
491 + phdr = smem->partitions[host];
493 + p = (void *)phdr + sizeof(*phdr);
494 + while (p < (void *)phdr + phdr->offset_free_uncached) {
497 + if (hdr->canary != SMEM_PRIVATE_CANARY) {
499 + "Found invalid canary in host %d partition\n",
504 + if (hdr->item == item) {
506 + *ptr = p + sizeof(*hdr) + hdr->padding_hdr;
509 + *size = hdr->size - hdr->padding_data;
514 + p += sizeof(*hdr) + hdr->padding_hdr + hdr->size;
521 + * qcom_smem_get - resolve ptr of size of a smem item
522 + * @host: the remote processor, or -1
523 + * @item: smem item handle
524 + * @ptr: pointer to be filled out with address of the item
525 + * @size: pointer to be filled out with size of the item
527 + * Looks up pointer and size of a smem item.
529 +int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size)
531 + unsigned long flags;
535 + return -EPROBE_DEFER;
537 + ret = hwspin_lock_timeout_irqsave(__smem->hwlock,
538 + HWSPINLOCK_TIMEOUT,
543 + ret = qcom_smem_get_private(__smem, host, item, ptr, size);
544 + if (ret == -ENOENT)
545 + ret = qcom_smem_get_global(__smem, item, ptr, size);
547 + hwspin_unlock_irqrestore(__smem->hwlock, &flags);
551 +EXPORT_SYMBOL(qcom_smem_get);
554 + * qcom_smem_get_free_space - retrieve amont of free space in a partition
555 + * @host: the remote processor identifing a partition, or -1
557 + * To be used by smem clients as a quick way to determine if any new
558 + * allocations has been made.
560 +int qcom_smem_get_free_space(unsigned host)
562 + struct smem_partition_header *phdr;
563 + struct smem_header *header;
567 + return -EPROBE_DEFER;
569 + if (host < SMEM_HOST_COUNT && __smem->partitions[host]) {
570 + phdr = __smem->partitions[host];
571 + ret = phdr->offset_free_uncached;
573 + header = __smem->regions[0].virt_base;
574 + ret = header->available;
579 +EXPORT_SYMBOL(qcom_smem_get_free_space);
581 +static int qcom_smem_get_sbl_version(struct qcom_smem *smem)
583 + unsigned *versions;
587 + ret = qcom_smem_get_global(smem, SMEM_ITEM_VERSION,
588 + (void **)&versions, &size);
590 + dev_err(smem->dev, "Unable to read the version item\n");
594 + if (size < sizeof(unsigned) * SMEM_MASTER_SBL_VERSION_INDEX) {
595 + dev_err(smem->dev, "Version item is too small\n");
599 + return versions[SMEM_MASTER_SBL_VERSION_INDEX];
602 +static int qcom_smem_enumerate_partitions(struct qcom_smem *smem,
603 + unsigned local_host)
605 + struct smem_partition_header *header;
606 + struct smem_ptable_entry *entry;
607 + struct smem_ptable *ptable;
608 + unsigned remote_host;
611 + ptable = smem->regions[0].virt_base + smem->regions[0].size - 4 * 1024;
612 + if (ptable->magic != SMEM_PTABLE_MAGIC)
615 + if (ptable->version != 1) {
617 + "Unsupported partition header version %d\n",
622 + for (i = 0; i < ptable->num_entries; i++) {
623 + entry = &ptable->entry[i];
625 + if (entry->host0 != local_host && entry->host1 != local_host)
628 + if (!entry->offset)
634 + if (entry->host0 == local_host)
635 + remote_host = entry->host1;
637 + remote_host = entry->host0;
639 + if (smem->partitions[remote_host]) {
641 + "Already found a partition for host %d\n",
646 + header = smem->regions[0].virt_base + entry->offset;
648 + if (header->magic != SMEM_PART_MAGIC) {
650 + "Partition %d has invalid magic\n", i);
654 + if (header->host0 != local_host && header->host1 != local_host) {
656 + "Partition %d hosts are invalid\n", i);
660 + if (header->host0 != remote_host && header->host1 != remote_host) {
662 + "Partition %d hosts are invalid\n", i);
666 + if (header->size != entry->size) {
668 + "Partition %d has invalid size\n", i);
672 + if (header->offset_free_uncached > header->size) {
674 + "Partition %d has invalid free pointer\n", i);
678 + smem->partitions[remote_host] = header;
684 +static int qcom_smem_count_mem_regions(struct platform_device *pdev)
686 + struct resource *res;
687 + int num_regions = 0;
690 + for (i = 0; i < pdev->num_resources; i++) {
691 + res = &pdev->resource[i];
693 + if (resource_type(res) == IORESOURCE_MEM)
697 + return num_regions;
700 +static int qcom_smem_probe(struct platform_device *pdev)
702 + struct smem_header *header;
703 + struct device_node *np;
704 + struct qcom_smem *smem;
705 + struct resource *res;
708 + int num_regions = 0;
714 + num_regions = qcom_smem_count_mem_regions(pdev) + 1;
716 + array_size = num_regions * sizeof(struct smem_region);
717 + smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL);
721 + smem->dev = &pdev->dev;
722 + smem->num_regions = num_regions;
724 + np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
726 + dev_err(&pdev->dev, "No memory-region specified\n");
730 + ret = of_address_to_resource(np, 0, &r);
735 + smem->regions[0].aux_base = (u32)r.start;
736 + smem->regions[0].size = resource_size(&r);
737 + smem->regions[0].virt_base = devm_ioremap_nocache(&pdev->dev,
739 + resource_size(&r));
740 + if (!smem->regions[0].virt_base)
743 + for (i = 1; i < num_regions; i++) {
744 + res = platform_get_resource(pdev, IORESOURCE_MEM, i - 1);
746 + smem->regions[i].aux_base = (u32)res->start;
747 + smem->regions[i].size = resource_size(res);
748 + smem->regions[i].virt_base = devm_ioremap_nocache(&pdev->dev,
750 + resource_size(res));
751 + if (!smem->regions[i].virt_base)
755 + header = smem->regions[0].virt_base;
756 + if (header->initialized != 1 || header->reserved) {
757 + dev_err(&pdev->dev, "SMEM is not initilized by SBL\n");
761 + version = qcom_smem_get_sbl_version(smem);
762 + if (version >> 16 != SMEM_EXPECTED_VERSION) {
763 + dev_err(&pdev->dev, "Unsupported smem version 0x%x\n", version);
767 + ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS);
771 + hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0);
772 + if (hwlock_id < 0) {
773 + dev_err(&pdev->dev, "failed to retrieve hwlock\n");
777 + smem->hwlock = hwspin_lock_request_specific(hwlock_id);
786 +static int qcom_smem_remove(struct platform_device *pdev)
788 + hwspin_lock_free(__smem->hwlock);
794 +static const struct of_device_id qcom_smem_of_match[] = {
795 + { .compatible = "qcom,smem" },
798 +MODULE_DEVICE_TABLE(of, qcom_smem_of_match);
800 +static struct platform_driver qcom_smem_driver = {
801 + .probe = qcom_smem_probe,
802 + .remove = qcom_smem_remove,
804 + .name = "qcom_smem",
805 + .of_match_table = qcom_smem_of_match,
806 + .suppress_bind_attrs = true,
810 +static int __init qcom_smem_init(void)
812 + return platform_driver_register(&qcom_smem_driver);
814 +arch_initcall(qcom_smem_init);
816 +static void __exit qcom_smem_exit(void)
818 + platform_driver_unregister(&qcom_smem_driver);
820 +module_exit(qcom_smem_exit)
822 +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
823 +MODULE_DESCRIPTION("Qualcomm Shared Memory Manager");
824 +MODULE_LICENSE("GPLv2");
826 +++ b/include/linux/soc/qcom/smem.h
828 +#ifndef __QCOM_SMEM_H__
829 +#define __QCOM_SMEM_H__
834 +#define QCOM_SMEM_HOST_ANY -1
836 +int qcom_smem_alloc(unsigned host, unsigned item, size_t size);
837 +int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size);
839 +int qcom_smem_get_free_space(unsigned host);