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 55c7c1dc35SBjorn Andersson * is hence the region between the cached and non-cached offsets. The header of 56c7c1dc35SBjorn Andersson * cached items comes after the data. 574b638df4SBjorn Andersson * 58d52e4048SChris Lew * Version 12 (SMEM_GLOBAL_PART_VERSION) changes the item alloc/get procedure 59d52e4048SChris Lew * for the global heap. A new global partition is created from the global heap 60d52e4048SChris Lew * region with partition type (SMEM_GLOBAL_HOST) and the max smem item count is 61d52e4048SChris Lew * set by the bootloader. 624b638df4SBjorn Andersson * 634b638df4SBjorn Andersson * To synchronize allocations in the shared memory heaps a remote spinlock must 644b638df4SBjorn Andersson * be held - currently lock number 3 of the sfpb or tcsr is used for this on all 654b638df4SBjorn Andersson * platforms. 664b638df4SBjorn Andersson * 674b638df4SBjorn Andersson */ 684b638df4SBjorn Andersson 694b638df4SBjorn Andersson /* 70dcc0967dSChris Lew * The version member of the smem header contains an array of versions for the 71dcc0967dSChris Lew * various software components in the SoC. We verify that the boot loader 72dcc0967dSChris Lew * version is a valid version as a sanity check. 734b638df4SBjorn Andersson */ 744b638df4SBjorn Andersson #define SMEM_MASTER_SBL_VERSION_INDEX 7 75d52e4048SChris Lew #define SMEM_GLOBAL_HEAP_VERSION 11 76d52e4048SChris Lew #define SMEM_GLOBAL_PART_VERSION 12 774b638df4SBjorn Andersson 784b638df4SBjorn Andersson /* 794b638df4SBjorn Andersson * The first 8 items are only to be allocated by the boot loader while 804b638df4SBjorn Andersson * initializing the heap. 814b638df4SBjorn Andersson */ 824b638df4SBjorn Andersson #define SMEM_ITEM_LAST_FIXED 8 834b638df4SBjorn Andersson 844b638df4SBjorn Andersson /* Highest accepted item number, for both global and private heaps */ 854b638df4SBjorn Andersson #define SMEM_ITEM_COUNT 512 864b638df4SBjorn Andersson 874b638df4SBjorn Andersson /* Processor/host identifier for the application processor */ 884b638df4SBjorn Andersson #define SMEM_HOST_APPS 0 894b638df4SBjorn Andersson 90d52e4048SChris Lew /* Processor/host identifier for the global partition */ 91d52e4048SChris Lew #define SMEM_GLOBAL_HOST 0xfffe 92d52e4048SChris Lew 934b638df4SBjorn Andersson /* Max number of processors/hosts in a system */ 94e691b48dSChris Lew #define SMEM_HOST_COUNT 10 954b638df4SBjorn Andersson 964b638df4SBjorn Andersson /** 974b638df4SBjorn Andersson * struct smem_proc_comm - proc_comm communication struct (legacy) 984b638df4SBjorn Andersson * @command: current command to be executed 994b638df4SBjorn Andersson * @status: status of the currently requested command 1004b638df4SBjorn Andersson * @params: parameters to the command 1014b638df4SBjorn Andersson */ 1024b638df4SBjorn Andersson struct smem_proc_comm { 1039806884dSStephen Boyd __le32 command; 1049806884dSStephen Boyd __le32 status; 1059806884dSStephen Boyd __le32 params[2]; 1064b638df4SBjorn Andersson }; 1074b638df4SBjorn Andersson 1084b638df4SBjorn Andersson /** 1094b638df4SBjorn Andersson * struct smem_global_entry - entry to reference smem items on the heap 1104b638df4SBjorn Andersson * @allocated: boolean to indicate if this entry is used 1114b638df4SBjorn Andersson * @offset: offset to the allocated space 1124b638df4SBjorn Andersson * @size: size of the allocated space, 8 byte aligned 1134b638df4SBjorn Andersson * @aux_base: base address for the memory region used by this unit, or 0 for 1144b638df4SBjorn Andersson * the default region. bits 0,1 are reserved 1154b638df4SBjorn Andersson */ 1164b638df4SBjorn Andersson struct smem_global_entry { 1179806884dSStephen Boyd __le32 allocated; 1189806884dSStephen Boyd __le32 offset; 1199806884dSStephen Boyd __le32 size; 1209806884dSStephen Boyd __le32 aux_base; /* bits 1:0 reserved */ 1214b638df4SBjorn Andersson }; 1224b638df4SBjorn Andersson #define AUX_BASE_MASK 0xfffffffc 1234b638df4SBjorn Andersson 1244b638df4SBjorn Andersson /** 1254b638df4SBjorn Andersson * struct smem_header - header found in beginning of primary smem region 1264b638df4SBjorn Andersson * @proc_comm: proc_comm communication interface (legacy) 1274b638df4SBjorn Andersson * @version: array of versions for the various subsystems 1284b638df4SBjorn Andersson * @initialized: boolean to indicate that smem is initialized 1294b638df4SBjorn Andersson * @free_offset: index of the first unallocated byte in smem 1304b638df4SBjorn Andersson * @available: number of bytes available for allocation 1314b638df4SBjorn Andersson * @reserved: reserved field, must be 0 1324b638df4SBjorn Andersson * toc: array of references to items 1334b638df4SBjorn Andersson */ 1344b638df4SBjorn Andersson struct smem_header { 1354b638df4SBjorn Andersson struct smem_proc_comm proc_comm[4]; 1369806884dSStephen Boyd __le32 version[32]; 1379806884dSStephen Boyd __le32 initialized; 1389806884dSStephen Boyd __le32 free_offset; 1399806884dSStephen Boyd __le32 available; 1409806884dSStephen Boyd __le32 reserved; 1414b638df4SBjorn Andersson struct smem_global_entry toc[SMEM_ITEM_COUNT]; 1424b638df4SBjorn Andersson }; 1434b638df4SBjorn Andersson 1444b638df4SBjorn Andersson /** 1454b638df4SBjorn Andersson * struct smem_ptable_entry - one entry in the @smem_ptable list 1464b638df4SBjorn Andersson * @offset: offset, within the main shared memory region, of the partition 1474b638df4SBjorn Andersson * @size: size of the partition 1484b638df4SBjorn Andersson * @flags: flags for the partition (currently unused) 1494b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1504b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 151c7c1dc35SBjorn Andersson * @cacheline: alignment for "cached" entries 1524b638df4SBjorn Andersson * @reserved: reserved entries for later use 1534b638df4SBjorn Andersson */ 1544b638df4SBjorn Andersson struct smem_ptable_entry { 1559806884dSStephen Boyd __le32 offset; 1569806884dSStephen Boyd __le32 size; 1579806884dSStephen Boyd __le32 flags; 1589806884dSStephen Boyd __le16 host0; 1599806884dSStephen Boyd __le16 host1; 160c7c1dc35SBjorn Andersson __le32 cacheline; 161c7c1dc35SBjorn Andersson __le32 reserved[7]; 1624b638df4SBjorn Andersson }; 1634b638df4SBjorn Andersson 1644b638df4SBjorn Andersson /** 1654b638df4SBjorn Andersson * struct smem_ptable - partition table for the private partitions 1664b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PTABLE_MAGIC 1674b638df4SBjorn Andersson * @version: version of the partition table 1684b638df4SBjorn Andersson * @num_entries: number of partitions in the table 1694b638df4SBjorn Andersson * @reserved: for now reserved entries 1704b638df4SBjorn Andersson * @entry: list of @smem_ptable_entry for the @num_entries partitions 1714b638df4SBjorn Andersson */ 1724b638df4SBjorn Andersson struct smem_ptable { 1739806884dSStephen Boyd u8 magic[4]; 1749806884dSStephen Boyd __le32 version; 1759806884dSStephen Boyd __le32 num_entries; 1769806884dSStephen Boyd __le32 reserved[5]; 1774b638df4SBjorn Andersson struct smem_ptable_entry entry[]; 1784b638df4SBjorn Andersson }; 1799806884dSStephen Boyd 1809806884dSStephen Boyd static const u8 SMEM_PTABLE_MAGIC[] = { 0x24, 0x54, 0x4f, 0x43 }; /* "$TOC" */ 1814b638df4SBjorn Andersson 1824b638df4SBjorn Andersson /** 1834b638df4SBjorn Andersson * struct smem_partition_header - header of the partitions 1844b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PART_MAGIC 1854b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1864b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 1874b638df4SBjorn Andersson * @size: size of the partition 1884b638df4SBjorn Andersson * @offset_free_uncached: offset to the first free byte of uncached memory in 1894b638df4SBjorn Andersson * this partition 1904b638df4SBjorn Andersson * @offset_free_cached: offset to the first free byte of cached memory in this 1914b638df4SBjorn Andersson * partition 1924b638df4SBjorn Andersson * @reserved: for now reserved entries 1934b638df4SBjorn Andersson */ 1944b638df4SBjorn Andersson struct smem_partition_header { 1959806884dSStephen Boyd u8 magic[4]; 1969806884dSStephen Boyd __le16 host0; 1979806884dSStephen Boyd __le16 host1; 1989806884dSStephen Boyd __le32 size; 1999806884dSStephen Boyd __le32 offset_free_uncached; 2009806884dSStephen Boyd __le32 offset_free_cached; 2019806884dSStephen Boyd __le32 reserved[3]; 2024b638df4SBjorn Andersson }; 2039806884dSStephen Boyd 2049806884dSStephen Boyd static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; 2054b638df4SBjorn Andersson 2064b638df4SBjorn Andersson /** 2074b638df4SBjorn Andersson * struct smem_private_entry - header of each item in the private partition 2084b638df4SBjorn Andersson * @canary: magic number, must be SMEM_PRIVATE_CANARY 2094b638df4SBjorn Andersson * @item: identifying number of the smem item 2104b638df4SBjorn Andersson * @size: size of the data, including padding bytes 2114b638df4SBjorn Andersson * @padding_data: number of bytes of padding of data 2124b638df4SBjorn Andersson * @padding_hdr: number of bytes of padding between the header and the data 2134b638df4SBjorn Andersson * @reserved: for now reserved entry 2144b638df4SBjorn Andersson */ 2154b638df4SBjorn Andersson struct smem_private_entry { 2169806884dSStephen Boyd u16 canary; /* bytes are the same so no swapping needed */ 2179806884dSStephen Boyd __le16 item; 2189806884dSStephen Boyd __le32 size; /* includes padding bytes */ 2199806884dSStephen Boyd __le16 padding_data; 2209806884dSStephen Boyd __le16 padding_hdr; 2219806884dSStephen Boyd __le32 reserved; 2224b638df4SBjorn Andersson }; 2234b638df4SBjorn Andersson #define SMEM_PRIVATE_CANARY 0xa5a5 2244b638df4SBjorn Andersson 2254b638df4SBjorn Andersson /** 2265b394067SChris Lew * struct smem_info - smem region info located after the table of contents 2275b394067SChris Lew * @magic: magic number, must be SMEM_INFO_MAGIC 2285b394067SChris Lew * @size: size of the smem region 2295b394067SChris Lew * @base_addr: base address of the smem region 2305b394067SChris Lew * @reserved: for now reserved entry 2315b394067SChris Lew * @num_items: highest accepted item number 2325b394067SChris Lew */ 2335b394067SChris Lew struct smem_info { 2345b394067SChris Lew u8 magic[4]; 2355b394067SChris Lew __le32 size; 2365b394067SChris Lew __le32 base_addr; 2375b394067SChris Lew __le32 reserved; 2385b394067SChris Lew __le16 num_items; 2395b394067SChris Lew }; 2405b394067SChris Lew 2415b394067SChris Lew static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ 2425b394067SChris Lew 2435b394067SChris Lew /** 2444b638df4SBjorn Andersson * struct smem_region - representation of a chunk of memory used for smem 2454b638df4SBjorn Andersson * @aux_base: identifier of aux_mem base 2464b638df4SBjorn Andersson * @virt_base: virtual base address of memory with this aux_mem identifier 2474b638df4SBjorn Andersson * @size: size of the memory region 2484b638df4SBjorn Andersson */ 2494b638df4SBjorn Andersson struct smem_region { 2504b638df4SBjorn Andersson u32 aux_base; 2514b638df4SBjorn Andersson void __iomem *virt_base; 2524b638df4SBjorn Andersson size_t size; 2534b638df4SBjorn Andersson }; 2544b638df4SBjorn Andersson 2554b638df4SBjorn Andersson /** 2564b638df4SBjorn Andersson * struct qcom_smem - device data for the smem device 2574b638df4SBjorn Andersson * @dev: device pointer 2584b638df4SBjorn Andersson * @hwlock: reference to a hwspinlock 259d52e4048SChris Lew * @global_partition: pointer to global partition when in use 260d52e4048SChris Lew * @global_cacheline: cacheline size for global partition 2614b638df4SBjorn Andersson * @partitions: list of pointers to partitions affecting the current 2624b638df4SBjorn Andersson * processor/host 263c7c1dc35SBjorn Andersson * @cacheline: list of cacheline sizes for each host 2645b394067SChris Lew * @item_count: max accepted item number 2654b638df4SBjorn Andersson * @num_regions: number of @regions 2664b638df4SBjorn Andersson * @regions: list of the memory regions defining the shared memory 2674b638df4SBjorn Andersson */ 2684b638df4SBjorn Andersson struct qcom_smem { 2694b638df4SBjorn Andersson struct device *dev; 2704b638df4SBjorn Andersson 2714b638df4SBjorn Andersson struct hwspinlock *hwlock; 2724b638df4SBjorn Andersson 273d52e4048SChris Lew struct smem_partition_header *global_partition; 274d52e4048SChris Lew size_t global_cacheline; 2754b638df4SBjorn Andersson struct smem_partition_header *partitions[SMEM_HOST_COUNT]; 276c7c1dc35SBjorn Andersson size_t cacheline[SMEM_HOST_COUNT]; 2775b394067SChris Lew u32 item_count; 2784b638df4SBjorn Andersson 2794b638df4SBjorn Andersson unsigned num_regions; 2804b638df4SBjorn Andersson struct smem_region regions[0]; 2814b638df4SBjorn Andersson }; 2824b638df4SBjorn Andersson 283e221a1daSAlex Elder static void * 28401f14154SBjorn Andersson phdr_to_last_uncached_entry(struct smem_partition_header *phdr) 2859806884dSStephen Boyd { 2869806884dSStephen Boyd void *p = phdr; 2879806884dSStephen Boyd 2889806884dSStephen Boyd return p + le32_to_cpu(phdr->offset_free_uncached); 2899806884dSStephen Boyd } 2909806884dSStephen Boyd 291e221a1daSAlex Elder static struct smem_private_entry * 292e221a1daSAlex Elder phdr_to_first_cached_entry(struct smem_partition_header *phdr, 293c7c1dc35SBjorn Andersson size_t cacheline) 294c7c1dc35SBjorn Andersson { 295c7c1dc35SBjorn Andersson void *p = phdr; 29670708749SAlex Elder struct smem_private_entry *e; 297c7c1dc35SBjorn Andersson 29870708749SAlex Elder return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); 299c7c1dc35SBjorn Andersson } 300c7c1dc35SBjorn Andersson 301e221a1daSAlex Elder static void * 302e221a1daSAlex Elder phdr_to_last_cached_entry(struct smem_partition_header *phdr) 3039806884dSStephen Boyd { 3049806884dSStephen Boyd void *p = phdr; 3059806884dSStephen Boyd 3069806884dSStephen Boyd return p + le32_to_cpu(phdr->offset_free_cached); 3079806884dSStephen Boyd } 3089806884dSStephen Boyd 3099806884dSStephen Boyd static struct smem_private_entry * 31001f14154SBjorn Andersson phdr_to_first_uncached_entry(struct smem_partition_header *phdr) 3119806884dSStephen Boyd { 3129806884dSStephen Boyd void *p = phdr; 3139806884dSStephen Boyd 3149806884dSStephen Boyd return p + sizeof(*phdr); 3159806884dSStephen Boyd } 3169806884dSStephen Boyd 3179806884dSStephen Boyd static struct smem_private_entry * 31801f14154SBjorn Andersson uncached_entry_next(struct smem_private_entry *e) 3199806884dSStephen Boyd { 3209806884dSStephen Boyd void *p = e; 3219806884dSStephen Boyd 3229806884dSStephen Boyd return p + sizeof(*e) + le16_to_cpu(e->padding_hdr) + 3239806884dSStephen Boyd le32_to_cpu(e->size); 3249806884dSStephen Boyd } 3259806884dSStephen Boyd 326c7c1dc35SBjorn Andersson static struct smem_private_entry * 327c7c1dc35SBjorn Andersson cached_entry_next(struct smem_private_entry *e, size_t cacheline) 328c7c1dc35SBjorn Andersson { 329c7c1dc35SBjorn Andersson void *p = e; 330c7c1dc35SBjorn Andersson 331c7c1dc35SBjorn Andersson return p - le32_to_cpu(e->size) - ALIGN(sizeof(*e), cacheline); 332c7c1dc35SBjorn Andersson } 333c7c1dc35SBjorn Andersson 33401f14154SBjorn Andersson static void *uncached_entry_to_item(struct smem_private_entry *e) 3359806884dSStephen Boyd { 3369806884dSStephen Boyd void *p = e; 3379806884dSStephen Boyd 3389806884dSStephen Boyd return p + sizeof(*e) + le16_to_cpu(e->padding_hdr); 3399806884dSStephen Boyd } 3409806884dSStephen Boyd 341c7c1dc35SBjorn Andersson static void *cached_entry_to_item(struct smem_private_entry *e) 342c7c1dc35SBjorn Andersson { 343c7c1dc35SBjorn Andersson void *p = e; 344c7c1dc35SBjorn Andersson 345c7c1dc35SBjorn Andersson return p - le32_to_cpu(e->size); 346c7c1dc35SBjorn Andersson } 347c7c1dc35SBjorn Andersson 3484b638df4SBjorn Andersson /* Pointer to the one and only smem handle */ 3494b638df4SBjorn Andersson static struct qcom_smem *__smem; 3504b638df4SBjorn Andersson 3514b638df4SBjorn Andersson /* Timeout (ms) for the trylock of remote spinlocks */ 3524b638df4SBjorn Andersson #define HWSPINLOCK_TIMEOUT 1000 3534b638df4SBjorn Andersson 3544b638df4SBjorn Andersson static int qcom_smem_alloc_private(struct qcom_smem *smem, 355d52e4048SChris Lew struct smem_partition_header *phdr, 3564b638df4SBjorn Andersson unsigned item, 3574b638df4SBjorn Andersson size_t size) 3584b638df4SBjorn Andersson { 3599806884dSStephen Boyd struct smem_private_entry *hdr, *end; 3604b638df4SBjorn Andersson size_t alloc_size; 3619806884dSStephen Boyd void *cached; 3624b638df4SBjorn Andersson 36301f14154SBjorn Andersson hdr = phdr_to_first_uncached_entry(phdr); 36401f14154SBjorn Andersson end = phdr_to_last_uncached_entry(phdr); 36501f14154SBjorn Andersson cached = phdr_to_last_cached_entry(phdr); 3664b638df4SBjorn Andersson 3679806884dSStephen Boyd while (hdr < end) { 36804a512feSAlex Elder if (hdr->canary != SMEM_PRIVATE_CANARY) 36904a512feSAlex Elder goto bad_canary; 3709806884dSStephen Boyd if (le16_to_cpu(hdr->item) == item) 3714b638df4SBjorn Andersson return -EEXIST; 3724b638df4SBjorn Andersson 37301f14154SBjorn Andersson hdr = uncached_entry_next(hdr); 3744b638df4SBjorn Andersson } 3754b638df4SBjorn Andersson 3764b638df4SBjorn Andersson /* Check that we don't grow into the cached region */ 3774b638df4SBjorn Andersson alloc_size = sizeof(*hdr) + ALIGN(size, 8); 3788377f818SAlex Elder if ((void *)hdr + alloc_size > cached) { 3794b638df4SBjorn Andersson dev_err(smem->dev, "Out of memory\n"); 3804b638df4SBjorn Andersson return -ENOSPC; 3814b638df4SBjorn Andersson } 3824b638df4SBjorn Andersson 3834b638df4SBjorn Andersson hdr->canary = SMEM_PRIVATE_CANARY; 3849806884dSStephen Boyd hdr->item = cpu_to_le16(item); 3859806884dSStephen Boyd hdr->size = cpu_to_le32(ALIGN(size, 8)); 3869806884dSStephen Boyd hdr->padding_data = cpu_to_le16(le32_to_cpu(hdr->size) - size); 3874b638df4SBjorn Andersson hdr->padding_hdr = 0; 3884b638df4SBjorn Andersson 3894b638df4SBjorn Andersson /* 3904b638df4SBjorn Andersson * Ensure the header is written before we advance the free offset, so 3914b638df4SBjorn Andersson * that remote processors that does not take the remote spinlock still 3924b638df4SBjorn Andersson * gets a consistent view of the linked list. 3934b638df4SBjorn Andersson */ 3944b638df4SBjorn Andersson wmb(); 3959806884dSStephen Boyd le32_add_cpu(&phdr->offset_free_uncached, alloc_size); 3964b638df4SBjorn Andersson 3974b638df4SBjorn Andersson return 0; 39804a512feSAlex Elder bad_canary: 39904a512feSAlex Elder dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", 40004a512feSAlex Elder le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); 40104a512feSAlex Elder 40204a512feSAlex Elder return -EINVAL; 4034b638df4SBjorn Andersson } 4044b638df4SBjorn Andersson 4054b638df4SBjorn Andersson static int qcom_smem_alloc_global(struct qcom_smem *smem, 4064b638df4SBjorn Andersson unsigned item, 4074b638df4SBjorn Andersson size_t size) 4084b638df4SBjorn Andersson { 4094b638df4SBjorn Andersson struct smem_global_entry *entry; 410d52e4048SChris Lew struct smem_header *header; 4114b638df4SBjorn Andersson 4124b638df4SBjorn Andersson header = smem->regions[0].virt_base; 4134b638df4SBjorn Andersson entry = &header->toc[item]; 4144b638df4SBjorn Andersson if (entry->allocated) 4154b638df4SBjorn Andersson return -EEXIST; 4164b638df4SBjorn Andersson 4174b638df4SBjorn Andersson size = ALIGN(size, 8); 4189806884dSStephen Boyd if (WARN_ON(size > le32_to_cpu(header->available))) 4194b638df4SBjorn Andersson return -ENOMEM; 4204b638df4SBjorn Andersson 4214b638df4SBjorn Andersson entry->offset = header->free_offset; 4229806884dSStephen Boyd entry->size = cpu_to_le32(size); 4234b638df4SBjorn Andersson 4244b638df4SBjorn Andersson /* 4254b638df4SBjorn Andersson * Ensure the header is consistent before we mark the item allocated, 4264b638df4SBjorn Andersson * so that remote processors will get a consistent view of the item 4274b638df4SBjorn Andersson * even though they do not take the spinlock on read. 4284b638df4SBjorn Andersson */ 4294b638df4SBjorn Andersson wmb(); 4309806884dSStephen Boyd entry->allocated = cpu_to_le32(1); 4314b638df4SBjorn Andersson 4329806884dSStephen Boyd le32_add_cpu(&header->free_offset, size); 4339806884dSStephen Boyd le32_add_cpu(&header->available, -size); 4344b638df4SBjorn Andersson 4354b638df4SBjorn Andersson return 0; 4364b638df4SBjorn Andersson } 4374b638df4SBjorn Andersson 4384b638df4SBjorn Andersson /** 4394b638df4SBjorn Andersson * qcom_smem_alloc() - allocate space for a smem item 4404b638df4SBjorn Andersson * @host: remote processor id, or -1 4414b638df4SBjorn Andersson * @item: smem item handle 4424b638df4SBjorn Andersson * @size: number of bytes to be allocated 4434b638df4SBjorn Andersson * 4444b638df4SBjorn Andersson * Allocate space for a given smem item of size @size, given that the item is 4454b638df4SBjorn Andersson * not yet allocated. 4464b638df4SBjorn Andersson */ 4474b638df4SBjorn Andersson int qcom_smem_alloc(unsigned host, unsigned item, size_t size) 4484b638df4SBjorn Andersson { 449d52e4048SChris Lew struct smem_partition_header *phdr; 4504b638df4SBjorn Andersson unsigned long flags; 4514b638df4SBjorn Andersson int ret; 4524b638df4SBjorn Andersson 4534b638df4SBjorn Andersson if (!__smem) 4544b638df4SBjorn Andersson return -EPROBE_DEFER; 4554b638df4SBjorn Andersson 4564b638df4SBjorn Andersson if (item < SMEM_ITEM_LAST_FIXED) { 4574b638df4SBjorn Andersson dev_err(__smem->dev, 4584b638df4SBjorn Andersson "Rejecting allocation of static entry %d\n", item); 4594b638df4SBjorn Andersson return -EINVAL; 4604b638df4SBjorn Andersson } 4614b638df4SBjorn Andersson 4625b394067SChris Lew if (WARN_ON(item >= __smem->item_count)) 4635b394067SChris Lew return -EINVAL; 4645b394067SChris Lew 4654b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 4664b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 4674b638df4SBjorn Andersson &flags); 4684b638df4SBjorn Andersson if (ret) 4694b638df4SBjorn Andersson return ret; 4704b638df4SBjorn Andersson 471d52e4048SChris Lew if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 472d52e4048SChris Lew phdr = __smem->partitions[host]; 473d52e4048SChris Lew ret = qcom_smem_alloc_private(__smem, phdr, item, size); 474d52e4048SChris Lew } else if (__smem->global_partition) { 475d52e4048SChris Lew phdr = __smem->global_partition; 476d52e4048SChris Lew ret = qcom_smem_alloc_private(__smem, phdr, item, size); 477d52e4048SChris Lew } else { 4784b638df4SBjorn Andersson ret = qcom_smem_alloc_global(__smem, item, size); 479d52e4048SChris Lew } 4804b638df4SBjorn Andersson 4814b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 4824b638df4SBjorn Andersson 4834b638df4SBjorn Andersson return ret; 4844b638df4SBjorn Andersson } 4854b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_alloc); 4864b638df4SBjorn Andersson 4871a03964dSStephen Boyd static void *qcom_smem_get_global(struct qcom_smem *smem, 4884b638df4SBjorn Andersson unsigned item, 4894b638df4SBjorn Andersson size_t *size) 4904b638df4SBjorn Andersson { 4914b638df4SBjorn Andersson struct smem_header *header; 4924b638df4SBjorn Andersson struct smem_region *area; 4934b638df4SBjorn Andersson struct smem_global_entry *entry; 4944b638df4SBjorn Andersson u32 aux_base; 4954b638df4SBjorn Andersson unsigned i; 4964b638df4SBjorn Andersson 4974b638df4SBjorn Andersson header = smem->regions[0].virt_base; 4984b638df4SBjorn Andersson entry = &header->toc[item]; 4994b638df4SBjorn Andersson if (!entry->allocated) 5001a03964dSStephen Boyd return ERR_PTR(-ENXIO); 5014b638df4SBjorn Andersson 5029806884dSStephen Boyd aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; 5034b638df4SBjorn Andersson 5044b638df4SBjorn Andersson for (i = 0; i < smem->num_regions; i++) { 5054b638df4SBjorn Andersson area = &smem->regions[i]; 5064b638df4SBjorn Andersson 5074b638df4SBjorn Andersson if (area->aux_base == aux_base || !aux_base) { 5084b638df4SBjorn Andersson if (size != NULL) 5099806884dSStephen Boyd *size = le32_to_cpu(entry->size); 5109806884dSStephen Boyd return area->virt_base + le32_to_cpu(entry->offset); 5111a03964dSStephen Boyd } 5124b638df4SBjorn Andersson } 5134b638df4SBjorn Andersson 5141a03964dSStephen Boyd return ERR_PTR(-ENOENT); 5151a03964dSStephen Boyd } 5161a03964dSStephen Boyd 5171a03964dSStephen Boyd static void *qcom_smem_get_private(struct qcom_smem *smem, 518d52e4048SChris Lew struct smem_partition_header *phdr, 519d52e4048SChris Lew size_t cacheline, 5204b638df4SBjorn Andersson unsigned item, 5214b638df4SBjorn Andersson size_t *size) 5224b638df4SBjorn Andersson { 5239806884dSStephen Boyd struct smem_private_entry *e, *end; 524c7c1dc35SBjorn Andersson 52501f14154SBjorn Andersson e = phdr_to_first_uncached_entry(phdr); 52601f14154SBjorn Andersson end = phdr_to_last_uncached_entry(phdr); 5274b638df4SBjorn Andersson 5289806884dSStephen Boyd while (e < end) { 529c7c1dc35SBjorn Andersson if (e->canary != SMEM_PRIVATE_CANARY) 530c7c1dc35SBjorn Andersson goto invalid_canary; 5314b638df4SBjorn Andersson 5329806884dSStephen Boyd if (le16_to_cpu(e->item) == item) { 5334b638df4SBjorn Andersson if (size != NULL) 5349806884dSStephen Boyd *size = le32_to_cpu(e->size) - 5359806884dSStephen Boyd le16_to_cpu(e->padding_data); 5364b638df4SBjorn Andersson 53701f14154SBjorn Andersson return uncached_entry_to_item(e); 5384b638df4SBjorn Andersson } 5394b638df4SBjorn Andersson 54001f14154SBjorn Andersson e = uncached_entry_next(e); 5414b638df4SBjorn Andersson } 5424b638df4SBjorn Andersson 543c7c1dc35SBjorn Andersson /* Item was not found in the uncached list, search the cached list */ 544c7c1dc35SBjorn Andersson 545c7c1dc35SBjorn Andersson e = phdr_to_first_cached_entry(phdr, cacheline); 546c7c1dc35SBjorn Andersson end = phdr_to_last_cached_entry(phdr); 547c7c1dc35SBjorn Andersson 548c7c1dc35SBjorn Andersson while (e > end) { 549c7c1dc35SBjorn Andersson if (e->canary != SMEM_PRIVATE_CANARY) 550c7c1dc35SBjorn Andersson goto invalid_canary; 551c7c1dc35SBjorn Andersson 552c7c1dc35SBjorn Andersson if (le16_to_cpu(e->item) == item) { 553c7c1dc35SBjorn Andersson if (size != NULL) 554c7c1dc35SBjorn Andersson *size = le32_to_cpu(e->size) - 555c7c1dc35SBjorn Andersson le16_to_cpu(e->padding_data); 556c7c1dc35SBjorn Andersson 557c7c1dc35SBjorn Andersson return cached_entry_to_item(e); 558c7c1dc35SBjorn Andersson } 559c7c1dc35SBjorn Andersson 560c7c1dc35SBjorn Andersson e = cached_entry_next(e, cacheline); 561c7c1dc35SBjorn Andersson } 562c7c1dc35SBjorn Andersson 5631a03964dSStephen Boyd return ERR_PTR(-ENOENT); 564c7c1dc35SBjorn Andersson 565c7c1dc35SBjorn Andersson invalid_canary: 56604a512feSAlex Elder dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", 56704a512feSAlex Elder le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); 568c7c1dc35SBjorn Andersson 569c7c1dc35SBjorn Andersson return ERR_PTR(-EINVAL); 5704b638df4SBjorn Andersson } 5714b638df4SBjorn Andersson 5724b638df4SBjorn Andersson /** 5734b638df4SBjorn Andersson * qcom_smem_get() - resolve ptr of size of a smem item 5744b638df4SBjorn Andersson * @host: the remote processor, or -1 5754b638df4SBjorn Andersson * @item: smem item handle 5764b638df4SBjorn Andersson * @size: pointer to be filled out with size of the item 5774b638df4SBjorn Andersson * 5781a03964dSStephen Boyd * Looks up smem item and returns pointer to it. Size of smem 5791a03964dSStephen Boyd * item is returned in @size. 5804b638df4SBjorn Andersson */ 5811a03964dSStephen Boyd void *qcom_smem_get(unsigned host, unsigned item, size_t *size) 5824b638df4SBjorn Andersson { 583d52e4048SChris Lew struct smem_partition_header *phdr; 5844b638df4SBjorn Andersson unsigned long flags; 585d52e4048SChris Lew size_t cacheln; 5864b638df4SBjorn Andersson int ret; 5871a03964dSStephen Boyd void *ptr = ERR_PTR(-EPROBE_DEFER); 5884b638df4SBjorn Andersson 5894b638df4SBjorn Andersson if (!__smem) 5901a03964dSStephen Boyd return ptr; 5914b638df4SBjorn Andersson 5925b394067SChris Lew if (WARN_ON(item >= __smem->item_count)) 5935b394067SChris Lew return ERR_PTR(-EINVAL); 5945b394067SChris Lew 5954b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 5964b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 5974b638df4SBjorn Andersson &flags); 5984b638df4SBjorn Andersson if (ret) 5991a03964dSStephen Boyd return ERR_PTR(ret); 6004b638df4SBjorn Andersson 601d52e4048SChris Lew if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 602d52e4048SChris Lew phdr = __smem->partitions[host]; 603d52e4048SChris Lew cacheln = __smem->cacheline[host]; 604d52e4048SChris Lew ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); 605d52e4048SChris Lew } else if (__smem->global_partition) { 606d52e4048SChris Lew phdr = __smem->global_partition; 607d52e4048SChris Lew cacheln = __smem->global_cacheline; 608d52e4048SChris Lew ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); 609d52e4048SChris Lew } else { 6101a03964dSStephen Boyd ptr = qcom_smem_get_global(__smem, item, size); 611d52e4048SChris Lew } 6124b638df4SBjorn Andersson 6134b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 6141a03964dSStephen Boyd 6151a03964dSStephen Boyd return ptr; 6164b638df4SBjorn Andersson 6174b638df4SBjorn Andersson } 6184b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get); 6194b638df4SBjorn Andersson 6204b638df4SBjorn Andersson /** 6214b638df4SBjorn Andersson * qcom_smem_get_free_space() - retrieve amount of free space in a partition 6224b638df4SBjorn Andersson * @host: the remote processor identifying a partition, or -1 6234b638df4SBjorn Andersson * 6244b638df4SBjorn Andersson * To be used by smem clients as a quick way to determine if any new 6254b638df4SBjorn Andersson * allocations has been made. 6264b638df4SBjorn Andersson */ 6274b638df4SBjorn Andersson int qcom_smem_get_free_space(unsigned host) 6284b638df4SBjorn Andersson { 6294b638df4SBjorn Andersson struct smem_partition_header *phdr; 6304b638df4SBjorn Andersson struct smem_header *header; 6314b638df4SBjorn Andersson unsigned ret; 6324b638df4SBjorn Andersson 6334b638df4SBjorn Andersson if (!__smem) 6344b638df4SBjorn Andersson return -EPROBE_DEFER; 6354b638df4SBjorn Andersson 6364b638df4SBjorn Andersson if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 6374b638df4SBjorn Andersson phdr = __smem->partitions[host]; 6389806884dSStephen Boyd ret = le32_to_cpu(phdr->offset_free_cached) - 6399806884dSStephen Boyd le32_to_cpu(phdr->offset_free_uncached); 640d52e4048SChris Lew } else if (__smem->global_partition) { 641d52e4048SChris Lew phdr = __smem->global_partition; 642d52e4048SChris Lew ret = le32_to_cpu(phdr->offset_free_cached) - 643d52e4048SChris Lew le32_to_cpu(phdr->offset_free_uncached); 6444b638df4SBjorn Andersson } else { 6454b638df4SBjorn Andersson header = __smem->regions[0].virt_base; 6469806884dSStephen Boyd ret = le32_to_cpu(header->available); 6474b638df4SBjorn Andersson } 6484b638df4SBjorn Andersson 6494b638df4SBjorn Andersson return ret; 6504b638df4SBjorn Andersson } 6514b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get_free_space); 6524b638df4SBjorn Andersson 6534b638df4SBjorn Andersson static int qcom_smem_get_sbl_version(struct qcom_smem *smem) 6544b638df4SBjorn Andersson { 655dcc0967dSChris Lew struct smem_header *header; 6569806884dSStephen Boyd __le32 *versions; 6574b638df4SBjorn Andersson 658dcc0967dSChris Lew header = smem->regions[0].virt_base; 659dcc0967dSChris Lew versions = header->version; 6604b638df4SBjorn Andersson 6619806884dSStephen Boyd return le32_to_cpu(versions[SMEM_MASTER_SBL_VERSION_INDEX]); 6624b638df4SBjorn Andersson } 6634b638df4SBjorn Andersson 664d52e4048SChris Lew static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem) 6654b638df4SBjorn Andersson { 6664b638df4SBjorn Andersson struct smem_ptable *ptable; 667d52e4048SChris Lew u32 version; 6684b638df4SBjorn Andersson 6694b638df4SBjorn Andersson ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; 6709806884dSStephen Boyd if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) 671d52e4048SChris Lew return ERR_PTR(-ENOENT); 6724b638df4SBjorn Andersson 6739806884dSStephen Boyd version = le32_to_cpu(ptable->version); 6749806884dSStephen Boyd if (version != 1) { 6754b638df4SBjorn Andersson dev_err(smem->dev, 6769806884dSStephen Boyd "Unsupported partition header version %d\n", version); 677d52e4048SChris Lew return ERR_PTR(-EINVAL); 678d52e4048SChris Lew } 679d52e4048SChris Lew return ptable; 680d52e4048SChris Lew } 681d52e4048SChris Lew 6825b394067SChris Lew static u32 qcom_smem_get_item_count(struct qcom_smem *smem) 6835b394067SChris Lew { 6845b394067SChris Lew struct smem_ptable *ptable; 6855b394067SChris Lew struct smem_info *info; 6865b394067SChris Lew 6875b394067SChris Lew ptable = qcom_smem_get_ptable(smem); 6885b394067SChris Lew if (IS_ERR_OR_NULL(ptable)) 6895b394067SChris Lew return SMEM_ITEM_COUNT; 6905b394067SChris Lew 6915b394067SChris Lew info = (struct smem_info *)&ptable->entry[ptable->num_entries]; 6925b394067SChris Lew if (memcmp(info->magic, SMEM_INFO_MAGIC, sizeof(info->magic))) 6935b394067SChris Lew return SMEM_ITEM_COUNT; 6945b394067SChris Lew 6955b394067SChris Lew return le16_to_cpu(info->num_items); 6965b394067SChris Lew } 6975b394067SChris Lew 698d52e4048SChris Lew static int qcom_smem_set_global_partition(struct qcom_smem *smem) 699d52e4048SChris Lew { 700d52e4048SChris Lew struct smem_partition_header *header; 701*8fa1a214SAlex Elder struct smem_ptable_entry *entry; 702d52e4048SChris Lew struct smem_ptable *ptable; 703d52e4048SChris Lew u32 host0, host1, size; 704*8fa1a214SAlex Elder bool found = false; 705d52e4048SChris Lew int i; 706d52e4048SChris Lew 707d52e4048SChris Lew ptable = qcom_smem_get_ptable(smem); 708d52e4048SChris Lew if (IS_ERR(ptable)) 709d52e4048SChris Lew return PTR_ERR(ptable); 710d52e4048SChris Lew 711d52e4048SChris Lew for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { 712d52e4048SChris Lew entry = &ptable->entry[i]; 713d52e4048SChris Lew host0 = le16_to_cpu(entry->host0); 714d52e4048SChris Lew host1 = le16_to_cpu(entry->host1); 715d52e4048SChris Lew 716*8fa1a214SAlex Elder if (host0 == SMEM_GLOBAL_HOST && host0 == host1) { 717*8fa1a214SAlex Elder found = true; 718d52e4048SChris Lew break; 719d52e4048SChris Lew } 720*8fa1a214SAlex Elder } 721d52e4048SChris Lew 722*8fa1a214SAlex Elder if (!found) { 723d52e4048SChris Lew dev_err(smem->dev, "Missing entry for global partition\n"); 7244b638df4SBjorn Andersson return -EINVAL; 7254b638df4SBjorn Andersson } 7264b638df4SBjorn Andersson 727d52e4048SChris Lew if (!le32_to_cpu(entry->offset) || !le32_to_cpu(entry->size)) { 728d52e4048SChris Lew dev_err(smem->dev, "Invalid entry for global partition\n"); 729d52e4048SChris Lew return -EINVAL; 730d52e4048SChris Lew } 731d52e4048SChris Lew 732d52e4048SChris Lew if (smem->global_partition) { 733d52e4048SChris Lew dev_err(smem->dev, "Already found the global partition\n"); 734d52e4048SChris Lew return -EINVAL; 735d52e4048SChris Lew } 736d52e4048SChris Lew 737d52e4048SChris Lew header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); 738d52e4048SChris Lew host0 = le16_to_cpu(header->host0); 739d52e4048SChris Lew host1 = le16_to_cpu(header->host1); 740d52e4048SChris Lew 741d52e4048SChris Lew if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { 742d52e4048SChris Lew dev_err(smem->dev, "Global partition has invalid magic\n"); 743d52e4048SChris Lew return -EINVAL; 744d52e4048SChris Lew } 745d52e4048SChris Lew 746d52e4048SChris Lew if (host0 != SMEM_GLOBAL_HOST && host1 != SMEM_GLOBAL_HOST) { 747d52e4048SChris Lew dev_err(smem->dev, "Global partition hosts are invalid\n"); 748d52e4048SChris Lew return -EINVAL; 749d52e4048SChris Lew } 750d52e4048SChris Lew 751d52e4048SChris Lew if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { 752d52e4048SChris Lew dev_err(smem->dev, "Global partition has invalid size\n"); 753d52e4048SChris Lew return -EINVAL; 754d52e4048SChris Lew } 755d52e4048SChris Lew 756d52e4048SChris Lew size = le32_to_cpu(header->offset_free_uncached); 757d52e4048SChris Lew if (size > le32_to_cpu(header->size)) { 758d52e4048SChris Lew dev_err(smem->dev, 759d52e4048SChris Lew "Global partition has invalid free pointer\n"); 760d52e4048SChris Lew return -EINVAL; 761d52e4048SChris Lew } 762d52e4048SChris Lew 763d52e4048SChris Lew smem->global_partition = header; 764d52e4048SChris Lew smem->global_cacheline = le32_to_cpu(entry->cacheline); 765d52e4048SChris Lew 766d52e4048SChris Lew return 0; 767d52e4048SChris Lew } 768d52e4048SChris Lew 769d52e4048SChris Lew static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, 770d52e4048SChris Lew unsigned int local_host) 771d52e4048SChris Lew { 772d52e4048SChris Lew struct smem_partition_header *header; 773d52e4048SChris Lew struct smem_ptable_entry *entry; 774d52e4048SChris Lew struct smem_ptable *ptable; 775d52e4048SChris Lew unsigned int remote_host; 776d52e4048SChris Lew u32 host0, host1; 777d52e4048SChris Lew int i; 778d52e4048SChris Lew 779d52e4048SChris Lew ptable = qcom_smem_get_ptable(smem); 780d52e4048SChris Lew if (IS_ERR(ptable)) 781d52e4048SChris Lew return PTR_ERR(ptable); 782d52e4048SChris Lew 7839806884dSStephen Boyd for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { 7844b638df4SBjorn Andersson entry = &ptable->entry[i]; 7859806884dSStephen Boyd host0 = le16_to_cpu(entry->host0); 7869806884dSStephen Boyd host1 = le16_to_cpu(entry->host1); 7874b638df4SBjorn Andersson 7889806884dSStephen Boyd if (host0 != local_host && host1 != local_host) 7894b638df4SBjorn Andersson continue; 7904b638df4SBjorn Andersson 7919806884dSStephen Boyd if (!le32_to_cpu(entry->offset)) 7924b638df4SBjorn Andersson continue; 7934b638df4SBjorn Andersson 7949806884dSStephen Boyd if (!le32_to_cpu(entry->size)) 7954b638df4SBjorn Andersson continue; 7964b638df4SBjorn Andersson 7979806884dSStephen Boyd if (host0 == local_host) 7989806884dSStephen Boyd remote_host = host1; 7994b638df4SBjorn Andersson else 8009806884dSStephen Boyd remote_host = host0; 8014b638df4SBjorn Andersson 8024b638df4SBjorn Andersson if (remote_host >= SMEM_HOST_COUNT) { 8034b638df4SBjorn Andersson dev_err(smem->dev, 8044b638df4SBjorn Andersson "Invalid remote host %d\n", 8054b638df4SBjorn Andersson remote_host); 8064b638df4SBjorn Andersson return -EINVAL; 8074b638df4SBjorn Andersson } 8084b638df4SBjorn Andersson 8094b638df4SBjorn Andersson if (smem->partitions[remote_host]) { 8104b638df4SBjorn Andersson dev_err(smem->dev, 8114b638df4SBjorn Andersson "Already found a partition for host %d\n", 8124b638df4SBjorn Andersson remote_host); 8134b638df4SBjorn Andersson return -EINVAL; 8144b638df4SBjorn Andersson } 8154b638df4SBjorn Andersson 8169806884dSStephen Boyd header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); 8179806884dSStephen Boyd host0 = le16_to_cpu(header->host0); 8189806884dSStephen Boyd host1 = le16_to_cpu(header->host1); 8194b638df4SBjorn Andersson 8209806884dSStephen Boyd if (memcmp(header->magic, SMEM_PART_MAGIC, 8219806884dSStephen Boyd sizeof(header->magic))) { 8224b638df4SBjorn Andersson dev_err(smem->dev, 8234b638df4SBjorn Andersson "Partition %d has invalid magic\n", i); 8244b638df4SBjorn Andersson return -EINVAL; 8254b638df4SBjorn Andersson } 8264b638df4SBjorn Andersson 8279806884dSStephen Boyd if (host0 != local_host && host1 != local_host) { 8284b638df4SBjorn Andersson dev_err(smem->dev, 8294b638df4SBjorn Andersson "Partition %d hosts are invalid\n", i); 8304b638df4SBjorn Andersson return -EINVAL; 8314b638df4SBjorn Andersson } 8324b638df4SBjorn Andersson 8339806884dSStephen Boyd if (host0 != remote_host && host1 != remote_host) { 8344b638df4SBjorn Andersson dev_err(smem->dev, 8354b638df4SBjorn Andersson "Partition %d hosts are invalid\n", i); 8364b638df4SBjorn Andersson return -EINVAL; 8374b638df4SBjorn Andersson } 8384b638df4SBjorn Andersson 839a216000fSChris Lew if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { 8404b638df4SBjorn Andersson dev_err(smem->dev, 8414b638df4SBjorn Andersson "Partition %d has invalid size\n", i); 8424b638df4SBjorn Andersson return -EINVAL; 8434b638df4SBjorn Andersson } 8444b638df4SBjorn Andersson 8459806884dSStephen Boyd if (le32_to_cpu(header->offset_free_uncached) > le32_to_cpu(header->size)) { 8464b638df4SBjorn Andersson dev_err(smem->dev, 8474b638df4SBjorn Andersson "Partition %d has invalid free pointer\n", i); 8484b638df4SBjorn Andersson return -EINVAL; 8494b638df4SBjorn Andersson } 8504b638df4SBjorn Andersson 8514b638df4SBjorn Andersson smem->partitions[remote_host] = header; 852c7c1dc35SBjorn Andersson smem->cacheline[remote_host] = le32_to_cpu(entry->cacheline); 8534b638df4SBjorn Andersson } 8544b638df4SBjorn Andersson 8554b638df4SBjorn Andersson return 0; 8564b638df4SBjorn Andersson } 8574b638df4SBjorn Andersson 858d0bfd7c9SStephen Boyd static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, 859d0bfd7c9SStephen Boyd const char *name, int i) 8604b638df4SBjorn Andersson { 861d0bfd7c9SStephen Boyd struct device_node *np; 862d0bfd7c9SStephen Boyd struct resource r; 863d0bfd7c9SStephen Boyd int ret; 8644b638df4SBjorn Andersson 865d0bfd7c9SStephen Boyd np = of_parse_phandle(dev->of_node, name, 0); 866d0bfd7c9SStephen Boyd if (!np) { 867d0bfd7c9SStephen Boyd dev_err(dev, "No %s specified\n", name); 868d0bfd7c9SStephen Boyd return -EINVAL; 8694b638df4SBjorn Andersson } 8704b638df4SBjorn Andersson 871d0bfd7c9SStephen Boyd ret = of_address_to_resource(np, 0, &r); 872d0bfd7c9SStephen Boyd of_node_put(np); 873d0bfd7c9SStephen Boyd if (ret) 874d0bfd7c9SStephen Boyd return ret; 875d0bfd7c9SStephen Boyd 876d0bfd7c9SStephen Boyd smem->regions[i].aux_base = (u32)r.start; 877d0bfd7c9SStephen Boyd smem->regions[i].size = resource_size(&r); 878afd356dfSBjorn Andersson smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, resource_size(&r)); 879d0bfd7c9SStephen Boyd if (!smem->regions[i].virt_base) 880d0bfd7c9SStephen Boyd return -ENOMEM; 881d0bfd7c9SStephen Boyd 882d0bfd7c9SStephen Boyd return 0; 8834b638df4SBjorn Andersson } 8844b638df4SBjorn Andersson 8854b638df4SBjorn Andersson static int qcom_smem_probe(struct platform_device *pdev) 8864b638df4SBjorn Andersson { 8874b638df4SBjorn Andersson struct smem_header *header; 8884b638df4SBjorn Andersson struct qcom_smem *smem; 8894b638df4SBjorn Andersson size_t array_size; 890d0bfd7c9SStephen Boyd int num_regions; 8914b638df4SBjorn Andersson int hwlock_id; 8924b638df4SBjorn Andersson u32 version; 8934b638df4SBjorn Andersson int ret; 8944b638df4SBjorn Andersson 895d0bfd7c9SStephen Boyd num_regions = 1; 896d0bfd7c9SStephen Boyd if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL)) 897d0bfd7c9SStephen Boyd num_regions++; 8984b638df4SBjorn Andersson 8994b638df4SBjorn Andersson array_size = num_regions * sizeof(struct smem_region); 9004b638df4SBjorn Andersson smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); 9014b638df4SBjorn Andersson if (!smem) 9024b638df4SBjorn Andersson return -ENOMEM; 9034b638df4SBjorn Andersson 9044b638df4SBjorn Andersson smem->dev = &pdev->dev; 9054b638df4SBjorn Andersson smem->num_regions = num_regions; 9064b638df4SBjorn Andersson 907d0bfd7c9SStephen Boyd ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); 9084b638df4SBjorn Andersson if (ret) 9094b638df4SBjorn Andersson return ret; 9104b638df4SBjorn Andersson 911d0bfd7c9SStephen Boyd if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev, 912d0bfd7c9SStephen Boyd "qcom,rpm-msg-ram", 1))) 913d0bfd7c9SStephen Boyd return ret; 9144b638df4SBjorn Andersson 9154b638df4SBjorn Andersson header = smem->regions[0].virt_base; 9169806884dSStephen Boyd if (le32_to_cpu(header->initialized) != 1 || 9179806884dSStephen Boyd le32_to_cpu(header->reserved)) { 9184b638df4SBjorn Andersson dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); 9194b638df4SBjorn Andersson return -EINVAL; 9204b638df4SBjorn Andersson } 9214b638df4SBjorn Andersson 9224b638df4SBjorn Andersson version = qcom_smem_get_sbl_version(smem); 923d52e4048SChris Lew switch (version >> 16) { 924d52e4048SChris Lew case SMEM_GLOBAL_PART_VERSION: 925d52e4048SChris Lew ret = qcom_smem_set_global_partition(smem); 926d52e4048SChris Lew if (ret < 0) 927d52e4048SChris Lew return ret; 9285b394067SChris Lew smem->item_count = qcom_smem_get_item_count(smem); 9295b394067SChris Lew break; 930d52e4048SChris Lew case SMEM_GLOBAL_HEAP_VERSION: 9315b394067SChris Lew smem->item_count = SMEM_ITEM_COUNT; 932d52e4048SChris Lew break; 933d52e4048SChris Lew default: 9344b638df4SBjorn Andersson dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); 9354b638df4SBjorn Andersson return -EINVAL; 9364b638df4SBjorn Andersson } 9374b638df4SBjorn Andersson 9384b638df4SBjorn Andersson ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); 939d52e4048SChris Lew if (ret < 0 && ret != -ENOENT) 9404b638df4SBjorn Andersson return ret; 9414b638df4SBjorn Andersson 9424b638df4SBjorn Andersson hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); 9434b638df4SBjorn Andersson if (hwlock_id < 0) { 944951a5af9SStephen Boyd if (hwlock_id != -EPROBE_DEFER) 9454b638df4SBjorn Andersson dev_err(&pdev->dev, "failed to retrieve hwlock\n"); 9464b638df4SBjorn Andersson return hwlock_id; 9474b638df4SBjorn Andersson } 9484b638df4SBjorn Andersson 9494b638df4SBjorn Andersson smem->hwlock = hwspin_lock_request_specific(hwlock_id); 9504b638df4SBjorn Andersson if (!smem->hwlock) 9514b638df4SBjorn Andersson return -ENXIO; 9524b638df4SBjorn Andersson 9534b638df4SBjorn Andersson __smem = smem; 9544b638df4SBjorn Andersson 9554b638df4SBjorn Andersson return 0; 9564b638df4SBjorn Andersson } 9574b638df4SBjorn Andersson 9584b638df4SBjorn Andersson static int qcom_smem_remove(struct platform_device *pdev) 9594b638df4SBjorn Andersson { 9604b638df4SBjorn Andersson hwspin_lock_free(__smem->hwlock); 961f8c67df7SStephen Boyd __smem = NULL; 9624b638df4SBjorn Andersson 9634b638df4SBjorn Andersson return 0; 9644b638df4SBjorn Andersson } 9654b638df4SBjorn Andersson 9664b638df4SBjorn Andersson static const struct of_device_id qcom_smem_of_match[] = { 9674b638df4SBjorn Andersson { .compatible = "qcom,smem" }, 9684b638df4SBjorn Andersson {} 9694b638df4SBjorn Andersson }; 9704b638df4SBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smem_of_match); 9714b638df4SBjorn Andersson 9724b638df4SBjorn Andersson static struct platform_driver qcom_smem_driver = { 9734b638df4SBjorn Andersson .probe = qcom_smem_probe, 9744b638df4SBjorn Andersson .remove = qcom_smem_remove, 9754b638df4SBjorn Andersson .driver = { 9764b638df4SBjorn Andersson .name = "qcom-smem", 9774b638df4SBjorn Andersson .of_match_table = qcom_smem_of_match, 9784b638df4SBjorn Andersson .suppress_bind_attrs = true, 9794b638df4SBjorn Andersson }, 9804b638df4SBjorn Andersson }; 9814b638df4SBjorn Andersson 9824b638df4SBjorn Andersson static int __init qcom_smem_init(void) 9834b638df4SBjorn Andersson { 9844b638df4SBjorn Andersson return platform_driver_register(&qcom_smem_driver); 9854b638df4SBjorn Andersson } 9864b638df4SBjorn Andersson arch_initcall(qcom_smem_init); 9874b638df4SBjorn Andersson 9884b638df4SBjorn Andersson static void __exit qcom_smem_exit(void) 9894b638df4SBjorn Andersson { 9904b638df4SBjorn Andersson platform_driver_unregister(&qcom_smem_driver); 9914b638df4SBjorn Andersson } 9924b638df4SBjorn Andersson module_exit(qcom_smem_exit) 9934b638df4SBjorn Andersson 9944b638df4SBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 9954b638df4SBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); 9964b638df4SBjorn Andersson MODULE_LICENSE("GPL v2"); 997