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> 21*9487e2abSNiklas Cassel #include <linux/sizes.h> 224b638df4SBjorn Andersson #include <linux/slab.h> 234b638df4SBjorn Andersson #include <linux/soc/qcom/smem.h> 244b638df4SBjorn Andersson 254b638df4SBjorn Andersson /* 264b638df4SBjorn Andersson * The Qualcomm shared memory system is a allocate only heap structure that 274b638df4SBjorn Andersson * consists of one of more memory areas that can be accessed by the processors 284b638df4SBjorn Andersson * in the SoC. 294b638df4SBjorn Andersson * 304b638df4SBjorn Andersson * All systems contains a global heap, accessible by all processors in the SoC, 314b638df4SBjorn Andersson * with a table of contents data structure (@smem_header) at the beginning of 324b638df4SBjorn Andersson * the main shared memory block. 334b638df4SBjorn Andersson * 344b638df4SBjorn Andersson * The global header contains meta data for allocations as well as a fixed list 354b638df4SBjorn Andersson * of 512 entries (@smem_global_entry) that can be initialized to reference 364b638df4SBjorn Andersson * parts of the shared memory space. 374b638df4SBjorn Andersson * 384b638df4SBjorn Andersson * 394b638df4SBjorn Andersson * In addition to this global heap a set of "private" heaps can be set up at 404b638df4SBjorn Andersson * boot time with access restrictions so that only certain processor pairs can 414b638df4SBjorn Andersson * access the data. 424b638df4SBjorn Andersson * 434b638df4SBjorn Andersson * These partitions are referenced from an optional partition table 444b638df4SBjorn Andersson * (@smem_ptable), that is found 4kB from the end of the main smem region. The 454b638df4SBjorn Andersson * partition table entries (@smem_ptable_entry) lists the involved processors 464b638df4SBjorn Andersson * (or hosts) and their location in the main shared memory region. 474b638df4SBjorn Andersson * 484b638df4SBjorn Andersson * Each partition starts with a header (@smem_partition_header) that identifies 494b638df4SBjorn Andersson * the partition and holds properties for the two internal memory regions. The 504b638df4SBjorn Andersson * two regions are cached and non-cached memory respectively. Each region 514b638df4SBjorn Andersson * contain a link list of allocation headers (@smem_private_entry) followed by 524b638df4SBjorn Andersson * their data. 534b638df4SBjorn Andersson * 544b638df4SBjorn Andersson * Items in the non-cached region are allocated from the start of the partition 554b638df4SBjorn Andersson * while items in the cached region are allocated from the end. The free area 56c7c1dc35SBjorn Andersson * is hence the region between the cached and non-cached offsets. The header of 57c7c1dc35SBjorn Andersson * cached items comes after the data. 584b638df4SBjorn Andersson * 59d52e4048SChris Lew * Version 12 (SMEM_GLOBAL_PART_VERSION) changes the item alloc/get procedure 60d52e4048SChris Lew * for the global heap. A new global partition is created from the global heap 61d52e4048SChris Lew * region with partition type (SMEM_GLOBAL_HOST) and the max smem item count is 62d52e4048SChris Lew * set by the bootloader. 634b638df4SBjorn Andersson * 644b638df4SBjorn Andersson * To synchronize allocations in the shared memory heaps a remote spinlock must 654b638df4SBjorn Andersson * be held - currently lock number 3 of the sfpb or tcsr is used for this on all 664b638df4SBjorn Andersson * platforms. 674b638df4SBjorn Andersson * 684b638df4SBjorn Andersson */ 694b638df4SBjorn Andersson 704b638df4SBjorn Andersson /* 71dcc0967dSChris Lew * The version member of the smem header contains an array of versions for the 72dcc0967dSChris Lew * various software components in the SoC. We verify that the boot loader 73dcc0967dSChris Lew * version is a valid version as a sanity check. 744b638df4SBjorn Andersson */ 754b638df4SBjorn Andersson #define SMEM_MASTER_SBL_VERSION_INDEX 7 76d52e4048SChris Lew #define SMEM_GLOBAL_HEAP_VERSION 11 77d52e4048SChris Lew #define SMEM_GLOBAL_PART_VERSION 12 784b638df4SBjorn Andersson 794b638df4SBjorn Andersson /* 804b638df4SBjorn Andersson * The first 8 items are only to be allocated by the boot loader while 814b638df4SBjorn Andersson * initializing the heap. 824b638df4SBjorn Andersson */ 834b638df4SBjorn Andersson #define SMEM_ITEM_LAST_FIXED 8 844b638df4SBjorn Andersson 854b638df4SBjorn Andersson /* Highest accepted item number, for both global and private heaps */ 864b638df4SBjorn Andersson #define SMEM_ITEM_COUNT 512 874b638df4SBjorn Andersson 884b638df4SBjorn Andersson /* Processor/host identifier for the application processor */ 894b638df4SBjorn Andersson #define SMEM_HOST_APPS 0 904b638df4SBjorn Andersson 91d52e4048SChris Lew /* Processor/host identifier for the global partition */ 92d52e4048SChris Lew #define SMEM_GLOBAL_HOST 0xfffe 93d52e4048SChris Lew 944b638df4SBjorn Andersson /* Max number of processors/hosts in a system */ 95e691b48dSChris Lew #define SMEM_HOST_COUNT 10 964b638df4SBjorn Andersson 974b638df4SBjorn Andersson /** 984b638df4SBjorn Andersson * struct smem_proc_comm - proc_comm communication struct (legacy) 994b638df4SBjorn Andersson * @command: current command to be executed 1004b638df4SBjorn Andersson * @status: status of the currently requested command 1014b638df4SBjorn Andersson * @params: parameters to the command 1024b638df4SBjorn Andersson */ 1034b638df4SBjorn Andersson struct smem_proc_comm { 1049806884dSStephen Boyd __le32 command; 1059806884dSStephen Boyd __le32 status; 1069806884dSStephen Boyd __le32 params[2]; 1074b638df4SBjorn Andersson }; 1084b638df4SBjorn Andersson 1094b638df4SBjorn Andersson /** 1104b638df4SBjorn Andersson * struct smem_global_entry - entry to reference smem items on the heap 1114b638df4SBjorn Andersson * @allocated: boolean to indicate if this entry is used 1124b638df4SBjorn Andersson * @offset: offset to the allocated space 1134b638df4SBjorn Andersson * @size: size of the allocated space, 8 byte aligned 1144b638df4SBjorn Andersson * @aux_base: base address for the memory region used by this unit, or 0 for 1154b638df4SBjorn Andersson * the default region. bits 0,1 are reserved 1164b638df4SBjorn Andersson */ 1174b638df4SBjorn Andersson struct smem_global_entry { 1189806884dSStephen Boyd __le32 allocated; 1199806884dSStephen Boyd __le32 offset; 1209806884dSStephen Boyd __le32 size; 1219806884dSStephen Boyd __le32 aux_base; /* bits 1:0 reserved */ 1224b638df4SBjorn Andersson }; 1234b638df4SBjorn Andersson #define AUX_BASE_MASK 0xfffffffc 1244b638df4SBjorn Andersson 1254b638df4SBjorn Andersson /** 1264b638df4SBjorn Andersson * struct smem_header - header found in beginning of primary smem region 1274b638df4SBjorn Andersson * @proc_comm: proc_comm communication interface (legacy) 1284b638df4SBjorn Andersson * @version: array of versions for the various subsystems 1294b638df4SBjorn Andersson * @initialized: boolean to indicate that smem is initialized 1304b638df4SBjorn Andersson * @free_offset: index of the first unallocated byte in smem 1314b638df4SBjorn Andersson * @available: number of bytes available for allocation 1324b638df4SBjorn Andersson * @reserved: reserved field, must be 0 1334b638df4SBjorn Andersson * toc: array of references to items 1344b638df4SBjorn Andersson */ 1354b638df4SBjorn Andersson struct smem_header { 1364b638df4SBjorn Andersson struct smem_proc_comm proc_comm[4]; 1379806884dSStephen Boyd __le32 version[32]; 1389806884dSStephen Boyd __le32 initialized; 1399806884dSStephen Boyd __le32 free_offset; 1409806884dSStephen Boyd __le32 available; 1419806884dSStephen Boyd __le32 reserved; 1424b638df4SBjorn Andersson struct smem_global_entry toc[SMEM_ITEM_COUNT]; 1434b638df4SBjorn Andersson }; 1444b638df4SBjorn Andersson 1454b638df4SBjorn Andersson /** 1464b638df4SBjorn Andersson * struct smem_ptable_entry - one entry in the @smem_ptable list 1474b638df4SBjorn Andersson * @offset: offset, within the main shared memory region, of the partition 1484b638df4SBjorn Andersson * @size: size of the partition 1494b638df4SBjorn Andersson * @flags: flags for the partition (currently unused) 1504b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1514b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 152c7c1dc35SBjorn Andersson * @cacheline: alignment for "cached" entries 1534b638df4SBjorn Andersson * @reserved: reserved entries for later use 1544b638df4SBjorn Andersson */ 1554b638df4SBjorn Andersson struct smem_ptable_entry { 1569806884dSStephen Boyd __le32 offset; 1579806884dSStephen Boyd __le32 size; 1589806884dSStephen Boyd __le32 flags; 1599806884dSStephen Boyd __le16 host0; 1609806884dSStephen Boyd __le16 host1; 161c7c1dc35SBjorn Andersson __le32 cacheline; 162c7c1dc35SBjorn Andersson __le32 reserved[7]; 1634b638df4SBjorn Andersson }; 1644b638df4SBjorn Andersson 1654b638df4SBjorn Andersson /** 1664b638df4SBjorn Andersson * struct smem_ptable - partition table for the private partitions 1674b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PTABLE_MAGIC 1684b638df4SBjorn Andersson * @version: version of the partition table 1694b638df4SBjorn Andersson * @num_entries: number of partitions in the table 1704b638df4SBjorn Andersson * @reserved: for now reserved entries 1714b638df4SBjorn Andersson * @entry: list of @smem_ptable_entry for the @num_entries partitions 1724b638df4SBjorn Andersson */ 1734b638df4SBjorn Andersson struct smem_ptable { 1749806884dSStephen Boyd u8 magic[4]; 1759806884dSStephen Boyd __le32 version; 1769806884dSStephen Boyd __le32 num_entries; 1779806884dSStephen Boyd __le32 reserved[5]; 1784b638df4SBjorn Andersson struct smem_ptable_entry entry[]; 1794b638df4SBjorn Andersson }; 1809806884dSStephen Boyd 1819806884dSStephen Boyd static const u8 SMEM_PTABLE_MAGIC[] = { 0x24, 0x54, 0x4f, 0x43 }; /* "$TOC" */ 1824b638df4SBjorn Andersson 1834b638df4SBjorn Andersson /** 1844b638df4SBjorn Andersson * struct smem_partition_header - header of the partitions 1854b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PART_MAGIC 1864b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1874b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 1884b638df4SBjorn Andersson * @size: size of the partition 1894b638df4SBjorn Andersson * @offset_free_uncached: offset to the first free byte of uncached memory in 1904b638df4SBjorn Andersson * this partition 1914b638df4SBjorn Andersson * @offset_free_cached: offset to the first free byte of cached memory in this 1924b638df4SBjorn Andersson * partition 1934b638df4SBjorn Andersson * @reserved: for now reserved entries 1944b638df4SBjorn Andersson */ 1954b638df4SBjorn Andersson struct smem_partition_header { 1969806884dSStephen Boyd u8 magic[4]; 1979806884dSStephen Boyd __le16 host0; 1989806884dSStephen Boyd __le16 host1; 1999806884dSStephen Boyd __le32 size; 2009806884dSStephen Boyd __le32 offset_free_uncached; 2019806884dSStephen Boyd __le32 offset_free_cached; 2029806884dSStephen Boyd __le32 reserved[3]; 2034b638df4SBjorn Andersson }; 2049806884dSStephen Boyd 2059806884dSStephen Boyd static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; 2064b638df4SBjorn Andersson 2074b638df4SBjorn Andersson /** 2084b638df4SBjorn Andersson * struct smem_private_entry - header of each item in the private partition 2094b638df4SBjorn Andersson * @canary: magic number, must be SMEM_PRIVATE_CANARY 2104b638df4SBjorn Andersson * @item: identifying number of the smem item 2114b638df4SBjorn Andersson * @size: size of the data, including padding bytes 2124b638df4SBjorn Andersson * @padding_data: number of bytes of padding of data 2134b638df4SBjorn Andersson * @padding_hdr: number of bytes of padding between the header and the data 2144b638df4SBjorn Andersson * @reserved: for now reserved entry 2154b638df4SBjorn Andersson */ 2164b638df4SBjorn Andersson struct smem_private_entry { 2179806884dSStephen Boyd u16 canary; /* bytes are the same so no swapping needed */ 2189806884dSStephen Boyd __le16 item; 2199806884dSStephen Boyd __le32 size; /* includes padding bytes */ 2209806884dSStephen Boyd __le16 padding_data; 2219806884dSStephen Boyd __le16 padding_hdr; 2229806884dSStephen Boyd __le32 reserved; 2234b638df4SBjorn Andersson }; 2244b638df4SBjorn Andersson #define SMEM_PRIVATE_CANARY 0xa5a5 2254b638df4SBjorn Andersson 2264b638df4SBjorn Andersson /** 2275b394067SChris Lew * struct smem_info - smem region info located after the table of contents 2285b394067SChris Lew * @magic: magic number, must be SMEM_INFO_MAGIC 2295b394067SChris Lew * @size: size of the smem region 2305b394067SChris Lew * @base_addr: base address of the smem region 2315b394067SChris Lew * @reserved: for now reserved entry 2325b394067SChris Lew * @num_items: highest accepted item number 2335b394067SChris Lew */ 2345b394067SChris Lew struct smem_info { 2355b394067SChris Lew u8 magic[4]; 2365b394067SChris Lew __le32 size; 2375b394067SChris Lew __le32 base_addr; 2385b394067SChris Lew __le32 reserved; 2395b394067SChris Lew __le16 num_items; 2405b394067SChris Lew }; 2415b394067SChris Lew 2425b394067SChris Lew static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ 2435b394067SChris Lew 2445b394067SChris Lew /** 2454b638df4SBjorn Andersson * struct smem_region - representation of a chunk of memory used for smem 2464b638df4SBjorn Andersson * @aux_base: identifier of aux_mem base 2474b638df4SBjorn Andersson * @virt_base: virtual base address of memory with this aux_mem identifier 2484b638df4SBjorn Andersson * @size: size of the memory region 2494b638df4SBjorn Andersson */ 2504b638df4SBjorn Andersson struct smem_region { 2514b638df4SBjorn Andersson u32 aux_base; 2524b638df4SBjorn Andersson void __iomem *virt_base; 2534b638df4SBjorn Andersson size_t size; 2544b638df4SBjorn Andersson }; 2554b638df4SBjorn Andersson 2564b638df4SBjorn Andersson /** 2574b638df4SBjorn Andersson * struct qcom_smem - device data for the smem device 2584b638df4SBjorn Andersson * @dev: device pointer 2594b638df4SBjorn Andersson * @hwlock: reference to a hwspinlock 260d52e4048SChris Lew * @global_partition: pointer to global partition when in use 261d52e4048SChris Lew * @global_cacheline: cacheline size for global partition 2624b638df4SBjorn Andersson * @partitions: list of pointers to partitions affecting the current 2634b638df4SBjorn Andersson * processor/host 264c7c1dc35SBjorn Andersson * @cacheline: list of cacheline sizes for each host 2655b394067SChris Lew * @item_count: max accepted item number 2664b638df4SBjorn Andersson * @num_regions: number of @regions 2674b638df4SBjorn Andersson * @regions: list of the memory regions defining the shared memory 2684b638df4SBjorn Andersson */ 2694b638df4SBjorn Andersson struct qcom_smem { 2704b638df4SBjorn Andersson struct device *dev; 2714b638df4SBjorn Andersson 2724b638df4SBjorn Andersson struct hwspinlock *hwlock; 2734b638df4SBjorn Andersson 274d52e4048SChris Lew struct smem_partition_header *global_partition; 275d52e4048SChris Lew size_t global_cacheline; 2764b638df4SBjorn Andersson struct smem_partition_header *partitions[SMEM_HOST_COUNT]; 277c7c1dc35SBjorn Andersson size_t cacheline[SMEM_HOST_COUNT]; 2785b394067SChris Lew u32 item_count; 2794b638df4SBjorn Andersson 2804b638df4SBjorn Andersson unsigned num_regions; 2814b638df4SBjorn Andersson struct smem_region regions[0]; 2824b638df4SBjorn Andersson }; 2834b638df4SBjorn Andersson 284e221a1daSAlex Elder static void * 28501f14154SBjorn Andersson phdr_to_last_uncached_entry(struct smem_partition_header *phdr) 2869806884dSStephen Boyd { 2879806884dSStephen Boyd void *p = phdr; 2889806884dSStephen Boyd 2899806884dSStephen Boyd return p + le32_to_cpu(phdr->offset_free_uncached); 2909806884dSStephen Boyd } 2919806884dSStephen Boyd 292e221a1daSAlex Elder static struct smem_private_entry * 293e221a1daSAlex Elder phdr_to_first_cached_entry(struct smem_partition_header *phdr, 294c7c1dc35SBjorn Andersson size_t cacheline) 295c7c1dc35SBjorn Andersson { 296c7c1dc35SBjorn Andersson void *p = phdr; 29770708749SAlex Elder struct smem_private_entry *e; 298c7c1dc35SBjorn Andersson 29970708749SAlex Elder return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); 300c7c1dc35SBjorn Andersson } 301c7c1dc35SBjorn Andersson 302e221a1daSAlex Elder static void * 303e221a1daSAlex Elder phdr_to_last_cached_entry(struct smem_partition_header *phdr) 3049806884dSStephen Boyd { 3059806884dSStephen Boyd void *p = phdr; 3069806884dSStephen Boyd 3079806884dSStephen Boyd return p + le32_to_cpu(phdr->offset_free_cached); 3089806884dSStephen Boyd } 3099806884dSStephen Boyd 3109806884dSStephen Boyd static struct smem_private_entry * 31101f14154SBjorn Andersson phdr_to_first_uncached_entry(struct smem_partition_header *phdr) 3129806884dSStephen Boyd { 3139806884dSStephen Boyd void *p = phdr; 3149806884dSStephen Boyd 3159806884dSStephen Boyd return p + sizeof(*phdr); 3169806884dSStephen Boyd } 3179806884dSStephen Boyd 3189806884dSStephen Boyd static struct smem_private_entry * 31901f14154SBjorn Andersson uncached_entry_next(struct smem_private_entry *e) 3209806884dSStephen Boyd { 3219806884dSStephen Boyd void *p = e; 3229806884dSStephen Boyd 3239806884dSStephen Boyd return p + sizeof(*e) + le16_to_cpu(e->padding_hdr) + 3249806884dSStephen Boyd le32_to_cpu(e->size); 3259806884dSStephen Boyd } 3269806884dSStephen Boyd 327c7c1dc35SBjorn Andersson static struct smem_private_entry * 328c7c1dc35SBjorn Andersson cached_entry_next(struct smem_private_entry *e, size_t cacheline) 329c7c1dc35SBjorn Andersson { 330c7c1dc35SBjorn Andersson void *p = e; 331c7c1dc35SBjorn Andersson 332c7c1dc35SBjorn Andersson return p - le32_to_cpu(e->size) - ALIGN(sizeof(*e), cacheline); 333c7c1dc35SBjorn Andersson } 334c7c1dc35SBjorn Andersson 33501f14154SBjorn Andersson static void *uncached_entry_to_item(struct smem_private_entry *e) 3369806884dSStephen Boyd { 3379806884dSStephen Boyd void *p = e; 3389806884dSStephen Boyd 3399806884dSStephen Boyd return p + sizeof(*e) + le16_to_cpu(e->padding_hdr); 3409806884dSStephen Boyd } 3419806884dSStephen Boyd 342c7c1dc35SBjorn Andersson static void *cached_entry_to_item(struct smem_private_entry *e) 343c7c1dc35SBjorn Andersson { 344c7c1dc35SBjorn Andersson void *p = e; 345c7c1dc35SBjorn Andersson 346c7c1dc35SBjorn Andersson return p - le32_to_cpu(e->size); 347c7c1dc35SBjorn Andersson } 348c7c1dc35SBjorn Andersson 3494b638df4SBjorn Andersson /* Pointer to the one and only smem handle */ 3504b638df4SBjorn Andersson static struct qcom_smem *__smem; 3514b638df4SBjorn Andersson 3524b638df4SBjorn Andersson /* Timeout (ms) for the trylock of remote spinlocks */ 3534b638df4SBjorn Andersson #define HWSPINLOCK_TIMEOUT 1000 3544b638df4SBjorn Andersson 3554b638df4SBjorn Andersson static int qcom_smem_alloc_private(struct qcom_smem *smem, 356d52e4048SChris Lew struct smem_partition_header *phdr, 3574b638df4SBjorn Andersson unsigned item, 3584b638df4SBjorn Andersson size_t size) 3594b638df4SBjorn Andersson { 3609806884dSStephen Boyd struct smem_private_entry *hdr, *end; 3614b638df4SBjorn Andersson size_t alloc_size; 3629806884dSStephen Boyd void *cached; 3634b638df4SBjorn Andersson 36401f14154SBjorn Andersson hdr = phdr_to_first_uncached_entry(phdr); 36501f14154SBjorn Andersson end = phdr_to_last_uncached_entry(phdr); 36601f14154SBjorn Andersson cached = phdr_to_last_cached_entry(phdr); 3674b638df4SBjorn Andersson 3689806884dSStephen Boyd while (hdr < end) { 36904a512feSAlex Elder if (hdr->canary != SMEM_PRIVATE_CANARY) 37004a512feSAlex Elder goto bad_canary; 3719806884dSStephen Boyd if (le16_to_cpu(hdr->item) == item) 3724b638df4SBjorn Andersson return -EEXIST; 3734b638df4SBjorn Andersson 37401f14154SBjorn Andersson hdr = uncached_entry_next(hdr); 3754b638df4SBjorn Andersson } 3764b638df4SBjorn Andersson 3774b638df4SBjorn Andersson /* Check that we don't grow into the cached region */ 3784b638df4SBjorn Andersson alloc_size = sizeof(*hdr) + ALIGN(size, 8); 3798377f818SAlex Elder if ((void *)hdr + alloc_size > cached) { 3804b638df4SBjorn Andersson dev_err(smem->dev, "Out of memory\n"); 3814b638df4SBjorn Andersson return -ENOSPC; 3824b638df4SBjorn Andersson } 3834b638df4SBjorn Andersson 3844b638df4SBjorn Andersson hdr->canary = SMEM_PRIVATE_CANARY; 3859806884dSStephen Boyd hdr->item = cpu_to_le16(item); 3869806884dSStephen Boyd hdr->size = cpu_to_le32(ALIGN(size, 8)); 3879806884dSStephen Boyd hdr->padding_data = cpu_to_le16(le32_to_cpu(hdr->size) - size); 3884b638df4SBjorn Andersson hdr->padding_hdr = 0; 3894b638df4SBjorn Andersson 3904b638df4SBjorn Andersson /* 3914b638df4SBjorn Andersson * Ensure the header is written before we advance the free offset, so 3924b638df4SBjorn Andersson * that remote processors that does not take the remote spinlock still 3934b638df4SBjorn Andersson * gets a consistent view of the linked list. 3944b638df4SBjorn Andersson */ 3954b638df4SBjorn Andersson wmb(); 3969806884dSStephen Boyd le32_add_cpu(&phdr->offset_free_uncached, alloc_size); 3974b638df4SBjorn Andersson 3984b638df4SBjorn Andersson return 0; 39904a512feSAlex Elder bad_canary: 40004a512feSAlex Elder dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", 40104a512feSAlex Elder le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); 40204a512feSAlex Elder 40304a512feSAlex Elder return -EINVAL; 4044b638df4SBjorn Andersson } 4054b638df4SBjorn Andersson 4064b638df4SBjorn Andersson static int qcom_smem_alloc_global(struct qcom_smem *smem, 4074b638df4SBjorn Andersson unsigned item, 4084b638df4SBjorn Andersson size_t size) 4094b638df4SBjorn Andersson { 4104b638df4SBjorn Andersson struct smem_global_entry *entry; 411d52e4048SChris Lew struct smem_header *header; 4124b638df4SBjorn Andersson 4134b638df4SBjorn Andersson header = smem->regions[0].virt_base; 4144b638df4SBjorn Andersson entry = &header->toc[item]; 4154b638df4SBjorn Andersson if (entry->allocated) 4164b638df4SBjorn Andersson return -EEXIST; 4174b638df4SBjorn Andersson 4184b638df4SBjorn Andersson size = ALIGN(size, 8); 4199806884dSStephen Boyd if (WARN_ON(size > le32_to_cpu(header->available))) 4204b638df4SBjorn Andersson return -ENOMEM; 4214b638df4SBjorn Andersson 4224b638df4SBjorn Andersson entry->offset = header->free_offset; 4239806884dSStephen Boyd entry->size = cpu_to_le32(size); 4244b638df4SBjorn Andersson 4254b638df4SBjorn Andersson /* 4264b638df4SBjorn Andersson * Ensure the header is consistent before we mark the item allocated, 4274b638df4SBjorn Andersson * so that remote processors will get a consistent view of the item 4284b638df4SBjorn Andersson * even though they do not take the spinlock on read. 4294b638df4SBjorn Andersson */ 4304b638df4SBjorn Andersson wmb(); 4319806884dSStephen Boyd entry->allocated = cpu_to_le32(1); 4324b638df4SBjorn Andersson 4339806884dSStephen Boyd le32_add_cpu(&header->free_offset, size); 4349806884dSStephen Boyd le32_add_cpu(&header->available, -size); 4354b638df4SBjorn Andersson 4364b638df4SBjorn Andersson return 0; 4374b638df4SBjorn Andersson } 4384b638df4SBjorn Andersson 4394b638df4SBjorn Andersson /** 4404b638df4SBjorn Andersson * qcom_smem_alloc() - allocate space for a smem item 4414b638df4SBjorn Andersson * @host: remote processor id, or -1 4424b638df4SBjorn Andersson * @item: smem item handle 4434b638df4SBjorn Andersson * @size: number of bytes to be allocated 4444b638df4SBjorn Andersson * 4454b638df4SBjorn Andersson * Allocate space for a given smem item of size @size, given that the item is 4464b638df4SBjorn Andersson * not yet allocated. 4474b638df4SBjorn Andersson */ 4484b638df4SBjorn Andersson int qcom_smem_alloc(unsigned host, unsigned item, size_t size) 4494b638df4SBjorn Andersson { 450d52e4048SChris Lew struct smem_partition_header *phdr; 4514b638df4SBjorn Andersson unsigned long flags; 4524b638df4SBjorn Andersson int ret; 4534b638df4SBjorn Andersson 4544b638df4SBjorn Andersson if (!__smem) 4554b638df4SBjorn Andersson return -EPROBE_DEFER; 4564b638df4SBjorn Andersson 4574b638df4SBjorn Andersson if (item < SMEM_ITEM_LAST_FIXED) { 4584b638df4SBjorn Andersson dev_err(__smem->dev, 4594b638df4SBjorn Andersson "Rejecting allocation of static entry %d\n", item); 4604b638df4SBjorn Andersson return -EINVAL; 4614b638df4SBjorn Andersson } 4624b638df4SBjorn Andersson 4635b394067SChris Lew if (WARN_ON(item >= __smem->item_count)) 4645b394067SChris Lew return -EINVAL; 4655b394067SChris Lew 4664b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 4674b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 4684b638df4SBjorn Andersson &flags); 4694b638df4SBjorn Andersson if (ret) 4704b638df4SBjorn Andersson return ret; 4714b638df4SBjorn Andersson 472d52e4048SChris Lew if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 473d52e4048SChris Lew phdr = __smem->partitions[host]; 474d52e4048SChris Lew ret = qcom_smem_alloc_private(__smem, phdr, item, size); 475d52e4048SChris Lew } else if (__smem->global_partition) { 476d52e4048SChris Lew phdr = __smem->global_partition; 477d52e4048SChris Lew ret = qcom_smem_alloc_private(__smem, phdr, item, size); 478d52e4048SChris Lew } else { 4794b638df4SBjorn Andersson ret = qcom_smem_alloc_global(__smem, item, size); 480d52e4048SChris Lew } 4814b638df4SBjorn Andersson 4824b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 4834b638df4SBjorn Andersson 4844b638df4SBjorn Andersson return ret; 4854b638df4SBjorn Andersson } 4864b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_alloc); 4874b638df4SBjorn Andersson 4881a03964dSStephen Boyd static void *qcom_smem_get_global(struct qcom_smem *smem, 4894b638df4SBjorn Andersson unsigned item, 4904b638df4SBjorn Andersson size_t *size) 4914b638df4SBjorn Andersson { 4924b638df4SBjorn Andersson struct smem_header *header; 4934b638df4SBjorn Andersson struct smem_region *area; 4944b638df4SBjorn Andersson struct smem_global_entry *entry; 4954b638df4SBjorn Andersson u32 aux_base; 4964b638df4SBjorn Andersson unsigned i; 4974b638df4SBjorn Andersson 4984b638df4SBjorn Andersson header = smem->regions[0].virt_base; 4994b638df4SBjorn Andersson entry = &header->toc[item]; 5004b638df4SBjorn Andersson if (!entry->allocated) 5011a03964dSStephen Boyd return ERR_PTR(-ENXIO); 5024b638df4SBjorn Andersson 5039806884dSStephen Boyd aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; 5044b638df4SBjorn Andersson 5054b638df4SBjorn Andersson for (i = 0; i < smem->num_regions; i++) { 5064b638df4SBjorn Andersson area = &smem->regions[i]; 5074b638df4SBjorn Andersson 5084b638df4SBjorn Andersson if (area->aux_base == aux_base || !aux_base) { 5094b638df4SBjorn Andersson if (size != NULL) 5109806884dSStephen Boyd *size = le32_to_cpu(entry->size); 5119806884dSStephen Boyd return area->virt_base + le32_to_cpu(entry->offset); 5121a03964dSStephen Boyd } 5134b638df4SBjorn Andersson } 5144b638df4SBjorn Andersson 5151a03964dSStephen Boyd return ERR_PTR(-ENOENT); 5161a03964dSStephen Boyd } 5171a03964dSStephen Boyd 5181a03964dSStephen Boyd static void *qcom_smem_get_private(struct qcom_smem *smem, 519d52e4048SChris Lew struct smem_partition_header *phdr, 520d52e4048SChris Lew size_t cacheline, 5214b638df4SBjorn Andersson unsigned item, 5224b638df4SBjorn Andersson size_t *size) 5234b638df4SBjorn Andersson { 5249806884dSStephen Boyd struct smem_private_entry *e, *end; 525c7c1dc35SBjorn Andersson 52601f14154SBjorn Andersson e = phdr_to_first_uncached_entry(phdr); 52701f14154SBjorn Andersson end = phdr_to_last_uncached_entry(phdr); 5284b638df4SBjorn Andersson 5299806884dSStephen Boyd while (e < end) { 530c7c1dc35SBjorn Andersson if (e->canary != SMEM_PRIVATE_CANARY) 531c7c1dc35SBjorn Andersson goto invalid_canary; 5324b638df4SBjorn Andersson 5339806884dSStephen Boyd if (le16_to_cpu(e->item) == item) { 5344b638df4SBjorn Andersson if (size != NULL) 5359806884dSStephen Boyd *size = le32_to_cpu(e->size) - 5369806884dSStephen Boyd le16_to_cpu(e->padding_data); 5374b638df4SBjorn Andersson 53801f14154SBjorn Andersson return uncached_entry_to_item(e); 5394b638df4SBjorn Andersson } 5404b638df4SBjorn Andersson 54101f14154SBjorn Andersson e = uncached_entry_next(e); 5424b638df4SBjorn Andersson } 5434b638df4SBjorn Andersson 544c7c1dc35SBjorn Andersson /* Item was not found in the uncached list, search the cached list */ 545c7c1dc35SBjorn Andersson 546c7c1dc35SBjorn Andersson e = phdr_to_first_cached_entry(phdr, cacheline); 547c7c1dc35SBjorn Andersson end = phdr_to_last_cached_entry(phdr); 548c7c1dc35SBjorn Andersson 549c7c1dc35SBjorn Andersson while (e > end) { 550c7c1dc35SBjorn Andersson if (e->canary != SMEM_PRIVATE_CANARY) 551c7c1dc35SBjorn Andersson goto invalid_canary; 552c7c1dc35SBjorn Andersson 553c7c1dc35SBjorn Andersson if (le16_to_cpu(e->item) == item) { 554c7c1dc35SBjorn Andersson if (size != NULL) 555c7c1dc35SBjorn Andersson *size = le32_to_cpu(e->size) - 556c7c1dc35SBjorn Andersson le16_to_cpu(e->padding_data); 557c7c1dc35SBjorn Andersson 558c7c1dc35SBjorn Andersson return cached_entry_to_item(e); 559c7c1dc35SBjorn Andersson } 560c7c1dc35SBjorn Andersson 561c7c1dc35SBjorn Andersson e = cached_entry_next(e, cacheline); 562c7c1dc35SBjorn Andersson } 563c7c1dc35SBjorn Andersson 5641a03964dSStephen Boyd return ERR_PTR(-ENOENT); 565c7c1dc35SBjorn Andersson 566c7c1dc35SBjorn Andersson invalid_canary: 56704a512feSAlex Elder dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", 56804a512feSAlex Elder le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); 569c7c1dc35SBjorn Andersson 570c7c1dc35SBjorn Andersson return ERR_PTR(-EINVAL); 5714b638df4SBjorn Andersson } 5724b638df4SBjorn Andersson 5734b638df4SBjorn Andersson /** 5744b638df4SBjorn Andersson * qcom_smem_get() - resolve ptr of size of a smem item 5754b638df4SBjorn Andersson * @host: the remote processor, or -1 5764b638df4SBjorn Andersson * @item: smem item handle 5774b638df4SBjorn Andersson * @size: pointer to be filled out with size of the item 5784b638df4SBjorn Andersson * 5791a03964dSStephen Boyd * Looks up smem item and returns pointer to it. Size of smem 5801a03964dSStephen Boyd * item is returned in @size. 5814b638df4SBjorn Andersson */ 5821a03964dSStephen Boyd void *qcom_smem_get(unsigned host, unsigned item, size_t *size) 5834b638df4SBjorn Andersson { 584d52e4048SChris Lew struct smem_partition_header *phdr; 5854b638df4SBjorn Andersson unsigned long flags; 586d52e4048SChris Lew size_t cacheln; 5874b638df4SBjorn Andersson int ret; 5881a03964dSStephen Boyd void *ptr = ERR_PTR(-EPROBE_DEFER); 5894b638df4SBjorn Andersson 5904b638df4SBjorn Andersson if (!__smem) 5911a03964dSStephen Boyd return ptr; 5924b638df4SBjorn Andersson 5935b394067SChris Lew if (WARN_ON(item >= __smem->item_count)) 5945b394067SChris Lew return ERR_PTR(-EINVAL); 5955b394067SChris Lew 5964b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 5974b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 5984b638df4SBjorn Andersson &flags); 5994b638df4SBjorn Andersson if (ret) 6001a03964dSStephen Boyd return ERR_PTR(ret); 6014b638df4SBjorn Andersson 602d52e4048SChris Lew if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 603d52e4048SChris Lew phdr = __smem->partitions[host]; 604d52e4048SChris Lew cacheln = __smem->cacheline[host]; 605d52e4048SChris Lew ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); 606d52e4048SChris Lew } else if (__smem->global_partition) { 607d52e4048SChris Lew phdr = __smem->global_partition; 608d52e4048SChris Lew cacheln = __smem->global_cacheline; 609d52e4048SChris Lew ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); 610d52e4048SChris Lew } else { 6111a03964dSStephen Boyd ptr = qcom_smem_get_global(__smem, item, size); 612d52e4048SChris Lew } 6134b638df4SBjorn Andersson 6144b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 6151a03964dSStephen Boyd 6161a03964dSStephen Boyd return ptr; 6174b638df4SBjorn Andersson 6184b638df4SBjorn Andersson } 6194b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get); 6204b638df4SBjorn Andersson 6214b638df4SBjorn Andersson /** 6224b638df4SBjorn Andersson * qcom_smem_get_free_space() - retrieve amount of free space in a partition 6234b638df4SBjorn Andersson * @host: the remote processor identifying a partition, or -1 6244b638df4SBjorn Andersson * 6254b638df4SBjorn Andersson * To be used by smem clients as a quick way to determine if any new 6264b638df4SBjorn Andersson * allocations has been made. 6274b638df4SBjorn Andersson */ 6284b638df4SBjorn Andersson int qcom_smem_get_free_space(unsigned host) 6294b638df4SBjorn Andersson { 6304b638df4SBjorn Andersson struct smem_partition_header *phdr; 6314b638df4SBjorn Andersson struct smem_header *header; 6324b638df4SBjorn Andersson unsigned ret; 6334b638df4SBjorn Andersson 6344b638df4SBjorn Andersson if (!__smem) 6354b638df4SBjorn Andersson return -EPROBE_DEFER; 6364b638df4SBjorn Andersson 6374b638df4SBjorn Andersson if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 6384b638df4SBjorn Andersson phdr = __smem->partitions[host]; 6399806884dSStephen Boyd ret = le32_to_cpu(phdr->offset_free_cached) - 6409806884dSStephen Boyd le32_to_cpu(phdr->offset_free_uncached); 641d52e4048SChris Lew } else if (__smem->global_partition) { 642d52e4048SChris Lew phdr = __smem->global_partition; 643d52e4048SChris Lew ret = le32_to_cpu(phdr->offset_free_cached) - 644d52e4048SChris Lew le32_to_cpu(phdr->offset_free_uncached); 6454b638df4SBjorn Andersson } else { 6464b638df4SBjorn Andersson header = __smem->regions[0].virt_base; 6479806884dSStephen Boyd ret = le32_to_cpu(header->available); 6484b638df4SBjorn Andersson } 6494b638df4SBjorn Andersson 6504b638df4SBjorn Andersson return ret; 6514b638df4SBjorn Andersson } 6524b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get_free_space); 6534b638df4SBjorn Andersson 6546d361c1dSAlex Elder /** 6556d361c1dSAlex Elder * qcom_smem_virt_to_phys() - return the physical address associated 6566d361c1dSAlex Elder * with an smem item pointer (previously returned by qcom_smem_get() 6576d361c1dSAlex Elder * @p: the virtual address to convert 6586d361c1dSAlex Elder * 6596d361c1dSAlex Elder * Returns 0 if the pointer provided is not within any smem region. 6606d361c1dSAlex Elder */ 6616d361c1dSAlex Elder phys_addr_t qcom_smem_virt_to_phys(void *p) 6626d361c1dSAlex Elder { 6636d361c1dSAlex Elder unsigned i; 6646d361c1dSAlex Elder 6656d361c1dSAlex Elder for (i = 0; i < __smem->num_regions; i++) { 6666d361c1dSAlex Elder struct smem_region *region = &__smem->regions[i]; 6676d361c1dSAlex Elder 6686d361c1dSAlex Elder if (p < region->virt_base) 6696d361c1dSAlex Elder continue; 6706d361c1dSAlex Elder if (p < region->virt_base + region->size) { 6716d361c1dSAlex Elder u64 offset = p - region->virt_base; 6726d361c1dSAlex Elder 6736d361c1dSAlex Elder return (phys_addr_t)region->aux_base + offset; 6746d361c1dSAlex Elder } 6756d361c1dSAlex Elder } 6766d361c1dSAlex Elder 6776d361c1dSAlex Elder return 0; 6786d361c1dSAlex Elder } 6796d361c1dSAlex Elder EXPORT_SYMBOL(qcom_smem_virt_to_phys); 6806d361c1dSAlex Elder 6814b638df4SBjorn Andersson static int qcom_smem_get_sbl_version(struct qcom_smem *smem) 6824b638df4SBjorn Andersson { 683dcc0967dSChris Lew struct smem_header *header; 6849806884dSStephen Boyd __le32 *versions; 6854b638df4SBjorn Andersson 686dcc0967dSChris Lew header = smem->regions[0].virt_base; 687dcc0967dSChris Lew versions = header->version; 6884b638df4SBjorn Andersson 6899806884dSStephen Boyd return le32_to_cpu(versions[SMEM_MASTER_SBL_VERSION_INDEX]); 6904b638df4SBjorn Andersson } 6914b638df4SBjorn Andersson 692d52e4048SChris Lew static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem) 6934b638df4SBjorn Andersson { 6944b638df4SBjorn Andersson struct smem_ptable *ptable; 695d52e4048SChris Lew u32 version; 6964b638df4SBjorn Andersson 6974b638df4SBjorn Andersson ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; 6989806884dSStephen Boyd if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) 699d52e4048SChris Lew return ERR_PTR(-ENOENT); 7004b638df4SBjorn Andersson 7019806884dSStephen Boyd version = le32_to_cpu(ptable->version); 7029806884dSStephen Boyd if (version != 1) { 7034b638df4SBjorn Andersson dev_err(smem->dev, 7049806884dSStephen Boyd "Unsupported partition header version %d\n", version); 705d52e4048SChris Lew return ERR_PTR(-EINVAL); 706d52e4048SChris Lew } 707d52e4048SChris Lew return ptable; 708d52e4048SChris Lew } 709d52e4048SChris Lew 7105b394067SChris Lew static u32 qcom_smem_get_item_count(struct qcom_smem *smem) 7115b394067SChris Lew { 7125b394067SChris Lew struct smem_ptable *ptable; 7135b394067SChris Lew struct smem_info *info; 7145b394067SChris Lew 7155b394067SChris Lew ptable = qcom_smem_get_ptable(smem); 7165b394067SChris Lew if (IS_ERR_OR_NULL(ptable)) 7175b394067SChris Lew return SMEM_ITEM_COUNT; 7185b394067SChris Lew 7195b394067SChris Lew info = (struct smem_info *)&ptable->entry[ptable->num_entries]; 7205b394067SChris Lew if (memcmp(info->magic, SMEM_INFO_MAGIC, sizeof(info->magic))) 7215b394067SChris Lew return SMEM_ITEM_COUNT; 7225b394067SChris Lew 7235b394067SChris Lew return le16_to_cpu(info->num_items); 7245b394067SChris Lew } 7255b394067SChris Lew 726d52e4048SChris Lew static int qcom_smem_set_global_partition(struct qcom_smem *smem) 727d52e4048SChris Lew { 728d52e4048SChris Lew struct smem_partition_header *header; 7298fa1a214SAlex Elder struct smem_ptable_entry *entry; 730d52e4048SChris Lew struct smem_ptable *ptable; 731d52e4048SChris Lew u32 host0, host1, size; 7328fa1a214SAlex Elder bool found = false; 733d52e4048SChris Lew int i; 734d52e4048SChris Lew 7350b65c59eSBjorn Andersson if (smem->global_partition) { 7360b65c59eSBjorn Andersson dev_err(smem->dev, "Already found the global partition\n"); 7370b65c59eSBjorn Andersson return -EINVAL; 7380b65c59eSBjorn Andersson } 7390b65c59eSBjorn Andersson 740d52e4048SChris Lew ptable = qcom_smem_get_ptable(smem); 741d52e4048SChris Lew if (IS_ERR(ptable)) 742d52e4048SChris Lew return PTR_ERR(ptable); 743d52e4048SChris Lew 744d52e4048SChris Lew for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { 745d52e4048SChris Lew entry = &ptable->entry[i]; 746d52e4048SChris Lew host0 = le16_to_cpu(entry->host0); 747d52e4048SChris Lew host1 = le16_to_cpu(entry->host1); 748d52e4048SChris Lew 7498fa1a214SAlex Elder if (host0 == SMEM_GLOBAL_HOST && host0 == host1) { 7508fa1a214SAlex Elder found = true; 751d52e4048SChris Lew break; 752d52e4048SChris Lew } 7538fa1a214SAlex Elder } 754d52e4048SChris Lew 7558fa1a214SAlex Elder if (!found) { 756d52e4048SChris Lew dev_err(smem->dev, "Missing entry for global partition\n"); 7574b638df4SBjorn Andersson return -EINVAL; 7584b638df4SBjorn Andersson } 7594b638df4SBjorn Andersson 760d52e4048SChris Lew if (!le32_to_cpu(entry->offset) || !le32_to_cpu(entry->size)) { 761d52e4048SChris Lew dev_err(smem->dev, "Invalid entry for global partition\n"); 762d52e4048SChris Lew return -EINVAL; 763d52e4048SChris Lew } 764d52e4048SChris Lew 765d52e4048SChris Lew header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); 766d52e4048SChris Lew host0 = le16_to_cpu(header->host0); 767d52e4048SChris Lew host1 = le16_to_cpu(header->host1); 768d52e4048SChris Lew 769d52e4048SChris Lew if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { 770d52e4048SChris Lew dev_err(smem->dev, "Global partition has invalid magic\n"); 771d52e4048SChris Lew return -EINVAL; 772d52e4048SChris Lew } 773d52e4048SChris Lew 774d52e4048SChris Lew if (host0 != SMEM_GLOBAL_HOST && host1 != SMEM_GLOBAL_HOST) { 775d52e4048SChris Lew dev_err(smem->dev, "Global partition hosts are invalid\n"); 776d52e4048SChris Lew return -EINVAL; 777d52e4048SChris Lew } 778d52e4048SChris Lew 779d52e4048SChris Lew if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { 780d52e4048SChris Lew dev_err(smem->dev, "Global partition has invalid size\n"); 781d52e4048SChris Lew return -EINVAL; 782d52e4048SChris Lew } 783d52e4048SChris Lew 784d52e4048SChris Lew size = le32_to_cpu(header->offset_free_uncached); 785d52e4048SChris Lew if (size > le32_to_cpu(header->size)) { 786d52e4048SChris Lew dev_err(smem->dev, 787d52e4048SChris Lew "Global partition has invalid free pointer\n"); 788d52e4048SChris Lew return -EINVAL; 789d52e4048SChris Lew } 790d52e4048SChris Lew 791d52e4048SChris Lew smem->global_partition = header; 792d52e4048SChris Lew smem->global_cacheline = le32_to_cpu(entry->cacheline); 793d52e4048SChris Lew 794d52e4048SChris Lew return 0; 795d52e4048SChris Lew } 796d52e4048SChris Lew 797d52e4048SChris Lew static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, 798d52e4048SChris Lew unsigned int local_host) 799d52e4048SChris Lew { 800d52e4048SChris Lew struct smem_partition_header *header; 801d52e4048SChris Lew struct smem_ptable_entry *entry; 802d52e4048SChris Lew struct smem_ptable *ptable; 803d52e4048SChris Lew unsigned int remote_host; 804d52e4048SChris Lew u32 host0, host1; 805d52e4048SChris Lew int i; 806d52e4048SChris Lew 807d52e4048SChris Lew ptable = qcom_smem_get_ptable(smem); 808d52e4048SChris Lew if (IS_ERR(ptable)) 809d52e4048SChris Lew return PTR_ERR(ptable); 810d52e4048SChris Lew 8119806884dSStephen Boyd for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { 8124b638df4SBjorn Andersson entry = &ptable->entry[i]; 8139806884dSStephen Boyd host0 = le16_to_cpu(entry->host0); 8149806884dSStephen Boyd host1 = le16_to_cpu(entry->host1); 8154b638df4SBjorn Andersson 8169806884dSStephen Boyd if (host0 != local_host && host1 != local_host) 8174b638df4SBjorn Andersson continue; 8184b638df4SBjorn Andersson 8199806884dSStephen Boyd if (!le32_to_cpu(entry->offset)) 8204b638df4SBjorn Andersson continue; 8214b638df4SBjorn Andersson 8229806884dSStephen Boyd if (!le32_to_cpu(entry->size)) 8234b638df4SBjorn Andersson continue; 8244b638df4SBjorn Andersson 8259806884dSStephen Boyd if (host0 == local_host) 8269806884dSStephen Boyd remote_host = host1; 8274b638df4SBjorn Andersson else 8289806884dSStephen Boyd remote_host = host0; 8294b638df4SBjorn Andersson 8304b638df4SBjorn Andersson if (remote_host >= SMEM_HOST_COUNT) { 8314b638df4SBjorn Andersson dev_err(smem->dev, 8324b638df4SBjorn Andersson "Invalid remote host %d\n", 8334b638df4SBjorn Andersson remote_host); 8344b638df4SBjorn Andersson return -EINVAL; 8354b638df4SBjorn Andersson } 8364b638df4SBjorn Andersson 8374b638df4SBjorn Andersson if (smem->partitions[remote_host]) { 8384b638df4SBjorn Andersson dev_err(smem->dev, 8394b638df4SBjorn Andersson "Already found a partition for host %d\n", 8404b638df4SBjorn Andersson remote_host); 8414b638df4SBjorn Andersson return -EINVAL; 8424b638df4SBjorn Andersson } 8434b638df4SBjorn Andersson 8449806884dSStephen Boyd header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); 8459806884dSStephen Boyd host0 = le16_to_cpu(header->host0); 8469806884dSStephen Boyd host1 = le16_to_cpu(header->host1); 8474b638df4SBjorn Andersson 8489806884dSStephen Boyd if (memcmp(header->magic, SMEM_PART_MAGIC, 8499806884dSStephen Boyd sizeof(header->magic))) { 8504b638df4SBjorn Andersson dev_err(smem->dev, 8514b638df4SBjorn Andersson "Partition %d has invalid magic\n", i); 8524b638df4SBjorn Andersson return -EINVAL; 8534b638df4SBjorn Andersson } 8544b638df4SBjorn Andersson 8559806884dSStephen Boyd if (host0 != local_host && host1 != local_host) { 8564b638df4SBjorn Andersson dev_err(smem->dev, 8574b638df4SBjorn Andersson "Partition %d hosts are invalid\n", i); 8584b638df4SBjorn Andersson return -EINVAL; 8594b638df4SBjorn Andersson } 8604b638df4SBjorn Andersson 8619806884dSStephen Boyd if (host0 != remote_host && host1 != remote_host) { 8624b638df4SBjorn Andersson dev_err(smem->dev, 8634b638df4SBjorn Andersson "Partition %d hosts are invalid\n", i); 8644b638df4SBjorn Andersson return -EINVAL; 8654b638df4SBjorn Andersson } 8664b638df4SBjorn Andersson 867a216000fSChris Lew if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { 8684b638df4SBjorn Andersson dev_err(smem->dev, 8694b638df4SBjorn Andersson "Partition %d has invalid size\n", i); 8704b638df4SBjorn Andersson return -EINVAL; 8714b638df4SBjorn Andersson } 8724b638df4SBjorn Andersson 8739806884dSStephen Boyd if (le32_to_cpu(header->offset_free_uncached) > le32_to_cpu(header->size)) { 8744b638df4SBjorn Andersson dev_err(smem->dev, 8754b638df4SBjorn Andersson "Partition %d has invalid free pointer\n", i); 8764b638df4SBjorn Andersson return -EINVAL; 8774b638df4SBjorn Andersson } 8784b638df4SBjorn Andersson 8794b638df4SBjorn Andersson smem->partitions[remote_host] = header; 880c7c1dc35SBjorn Andersson smem->cacheline[remote_host] = le32_to_cpu(entry->cacheline); 8814b638df4SBjorn Andersson } 8824b638df4SBjorn Andersson 8834b638df4SBjorn Andersson return 0; 8844b638df4SBjorn Andersson } 8854b638df4SBjorn Andersson 886d0bfd7c9SStephen Boyd static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, 887d0bfd7c9SStephen Boyd const char *name, int i) 8884b638df4SBjorn Andersson { 889d0bfd7c9SStephen Boyd struct device_node *np; 890d0bfd7c9SStephen Boyd struct resource r; 891d0bfd7c9SStephen Boyd int ret; 8924b638df4SBjorn Andersson 893d0bfd7c9SStephen Boyd np = of_parse_phandle(dev->of_node, name, 0); 894d0bfd7c9SStephen Boyd if (!np) { 895d0bfd7c9SStephen Boyd dev_err(dev, "No %s specified\n", name); 896d0bfd7c9SStephen Boyd return -EINVAL; 8974b638df4SBjorn Andersson } 8984b638df4SBjorn Andersson 899d0bfd7c9SStephen Boyd ret = of_address_to_resource(np, 0, &r); 900d0bfd7c9SStephen Boyd of_node_put(np); 901d0bfd7c9SStephen Boyd if (ret) 902d0bfd7c9SStephen Boyd return ret; 903d0bfd7c9SStephen Boyd 904d0bfd7c9SStephen Boyd smem->regions[i].aux_base = (u32)r.start; 905d0bfd7c9SStephen Boyd smem->regions[i].size = resource_size(&r); 906afd356dfSBjorn Andersson smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, resource_size(&r)); 907d0bfd7c9SStephen Boyd if (!smem->regions[i].virt_base) 908d0bfd7c9SStephen Boyd return -ENOMEM; 909d0bfd7c9SStephen Boyd 910d0bfd7c9SStephen Boyd return 0; 9114b638df4SBjorn Andersson } 9124b638df4SBjorn Andersson 9134b638df4SBjorn Andersson static int qcom_smem_probe(struct platform_device *pdev) 9144b638df4SBjorn Andersson { 9154b638df4SBjorn Andersson struct smem_header *header; 9164b638df4SBjorn Andersson struct qcom_smem *smem; 9174b638df4SBjorn Andersson size_t array_size; 918d0bfd7c9SStephen Boyd int num_regions; 9194b638df4SBjorn Andersson int hwlock_id; 9204b638df4SBjorn Andersson u32 version; 9214b638df4SBjorn Andersson int ret; 9224b638df4SBjorn Andersson 923d0bfd7c9SStephen Boyd num_regions = 1; 924d0bfd7c9SStephen Boyd if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL)) 925d0bfd7c9SStephen Boyd num_regions++; 9264b638df4SBjorn Andersson 9274b638df4SBjorn Andersson array_size = num_regions * sizeof(struct smem_region); 9284b638df4SBjorn Andersson smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); 9294b638df4SBjorn Andersson if (!smem) 9304b638df4SBjorn Andersson return -ENOMEM; 9314b638df4SBjorn Andersson 9324b638df4SBjorn Andersson smem->dev = &pdev->dev; 9334b638df4SBjorn Andersson smem->num_regions = num_regions; 9344b638df4SBjorn Andersson 935d0bfd7c9SStephen Boyd ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); 9364b638df4SBjorn Andersson if (ret) 9374b638df4SBjorn Andersson return ret; 9384b638df4SBjorn Andersson 939d0bfd7c9SStephen Boyd if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev, 940d0bfd7c9SStephen Boyd "qcom,rpm-msg-ram", 1))) 941d0bfd7c9SStephen Boyd return ret; 9424b638df4SBjorn Andersson 9434b638df4SBjorn Andersson header = smem->regions[0].virt_base; 9449806884dSStephen Boyd if (le32_to_cpu(header->initialized) != 1 || 9459806884dSStephen Boyd le32_to_cpu(header->reserved)) { 9464b638df4SBjorn Andersson dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); 9474b638df4SBjorn Andersson return -EINVAL; 9484b638df4SBjorn Andersson } 9494b638df4SBjorn Andersson 9504b638df4SBjorn Andersson version = qcom_smem_get_sbl_version(smem); 951d52e4048SChris Lew switch (version >> 16) { 952d52e4048SChris Lew case SMEM_GLOBAL_PART_VERSION: 953d52e4048SChris Lew ret = qcom_smem_set_global_partition(smem); 954d52e4048SChris Lew if (ret < 0) 955d52e4048SChris Lew return ret; 9565b394067SChris Lew smem->item_count = qcom_smem_get_item_count(smem); 9575b394067SChris Lew break; 958d52e4048SChris Lew case SMEM_GLOBAL_HEAP_VERSION: 9595b394067SChris Lew smem->item_count = SMEM_ITEM_COUNT; 960d52e4048SChris Lew break; 961d52e4048SChris Lew default: 9624b638df4SBjorn Andersson dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); 9634b638df4SBjorn Andersson return -EINVAL; 9644b638df4SBjorn Andersson } 9654b638df4SBjorn Andersson 9664b638df4SBjorn Andersson ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); 967d52e4048SChris Lew if (ret < 0 && ret != -ENOENT) 9684b638df4SBjorn Andersson return ret; 9694b638df4SBjorn Andersson 9704b638df4SBjorn Andersson hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); 9714b638df4SBjorn Andersson if (hwlock_id < 0) { 972951a5af9SStephen Boyd if (hwlock_id != -EPROBE_DEFER) 9734b638df4SBjorn Andersson dev_err(&pdev->dev, "failed to retrieve hwlock\n"); 9744b638df4SBjorn Andersson return hwlock_id; 9754b638df4SBjorn Andersson } 9764b638df4SBjorn Andersson 9774b638df4SBjorn Andersson smem->hwlock = hwspin_lock_request_specific(hwlock_id); 9784b638df4SBjorn Andersson if (!smem->hwlock) 9794b638df4SBjorn Andersson return -ENXIO; 9804b638df4SBjorn Andersson 9814b638df4SBjorn Andersson __smem = smem; 9824b638df4SBjorn Andersson 9834b638df4SBjorn Andersson return 0; 9844b638df4SBjorn Andersson } 9854b638df4SBjorn Andersson 9864b638df4SBjorn Andersson static int qcom_smem_remove(struct platform_device *pdev) 9874b638df4SBjorn Andersson { 9884b638df4SBjorn Andersson hwspin_lock_free(__smem->hwlock); 989f8c67df7SStephen Boyd __smem = NULL; 9904b638df4SBjorn Andersson 9914b638df4SBjorn Andersson return 0; 9924b638df4SBjorn Andersson } 9934b638df4SBjorn Andersson 9944b638df4SBjorn Andersson static const struct of_device_id qcom_smem_of_match[] = { 9954b638df4SBjorn Andersson { .compatible = "qcom,smem" }, 9964b638df4SBjorn Andersson {} 9974b638df4SBjorn Andersson }; 9984b638df4SBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smem_of_match); 9994b638df4SBjorn Andersson 10004b638df4SBjorn Andersson static struct platform_driver qcom_smem_driver = { 10014b638df4SBjorn Andersson .probe = qcom_smem_probe, 10024b638df4SBjorn Andersson .remove = qcom_smem_remove, 10034b638df4SBjorn Andersson .driver = { 10044b638df4SBjorn Andersson .name = "qcom-smem", 10054b638df4SBjorn Andersson .of_match_table = qcom_smem_of_match, 10064b638df4SBjorn Andersson .suppress_bind_attrs = true, 10074b638df4SBjorn Andersson }, 10084b638df4SBjorn Andersson }; 10094b638df4SBjorn Andersson 10104b638df4SBjorn Andersson static int __init qcom_smem_init(void) 10114b638df4SBjorn Andersson { 10124b638df4SBjorn Andersson return platform_driver_register(&qcom_smem_driver); 10134b638df4SBjorn Andersson } 10144b638df4SBjorn Andersson arch_initcall(qcom_smem_init); 10154b638df4SBjorn Andersson 10164b638df4SBjorn Andersson static void __exit qcom_smem_exit(void) 10174b638df4SBjorn Andersson { 10184b638df4SBjorn Andersson platform_driver_unregister(&qcom_smem_driver); 10194b638df4SBjorn Andersson } 10204b638df4SBjorn Andersson module_exit(qcom_smem_exit) 10214b638df4SBjorn Andersson 10224b638df4SBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 10234b638df4SBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); 10244b638df4SBjorn Andersson MODULE_LICENSE("GPL v2"); 1025