14b638df4SBjorn Andersson /* 24b638df4SBjorn Andersson * Copyright (c) 2015, Sony Mobile Communications AB. 34b638df4SBjorn Andersson * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 44b638df4SBjorn Andersson * 54b638df4SBjorn Andersson * This program is free software; you can redistribute it and/or modify 64b638df4SBjorn Andersson * it under the terms of the GNU General Public License version 2 and 74b638df4SBjorn Andersson * only version 2 as published by the Free Software Foundation. 84b638df4SBjorn Andersson * 94b638df4SBjorn Andersson * This program is distributed in the hope that it will be useful, 104b638df4SBjorn Andersson * but WITHOUT ANY WARRANTY; without even the implied warranty of 114b638df4SBjorn Andersson * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 124b638df4SBjorn Andersson * GNU General Public License for more details. 134b638df4SBjorn Andersson */ 144b638df4SBjorn Andersson 154b638df4SBjorn Andersson #include <linux/hwspinlock.h> 164b638df4SBjorn Andersson #include <linux/io.h> 174b638df4SBjorn Andersson #include <linux/module.h> 184b638df4SBjorn Andersson #include <linux/of.h> 194b638df4SBjorn Andersson #include <linux/of_address.h> 204b638df4SBjorn Andersson #include <linux/platform_device.h> 214b638df4SBjorn Andersson #include <linux/slab.h> 224b638df4SBjorn Andersson #include <linux/soc/qcom/smem.h> 234b638df4SBjorn Andersson 244b638df4SBjorn Andersson /* 254b638df4SBjorn Andersson * The Qualcomm shared memory system is a allocate only heap structure that 264b638df4SBjorn Andersson * consists of one of more memory areas that can be accessed by the processors 274b638df4SBjorn Andersson * in the SoC. 284b638df4SBjorn Andersson * 294b638df4SBjorn Andersson * All systems contains a global heap, accessible by all processors in the SoC, 304b638df4SBjorn Andersson * with a table of contents data structure (@smem_header) at the beginning of 314b638df4SBjorn Andersson * the main shared memory block. 324b638df4SBjorn Andersson * 334b638df4SBjorn Andersson * The global header contains meta data for allocations as well as a fixed list 344b638df4SBjorn Andersson * of 512 entries (@smem_global_entry) that can be initialized to reference 354b638df4SBjorn Andersson * parts of the shared memory space. 364b638df4SBjorn Andersson * 374b638df4SBjorn Andersson * 384b638df4SBjorn Andersson * In addition to this global heap a set of "private" heaps can be set up at 394b638df4SBjorn Andersson * boot time with access restrictions so that only certain processor pairs can 404b638df4SBjorn Andersson * access the data. 414b638df4SBjorn Andersson * 424b638df4SBjorn Andersson * These partitions are referenced from an optional partition table 434b638df4SBjorn Andersson * (@smem_ptable), that is found 4kB from the end of the main smem region. The 444b638df4SBjorn Andersson * partition table entries (@smem_ptable_entry) lists the involved processors 454b638df4SBjorn Andersson * (or hosts) and their location in the main shared memory region. 464b638df4SBjorn Andersson * 474b638df4SBjorn Andersson * Each partition starts with a header (@smem_partition_header) that identifies 484b638df4SBjorn Andersson * the partition and holds properties for the two internal memory regions. The 494b638df4SBjorn Andersson * two regions are cached and non-cached memory respectively. Each region 504b638df4SBjorn Andersson * contain a link list of allocation headers (@smem_private_entry) followed by 514b638df4SBjorn Andersson * their data. 524b638df4SBjorn Andersson * 534b638df4SBjorn Andersson * Items in the non-cached region are allocated from the start of the partition 544b638df4SBjorn Andersson * while items in the cached region are allocated from the end. The free area 554b638df4SBjorn Andersson * is hence the region between the cached and non-cached offsets. 564b638df4SBjorn Andersson * 574b638df4SBjorn Andersson * 584b638df4SBjorn Andersson * To synchronize allocations in the shared memory heaps a remote spinlock must 594b638df4SBjorn Andersson * be held - currently lock number 3 of the sfpb or tcsr is used for this on all 604b638df4SBjorn Andersson * platforms. 614b638df4SBjorn Andersson * 624b638df4SBjorn Andersson */ 634b638df4SBjorn Andersson 644b638df4SBjorn Andersson /* 654b638df4SBjorn Andersson * Item 3 of the global heap contains an array of versions for the various 664b638df4SBjorn Andersson * software components in the SoC. We verify that the boot loader version is 674b638df4SBjorn Andersson * what the expected version (SMEM_EXPECTED_VERSION) as a sanity check. 684b638df4SBjorn Andersson */ 694b638df4SBjorn Andersson #define SMEM_ITEM_VERSION 3 704b638df4SBjorn Andersson #define SMEM_MASTER_SBL_VERSION_INDEX 7 714b638df4SBjorn Andersson #define SMEM_EXPECTED_VERSION 11 724b638df4SBjorn Andersson 734b638df4SBjorn Andersson /* 744b638df4SBjorn Andersson * The first 8 items are only to be allocated by the boot loader while 754b638df4SBjorn Andersson * initializing the heap. 764b638df4SBjorn Andersson */ 774b638df4SBjorn Andersson #define SMEM_ITEM_LAST_FIXED 8 784b638df4SBjorn Andersson 794b638df4SBjorn Andersson /* Highest accepted item number, for both global and private heaps */ 804b638df4SBjorn Andersson #define SMEM_ITEM_COUNT 512 814b638df4SBjorn Andersson 824b638df4SBjorn Andersson /* Processor/host identifier for the application processor */ 834b638df4SBjorn Andersson #define SMEM_HOST_APPS 0 844b638df4SBjorn Andersson 854b638df4SBjorn Andersson /* Max number of processors/hosts in a system */ 864b638df4SBjorn Andersson #define SMEM_HOST_COUNT 9 874b638df4SBjorn Andersson 884b638df4SBjorn Andersson /** 894b638df4SBjorn Andersson * struct smem_proc_comm - proc_comm communication struct (legacy) 904b638df4SBjorn Andersson * @command: current command to be executed 914b638df4SBjorn Andersson * @status: status of the currently requested command 924b638df4SBjorn Andersson * @params: parameters to the command 934b638df4SBjorn Andersson */ 944b638df4SBjorn Andersson struct smem_proc_comm { 954b638df4SBjorn Andersson u32 command; 964b638df4SBjorn Andersson u32 status; 974b638df4SBjorn Andersson u32 params[2]; 984b638df4SBjorn Andersson }; 994b638df4SBjorn Andersson 1004b638df4SBjorn Andersson /** 1014b638df4SBjorn Andersson * struct smem_global_entry - entry to reference smem items on the heap 1024b638df4SBjorn Andersson * @allocated: boolean to indicate if this entry is used 1034b638df4SBjorn Andersson * @offset: offset to the allocated space 1044b638df4SBjorn Andersson * @size: size of the allocated space, 8 byte aligned 1054b638df4SBjorn Andersson * @aux_base: base address for the memory region used by this unit, or 0 for 1064b638df4SBjorn Andersson * the default region. bits 0,1 are reserved 1074b638df4SBjorn Andersson */ 1084b638df4SBjorn Andersson struct smem_global_entry { 1094b638df4SBjorn Andersson u32 allocated; 1104b638df4SBjorn Andersson u32 offset; 1114b638df4SBjorn Andersson u32 size; 1124b638df4SBjorn Andersson u32 aux_base; /* bits 1:0 reserved */ 1134b638df4SBjorn Andersson }; 1144b638df4SBjorn Andersson #define AUX_BASE_MASK 0xfffffffc 1154b638df4SBjorn Andersson 1164b638df4SBjorn Andersson /** 1174b638df4SBjorn Andersson * struct smem_header - header found in beginning of primary smem region 1184b638df4SBjorn Andersson * @proc_comm: proc_comm communication interface (legacy) 1194b638df4SBjorn Andersson * @version: array of versions for the various subsystems 1204b638df4SBjorn Andersson * @initialized: boolean to indicate that smem is initialized 1214b638df4SBjorn Andersson * @free_offset: index of the first unallocated byte in smem 1224b638df4SBjorn Andersson * @available: number of bytes available for allocation 1234b638df4SBjorn Andersson * @reserved: reserved field, must be 0 1244b638df4SBjorn Andersson * toc: array of references to items 1254b638df4SBjorn Andersson */ 1264b638df4SBjorn Andersson struct smem_header { 1274b638df4SBjorn Andersson struct smem_proc_comm proc_comm[4]; 1284b638df4SBjorn Andersson u32 version[32]; 1294b638df4SBjorn Andersson u32 initialized; 1304b638df4SBjorn Andersson u32 free_offset; 1314b638df4SBjorn Andersson u32 available; 1324b638df4SBjorn Andersson u32 reserved; 1334b638df4SBjorn Andersson struct smem_global_entry toc[SMEM_ITEM_COUNT]; 1344b638df4SBjorn Andersson }; 1354b638df4SBjorn Andersson 1364b638df4SBjorn Andersson /** 1374b638df4SBjorn Andersson * struct smem_ptable_entry - one entry in the @smem_ptable list 1384b638df4SBjorn Andersson * @offset: offset, within the main shared memory region, of the partition 1394b638df4SBjorn Andersson * @size: size of the partition 1404b638df4SBjorn Andersson * @flags: flags for the partition (currently unused) 1414b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1424b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 1434b638df4SBjorn Andersson * @reserved: reserved entries for later use 1444b638df4SBjorn Andersson */ 1454b638df4SBjorn Andersson struct smem_ptable_entry { 1464b638df4SBjorn Andersson u32 offset; 1474b638df4SBjorn Andersson u32 size; 1484b638df4SBjorn Andersson u32 flags; 1494b638df4SBjorn Andersson u16 host0; 1504b638df4SBjorn Andersson u16 host1; 1514b638df4SBjorn Andersson u32 reserved[8]; 1524b638df4SBjorn Andersson }; 1534b638df4SBjorn Andersson 1544b638df4SBjorn Andersson /** 1554b638df4SBjorn Andersson * struct smem_ptable - partition table for the private partitions 1564b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PTABLE_MAGIC 1574b638df4SBjorn Andersson * @version: version of the partition table 1584b638df4SBjorn Andersson * @num_entries: number of partitions in the table 1594b638df4SBjorn Andersson * @reserved: for now reserved entries 1604b638df4SBjorn Andersson * @entry: list of @smem_ptable_entry for the @num_entries partitions 1614b638df4SBjorn Andersson */ 1624b638df4SBjorn Andersson struct smem_ptable { 1634b638df4SBjorn Andersson u32 magic; 1644b638df4SBjorn Andersson u32 version; 1654b638df4SBjorn Andersson u32 num_entries; 1664b638df4SBjorn Andersson u32 reserved[5]; 1674b638df4SBjorn Andersson struct smem_ptable_entry entry[]; 1684b638df4SBjorn Andersson }; 1694b638df4SBjorn Andersson #define SMEM_PTABLE_MAGIC 0x434f5424 /* "$TOC" */ 1704b638df4SBjorn Andersson 1714b638df4SBjorn Andersson /** 1724b638df4SBjorn Andersson * struct smem_partition_header - header of the partitions 1734b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PART_MAGIC 1744b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1754b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 1764b638df4SBjorn Andersson * @size: size of the partition 1774b638df4SBjorn Andersson * @offset_free_uncached: offset to the first free byte of uncached memory in 1784b638df4SBjorn Andersson * this partition 1794b638df4SBjorn Andersson * @offset_free_cached: offset to the first free byte of cached memory in this 1804b638df4SBjorn Andersson * partition 1814b638df4SBjorn Andersson * @reserved: for now reserved entries 1824b638df4SBjorn Andersson */ 1834b638df4SBjorn Andersson struct smem_partition_header { 1844b638df4SBjorn Andersson u32 magic; 1854b638df4SBjorn Andersson u16 host0; 1864b638df4SBjorn Andersson u16 host1; 1874b638df4SBjorn Andersson u32 size; 1884b638df4SBjorn Andersson u32 offset_free_uncached; 1894b638df4SBjorn Andersson u32 offset_free_cached; 1904b638df4SBjorn Andersson u32 reserved[3]; 1914b638df4SBjorn Andersson }; 1924b638df4SBjorn Andersson #define SMEM_PART_MAGIC 0x54525024 /* "$PRT" */ 1934b638df4SBjorn Andersson 1944b638df4SBjorn Andersson /** 1954b638df4SBjorn Andersson * struct smem_private_entry - header of each item in the private partition 1964b638df4SBjorn Andersson * @canary: magic number, must be SMEM_PRIVATE_CANARY 1974b638df4SBjorn Andersson * @item: identifying number of the smem item 1984b638df4SBjorn Andersson * @size: size of the data, including padding bytes 1994b638df4SBjorn Andersson * @padding_data: number of bytes of padding of data 2004b638df4SBjorn Andersson * @padding_hdr: number of bytes of padding between the header and the data 2014b638df4SBjorn Andersson * @reserved: for now reserved entry 2024b638df4SBjorn Andersson */ 2034b638df4SBjorn Andersson struct smem_private_entry { 2044b638df4SBjorn Andersson u16 canary; 2054b638df4SBjorn Andersson u16 item; 2064b638df4SBjorn Andersson u32 size; /* includes padding bytes */ 2074b638df4SBjorn Andersson u16 padding_data; 2084b638df4SBjorn Andersson u16 padding_hdr; 2094b638df4SBjorn Andersson u32 reserved; 2104b638df4SBjorn Andersson }; 2114b638df4SBjorn Andersson #define SMEM_PRIVATE_CANARY 0xa5a5 2124b638df4SBjorn Andersson 2134b638df4SBjorn Andersson /** 2144b638df4SBjorn Andersson * struct smem_region - representation of a chunk of memory used for smem 2154b638df4SBjorn Andersson * @aux_base: identifier of aux_mem base 2164b638df4SBjorn Andersson * @virt_base: virtual base address of memory with this aux_mem identifier 2174b638df4SBjorn Andersson * @size: size of the memory region 2184b638df4SBjorn Andersson */ 2194b638df4SBjorn Andersson struct smem_region { 2204b638df4SBjorn Andersson u32 aux_base; 2214b638df4SBjorn Andersson void __iomem *virt_base; 2224b638df4SBjorn Andersson size_t size; 2234b638df4SBjorn Andersson }; 2244b638df4SBjorn Andersson 2254b638df4SBjorn Andersson /** 2264b638df4SBjorn Andersson * struct qcom_smem - device data for the smem device 2274b638df4SBjorn Andersson * @dev: device pointer 2284b638df4SBjorn Andersson * @hwlock: reference to a hwspinlock 2294b638df4SBjorn Andersson * @partitions: list of pointers to partitions affecting the current 2304b638df4SBjorn Andersson * processor/host 2314b638df4SBjorn Andersson * @num_regions: number of @regions 2324b638df4SBjorn Andersson * @regions: list of the memory regions defining the shared memory 2334b638df4SBjorn Andersson */ 2344b638df4SBjorn Andersson struct qcom_smem { 2354b638df4SBjorn Andersson struct device *dev; 2364b638df4SBjorn Andersson 2374b638df4SBjorn Andersson struct hwspinlock *hwlock; 2384b638df4SBjorn Andersson 2394b638df4SBjorn Andersson struct smem_partition_header *partitions[SMEM_HOST_COUNT]; 2404b638df4SBjorn Andersson 2414b638df4SBjorn Andersson unsigned num_regions; 2424b638df4SBjorn Andersson struct smem_region regions[0]; 2434b638df4SBjorn Andersson }; 2444b638df4SBjorn Andersson 2454b638df4SBjorn Andersson /* Pointer to the one and only smem handle */ 2464b638df4SBjorn Andersson static struct qcom_smem *__smem; 2474b638df4SBjorn Andersson 2484b638df4SBjorn Andersson /* Timeout (ms) for the trylock of remote spinlocks */ 2494b638df4SBjorn Andersson #define HWSPINLOCK_TIMEOUT 1000 2504b638df4SBjorn Andersson 2514b638df4SBjorn Andersson static int qcom_smem_alloc_private(struct qcom_smem *smem, 2524b638df4SBjorn Andersson unsigned host, 2534b638df4SBjorn Andersson unsigned item, 2544b638df4SBjorn Andersson size_t size) 2554b638df4SBjorn Andersson { 2564b638df4SBjorn Andersson struct smem_partition_header *phdr; 2574b638df4SBjorn Andersson struct smem_private_entry *hdr; 2584b638df4SBjorn Andersson size_t alloc_size; 2594b638df4SBjorn Andersson void *p; 2604b638df4SBjorn Andersson 2614b638df4SBjorn Andersson phdr = smem->partitions[host]; 2624b638df4SBjorn Andersson 2634b638df4SBjorn Andersson p = (void *)phdr + sizeof(*phdr); 2644b638df4SBjorn Andersson while (p < (void *)phdr + phdr->offset_free_uncached) { 2654b638df4SBjorn Andersson hdr = p; 2664b638df4SBjorn Andersson 2674b638df4SBjorn Andersson if (hdr->canary != SMEM_PRIVATE_CANARY) { 2684b638df4SBjorn Andersson dev_err(smem->dev, 2694b638df4SBjorn Andersson "Found invalid canary in host %d partition\n", 2704b638df4SBjorn Andersson host); 2714b638df4SBjorn Andersson return -EINVAL; 2724b638df4SBjorn Andersson } 2734b638df4SBjorn Andersson 2744b638df4SBjorn Andersson if (hdr->item == item) 2754b638df4SBjorn Andersson return -EEXIST; 2764b638df4SBjorn Andersson 2774b638df4SBjorn Andersson p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; 2784b638df4SBjorn Andersson } 2794b638df4SBjorn Andersson 2804b638df4SBjorn Andersson /* Check that we don't grow into the cached region */ 2814b638df4SBjorn Andersson alloc_size = sizeof(*hdr) + ALIGN(size, 8); 2824b638df4SBjorn Andersson if (p + alloc_size >= (void *)phdr + phdr->offset_free_cached) { 2834b638df4SBjorn Andersson dev_err(smem->dev, "Out of memory\n"); 2844b638df4SBjorn Andersson return -ENOSPC; 2854b638df4SBjorn Andersson } 2864b638df4SBjorn Andersson 2874b638df4SBjorn Andersson hdr = p; 2884b638df4SBjorn Andersson hdr->canary = SMEM_PRIVATE_CANARY; 2894b638df4SBjorn Andersson hdr->item = item; 2904b638df4SBjorn Andersson hdr->size = ALIGN(size, 8); 2914b638df4SBjorn Andersson hdr->padding_data = hdr->size - size; 2924b638df4SBjorn Andersson hdr->padding_hdr = 0; 2934b638df4SBjorn Andersson 2944b638df4SBjorn Andersson /* 2954b638df4SBjorn Andersson * Ensure the header is written before we advance the free offset, so 2964b638df4SBjorn Andersson * that remote processors that does not take the remote spinlock still 2974b638df4SBjorn Andersson * gets a consistent view of the linked list. 2984b638df4SBjorn Andersson */ 2994b638df4SBjorn Andersson wmb(); 3004b638df4SBjorn Andersson phdr->offset_free_uncached += alloc_size; 3014b638df4SBjorn Andersson 3024b638df4SBjorn Andersson return 0; 3034b638df4SBjorn Andersson } 3044b638df4SBjorn Andersson 3054b638df4SBjorn Andersson static int qcom_smem_alloc_global(struct qcom_smem *smem, 3064b638df4SBjorn Andersson unsigned item, 3074b638df4SBjorn Andersson size_t size) 3084b638df4SBjorn Andersson { 3094b638df4SBjorn Andersson struct smem_header *header; 3104b638df4SBjorn Andersson struct smem_global_entry *entry; 3114b638df4SBjorn Andersson 3124b638df4SBjorn Andersson if (WARN_ON(item >= SMEM_ITEM_COUNT)) 3134b638df4SBjorn Andersson return -EINVAL; 3144b638df4SBjorn Andersson 3154b638df4SBjorn Andersson header = smem->regions[0].virt_base; 3164b638df4SBjorn Andersson entry = &header->toc[item]; 3174b638df4SBjorn Andersson if (entry->allocated) 3184b638df4SBjorn Andersson return -EEXIST; 3194b638df4SBjorn Andersson 3204b638df4SBjorn Andersson size = ALIGN(size, 8); 3214b638df4SBjorn Andersson if (WARN_ON(size > header->available)) 3224b638df4SBjorn Andersson return -ENOMEM; 3234b638df4SBjorn Andersson 3244b638df4SBjorn Andersson entry->offset = header->free_offset; 3254b638df4SBjorn Andersson entry->size = size; 3264b638df4SBjorn Andersson 3274b638df4SBjorn Andersson /* 3284b638df4SBjorn Andersson * Ensure the header is consistent before we mark the item allocated, 3294b638df4SBjorn Andersson * so that remote processors will get a consistent view of the item 3304b638df4SBjorn Andersson * even though they do not take the spinlock on read. 3314b638df4SBjorn Andersson */ 3324b638df4SBjorn Andersson wmb(); 3334b638df4SBjorn Andersson entry->allocated = 1; 3344b638df4SBjorn Andersson 3354b638df4SBjorn Andersson header->free_offset += size; 3364b638df4SBjorn Andersson header->available -= size; 3374b638df4SBjorn Andersson 3384b638df4SBjorn Andersson return 0; 3394b638df4SBjorn Andersson } 3404b638df4SBjorn Andersson 3414b638df4SBjorn Andersson /** 3424b638df4SBjorn Andersson * qcom_smem_alloc() - allocate space for a smem item 3434b638df4SBjorn Andersson * @host: remote processor id, or -1 3444b638df4SBjorn Andersson * @item: smem item handle 3454b638df4SBjorn Andersson * @size: number of bytes to be allocated 3464b638df4SBjorn Andersson * 3474b638df4SBjorn Andersson * Allocate space for a given smem item of size @size, given that the item is 3484b638df4SBjorn Andersson * not yet allocated. 3494b638df4SBjorn Andersson */ 3504b638df4SBjorn Andersson int qcom_smem_alloc(unsigned host, unsigned item, size_t size) 3514b638df4SBjorn Andersson { 3524b638df4SBjorn Andersson unsigned long flags; 3534b638df4SBjorn Andersson int ret; 3544b638df4SBjorn Andersson 3554b638df4SBjorn Andersson if (!__smem) 3564b638df4SBjorn Andersson return -EPROBE_DEFER; 3574b638df4SBjorn Andersson 3584b638df4SBjorn Andersson if (item < SMEM_ITEM_LAST_FIXED) { 3594b638df4SBjorn Andersson dev_err(__smem->dev, 3604b638df4SBjorn Andersson "Rejecting allocation of static entry %d\n", item); 3614b638df4SBjorn Andersson return -EINVAL; 3624b638df4SBjorn Andersson } 3634b638df4SBjorn Andersson 3644b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 3654b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 3664b638df4SBjorn Andersson &flags); 3674b638df4SBjorn Andersson if (ret) 3684b638df4SBjorn Andersson return ret; 3694b638df4SBjorn Andersson 37018912806SAndy Gross if (host < SMEM_HOST_COUNT && __smem->partitions[host]) 3714b638df4SBjorn Andersson ret = qcom_smem_alloc_private(__smem, host, item, size); 37218912806SAndy Gross else 3734b638df4SBjorn Andersson ret = qcom_smem_alloc_global(__smem, item, size); 3744b638df4SBjorn Andersson 3754b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 3764b638df4SBjorn Andersson 3774b638df4SBjorn Andersson return ret; 3784b638df4SBjorn Andersson } 3794b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_alloc); 3804b638df4SBjorn Andersson 3814b638df4SBjorn Andersson static int qcom_smem_get_global(struct qcom_smem *smem, 3824b638df4SBjorn Andersson unsigned item, 3834b638df4SBjorn Andersson void **ptr, 3844b638df4SBjorn Andersson size_t *size) 3854b638df4SBjorn Andersson { 3864b638df4SBjorn Andersson struct smem_header *header; 3874b638df4SBjorn Andersson struct smem_region *area; 3884b638df4SBjorn Andersson struct smem_global_entry *entry; 3894b638df4SBjorn Andersson u32 aux_base; 3904b638df4SBjorn Andersson unsigned i; 3914b638df4SBjorn Andersson 3924b638df4SBjorn Andersson if (WARN_ON(item >= SMEM_ITEM_COUNT)) 3934b638df4SBjorn Andersson return -EINVAL; 3944b638df4SBjorn Andersson 3954b638df4SBjorn Andersson header = smem->regions[0].virt_base; 3964b638df4SBjorn Andersson entry = &header->toc[item]; 3974b638df4SBjorn Andersson if (!entry->allocated) 3984b638df4SBjorn Andersson return -ENXIO; 3994b638df4SBjorn Andersson 4004b638df4SBjorn Andersson if (ptr != NULL) { 4014b638df4SBjorn Andersson aux_base = entry->aux_base & AUX_BASE_MASK; 4024b638df4SBjorn Andersson 4034b638df4SBjorn Andersson for (i = 0; i < smem->num_regions; i++) { 4044b638df4SBjorn Andersson area = &smem->regions[i]; 4054b638df4SBjorn Andersson 4064b638df4SBjorn Andersson if (area->aux_base == aux_base || !aux_base) { 4074b638df4SBjorn Andersson *ptr = area->virt_base + entry->offset; 4084b638df4SBjorn Andersson break; 4094b638df4SBjorn Andersson } 4104b638df4SBjorn Andersson } 4114b638df4SBjorn Andersson } 4124b638df4SBjorn Andersson if (size != NULL) 4134b638df4SBjorn Andersson *size = entry->size; 4144b638df4SBjorn Andersson 4154b638df4SBjorn Andersson return 0; 4164b638df4SBjorn Andersson } 4174b638df4SBjorn Andersson 4184b638df4SBjorn Andersson static int qcom_smem_get_private(struct qcom_smem *smem, 4194b638df4SBjorn Andersson unsigned host, 4204b638df4SBjorn Andersson unsigned item, 4214b638df4SBjorn Andersson void **ptr, 4224b638df4SBjorn Andersson size_t *size) 4234b638df4SBjorn Andersson { 4244b638df4SBjorn Andersson struct smem_partition_header *phdr; 4254b638df4SBjorn Andersson struct smem_private_entry *hdr; 4264b638df4SBjorn Andersson void *p; 4274b638df4SBjorn Andersson 4284b638df4SBjorn Andersson phdr = smem->partitions[host]; 4294b638df4SBjorn Andersson 4304b638df4SBjorn Andersson p = (void *)phdr + sizeof(*phdr); 4314b638df4SBjorn Andersson while (p < (void *)phdr + phdr->offset_free_uncached) { 4324b638df4SBjorn Andersson hdr = p; 4334b638df4SBjorn Andersson 4344b638df4SBjorn Andersson if (hdr->canary != SMEM_PRIVATE_CANARY) { 4354b638df4SBjorn Andersson dev_err(smem->dev, 4364b638df4SBjorn Andersson "Found invalid canary in host %d partition\n", 4374b638df4SBjorn Andersson host); 4384b638df4SBjorn Andersson return -EINVAL; 4394b638df4SBjorn Andersson } 4404b638df4SBjorn Andersson 4414b638df4SBjorn Andersson if (hdr->item == item) { 4424b638df4SBjorn Andersson if (ptr != NULL) 4434b638df4SBjorn Andersson *ptr = p + sizeof(*hdr) + hdr->padding_hdr; 4444b638df4SBjorn Andersson 4454b638df4SBjorn Andersson if (size != NULL) 4464b638df4SBjorn Andersson *size = hdr->size - hdr->padding_data; 4474b638df4SBjorn Andersson 4484b638df4SBjorn Andersson return 0; 4494b638df4SBjorn Andersson } 4504b638df4SBjorn Andersson 4514b638df4SBjorn Andersson p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; 4524b638df4SBjorn Andersson } 4534b638df4SBjorn Andersson 4544b638df4SBjorn Andersson return -ENOENT; 4554b638df4SBjorn Andersson } 4564b638df4SBjorn Andersson 4574b638df4SBjorn Andersson /** 4584b638df4SBjorn Andersson * qcom_smem_get() - resolve ptr of size of a smem item 4594b638df4SBjorn Andersson * @host: the remote processor, or -1 4604b638df4SBjorn Andersson * @item: smem item handle 4614b638df4SBjorn Andersson * @ptr: pointer to be filled out with address of the item 4624b638df4SBjorn Andersson * @size: pointer to be filled out with size of the item 4634b638df4SBjorn Andersson * 4644b638df4SBjorn Andersson * Looks up pointer and size of a smem item. 4654b638df4SBjorn Andersson */ 4664b638df4SBjorn Andersson int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size) 4674b638df4SBjorn Andersson { 4684b638df4SBjorn Andersson unsigned long flags; 4694b638df4SBjorn Andersson int ret; 4704b638df4SBjorn Andersson 4714b638df4SBjorn Andersson if (!__smem) 4724b638df4SBjorn Andersson return -EPROBE_DEFER; 4734b638df4SBjorn Andersson 4744b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 4754b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 4764b638df4SBjorn Andersson &flags); 4774b638df4SBjorn Andersson if (ret) 4784b638df4SBjorn Andersson return ret; 4794b638df4SBjorn Andersson 48018912806SAndy Gross if (host < SMEM_HOST_COUNT && __smem->partitions[host]) 4814b638df4SBjorn Andersson ret = qcom_smem_get_private(__smem, host, item, ptr, size); 48218912806SAndy Gross else 4834b638df4SBjorn Andersson ret = qcom_smem_get_global(__smem, item, ptr, size); 4844b638df4SBjorn Andersson 4854b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 4864b638df4SBjorn Andersson return ret; 4874b638df4SBjorn Andersson 4884b638df4SBjorn Andersson } 4894b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get); 4904b638df4SBjorn Andersson 4914b638df4SBjorn Andersson /** 4924b638df4SBjorn Andersson * qcom_smem_get_free_space() - retrieve amount of free space in a partition 4934b638df4SBjorn Andersson * @host: the remote processor identifying a partition, or -1 4944b638df4SBjorn Andersson * 4954b638df4SBjorn Andersson * To be used by smem clients as a quick way to determine if any new 4964b638df4SBjorn Andersson * allocations has been made. 4974b638df4SBjorn Andersson */ 4984b638df4SBjorn Andersson int qcom_smem_get_free_space(unsigned host) 4994b638df4SBjorn Andersson { 5004b638df4SBjorn Andersson struct smem_partition_header *phdr; 5014b638df4SBjorn Andersson struct smem_header *header; 5024b638df4SBjorn Andersson unsigned ret; 5034b638df4SBjorn Andersson 5044b638df4SBjorn Andersson if (!__smem) 5054b638df4SBjorn Andersson return -EPROBE_DEFER; 5064b638df4SBjorn Andersson 5074b638df4SBjorn Andersson if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 5084b638df4SBjorn Andersson phdr = __smem->partitions[host]; 5094b638df4SBjorn Andersson ret = phdr->offset_free_cached - phdr->offset_free_uncached; 5104b638df4SBjorn Andersson } else { 5114b638df4SBjorn Andersson header = __smem->regions[0].virt_base; 5124b638df4SBjorn Andersson ret = header->available; 5134b638df4SBjorn Andersson } 5144b638df4SBjorn Andersson 5154b638df4SBjorn Andersson return ret; 5164b638df4SBjorn Andersson } 5174b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get_free_space); 5184b638df4SBjorn Andersson 5194b638df4SBjorn Andersson static int qcom_smem_get_sbl_version(struct qcom_smem *smem) 5204b638df4SBjorn Andersson { 5214b638df4SBjorn Andersson unsigned *versions; 5224b638df4SBjorn Andersson size_t size; 5234b638df4SBjorn Andersson int ret; 5244b638df4SBjorn Andersson 5254b638df4SBjorn Andersson ret = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, 5264b638df4SBjorn Andersson (void **)&versions, &size); 5274b638df4SBjorn Andersson if (ret < 0) { 5284b638df4SBjorn Andersson dev_err(smem->dev, "Unable to read the version item\n"); 5294b638df4SBjorn Andersson return -ENOENT; 5304b638df4SBjorn Andersson } 5314b638df4SBjorn Andersson 5324b638df4SBjorn Andersson if (size < sizeof(unsigned) * SMEM_MASTER_SBL_VERSION_INDEX) { 5334b638df4SBjorn Andersson dev_err(smem->dev, "Version item is too small\n"); 5344b638df4SBjorn Andersson return -EINVAL; 5354b638df4SBjorn Andersson } 5364b638df4SBjorn Andersson 5374b638df4SBjorn Andersson return versions[SMEM_MASTER_SBL_VERSION_INDEX]; 5384b638df4SBjorn Andersson } 5394b638df4SBjorn Andersson 5404b638df4SBjorn Andersson static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, 5414b638df4SBjorn Andersson unsigned local_host) 5424b638df4SBjorn Andersson { 5434b638df4SBjorn Andersson struct smem_partition_header *header; 5444b638df4SBjorn Andersson struct smem_ptable_entry *entry; 5454b638df4SBjorn Andersson struct smem_ptable *ptable; 5464b638df4SBjorn Andersson unsigned remote_host; 5474b638df4SBjorn Andersson int i; 5484b638df4SBjorn Andersson 5494b638df4SBjorn Andersson ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; 5504b638df4SBjorn Andersson if (ptable->magic != SMEM_PTABLE_MAGIC) 5514b638df4SBjorn Andersson return 0; 5524b638df4SBjorn Andersson 5534b638df4SBjorn Andersson if (ptable->version != 1) { 5544b638df4SBjorn Andersson dev_err(smem->dev, 5554b638df4SBjorn Andersson "Unsupported partition header version %d\n", 5564b638df4SBjorn Andersson ptable->version); 5574b638df4SBjorn Andersson return -EINVAL; 5584b638df4SBjorn Andersson } 5594b638df4SBjorn Andersson 5604b638df4SBjorn Andersson for (i = 0; i < ptable->num_entries; i++) { 5614b638df4SBjorn Andersson entry = &ptable->entry[i]; 5624b638df4SBjorn Andersson 5634b638df4SBjorn Andersson if (entry->host0 != local_host && entry->host1 != local_host) 5644b638df4SBjorn Andersson continue; 5654b638df4SBjorn Andersson 5664b638df4SBjorn Andersson if (!entry->offset) 5674b638df4SBjorn Andersson continue; 5684b638df4SBjorn Andersson 5694b638df4SBjorn Andersson if (!entry->size) 5704b638df4SBjorn Andersson continue; 5714b638df4SBjorn Andersson 5724b638df4SBjorn Andersson if (entry->host0 == local_host) 5734b638df4SBjorn Andersson remote_host = entry->host1; 5744b638df4SBjorn Andersson else 5754b638df4SBjorn Andersson remote_host = entry->host0; 5764b638df4SBjorn Andersson 5774b638df4SBjorn Andersson if (remote_host >= SMEM_HOST_COUNT) { 5784b638df4SBjorn Andersson dev_err(smem->dev, 5794b638df4SBjorn Andersson "Invalid remote host %d\n", 5804b638df4SBjorn Andersson remote_host); 5814b638df4SBjorn Andersson return -EINVAL; 5824b638df4SBjorn Andersson } 5834b638df4SBjorn Andersson 5844b638df4SBjorn Andersson if (smem->partitions[remote_host]) { 5854b638df4SBjorn Andersson dev_err(smem->dev, 5864b638df4SBjorn Andersson "Already found a partition for host %d\n", 5874b638df4SBjorn Andersson remote_host); 5884b638df4SBjorn Andersson return -EINVAL; 5894b638df4SBjorn Andersson } 5904b638df4SBjorn Andersson 5914b638df4SBjorn Andersson header = smem->regions[0].virt_base + entry->offset; 5924b638df4SBjorn Andersson 5934b638df4SBjorn Andersson if (header->magic != SMEM_PART_MAGIC) { 5944b638df4SBjorn Andersson dev_err(smem->dev, 5954b638df4SBjorn Andersson "Partition %d has invalid magic\n", i); 5964b638df4SBjorn Andersson return -EINVAL; 5974b638df4SBjorn Andersson } 5984b638df4SBjorn Andersson 5994b638df4SBjorn Andersson if (header->host0 != local_host && header->host1 != local_host) { 6004b638df4SBjorn Andersson dev_err(smem->dev, 6014b638df4SBjorn Andersson "Partition %d hosts are invalid\n", i); 6024b638df4SBjorn Andersson return -EINVAL; 6034b638df4SBjorn Andersson } 6044b638df4SBjorn Andersson 6054b638df4SBjorn Andersson if (header->host0 != remote_host && header->host1 != remote_host) { 6064b638df4SBjorn Andersson dev_err(smem->dev, 6074b638df4SBjorn Andersson "Partition %d hosts are invalid\n", i); 6084b638df4SBjorn Andersson return -EINVAL; 6094b638df4SBjorn Andersson } 6104b638df4SBjorn Andersson 6114b638df4SBjorn Andersson if (header->size != entry->size) { 6124b638df4SBjorn Andersson dev_err(smem->dev, 6134b638df4SBjorn Andersson "Partition %d has invalid size\n", i); 6144b638df4SBjorn Andersson return -EINVAL; 6154b638df4SBjorn Andersson } 6164b638df4SBjorn Andersson 6174b638df4SBjorn Andersson if (header->offset_free_uncached > header->size) { 6184b638df4SBjorn Andersson dev_err(smem->dev, 6194b638df4SBjorn Andersson "Partition %d has invalid free pointer\n", i); 6204b638df4SBjorn Andersson return -EINVAL; 6214b638df4SBjorn Andersson } 6224b638df4SBjorn Andersson 6234b638df4SBjorn Andersson smem->partitions[remote_host] = header; 6244b638df4SBjorn Andersson } 6254b638df4SBjorn Andersson 6264b638df4SBjorn Andersson return 0; 6274b638df4SBjorn Andersson } 6284b638df4SBjorn Andersson 6294b638df4SBjorn Andersson static int qcom_smem_count_mem_regions(struct platform_device *pdev) 6304b638df4SBjorn Andersson { 6314b638df4SBjorn Andersson struct resource *res; 6324b638df4SBjorn Andersson int num_regions = 0; 6334b638df4SBjorn Andersson int i; 6344b638df4SBjorn Andersson 6354b638df4SBjorn Andersson for (i = 0; i < pdev->num_resources; i++) { 6364b638df4SBjorn Andersson res = &pdev->resource[i]; 6374b638df4SBjorn Andersson 6384b638df4SBjorn Andersson if (resource_type(res) == IORESOURCE_MEM) 6394b638df4SBjorn Andersson num_regions++; 6404b638df4SBjorn Andersson } 6414b638df4SBjorn Andersson 6424b638df4SBjorn Andersson return num_regions; 6434b638df4SBjorn Andersson } 6444b638df4SBjorn Andersson 6454b638df4SBjorn Andersson static int qcom_smem_probe(struct platform_device *pdev) 6464b638df4SBjorn Andersson { 6474b638df4SBjorn Andersson struct smem_header *header; 6484b638df4SBjorn Andersson struct device_node *np; 6494b638df4SBjorn Andersson struct qcom_smem *smem; 6504b638df4SBjorn Andersson struct resource *res; 6514b638df4SBjorn Andersson struct resource r; 6524b638df4SBjorn Andersson size_t array_size; 6534b638df4SBjorn Andersson int num_regions = 0; 6544b638df4SBjorn Andersson int hwlock_id; 6554b638df4SBjorn Andersson u32 version; 6564b638df4SBjorn Andersson int ret; 6574b638df4SBjorn Andersson int i; 6584b638df4SBjorn Andersson 6594b638df4SBjorn Andersson num_regions = qcom_smem_count_mem_regions(pdev) + 1; 6604b638df4SBjorn Andersson 6614b638df4SBjorn Andersson array_size = num_regions * sizeof(struct smem_region); 6624b638df4SBjorn Andersson smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); 6634b638df4SBjorn Andersson if (!smem) 6644b638df4SBjorn Andersson return -ENOMEM; 6654b638df4SBjorn Andersson 6664b638df4SBjorn Andersson smem->dev = &pdev->dev; 6674b638df4SBjorn Andersson smem->num_regions = num_regions; 6684b638df4SBjorn Andersson 6694b638df4SBjorn Andersson np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); 6704b638df4SBjorn Andersson if (!np) { 6714b638df4SBjorn Andersson dev_err(&pdev->dev, "No memory-region specified\n"); 6724b638df4SBjorn Andersson return -EINVAL; 6734b638df4SBjorn Andersson } 6744b638df4SBjorn Andersson 6754b638df4SBjorn Andersson ret = of_address_to_resource(np, 0, &r); 6764b638df4SBjorn Andersson of_node_put(np); 6774b638df4SBjorn Andersson if (ret) 6784b638df4SBjorn Andersson return ret; 6794b638df4SBjorn Andersson 6804b638df4SBjorn Andersson smem->regions[0].aux_base = (u32)r.start; 6814b638df4SBjorn Andersson smem->regions[0].size = resource_size(&r); 6824b638df4SBjorn Andersson smem->regions[0].virt_base = devm_ioremap_nocache(&pdev->dev, 6834b638df4SBjorn Andersson r.start, 6844b638df4SBjorn Andersson resource_size(&r)); 6854b638df4SBjorn Andersson if (!smem->regions[0].virt_base) 6864b638df4SBjorn Andersson return -ENOMEM; 6874b638df4SBjorn Andersson 6884b638df4SBjorn Andersson for (i = 1; i < num_regions; i++) { 6894b638df4SBjorn Andersson res = platform_get_resource(pdev, IORESOURCE_MEM, i - 1); 6904b638df4SBjorn Andersson 6914b638df4SBjorn Andersson smem->regions[i].aux_base = (u32)res->start; 6924b638df4SBjorn Andersson smem->regions[i].size = resource_size(res); 6934b638df4SBjorn Andersson smem->regions[i].virt_base = devm_ioremap_nocache(&pdev->dev, 6944b638df4SBjorn Andersson res->start, 6954b638df4SBjorn Andersson resource_size(res)); 6964b638df4SBjorn Andersson if (!smem->regions[i].virt_base) 6974b638df4SBjorn Andersson return -ENOMEM; 6984b638df4SBjorn Andersson } 6994b638df4SBjorn Andersson 7004b638df4SBjorn Andersson header = smem->regions[0].virt_base; 7014b638df4SBjorn Andersson if (header->initialized != 1 || header->reserved) { 7024b638df4SBjorn Andersson dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); 7034b638df4SBjorn Andersson return -EINVAL; 7044b638df4SBjorn Andersson } 7054b638df4SBjorn Andersson 7064b638df4SBjorn Andersson version = qcom_smem_get_sbl_version(smem); 7074b638df4SBjorn Andersson if (version >> 16 != SMEM_EXPECTED_VERSION) { 7084b638df4SBjorn Andersson dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); 7094b638df4SBjorn Andersson return -EINVAL; 7104b638df4SBjorn Andersson } 7114b638df4SBjorn Andersson 7124b638df4SBjorn Andersson ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); 7134b638df4SBjorn Andersson if (ret < 0) 7144b638df4SBjorn Andersson return ret; 7154b638df4SBjorn Andersson 7164b638df4SBjorn Andersson hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); 7174b638df4SBjorn Andersson if (hwlock_id < 0) { 7184b638df4SBjorn Andersson dev_err(&pdev->dev, "failed to retrieve hwlock\n"); 7194b638df4SBjorn Andersson return hwlock_id; 7204b638df4SBjorn Andersson } 7214b638df4SBjorn Andersson 7224b638df4SBjorn Andersson smem->hwlock = hwspin_lock_request_specific(hwlock_id); 7234b638df4SBjorn Andersson if (!smem->hwlock) 7244b638df4SBjorn Andersson return -ENXIO; 7254b638df4SBjorn Andersson 7264b638df4SBjorn Andersson __smem = smem; 7274b638df4SBjorn Andersson 7284b638df4SBjorn Andersson return 0; 7294b638df4SBjorn Andersson } 7304b638df4SBjorn Andersson 7314b638df4SBjorn Andersson static int qcom_smem_remove(struct platform_device *pdev) 7324b638df4SBjorn Andersson { 7334b638df4SBjorn Andersson hwspin_lock_free(__smem->hwlock); 734*f8c67df7SStephen Boyd __smem = NULL; 7354b638df4SBjorn Andersson 7364b638df4SBjorn Andersson return 0; 7374b638df4SBjorn Andersson } 7384b638df4SBjorn Andersson 7394b638df4SBjorn Andersson static const struct of_device_id qcom_smem_of_match[] = { 7404b638df4SBjorn Andersson { .compatible = "qcom,smem" }, 7414b638df4SBjorn Andersson {} 7424b638df4SBjorn Andersson }; 7434b638df4SBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smem_of_match); 7444b638df4SBjorn Andersson 7454b638df4SBjorn Andersson static struct platform_driver qcom_smem_driver = { 7464b638df4SBjorn Andersson .probe = qcom_smem_probe, 7474b638df4SBjorn Andersson .remove = qcom_smem_remove, 7484b638df4SBjorn Andersson .driver = { 7494b638df4SBjorn Andersson .name = "qcom-smem", 7504b638df4SBjorn Andersson .of_match_table = qcom_smem_of_match, 7514b638df4SBjorn Andersson .suppress_bind_attrs = true, 7524b638df4SBjorn Andersson }, 7534b638df4SBjorn Andersson }; 7544b638df4SBjorn Andersson 7554b638df4SBjorn Andersson static int __init qcom_smem_init(void) 7564b638df4SBjorn Andersson { 7574b638df4SBjorn Andersson return platform_driver_register(&qcom_smem_driver); 7584b638df4SBjorn Andersson } 7594b638df4SBjorn Andersson arch_initcall(qcom_smem_init); 7604b638df4SBjorn Andersson 7614b638df4SBjorn Andersson static void __exit qcom_smem_exit(void) 7624b638df4SBjorn Andersson { 7634b638df4SBjorn Andersson platform_driver_unregister(&qcom_smem_driver); 7644b638df4SBjorn Andersson } 7654b638df4SBjorn Andersson module_exit(qcom_smem_exit) 7664b638df4SBjorn Andersson 7674b638df4SBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 7684b638df4SBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); 7694b638df4SBjorn Andersson MODULE_LICENSE("GPL v2"); 770