mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-26 17:01:14 +00:00
842 lines
23 KiB
Diff
842 lines
23 KiB
Diff
|
Content-Type: text/plain; charset="utf-8"
|
||
|
MIME-Version: 1.0
|
||
|
Content-Transfer-Encoding: 7bit
|
||
|
Subject: [v2,2/2] soc: qcom: Add Shared Memory Manager driver
|
||
|
From: Bjorn Andersson <bjorn.andersson@sonymobile.com>
|
||
|
X-Patchwork-Id: 6202211
|
||
|
Message-Id: <1428795178-24312-2-git-send-email-bjorn.andersson@sonymobile.com>
|
||
|
To: Kumar Gala <galak@codeaurora.org>, Andy Gross <agross@codeaurora.org>,
|
||
|
David Brown <davidb@codeaurora.org>, Jeffrey Hugo <jhugo@codeaurora.org>
|
||
|
Cc: <linux-kernel@vger.kernel.org>, <linux-arm-msm@vger.kernel.org>,
|
||
|
<linux-soc@vger.kernel.org>
|
||
|
Date: Sat, 11 Apr 2015 16:32:58 -0700
|
||
|
|
||
|
The Shared Memory Manager driver implements an interface for allocating
|
||
|
and accessing items in the memory area shared among all of the
|
||
|
processors in a Qualcomm platform.
|
||
|
|
||
|
Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
|
||
|
Reviewed-by: Andy Gross <agross@codeaurora.org>
|
||
|
Tested-by: Andy Gross <agross@codeaurora.org>
|
||
|
|
||
|
---
|
||
|
Changes since v1:
|
||
|
- ioremapping the regions nocache
|
||
|
- improved documentation of the two regions of partitions
|
||
|
- corrected free space check in private allocator
|
||
|
|
||
|
drivers/soc/qcom/Kconfig | 7 +
|
||
|
drivers/soc/qcom/Makefile | 1 +
|
||
|
drivers/soc/qcom/smem.c | 768 ++++++++++++++++++++++++++++++++++++++++++
|
||
|
include/linux/soc/qcom/smem.h | 14 +
|
||
|
4 files changed, 790 insertions(+)
|
||
|
create mode 100644 drivers/soc/qcom/smem.c
|
||
|
create mode 100644 include/linux/soc/qcom/smem.h
|
||
|
|
||
|
--- a/drivers/soc/qcom/Kconfig
|
||
|
+++ b/drivers/soc/qcom/Kconfig
|
||
|
@@ -9,3 +9,10 @@ config QCOM_GSBI
|
||
|
functions for connecting the underlying serial UART, SPI, and I2C
|
||
|
devices to the output pins.
|
||
|
|
||
|
+config QCOM_SMEM
|
||
|
+ tristate "Qualcomm Shared Memory Manager (SMEM)"
|
||
|
+ depends on ARCH_QCOM
|
||
|
+ help
|
||
|
+ Say y here to enable support for the Qualcomm Shared Memory Manager.
|
||
|
+ The driver provides an interface to items in a heap shared among all
|
||
|
+ processors in a Qualcomm platform.
|
||
|
--- a/drivers/soc/qcom/Makefile
|
||
|
+++ b/drivers/soc/qcom/Makefile
|
||
|
@@ -1 +1,2 @@
|
||
|
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
|
||
|
+obj-$(CONFIG_QCOM_SMEM) += smem.o
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/soc/qcom/smem.c
|
||
|
@@ -0,0 +1,768 @@
|
||
|
+/*
|
||
|
+ * Copyright (c) 2015, Sony Mobile Communications AB.
|
||
|
+ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
|
||
|
+ *
|
||
|
+ * This program is free software; you can redistribute it and/or modify
|
||
|
+ * it under the terms of the GNU General Public License version 2 and
|
||
|
+ * only version 2 as published by the Free Software Foundation.
|
||
|
+ *
|
||
|
+ * This program is distributed in the hope that it will be useful,
|
||
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
+ * GNU General Public License for more details.
|
||
|
+ */
|
||
|
+
|
||
|
+#include <linux/hwspinlock.h>
|
||
|
+#include <linux/io.h>
|
||
|
+#include <linux/module.h>
|
||
|
+#include <linux/of.h>
|
||
|
+#include <linux/of_address.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+#include <linux/slab.h>
|
||
|
+#include <linux/soc/qcom/smem.h>
|
||
|
+
|
||
|
+/*
|
||
|
+ * The Qualcomm shared memory system is a allocate only heap structure that
|
||
|
+ * consists of one of more memory areas that can be accessed by the processors
|
||
|
+ * in the SoC.
|
||
|
+ *
|
||
|
+ * All systems contains a global heap, accessible by all processors in the SoC,
|
||
|
+ * with a table of contents data structure (@smem_header) at the beginning of
|
||
|
+ * the main shared memory block.
|
||
|
+ *
|
||
|
+ * The global header contains metadata for allocations as well as a fixed list
|
||
|
+ * of 512 entries (@smem_global_entry) that can be initialized to reference
|
||
|
+ * parts of the shared memory space.
|
||
|
+ *
|
||
|
+ *
|
||
|
+ * In addition to this global heap a set of "private" heaps can be set up at
|
||
|
+ * boot time with access restrictions so that only certain processor pairs can
|
||
|
+ * access the data.
|
||
|
+ *
|
||
|
+ * These partitions are referenced from an optional partition table
|
||
|
+ * (@smem_ptable), that is found 4kB from the end of the main smem region. The
|
||
|
+ * partition table entries (@smem_ptable_entry) lists the involved processors
|
||
|
+ * (or hosts) and their location in the main shared memory region.
|
||
|
+ *
|
||
|
+ * Each partition starts with a header (@smem_partition_header) that identifies
|
||
|
+ * the partition and holds properties for the two internal memory regions. The
|
||
|
+ * two regions are cached and non-cached memory respectively. Each region
|
||
|
+ * contain a link list of allocation headers (@smem_private_entry) followed by
|
||
|
+ * their data.
|
||
|
+ *
|
||
|
+ * Items in the non-cached region are allocated from the start of the partition
|
||
|
+ * while items in the cached region are allocated from the end. The free area
|
||
|
+ * is hence the region between the cached and non-cached offsets.
|
||
|
+ *
|
||
|
+ *
|
||
|
+ * To synchronize allocations in the shared memory heaps a remote spinlock must
|
||
|
+ * be held - currently lock number 3 of the sfpb or tcsr is used for this on all
|
||
|
+ * platforms.
|
||
|
+ *
|
||
|
+ */
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_proc_comm - proc_comm communication struct (legacy)
|
||
|
+ * @command: current command to be executed
|
||
|
+ * @status: status of the currently requested command
|
||
|
+ * @params: parameters to the command
|
||
|
+ */
|
||
|
+struct smem_proc_comm {
|
||
|
+ u32 command;
|
||
|
+ u32 status;
|
||
|
+ u32 params[2];
|
||
|
+};
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_global_entry - entry to reference smem items on the heap
|
||
|
+ * @allocated: boolean to indicate if this entry is used
|
||
|
+ * @offset: offset to the allocated space
|
||
|
+ * @size: size of the allocated space, 8 byte aligned
|
||
|
+ * @aux_base: base address for the memory region used by this unit, or 0 for
|
||
|
+ * the default region. bits 0,1 are reserved
|
||
|
+ */
|
||
|
+struct smem_global_entry {
|
||
|
+ u32 allocated;
|
||
|
+ u32 offset;
|
||
|
+ u32 size;
|
||
|
+ u32 aux_base; /* bits 1:0 reserved */
|
||
|
+};
|
||
|
+#define AUX_BASE_MASK 0xfffffffc
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_header - header found in beginning of primary smem region
|
||
|
+ * @proc_comm: proc_comm communication interface (legacy)
|
||
|
+ * @version: array of versions for the various subsystems
|
||
|
+ * @initialized: boolean to indicate that smem is initialized
|
||
|
+ * @free_offset: index of the first unallocated byte in smem
|
||
|
+ * @available: number of bytes available for allocation
|
||
|
+ * @reserved: reserved field, must be 0
|
||
|
+ * toc: array of references to items
|
||
|
+ */
|
||
|
+struct smem_header {
|
||
|
+ struct smem_proc_comm proc_comm[4];
|
||
|
+ u32 version[32];
|
||
|
+ u32 initialized;
|
||
|
+ u32 free_offset;
|
||
|
+ u32 available;
|
||
|
+ u32 reserved;
|
||
|
+ struct smem_global_entry toc[];
|
||
|
+};
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_ptable_entry - one entry in the @smem_ptable list
|
||
|
+ * @offset: offset, within the main shared memory region, of the partition
|
||
|
+ * @size: size of the partition
|
||
|
+ * @flags: flags for the partition (currently unused)
|
||
|
+ * @host0: first processor/host with access to this partition
|
||
|
+ * @host1: second processor/host with access to this partition
|
||
|
+ * @reserved: reserved entries for later use
|
||
|
+ */
|
||
|
+struct smem_ptable_entry {
|
||
|
+ u32 offset;
|
||
|
+ u32 size;
|
||
|
+ u32 flags;
|
||
|
+ u16 host0;
|
||
|
+ u16 host1;
|
||
|
+ u32 reserved[8];
|
||
|
+};
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_ptable - partition table for the private partitions
|
||
|
+ * @magic: magic number, must be SMEM_PTABLE_MAGIC
|
||
|
+ * @version: version of the partition table
|
||
|
+ * @num_entries: number of partitions in the table
|
||
|
+ * @reserved: for now reserved entries
|
||
|
+ * @entry: list of @smem_ptable_entry for the @num_entries partitions
|
||
|
+ */
|
||
|
+struct smem_ptable {
|
||
|
+ u32 magic;
|
||
|
+ u32 version;
|
||
|
+ u32 num_entries;
|
||
|
+ u32 reserved[5];
|
||
|
+ struct smem_ptable_entry entry[];
|
||
|
+};
|
||
|
+#define SMEM_PTABLE_MAGIC 0x434f5424 /* "$TOC" */
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_partition_header - header of the partitions
|
||
|
+ * @magic: magic number, must be SMEM_PART_MAGIC
|
||
|
+ * @host0: first processor/host with access to this partition
|
||
|
+ * @host1: second processor/host with access to this partition
|
||
|
+ * @size: size of the partition
|
||
|
+ * @offset_free_uncached: offset to the first free byte of uncached memory in
|
||
|
+ * this partition
|
||
|
+ * @offset_free_cached: offset to the first free byte of cached memory in this
|
||
|
+ * partition
|
||
|
+ * @reserved: for now reserved entries
|
||
|
+ */
|
||
|
+struct smem_partition_header {
|
||
|
+ u32 magic;
|
||
|
+ u16 host0;
|
||
|
+ u16 host1;
|
||
|
+ u32 size;
|
||
|
+ u32 offset_free_uncached;
|
||
|
+ u32 offset_free_cached;
|
||
|
+ u32 reserved[3];
|
||
|
+};
|
||
|
+#define SMEM_PART_MAGIC 0x54525024 /* "$PRT" */
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_private_entry - header of each item in the private partition
|
||
|
+ * @canary: magic number, must be SMEM_PRIVATE_CANARY
|
||
|
+ * @item: identifying number of the smem item
|
||
|
+ * @size: size of the data, including padding bytes
|
||
|
+ * @padding_data: number of bytes of padding of data
|
||
|
+ * @padding_hdr: number of bytes of padding between the header and the data
|
||
|
+ * @reserved: for now reserved entry
|
||
|
+ */
|
||
|
+struct smem_private_entry {
|
||
|
+ u16 canary;
|
||
|
+ u16 item;
|
||
|
+ u32 size; /* includes padding bytes */
|
||
|
+ u16 padding_data;
|
||
|
+ u16 padding_hdr;
|
||
|
+ u32 reserved;
|
||
|
+};
|
||
|
+#define SMEM_PRIVATE_CANARY 0xa5a5
|
||
|
+
|
||
|
+/*
|
||
|
+ * Item 3 of the global heap contains an array of versions for the various
|
||
|
+ * software components in the SoC. We verify that the boot loader version is
|
||
|
+ * what the expected version (SMEM_EXPECTED_VERSION) as a sanity check.
|
||
|
+ */
|
||
|
+#define SMEM_ITEM_VERSION 3
|
||
|
+#define SMEM_MASTER_SBL_VERSION_INDEX 7
|
||
|
+#define SMEM_EXPECTED_VERSION 11
|
||
|
+
|
||
|
+/*
|
||
|
+ * The first 8 items are only to be allocated by the boot loader while
|
||
|
+ * initializing the heap.
|
||
|
+ */
|
||
|
+#define SMEM_ITEM_LAST_FIXED 8
|
||
|
+
|
||
|
+/* Highest accepted item number, for both global and private heaps */
|
||
|
+#define SMEM_ITEM_LAST 512
|
||
|
+
|
||
|
+/* Processor/host identifier for the application processor */
|
||
|
+#define SMEM_HOST_APPS 0
|
||
|
+
|
||
|
+/* Max number of processors/hosts in a system */
|
||
|
+#define SMEM_HOST_COUNT 7
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct smem_region - representation of a chunk of memory used for smem
|
||
|
+ * @aux_base: identifier of aux_mem base
|
||
|
+ * @virt_base: virtual base address of memory with this aux_mem identifier
|
||
|
+ * @size: size of the memory region
|
||
|
+ */
|
||
|
+struct smem_region {
|
||
|
+ u32 aux_base;
|
||
|
+ void __iomem *virt_base;
|
||
|
+ size_t size;
|
||
|
+};
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct qcom_smem - device data for the smem device
|
||
|
+ * @dev: device pointer
|
||
|
+ * @hwlock: reference to a hwspinlock
|
||
|
+ * @partitions: list of pointers to partitions affecting the current
|
||
|
+ * processor/host
|
||
|
+ * @num_regions: number of @regions
|
||
|
+ * @regions: list of the memory regions defining the shared memory
|
||
|
+ */
|
||
|
+struct qcom_smem {
|
||
|
+ struct device *dev;
|
||
|
+
|
||
|
+ struct hwspinlock *hwlock;
|
||
|
+
|
||
|
+ struct smem_partition_header *partitions[SMEM_HOST_COUNT];
|
||
|
+
|
||
|
+ unsigned num_regions;
|
||
|
+ struct smem_region regions[0];
|
||
|
+};
|
||
|
+
|
||
|
+/* Pointer to the one and only smem handle */
|
||
|
+static struct qcom_smem *__smem;
|
||
|
+
|
||
|
+/* Timeout (ms) for the trylock of remote spinlocks */
|
||
|
+#define HWSPINLOCK_TIMEOUT 1000
|
||
|
+
|
||
|
+static int qcom_smem_alloc_private(struct qcom_smem *smem,
|
||
|
+ unsigned host,
|
||
|
+ unsigned item,
|
||
|
+ size_t size)
|
||
|
+{
|
||
|
+ struct smem_partition_header *phdr;
|
||
|
+ struct smem_private_entry *hdr;
|
||
|
+ size_t alloc_size;
|
||
|
+ void *p;
|
||
|
+
|
||
|
+ /* We're not going to find it if there's no matching partition */
|
||
|
+ if (host >= SMEM_HOST_COUNT || !smem->partitions[host])
|
||
|
+ return -ENOENT;
|
||
|
+
|
||
|
+ phdr = smem->partitions[host];
|
||
|
+
|
||
|
+ p = (void *)phdr + sizeof(*phdr);
|
||
|
+ while (p < (void *)phdr + phdr->offset_free_uncached) {
|
||
|
+ hdr = p;
|
||
|
+
|
||
|
+ if (hdr->canary != SMEM_PRIVATE_CANARY) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Found invalid canary in host %d partition\n",
|
||
|
+ host);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (hdr->item == item)
|
||
|
+ return -EEXIST;
|
||
|
+
|
||
|
+ p += sizeof(*hdr) + hdr->padding_hdr + hdr->size;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Check that we don't grow into the cached region */
|
||
|
+ alloc_size = sizeof(*hdr) + ALIGN(size, 8);
|
||
|
+ if (p + alloc_size >= (void *)phdr + phdr->offset_free_cached) {
|
||
|
+ dev_err(smem->dev, "Out of memory\n");
|
||
|
+ return -ENOSPC;
|
||
|
+ }
|
||
|
+
|
||
|
+ hdr = p;
|
||
|
+ hdr->canary = SMEM_PRIVATE_CANARY;
|
||
|
+ hdr->item = item;
|
||
|
+ hdr->size = ALIGN(size, 8);
|
||
|
+ hdr->padding_data = hdr->size - size;
|
||
|
+ hdr->padding_hdr = 0;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Ensure the header is written before we advance the free offset, so
|
||
|
+ * that remote processors that does not take the remote spinlock still
|
||
|
+ * gets a consistent view of the linked list.
|
||
|
+ */
|
||
|
+ wmb();
|
||
|
+ phdr->offset_free_uncached += alloc_size;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int qcom_smem_alloc_global(struct qcom_smem *smem,
|
||
|
+ unsigned item,
|
||
|
+ size_t size)
|
||
|
+{
|
||
|
+ struct smem_header *header;
|
||
|
+ struct smem_global_entry *entry;
|
||
|
+
|
||
|
+ if (WARN_ON(item >= SMEM_ITEM_LAST))
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ header = smem->regions[0].virt_base;
|
||
|
+ entry = &header->toc[item];
|
||
|
+ if (entry->allocated)
|
||
|
+ return -EEXIST;
|
||
|
+
|
||
|
+ size = ALIGN(size, 8);
|
||
|
+ if (WARN_ON(size > header->available))
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ entry->offset = header->free_offset;
|
||
|
+ entry->size = size;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Ensure the header is consistent before we mark the item allocated,
|
||
|
+ * so that remote processors will get a consistent view of the item
|
||
|
+ * even though they do not take the spinlock on read.
|
||
|
+ */
|
||
|
+ wmb();
|
||
|
+ entry->allocated = 1;
|
||
|
+
|
||
|
+ header->free_offset += size;
|
||
|
+ header->available -= size;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * qcom_smem_alloc - allocate space for a smem item
|
||
|
+ * @host: remote processor id, or -1
|
||
|
+ * @item: smem item handle
|
||
|
+ * @size: number of bytes to be allocated
|
||
|
+ *
|
||
|
+ * Allocate space for a given smem item of size @size, given that the item is
|
||
|
+ * not yet allocated.
|
||
|
+ */
|
||
|
+int qcom_smem_alloc(unsigned host, unsigned item, size_t size)
|
||
|
+{
|
||
|
+ unsigned long flags;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (!__smem)
|
||
|
+ return -EPROBE_DEFER;
|
||
|
+
|
||
|
+ if (item < SMEM_ITEM_LAST_FIXED) {
|
||
|
+ dev_err(__smem->dev,
|
||
|
+ "Rejecting allocation of static entry %d\n", item);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = hwspin_lock_timeout_irqsave(__smem->hwlock,
|
||
|
+ HWSPINLOCK_TIMEOUT,
|
||
|
+ &flags);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = qcom_smem_alloc_private(__smem, host, item, size);
|
||
|
+ if (ret == -ENOENT)
|
||
|
+ ret = qcom_smem_alloc_global(__smem, item, size);
|
||
|
+
|
||
|
+ hwspin_unlock_irqrestore(__smem->hwlock, &flags);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(qcom_smem_alloc);
|
||
|
+
|
||
|
+static int qcom_smem_get_global(struct qcom_smem *smem,
|
||
|
+ unsigned item,
|
||
|
+ void **ptr,
|
||
|
+ size_t *size)
|
||
|
+{
|
||
|
+ struct smem_header *header;
|
||
|
+ struct smem_region *area;
|
||
|
+ struct smem_global_entry *entry;
|
||
|
+ u32 aux_base;
|
||
|
+ unsigned i;
|
||
|
+
|
||
|
+ if (WARN_ON(item >= SMEM_ITEM_LAST))
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ header = smem->regions[0].virt_base;
|
||
|
+ entry = &header->toc[item];
|
||
|
+ if (!entry->allocated)
|
||
|
+ return -ENXIO;
|
||
|
+
|
||
|
+ if (ptr != NULL) {
|
||
|
+ aux_base = entry->aux_base & AUX_BASE_MASK;
|
||
|
+
|
||
|
+ for (i = 0; i < smem->num_regions; i++) {
|
||
|
+ area = &smem->regions[i];
|
||
|
+
|
||
|
+ if (area->aux_base == aux_base || !aux_base) {
|
||
|
+ *ptr = area->virt_base + entry->offset;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+ if (size != NULL)
|
||
|
+ *size = entry->size;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int qcom_smem_get_private(struct qcom_smem *smem,
|
||
|
+ unsigned host,
|
||
|
+ unsigned item,
|
||
|
+ void **ptr,
|
||
|
+ size_t *size)
|
||
|
+{
|
||
|
+ struct smem_partition_header *phdr;
|
||
|
+ struct smem_private_entry *hdr;
|
||
|
+ void *p;
|
||
|
+
|
||
|
+ /* We're not going to find it if there's no matching partition */
|
||
|
+ if (host >= SMEM_HOST_COUNT || !smem->partitions[host])
|
||
|
+ return -ENOENT;
|
||
|
+
|
||
|
+ phdr = smem->partitions[host];
|
||
|
+
|
||
|
+ p = (void *)phdr + sizeof(*phdr);
|
||
|
+ while (p < (void *)phdr + phdr->offset_free_uncached) {
|
||
|
+ hdr = p;
|
||
|
+
|
||
|
+ if (hdr->canary != SMEM_PRIVATE_CANARY) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Found invalid canary in host %d partition\n",
|
||
|
+ host);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (hdr->item == item) {
|
||
|
+ if (ptr != NULL)
|
||
|
+ *ptr = p + sizeof(*hdr) + hdr->padding_hdr;
|
||
|
+
|
||
|
+ if (size != NULL)
|
||
|
+ *size = hdr->size - hdr->padding_data;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ p += sizeof(*hdr) + hdr->padding_hdr + hdr->size;
|
||
|
+ }
|
||
|
+
|
||
|
+ return -ENOENT;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * qcom_smem_get - resolve ptr of size of a smem item
|
||
|
+ * @host: the remote processor, or -1
|
||
|
+ * @item: smem item handle
|
||
|
+ * @ptr: pointer to be filled out with address of the item
|
||
|
+ * @size: pointer to be filled out with size of the item
|
||
|
+ *
|
||
|
+ * Looks up pointer and size of a smem item.
|
||
|
+ */
|
||
|
+int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size)
|
||
|
+{
|
||
|
+ unsigned long flags;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (!__smem)
|
||
|
+ return -EPROBE_DEFER;
|
||
|
+
|
||
|
+ ret = hwspin_lock_timeout_irqsave(__smem->hwlock,
|
||
|
+ HWSPINLOCK_TIMEOUT,
|
||
|
+ &flags);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = qcom_smem_get_private(__smem, host, item, ptr, size);
|
||
|
+ if (ret == -ENOENT)
|
||
|
+ ret = qcom_smem_get_global(__smem, item, ptr, size);
|
||
|
+
|
||
|
+ hwspin_unlock_irqrestore(__smem->hwlock, &flags);
|
||
|
+ return ret;
|
||
|
+
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(qcom_smem_get);
|
||
|
+
|
||
|
+/**
|
||
|
+ * qcom_smem_get_free_space - retrieve amont of free space in a partition
|
||
|
+ * @host: the remote processor identifing a partition, or -1
|
||
|
+ *
|
||
|
+ * To be used by smem clients as a quick way to determine if any new
|
||
|
+ * allocations has been made.
|
||
|
+ */
|
||
|
+int qcom_smem_get_free_space(unsigned host)
|
||
|
+{
|
||
|
+ struct smem_partition_header *phdr;
|
||
|
+ struct smem_header *header;
|
||
|
+ unsigned ret;
|
||
|
+
|
||
|
+ if (!__smem)
|
||
|
+ return -EPROBE_DEFER;
|
||
|
+
|
||
|
+ if (host < SMEM_HOST_COUNT && __smem->partitions[host]) {
|
||
|
+ phdr = __smem->partitions[host];
|
||
|
+ ret = phdr->offset_free_uncached;
|
||
|
+ } else {
|
||
|
+ header = __smem->regions[0].virt_base;
|
||
|
+ ret = header->available;
|
||
|
+ }
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(qcom_smem_get_free_space);
|
||
|
+
|
||
|
+static int qcom_smem_get_sbl_version(struct qcom_smem *smem)
|
||
|
+{
|
||
|
+ unsigned *versions;
|
||
|
+ size_t size;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = qcom_smem_get_global(smem, SMEM_ITEM_VERSION,
|
||
|
+ (void **)&versions, &size);
|
||
|
+ if (ret < 0) {
|
||
|
+ dev_err(smem->dev, "Unable to read the version item\n");
|
||
|
+ return -ENOENT;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (size < sizeof(unsigned) * SMEM_MASTER_SBL_VERSION_INDEX) {
|
||
|
+ dev_err(smem->dev, "Version item is too small\n");
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ return versions[SMEM_MASTER_SBL_VERSION_INDEX];
|
||
|
+}
|
||
|
+
|
||
|
+static int qcom_smem_enumerate_partitions(struct qcom_smem *smem,
|
||
|
+ unsigned local_host)
|
||
|
+{
|
||
|
+ struct smem_partition_header *header;
|
||
|
+ struct smem_ptable_entry *entry;
|
||
|
+ struct smem_ptable *ptable;
|
||
|
+ unsigned remote_host;
|
||
|
+ int i;
|
||
|
+
|
||
|
+ ptable = smem->regions[0].virt_base + smem->regions[0].size - 4 * 1024;
|
||
|
+ if (ptable->magic != SMEM_PTABLE_MAGIC)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ if (ptable->version != 1) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Unsupported partition header version %d\n",
|
||
|
+ ptable->version);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ for (i = 0; i < ptable->num_entries; i++) {
|
||
|
+ entry = &ptable->entry[i];
|
||
|
+
|
||
|
+ if (entry->host0 != local_host && entry->host1 != local_host)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ if (!entry->offset)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ if (!entry->size)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ if (entry->host0 == local_host)
|
||
|
+ remote_host = entry->host1;
|
||
|
+ else
|
||
|
+ remote_host = entry->host0;
|
||
|
+
|
||
|
+ if (smem->partitions[remote_host]) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Already found a partition for host %d\n",
|
||
|
+ remote_host);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ header = smem->regions[0].virt_base + entry->offset;
|
||
|
+
|
||
|
+ if (header->magic != SMEM_PART_MAGIC) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Partition %d has invalid magic\n", i);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (header->host0 != local_host && header->host1 != local_host) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Partition %d hosts are invalid\n", i);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (header->host0 != remote_host && header->host1 != remote_host) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Partition %d hosts are invalid\n", i);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (header->size != entry->size) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Partition %d has invalid size\n", i);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (header->offset_free_uncached > header->size) {
|
||
|
+ dev_err(smem->dev,
|
||
|
+ "Partition %d has invalid free pointer\n", i);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ smem->partitions[remote_host] = header;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int qcom_smem_count_mem_regions(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct resource *res;
|
||
|
+ int num_regions = 0;
|
||
|
+ int i;
|
||
|
+
|
||
|
+ for (i = 0; i < pdev->num_resources; i++) {
|
||
|
+ res = &pdev->resource[i];
|
||
|
+
|
||
|
+ if (resource_type(res) == IORESOURCE_MEM)
|
||
|
+ num_regions++;
|
||
|
+ }
|
||
|
+
|
||
|
+ return num_regions;
|
||
|
+}
|
||
|
+
|
||
|
+static int qcom_smem_probe(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct smem_header *header;
|
||
|
+ struct device_node *np;
|
||
|
+ struct qcom_smem *smem;
|
||
|
+ struct resource *res;
|
||
|
+ struct resource r;
|
||
|
+ size_t array_size;
|
||
|
+ int num_regions = 0;
|
||
|
+ int hwlock_id;
|
||
|
+ u32 version;
|
||
|
+ int ret;
|
||
|
+ int i;
|
||
|
+
|
||
|
+ num_regions = qcom_smem_count_mem_regions(pdev) + 1;
|
||
|
+
|
||
|
+ array_size = num_regions * sizeof(struct smem_region);
|
||
|
+ smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL);
|
||
|
+ if (!smem)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ smem->dev = &pdev->dev;
|
||
|
+ smem->num_regions = num_regions;
|
||
|
+
|
||
|
+ np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
|
||
|
+ if (!np) {
|
||
|
+ dev_err(&pdev->dev, "No memory-region specified\n");
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = of_address_to_resource(np, 0, &r);
|
||
|
+ of_node_put(np);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ smem->regions[0].aux_base = (u32)r.start;
|
||
|
+ smem->regions[0].size = resource_size(&r);
|
||
|
+ smem->regions[0].virt_base = devm_ioremap_nocache(&pdev->dev,
|
||
|
+ r.start,
|
||
|
+ resource_size(&r));
|
||
|
+ if (!smem->regions[0].virt_base)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ for (i = 1; i < num_regions; i++) {
|
||
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i - 1);
|
||
|
+
|
||
|
+ smem->regions[i].aux_base = (u32)res->start;
|
||
|
+ smem->regions[i].size = resource_size(res);
|
||
|
+ smem->regions[i].virt_base = devm_ioremap_nocache(&pdev->dev,
|
||
|
+ res->start,
|
||
|
+ resource_size(res));
|
||
|
+ if (!smem->regions[i].virt_base)
|
||
|
+ return -ENOMEM;
|
||
|
+ }
|
||
|
+
|
||
|
+ header = smem->regions[0].virt_base;
|
||
|
+ if (header->initialized != 1 || header->reserved) {
|
||
|
+ dev_err(&pdev->dev, "SMEM is not initilized by SBL\n");
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ version = qcom_smem_get_sbl_version(smem);
|
||
|
+ if (version >> 16 != SMEM_EXPECTED_VERSION) {
|
||
|
+ dev_err(&pdev->dev, "Unsupported smem version 0x%x\n", version);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS);
|
||
|
+ if (ret < 0)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0);
|
||
|
+ if (hwlock_id < 0) {
|
||
|
+ dev_err(&pdev->dev, "failed to retrieve hwlock\n");
|
||
|
+ return hwlock_id;
|
||
|
+ }
|
||
|
+
|
||
|
+ smem->hwlock = hwspin_lock_request_specific(hwlock_id);
|
||
|
+ if (!smem->hwlock)
|
||
|
+ return -ENXIO;
|
||
|
+
|
||
|
+ __smem = smem;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int qcom_smem_remove(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ hwspin_lock_free(__smem->hwlock);
|
||
|
+ __smem = NULL;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct of_device_id qcom_smem_of_match[] = {
|
||
|
+ { .compatible = "qcom,smem" },
|
||
|
+ {}
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(of, qcom_smem_of_match);
|
||
|
+
|
||
|
+static struct platform_driver qcom_smem_driver = {
|
||
|
+ .probe = qcom_smem_probe,
|
||
|
+ .remove = qcom_smem_remove,
|
||
|
+ .driver = {
|
||
|
+ .name = "qcom_smem",
|
||
|
+ .of_match_table = qcom_smem_of_match,
|
||
|
+ .suppress_bind_attrs = true,
|
||
|
+ },
|
||
|
+};
|
||
|
+
|
||
|
+static int __init qcom_smem_init(void)
|
||
|
+{
|
||
|
+ return platform_driver_register(&qcom_smem_driver);
|
||
|
+}
|
||
|
+arch_initcall(qcom_smem_init);
|
||
|
+
|
||
|
+static void __exit qcom_smem_exit(void)
|
||
|
+{
|
||
|
+ platform_driver_unregister(&qcom_smem_driver);
|
||
|
+}
|
||
|
+module_exit(qcom_smem_exit)
|
||
|
+
|
||
|
+MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
|
||
|
+MODULE_DESCRIPTION("Qualcomm Shared Memory Manager");
|
||
|
+MODULE_LICENSE("GPLv2");
|
||
|
--- /dev/null
|
||
|
+++ b/include/linux/soc/qcom/smem.h
|
||
|
@@ -0,0 +1,14 @@
|
||
|
+#ifndef __QCOM_SMEM_H__
|
||
|
+#define __QCOM_SMEM_H__
|
||
|
+
|
||
|
+struct device_node;
|
||
|
+struct qcom_smem;
|
||
|
+
|
||
|
+#define QCOM_SMEM_HOST_ANY -1
|
||
|
+
|
||
|
+int qcom_smem_alloc(unsigned host, unsigned item, size_t size);
|
||
|
+int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size);
|
||
|
+
|
||
|
+int qcom_smem_get_free_space(unsigned host);
|
||
|
+
|
||
|
+#endif
|