diff options
author | John Crispin <john@openwrt.org> | 2015-08-17 06:17:58 +0000 |
---|---|---|
committer | John Crispin <john@openwrt.org> | 2015-08-17 06:17:58 +0000 |
commit | 6529d23267da19200a29235edf74b174dc87d8f6 (patch) | |
tree | f44a2a68ba621c1bc50ac7f49f44f62d30f37fee /target/linux | |
parent | 6b775f4517b31f4226fbf08fa4e35e9100c92635 (diff) | |
download | openwrt-6529d23267da19200a29235edf74b174dc87d8f6.tar.gz openwrt-6529d23267da19200a29235edf74b174dc87d8f6.tar.bz2 openwrt-6529d23267da19200a29235edf74b174dc87d8f6.zip |
ipq806x: add SMEM support
2 patches are cherry-picked from the following LKML thread:
*https://lkml.org/lkml/2015/4/11/208
The last patch (036-soc-qcom-add-smem-to-IPQ806x-platforms.patch) is
adding the corresponding DT nodes required for IPQ806x.
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
SVN-Revision: 46656
Diffstat (limited to 'target/linux')
6 files changed, 1918 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches-3.18/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch b/target/linux/ipq806x/patches-3.18/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch new file mode 100644 index 0000000000..d22db2226e --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch @@ -0,0 +1,82 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,1/2] soc: qcom: Add device tree binding for SMEM +From: Bjorn Andersson <bjorn.andersson@sonymobile.com> +X-Patchwork-Id: 6202201 +Message-Id: <1428795178-24312-1-git-send-email-bjorn.andersson@sonymobile.com> +To: Rob Herring <robh+dt@kernel.org>, Pawel Moll <pawel.moll@arm.com>, + Mark Rutland <mark.rutland@arm.com>, + Ian Campbell <ijc+devicetree@hellion.org.uk>, + Kumar Gala <galak@codeaurora.org>, Jeffrey Hugo <jhugo@codeaurora.org>, + Andry Gross <agross@codeaurora.org> +Cc: <devicetree@vger.kernel.org>, + linux-arm-msm <linux-arm-msm@vger.kernel.org>, + <linux-kernel@vger.kernel.org> +Date: Sat, 11 Apr 2015 16:32:57 -0700 + +Add device tree binding documentation for the Qualcom Shared Memory +manager. + +Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com> + +--- +Changes since v1: +- None + + .../devicetree/bindings/soc/qcom/qcom,smem.txt | 49 ++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt +@@ -0,0 +1,49 @@ ++Qualcomm Shared Memory binding ++ ++This binding describes the Qualcomm Shared Memory, used to share data between ++various subsystems and OSes in Qualcomm platforms. ++ ++- compatible: ++ Usage: required ++ Value type: <stringlist> ++ Definition: must be: ++ "qcom,smem" ++ ++- memory-region: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: handle to memory reservation for main smem memory region. ++ ++- reg: ++ Usage: optional ++ Value type: <prop-encoded-array> ++ Definition: base address and size pair for any additional memory areas ++ of the shared memory. ++ ++- hwspinlocks: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: reference to a hwspinlock used to protect allocations from ++ the shared memory ++ ++= EXAMPLE ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ ++ smem_region: smem@fa00000 { ++ reg = <0xfa00000 0x200000>; ++ no-map; ++ }; ++ }; ++ ++ smem@fa00000 { ++ compatible = "qcom,smem"; ++ ++ memory-region = <&smem_region>; ++ reg = <0xfc428000 0x4000>; ++ ++ hwlocks = <&tcsr_mutex 3>; ++ }; diff --git a/target/linux/ipq806x/patches-3.18/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch b/target/linux/ipq806x/patches-3.18/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch new file mode 100644 index 0000000000..c8cff1a288 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch @@ -0,0 +1,841 @@ +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 diff --git a/target/linux/ipq806x/patches-3.18/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-3.18/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch new file mode 100644 index 0000000000..b2b9a749c3 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch @@ -0,0 +1,36 @@ +From f212be3a6134db8dd7c5f6f0987536a669401fae Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Fri, 14 Aug 2015 11:17:20 -0700 +Subject: [PATCH 2/3] ARM: qcom: add SMEM device node to IPQ806x dts + +SMEM is used on IPQ806x to store various board related information such +as boot device and flash partition layout. We'll declare it as a device +so we can make use of it thanks to the new SMEM soc driver. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -54,7 +54,7 @@ + no-map; + }; + +- smem@41000000 { ++ smem: smem@41000000 { + reg = <0x41000000 0x200000>; + no-map; + }; +@@ -304,4 +304,10 @@ + + #hwlock-cells = <1>; + }; ++ ++ smem { ++ compatible = "qcom,smem"; ++ memory-region = <&smem>; ++ hwlocks = <&sfpb_mutex 3>; ++ }; + }; diff --git a/target/linux/ipq806x/patches-4.1/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch b/target/linux/ipq806x/patches-4.1/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch new file mode 100644 index 0000000000..d22db2226e --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch @@ -0,0 +1,82 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,1/2] soc: qcom: Add device tree binding for SMEM +From: Bjorn Andersson <bjorn.andersson@sonymobile.com> +X-Patchwork-Id: 6202201 +Message-Id: <1428795178-24312-1-git-send-email-bjorn.andersson@sonymobile.com> +To: Rob Herring <robh+dt@kernel.org>, Pawel Moll <pawel.moll@arm.com>, + Mark Rutland <mark.rutland@arm.com>, + Ian Campbell <ijc+devicetree@hellion.org.uk>, + Kumar Gala <galak@codeaurora.org>, Jeffrey Hugo <jhugo@codeaurora.org>, + Andry Gross <agross@codeaurora.org> +Cc: <devicetree@vger.kernel.org>, + linux-arm-msm <linux-arm-msm@vger.kernel.org>, + <linux-kernel@vger.kernel.org> +Date: Sat, 11 Apr 2015 16:32:57 -0700 + +Add device tree binding documentation for the Qualcom Shared Memory +manager. + +Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com> + +--- +Changes since v1: +- None + + .../devicetree/bindings/soc/qcom/qcom,smem.txt | 49 ++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt +@@ -0,0 +1,49 @@ ++Qualcomm Shared Memory binding ++ ++This binding describes the Qualcomm Shared Memory, used to share data between ++various subsystems and OSes in Qualcomm platforms. ++ ++- compatible: ++ Usage: required ++ Value type: <stringlist> ++ Definition: must be: ++ "qcom,smem" ++ ++- memory-region: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: handle to memory reservation for main smem memory region. ++ ++- reg: ++ Usage: optional ++ Value type: <prop-encoded-array> ++ Definition: base address and size pair for any additional memory areas ++ of the shared memory. ++ ++- hwspinlocks: ++ Usage: required ++ Value type: <prop-encoded-array> ++ Definition: reference to a hwspinlock used to protect allocations from ++ the shared memory ++ ++= EXAMPLE ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ ++ smem_region: smem@fa00000 { ++ reg = <0xfa00000 0x200000>; ++ no-map; ++ }; ++ }; ++ ++ smem@fa00000 { ++ compatible = "qcom,smem"; ++ ++ memory-region = <&smem_region>; ++ reg = <0xfc428000 0x4000>; ++ ++ hwlocks = <&tcsr_mutex 3>; ++ }; diff --git a/target/linux/ipq806x/patches-4.1/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch b/target/linux/ipq806x/patches-4.1/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch new file mode 100644 index 0000000000..5598da26d8 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch @@ -0,0 +1,841 @@ +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 +@@ -10,3 +10,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 diff --git a/target/linux/ipq806x/patches-4.1/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-4.1/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch new file mode 100644 index 0000000000..b55f17475e --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch @@ -0,0 +1,36 @@ +From f212be3a6134db8dd7c5f6f0987536a669401fae Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Fri, 14 Aug 2015 11:17:20 -0700 +Subject: [PATCH 2/3] ARM: qcom: add SMEM device node to IPQ806x dts + +SMEM is used on IPQ806x to store various board related information such +as boot device and flash partition layout. We'll declare it as a device +so we can make use of it thanks to the new SMEM soc driver. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -55,7 +55,7 @@ + no-map; + }; + +- smem@41000000 { ++ smem: smem@41000000 { + reg = <0x41000000 0x200000>; + no-map; + }; +@@ -341,4 +341,10 @@ + + #hwlock-cells = <1>; + }; ++ ++ smem { ++ compatible = "qcom,smem"; ++ memory-region = <&smem>; ++ hwlocks = <&sfpb_mutex 3>; ++ }; + }; |