From patchwork Tue Sep 30 00:34:48 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bjorn Andersson X-Patchwork-Id: 4999621 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 6BE9F9F1D4 for ; Tue, 30 Sep 2014 00:38:05 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 1CB49201B4 for ; Tue, 30 Sep 2014 00:38:04 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id A99EE201EC for ; Tue, 30 Sep 2014 00:38:02 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1XYlQ5-0007ym-SL; Tue, 30 Sep 2014 00:35:57 +0000 Received: from seldrel01.sonyericsson.com ([212.209.106.2]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1XYlPT-0006be-89 for linux-arm-kernel@lists.infradead.org; Tue, 30 Sep 2014 00:35:21 +0000 From: Bjorn Andersson To: Kumar Gala , Andy Gross , Arnd Bergmann Subject: [RFC 4/7] soc: qcom: Add Shared Memory Manager driver Date: Mon, 29 Sep 2014 17:34:48 -0700 Message-ID: <1412037291-16880-5-git-send-email-bjorn.andersson@sonymobile.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1412037291-16880-1-git-send-email-bjorn.andersson@sonymobile.com> References: <1412037291-16880-1-git-send-email-bjorn.andersson@sonymobile.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140929_173519_949337_F0AAD69E X-CRM114-Status: GOOD ( 28.86 ) X-Spam-Score: -5.0 (-----) Cc: Mark Rutland , devicetree@vger.kernel.org, Samuel Ortiz , Pawel Moll , Ian Campbell , linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, Rob Herring , Liam Girdwood , Mark Brown , Grant Likely , Lee Jones , linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-2.7 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- In later platforms this is extended to support "secure smem", I do unfortunately not have any devices that I could test this with so I have only implemented the "insecure" version for now. I was thinking about implementing an of_xlate as this would make sense for many things. It is however not feasible for SMD, that would require us to list 257 items. It would make sense to enhance this with a method of keeping track of currently consumed items, both to take care of "mutual exclusion" between consumers as well as knowing when it's safe to remove the device and/or unload the driver. I did consider exposing the items as regmaps, but for many of the items the regmap context is consuming far more space then the actual data. And we would end up creating 3-400 regmap contexts in a normal system. Also note that the hwspinlock framework does not yet support devicetree based retrieval of spinlock handles, so that part needs to be changed. drivers/soc/qcom/Kconfig | 8 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qcom_smem.c | 328 ++++++++++++++++++++++++++++++++++++ include/linux/soc/qcom/qcom_smem.h | 14 ++ 4 files changed, 351 insertions(+) create mode 100644 drivers/soc/qcom/qcom_smem.c create mode 100644 include/linux/soc/qcom/qcom_smem.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 7bd2c94..9e6bc56 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -9,3 +9,11 @@ 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 Interface" + 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 and is used for exchanging + information as well as a fifo based communication mechanism (SMD). diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 4389012..b1f7b18 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o +obj-$(CONFIG_QCOM_SMEM) += qcom_smem.o diff --git a/drivers/soc/qcom/qcom_smem.c b/drivers/soc/qcom/qcom_smem.c new file mode 100644 index 0000000..9766c86 --- /dev/null +++ b/drivers/soc/qcom/qcom_smem.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2014, 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 +#include +#include +#include +#include +#include +#include + +#define AUX_BASE_MASK 0xfffffffc +#define HWSPINLOCK_TIMEOUT 1000 +#define SMEM_MAX_ITEMS 512 + +/** + * 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_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_entry { + u32 allocated; + u32 offset; + u32 size; + u32 aux_base; /* bits 1:0 reserved */ +}; + +/** + * 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 + * @smem_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 + * @unused: reserved field + * toc: array of references to smem entries + */ +struct smem_header { + struct smem_proc_comm proc_comm[4]; + u32 version[32]; + u32 smem_initialized; + u32 free_offset; + u32 available; + u32 unused; + struct smem_entry toc[SMEM_MAX_ITEMS]; +}; + +/** + * struct smem_area - memory region used for smem heap + * @aux_base: physical base address of the region, used for entry lookup + * @virt_base: virtual address of the mapping + */ +struct smem_region { + u32 aux_base; + void __iomem *virt_base; +}; + +/** + * struct qcom_smem - control struct for the smem driver + * @dev: device pointer + * @hwlock: hwspinlock to be held during heap operations + * @num_regions: number of entires in the regions array + * @regions: array of memory regions, region 0 contains smem_header + */ +struct qcom_smem { + struct device *dev; + + struct hwspinlock *hwlock; + + unsigned num_regions; + struct smem_region regions[0]; +}; + +/* Pointer to the one and only smem handle */ +static struct qcom_smem *smem_handle; + +/** + * of_get_qcom_smem - retrieve a smem handle from a phandle + * @client_node: of_node of the client + * + * Resolve the phandle in the property "qcom,smem" of @client_node and use this + * to retrieve an smem handle. + */ +struct qcom_smem *of_get_qcom_smem(struct device_node *client_node) +{ + struct device_node *node; + + node = of_parse_phandle(client_node, "qcom,smem", 0); + if (!node) + return ERR_PTR(-EINVAL); + + of_node_put(node); + + if (!smem_handle) + return ERR_PTR(-EPROBE_DEFER); + else if (smem_handle->dev->of_node != node) + return ERR_PTR(-EINVAL); + + return smem_handle; +} +EXPORT_SYMBOL(of_get_qcom_smem); + +/** + * qcom_smem_alloc - allocate space for a smem item + * @smem: smem handle + * @smem_id: item to be allocated + * @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(struct qcom_smem *smem, unsigned smem_id, size_t size) +{ + struct smem_header *header = smem->regions[0].virt_base; + struct smem_entry *entry; + unsigned long flags; + int ret; + + size = ALIGN(size, 8); + + if (WARN_ON(smem_id >= SMEM_MAX_ITEMS)) + return -EINVAL; + + ret = hwspin_lock_timeout_irqsave(smem->hwlock, + HWSPINLOCK_TIMEOUT, + &flags); + if (ret) + return ret; + + entry = &header->toc[smem_id]; + if (entry->allocated) { + ret = -EEXIST; + goto out; + } + + if (WARN_ON(size > header->available)) { + ret = -ENOMEM; + goto out; + } + + entry->offset = header->free_offset; + entry->size = size; + entry->allocated = 1; + + header->free_offset += size; + header->available -= size; + + /* Commit the changes before we release the spin lock */ + wmb(); +out: + hwspin_unlock_irqrestore(smem->hwlock, &flags); + return ret; +} +EXPORT_SYMBOL(qcom_smem_alloc); + +unsigned qcom_smem_get_free_space(struct qcom_smem *smem) +{ + struct smem_header *header = smem->regions[0].virt_base; + + return header->available; +} +EXPORT_SYMBOL(qcom_smem_get_free_space); + +/** + * qcom_smem_get - resolve ptr of size of a smem item + * @smem: smem handle + * @smem_id: item id to be resolved + * @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(struct qcom_smem *smem, + unsigned smem_id, + void **ptr, + size_t *size) +{ + struct smem_header *header = smem->regions[0].virt_base; + struct smem_region *area; + struct smem_entry *entry; + unsigned long flags; + u32 aux_base; + unsigned i; + int ret; + + if (WARN_ON(smem_id >= SMEM_MAX_ITEMS)) + return -EINVAL; + + ret = hwspin_lock_timeout_irqsave(smem->hwlock, + HWSPINLOCK_TIMEOUT, + &flags); + if (ret) + return ret; + + entry = &header->toc[smem_id]; + if (!entry->allocated) { + ret = -ENXIO; + goto out; + } + + 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; + +out: + hwspin_unlock_irqrestore(smem->hwlock, &flags); + return ret; +} +EXPORT_SYMBOL(qcom_smem_get); + +static int qcom_smem_probe(struct platform_device *pdev) +{ + struct qcom_smem *smem; + struct resource *res; + size_t array_size; + 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++; + } + + if (num_regions == 0) { + dev_err(&pdev->dev, "no smem regions specified\n"); + return -EINVAL; + } + + 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->hwlock = of_hwspin_lock_request(pdev->dev.of_node, NULL); + if (IS_ERR(smem->hwlock)) + return PTR_ERR(smem->hwlock); + + smem->num_regions = num_regions; + + for (i = 0; i < num_regions; i++) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + + smem->regions[i].aux_base = (u32)res->start; + smem->regions[i].virt_base = devm_ioremap(&pdev->dev, + res->start, + resource_size(res)); + if (!smem->regions[i].virt_base) + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, smem); + smem_handle = smem; + + dev_info(&pdev->dev, "Qualcomm Shared Memory Interface probed\n"); + + 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, + .driver = { + .name = "qcom_smem", + .owner = THIS_MODULE, + .of_match_table = qcom_smem_of_match, + }, +}; + +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 "); +MODULE_DESCRIPTION("Qualcomm Shared Memory Interface"); +MODULE_LICENSE("GPLv2"); diff --git a/include/linux/soc/qcom/qcom_smem.h b/include/linux/soc/qcom/qcom_smem.h new file mode 100644 index 0000000..7c0d3fd --- /dev/null +++ b/include/linux/soc/qcom/qcom_smem.h @@ -0,0 +1,14 @@ +#ifndef __QCOM_SMEM_H__ +#define __QCOM_SMEM_H__ + +struct device_node; +struct qcom_smem; + +struct qcom_smem *of_get_qcom_smem(struct device_node *node); + +int qcom_smem_alloc(struct qcom_smem *smem, unsigned smem_id, size_t size); +int qcom_smem_get(struct qcom_smem *smem, unsigned item, void **ptr, size_t *size); + +unsigned qcom_smem_get_free_space(struct qcom_smem *smem); + +#endif