197fb5e8dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 24b638df4SBjorn Andersson /* 34b638df4SBjorn Andersson * Copyright (c) 2015, Sony Mobile Communications AB. 44b638df4SBjorn Andersson * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 54b638df4SBjorn Andersson */ 64b638df4SBjorn Andersson 74b638df4SBjorn Andersson #include <linux/hwspinlock.h> 84b638df4SBjorn Andersson #include <linux/io.h> 94b638df4SBjorn Andersson #include <linux/module.h> 104b638df4SBjorn Andersson #include <linux/of.h> 114b638df4SBjorn Andersson #include <linux/of_address.h> 124b638df4SBjorn Andersson #include <linux/platform_device.h> 139487e2abSNiklas Cassel #include <linux/sizes.h> 144b638df4SBjorn Andersson #include <linux/slab.h> 154b638df4SBjorn Andersson #include <linux/soc/qcom/smem.h> 164b638df4SBjorn Andersson 174b638df4SBjorn Andersson /* 184b638df4SBjorn Andersson * The Qualcomm shared memory system is a allocate only heap structure that 194b638df4SBjorn Andersson * consists of one of more memory areas that can be accessed by the processors 204b638df4SBjorn Andersson * in the SoC. 214b638df4SBjorn Andersson * 224b638df4SBjorn Andersson * All systems contains a global heap, accessible by all processors in the SoC, 234b638df4SBjorn Andersson * with a table of contents data structure (@smem_header) at the beginning of 244b638df4SBjorn Andersson * the main shared memory block. 254b638df4SBjorn Andersson * 264b638df4SBjorn Andersson * The global header contains meta data for allocations as well as a fixed list 274b638df4SBjorn Andersson * of 512 entries (@smem_global_entry) that can be initialized to reference 284b638df4SBjorn Andersson * parts of the shared memory space. 294b638df4SBjorn Andersson * 304b638df4SBjorn Andersson * 314b638df4SBjorn Andersson * In addition to this global heap a set of "private" heaps can be set up at 324b638df4SBjorn Andersson * boot time with access restrictions so that only certain processor pairs can 334b638df4SBjorn Andersson * access the data. 344b638df4SBjorn Andersson * 354b638df4SBjorn Andersson * These partitions are referenced from an optional partition table 364b638df4SBjorn Andersson * (@smem_ptable), that is found 4kB from the end of the main smem region. The 374b638df4SBjorn Andersson * partition table entries (@smem_ptable_entry) lists the involved processors 384b638df4SBjorn Andersson * (or hosts) and their location in the main shared memory region. 394b638df4SBjorn Andersson * 404b638df4SBjorn Andersson * Each partition starts with a header (@smem_partition_header) that identifies 414b638df4SBjorn Andersson * the partition and holds properties for the two internal memory regions. The 424b638df4SBjorn Andersson * two regions are cached and non-cached memory respectively. Each region 434b638df4SBjorn Andersson * contain a link list of allocation headers (@smem_private_entry) followed by 444b638df4SBjorn Andersson * their data. 454b638df4SBjorn Andersson * 464b638df4SBjorn Andersson * Items in the non-cached region are allocated from the start of the partition 474b638df4SBjorn Andersson * while items in the cached region are allocated from the end. The free area 48c7c1dc35SBjorn Andersson * is hence the region between the cached and non-cached offsets. The header of 49c7c1dc35SBjorn Andersson * cached items comes after the data. 504b638df4SBjorn Andersson * 51d52e4048SChris Lew * Version 12 (SMEM_GLOBAL_PART_VERSION) changes the item alloc/get procedure 52d52e4048SChris Lew * for the global heap. A new global partition is created from the global heap 53d52e4048SChris Lew * region with partition type (SMEM_GLOBAL_HOST) and the max smem item count is 54d52e4048SChris Lew * set by the bootloader. 554b638df4SBjorn Andersson * 564b638df4SBjorn Andersson * To synchronize allocations in the shared memory heaps a remote spinlock must 574b638df4SBjorn Andersson * be held - currently lock number 3 of the sfpb or tcsr is used for this on all 584b638df4SBjorn Andersson * platforms. 594b638df4SBjorn Andersson * 604b638df4SBjorn Andersson */ 614b638df4SBjorn Andersson 624b638df4SBjorn Andersson /* 63dcc0967dSChris Lew * The version member of the smem header contains an array of versions for the 64dcc0967dSChris Lew * various software components in the SoC. We verify that the boot loader 65dcc0967dSChris Lew * version is a valid version as a sanity check. 664b638df4SBjorn Andersson */ 674b638df4SBjorn Andersson #define SMEM_MASTER_SBL_VERSION_INDEX 7 68d52e4048SChris Lew #define SMEM_GLOBAL_HEAP_VERSION 11 69d52e4048SChris Lew #define SMEM_GLOBAL_PART_VERSION 12 704b638df4SBjorn Andersson 714b638df4SBjorn Andersson /* 724b638df4SBjorn Andersson * The first 8 items are only to be allocated by the boot loader while 734b638df4SBjorn Andersson * initializing the heap. 744b638df4SBjorn Andersson */ 754b638df4SBjorn Andersson #define SMEM_ITEM_LAST_FIXED 8 764b638df4SBjorn Andersson 774b638df4SBjorn Andersson /* Highest accepted item number, for both global and private heaps */ 784b638df4SBjorn Andersson #define SMEM_ITEM_COUNT 512 794b638df4SBjorn Andersson 804b638df4SBjorn Andersson /* Processor/host identifier for the application processor */ 814b638df4SBjorn Andersson #define SMEM_HOST_APPS 0 824b638df4SBjorn Andersson 83d52e4048SChris Lew /* Processor/host identifier for the global partition */ 84d52e4048SChris Lew #define SMEM_GLOBAL_HOST 0xfffe 85d52e4048SChris Lew 864b638df4SBjorn Andersson /* Max number of processors/hosts in a system */ 87e691b48dSChris Lew #define SMEM_HOST_COUNT 10 884b638df4SBjorn Andersson 894b638df4SBjorn Andersson /** 904b638df4SBjorn Andersson * struct smem_proc_comm - proc_comm communication struct (legacy) 914b638df4SBjorn Andersson * @command: current command to be executed 924b638df4SBjorn Andersson * @status: status of the currently requested command 934b638df4SBjorn Andersson * @params: parameters to the command 944b638df4SBjorn Andersson */ 954b638df4SBjorn Andersson struct smem_proc_comm { 969806884dSStephen Boyd __le32 command; 979806884dSStephen Boyd __le32 status; 989806884dSStephen Boyd __le32 params[2]; 994b638df4SBjorn Andersson }; 1004b638df4SBjorn Andersson 1014b638df4SBjorn Andersson /** 1024b638df4SBjorn Andersson * struct smem_global_entry - entry to reference smem items on the heap 1034b638df4SBjorn Andersson * @allocated: boolean to indicate if this entry is used 1044b638df4SBjorn Andersson * @offset: offset to the allocated space 1054b638df4SBjorn Andersson * @size: size of the allocated space, 8 byte aligned 1064b638df4SBjorn Andersson * @aux_base: base address for the memory region used by this unit, or 0 for 1074b638df4SBjorn Andersson * the default region. bits 0,1 are reserved 1084b638df4SBjorn Andersson */ 1094b638df4SBjorn Andersson struct smem_global_entry { 1109806884dSStephen Boyd __le32 allocated; 1119806884dSStephen Boyd __le32 offset; 1129806884dSStephen Boyd __le32 size; 1139806884dSStephen Boyd __le32 aux_base; /* bits 1:0 reserved */ 1144b638df4SBjorn Andersson }; 1154b638df4SBjorn Andersson #define AUX_BASE_MASK 0xfffffffc 1164b638df4SBjorn Andersson 1174b638df4SBjorn Andersson /** 1184b638df4SBjorn Andersson * struct smem_header - header found in beginning of primary smem region 1194b638df4SBjorn Andersson * @proc_comm: proc_comm communication interface (legacy) 1204b638df4SBjorn Andersson * @version: array of versions for the various subsystems 1214b638df4SBjorn Andersson * @initialized: boolean to indicate that smem is initialized 1224b638df4SBjorn Andersson * @free_offset: index of the first unallocated byte in smem 1234b638df4SBjorn Andersson * @available: number of bytes available for allocation 1244b638df4SBjorn Andersson * @reserved: reserved field, must be 0 1254b638df4SBjorn Andersson * toc: array of references to items 1264b638df4SBjorn Andersson */ 1274b638df4SBjorn Andersson struct smem_header { 1284b638df4SBjorn Andersson struct smem_proc_comm proc_comm[4]; 1299806884dSStephen Boyd __le32 version[32]; 1309806884dSStephen Boyd __le32 initialized; 1319806884dSStephen Boyd __le32 free_offset; 1329806884dSStephen Boyd __le32 available; 1339806884dSStephen Boyd __le32 reserved; 1344b638df4SBjorn Andersson struct smem_global_entry toc[SMEM_ITEM_COUNT]; 1354b638df4SBjorn Andersson }; 1364b638df4SBjorn Andersson 1374b638df4SBjorn Andersson /** 1384b638df4SBjorn Andersson * struct smem_ptable_entry - one entry in the @smem_ptable list 1394b638df4SBjorn Andersson * @offset: offset, within the main shared memory region, of the partition 1404b638df4SBjorn Andersson * @size: size of the partition 1414b638df4SBjorn Andersson * @flags: flags for the partition (currently unused) 1424b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1434b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 144c7c1dc35SBjorn Andersson * @cacheline: alignment for "cached" entries 1454b638df4SBjorn Andersson * @reserved: reserved entries for later use 1464b638df4SBjorn Andersson */ 1474b638df4SBjorn Andersson struct smem_ptable_entry { 1489806884dSStephen Boyd __le32 offset; 1499806884dSStephen Boyd __le32 size; 1509806884dSStephen Boyd __le32 flags; 1519806884dSStephen Boyd __le16 host0; 1529806884dSStephen Boyd __le16 host1; 153c7c1dc35SBjorn Andersson __le32 cacheline; 154c7c1dc35SBjorn Andersson __le32 reserved[7]; 1554b638df4SBjorn Andersson }; 1564b638df4SBjorn Andersson 1574b638df4SBjorn Andersson /** 1584b638df4SBjorn Andersson * struct smem_ptable - partition table for the private partitions 1594b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PTABLE_MAGIC 1604b638df4SBjorn Andersson * @version: version of the partition table 1614b638df4SBjorn Andersson * @num_entries: number of partitions in the table 1624b638df4SBjorn Andersson * @reserved: for now reserved entries 1634b638df4SBjorn Andersson * @entry: list of @smem_ptable_entry for the @num_entries partitions 1644b638df4SBjorn Andersson */ 1654b638df4SBjorn Andersson struct smem_ptable { 1669806884dSStephen Boyd u8 magic[4]; 1679806884dSStephen Boyd __le32 version; 1689806884dSStephen Boyd __le32 num_entries; 1699806884dSStephen Boyd __le32 reserved[5]; 1704b638df4SBjorn Andersson struct smem_ptable_entry entry[]; 1714b638df4SBjorn Andersson }; 1729806884dSStephen Boyd 1739806884dSStephen Boyd static const u8 SMEM_PTABLE_MAGIC[] = { 0x24, 0x54, 0x4f, 0x43 }; /* "$TOC" */ 1744b638df4SBjorn Andersson 1754b638df4SBjorn Andersson /** 1764b638df4SBjorn Andersson * struct smem_partition_header - header of the partitions 1774b638df4SBjorn Andersson * @magic: magic number, must be SMEM_PART_MAGIC 1784b638df4SBjorn Andersson * @host0: first processor/host with access to this partition 1794b638df4SBjorn Andersson * @host1: second processor/host with access to this partition 1804b638df4SBjorn Andersson * @size: size of the partition 1814b638df4SBjorn Andersson * @offset_free_uncached: offset to the first free byte of uncached memory in 1824b638df4SBjorn Andersson * this partition 1834b638df4SBjorn Andersson * @offset_free_cached: offset to the first free byte of cached memory in this 1844b638df4SBjorn Andersson * partition 1854b638df4SBjorn Andersson * @reserved: for now reserved entries 1864b638df4SBjorn Andersson */ 1874b638df4SBjorn Andersson struct smem_partition_header { 1889806884dSStephen Boyd u8 magic[4]; 1899806884dSStephen Boyd __le16 host0; 1909806884dSStephen Boyd __le16 host1; 1919806884dSStephen Boyd __le32 size; 1929806884dSStephen Boyd __le32 offset_free_uncached; 1939806884dSStephen Boyd __le32 offset_free_cached; 1949806884dSStephen Boyd __le32 reserved[3]; 1954b638df4SBjorn Andersson }; 1969806884dSStephen Boyd 1979806884dSStephen Boyd static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; 1984b638df4SBjorn Andersson 1994b638df4SBjorn Andersson /** 2004b638df4SBjorn Andersson * struct smem_private_entry - header of each item in the private partition 2014b638df4SBjorn Andersson * @canary: magic number, must be SMEM_PRIVATE_CANARY 2024b638df4SBjorn Andersson * @item: identifying number of the smem item 2034b638df4SBjorn Andersson * @size: size of the data, including padding bytes 2044b638df4SBjorn Andersson * @padding_data: number of bytes of padding of data 2054b638df4SBjorn Andersson * @padding_hdr: number of bytes of padding between the header and the data 2064b638df4SBjorn Andersson * @reserved: for now reserved entry 2074b638df4SBjorn Andersson */ 2084b638df4SBjorn Andersson struct smem_private_entry { 2099806884dSStephen Boyd u16 canary; /* bytes are the same so no swapping needed */ 2109806884dSStephen Boyd __le16 item; 2119806884dSStephen Boyd __le32 size; /* includes padding bytes */ 2129806884dSStephen Boyd __le16 padding_data; 2139806884dSStephen Boyd __le16 padding_hdr; 2149806884dSStephen Boyd __le32 reserved; 2154b638df4SBjorn Andersson }; 2164b638df4SBjorn Andersson #define SMEM_PRIVATE_CANARY 0xa5a5 2174b638df4SBjorn Andersson 2184b638df4SBjorn Andersson /** 2195b394067SChris Lew * struct smem_info - smem region info located after the table of contents 2205b394067SChris Lew * @magic: magic number, must be SMEM_INFO_MAGIC 2215b394067SChris Lew * @size: size of the smem region 2225b394067SChris Lew * @base_addr: base address of the smem region 2235b394067SChris Lew * @reserved: for now reserved entry 2245b394067SChris Lew * @num_items: highest accepted item number 2255b394067SChris Lew */ 2265b394067SChris Lew struct smem_info { 2275b394067SChris Lew u8 magic[4]; 2285b394067SChris Lew __le32 size; 2295b394067SChris Lew __le32 base_addr; 2305b394067SChris Lew __le32 reserved; 2315b394067SChris Lew __le16 num_items; 2325b394067SChris Lew }; 2335b394067SChris Lew 2345b394067SChris Lew static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ 2355b394067SChris Lew 2365b394067SChris Lew /** 2374b638df4SBjorn Andersson * struct smem_region - representation of a chunk of memory used for smem 2384b638df4SBjorn Andersson * @aux_base: identifier of aux_mem base 2394b638df4SBjorn Andersson * @virt_base: virtual base address of memory with this aux_mem identifier 2404b638df4SBjorn Andersson * @size: size of the memory region 2414b638df4SBjorn Andersson */ 2424b638df4SBjorn Andersson struct smem_region { 2434b638df4SBjorn Andersson u32 aux_base; 2444b638df4SBjorn Andersson void __iomem *virt_base; 2454b638df4SBjorn Andersson size_t size; 2464b638df4SBjorn Andersson }; 2474b638df4SBjorn Andersson 2484b638df4SBjorn Andersson /** 2494b638df4SBjorn Andersson * struct qcom_smem - device data for the smem device 2504b638df4SBjorn Andersson * @dev: device pointer 2514b638df4SBjorn Andersson * @hwlock: reference to a hwspinlock 252d52e4048SChris Lew * @global_partition: pointer to global partition when in use 253d52e4048SChris Lew * @global_cacheline: cacheline size for global partition 2544b638df4SBjorn Andersson * @partitions: list of pointers to partitions affecting the current 2554b638df4SBjorn Andersson * processor/host 256c7c1dc35SBjorn Andersson * @cacheline: list of cacheline sizes for each host 2575b394067SChris Lew * @item_count: max accepted item number 2584b638df4SBjorn Andersson * @num_regions: number of @regions 2594b638df4SBjorn Andersson * @regions: list of the memory regions defining the shared memory 2604b638df4SBjorn Andersson */ 2614b638df4SBjorn Andersson struct qcom_smem { 2624b638df4SBjorn Andersson struct device *dev; 2634b638df4SBjorn Andersson 2644b638df4SBjorn Andersson struct hwspinlock *hwlock; 2654b638df4SBjorn Andersson 266d52e4048SChris Lew struct smem_partition_header *global_partition; 267d52e4048SChris Lew size_t global_cacheline; 2684b638df4SBjorn Andersson struct smem_partition_header *partitions[SMEM_HOST_COUNT]; 269c7c1dc35SBjorn Andersson size_t cacheline[SMEM_HOST_COUNT]; 2705b394067SChris Lew u32 item_count; 271*efb448d0SImran Khan struct platform_device *socinfo; 2724b638df4SBjorn Andersson 2734b638df4SBjorn Andersson unsigned num_regions; 2749f01b7a8SAlex Elder struct smem_region regions[]; 2754b638df4SBjorn Andersson }; 2764b638df4SBjorn Andersson 277e221a1daSAlex Elder static void * 27801f14154SBjorn Andersson phdr_to_last_uncached_entry(struct smem_partition_header *phdr) 2799806884dSStephen Boyd { 2809806884dSStephen Boyd void *p = phdr; 2819806884dSStephen Boyd 2829806884dSStephen Boyd return p + le32_to_cpu(phdr->offset_free_uncached); 2839806884dSStephen Boyd } 2849806884dSStephen Boyd 285e221a1daSAlex Elder static struct smem_private_entry * 286e221a1daSAlex Elder phdr_to_first_cached_entry(struct smem_partition_header *phdr, 287c7c1dc35SBjorn Andersson size_t cacheline) 288c7c1dc35SBjorn Andersson { 289c7c1dc35SBjorn Andersson void *p = phdr; 29070708749SAlex Elder struct smem_private_entry *e; 291c7c1dc35SBjorn Andersson 29270708749SAlex Elder return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); 293c7c1dc35SBjorn Andersson } 294c7c1dc35SBjorn Andersson 295e221a1daSAlex Elder static void * 296e221a1daSAlex Elder phdr_to_last_cached_entry(struct smem_partition_header *phdr) 2979806884dSStephen Boyd { 2989806884dSStephen Boyd void *p = phdr; 2999806884dSStephen Boyd 3009806884dSStephen Boyd return p + le32_to_cpu(phdr->offset_free_cached); 3019806884dSStephen Boyd } 3029806884dSStephen Boyd 3039806884dSStephen Boyd static struct smem_private_entry * 30401f14154SBjorn Andersson phdr_to_first_uncached_entry(struct smem_partition_header *phdr) 3059806884dSStephen Boyd { 3069806884dSStephen Boyd void *p = phdr; 3079806884dSStephen Boyd 3089806884dSStephen Boyd return p + sizeof(*phdr); 3099806884dSStephen Boyd } 3109806884dSStephen Boyd 3119806884dSStephen Boyd static struct smem_private_entry * 31201f14154SBjorn Andersson uncached_entry_next(struct smem_private_entry *e) 3139806884dSStephen Boyd { 3149806884dSStephen Boyd void *p = e; 3159806884dSStephen Boyd 3169806884dSStephen Boyd return p + sizeof(*e) + le16_to_cpu(e->padding_hdr) + 3179806884dSStephen Boyd le32_to_cpu(e->size); 3189806884dSStephen Boyd } 3199806884dSStephen Boyd 320c7c1dc35SBjorn Andersson static struct smem_private_entry * 321c7c1dc35SBjorn Andersson cached_entry_next(struct smem_private_entry *e, size_t cacheline) 322c7c1dc35SBjorn Andersson { 323c7c1dc35SBjorn Andersson void *p = e; 324c7c1dc35SBjorn Andersson 325c7c1dc35SBjorn Andersson return p - le32_to_cpu(e->size) - ALIGN(sizeof(*e), cacheline); 326c7c1dc35SBjorn Andersson } 327c7c1dc35SBjorn Andersson 32801f14154SBjorn Andersson static void *uncached_entry_to_item(struct smem_private_entry *e) 3299806884dSStephen Boyd { 3309806884dSStephen Boyd void *p = e; 3319806884dSStephen Boyd 3329806884dSStephen Boyd return p + sizeof(*e) + le16_to_cpu(e->padding_hdr); 3339806884dSStephen Boyd } 3349806884dSStephen Boyd 335c7c1dc35SBjorn Andersson static void *cached_entry_to_item(struct smem_private_entry *e) 336c7c1dc35SBjorn Andersson { 337c7c1dc35SBjorn Andersson void *p = e; 338c7c1dc35SBjorn Andersson 339c7c1dc35SBjorn Andersson return p - le32_to_cpu(e->size); 340c7c1dc35SBjorn Andersson } 341c7c1dc35SBjorn Andersson 3424b638df4SBjorn Andersson /* Pointer to the one and only smem handle */ 3434b638df4SBjorn Andersson static struct qcom_smem *__smem; 3444b638df4SBjorn Andersson 3454b638df4SBjorn Andersson /* Timeout (ms) for the trylock of remote spinlocks */ 3464b638df4SBjorn Andersson #define HWSPINLOCK_TIMEOUT 1000 3474b638df4SBjorn Andersson 3484b638df4SBjorn Andersson static int qcom_smem_alloc_private(struct qcom_smem *smem, 349d52e4048SChris Lew struct smem_partition_header *phdr, 3504b638df4SBjorn Andersson unsigned item, 3514b638df4SBjorn Andersson size_t size) 3524b638df4SBjorn Andersson { 3539806884dSStephen Boyd struct smem_private_entry *hdr, *end; 3544b638df4SBjorn Andersson size_t alloc_size; 3559806884dSStephen Boyd void *cached; 3564b638df4SBjorn Andersson 35701f14154SBjorn Andersson hdr = phdr_to_first_uncached_entry(phdr); 35801f14154SBjorn Andersson end = phdr_to_last_uncached_entry(phdr); 35901f14154SBjorn Andersson cached = phdr_to_last_cached_entry(phdr); 3604b638df4SBjorn Andersson 3619806884dSStephen Boyd while (hdr < end) { 36204a512feSAlex Elder if (hdr->canary != SMEM_PRIVATE_CANARY) 36304a512feSAlex Elder goto bad_canary; 3649806884dSStephen Boyd if (le16_to_cpu(hdr->item) == item) 3654b638df4SBjorn Andersson return -EEXIST; 3664b638df4SBjorn Andersson 36701f14154SBjorn Andersson hdr = uncached_entry_next(hdr); 3684b638df4SBjorn Andersson } 3694b638df4SBjorn Andersson 3704b638df4SBjorn Andersson /* Check that we don't grow into the cached region */ 3714b638df4SBjorn Andersson alloc_size = sizeof(*hdr) + ALIGN(size, 8); 3728377f818SAlex Elder if ((void *)hdr + alloc_size > cached) { 3734b638df4SBjorn Andersson dev_err(smem->dev, "Out of memory\n"); 3744b638df4SBjorn Andersson return -ENOSPC; 3754b638df4SBjorn Andersson } 3764b638df4SBjorn Andersson 3774b638df4SBjorn Andersson hdr->canary = SMEM_PRIVATE_CANARY; 3789806884dSStephen Boyd hdr->item = cpu_to_le16(item); 3799806884dSStephen Boyd hdr->size = cpu_to_le32(ALIGN(size, 8)); 3809806884dSStephen Boyd hdr->padding_data = cpu_to_le16(le32_to_cpu(hdr->size) - size); 3814b638df4SBjorn Andersson hdr->padding_hdr = 0; 3824b638df4SBjorn Andersson 3834b638df4SBjorn Andersson /* 3844b638df4SBjorn Andersson * Ensure the header is written before we advance the free offset, so 3854b638df4SBjorn Andersson * that remote processors that does not take the remote spinlock still 3864b638df4SBjorn Andersson * gets a consistent view of the linked list. 3874b638df4SBjorn Andersson */ 3884b638df4SBjorn Andersson wmb(); 3899806884dSStephen Boyd le32_add_cpu(&phdr->offset_free_uncached, alloc_size); 3904b638df4SBjorn Andersson 3914b638df4SBjorn Andersson return 0; 39204a512feSAlex Elder bad_canary: 39304a512feSAlex Elder dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", 39404a512feSAlex Elder le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); 39504a512feSAlex Elder 39604a512feSAlex Elder return -EINVAL; 3974b638df4SBjorn Andersson } 3984b638df4SBjorn Andersson 3994b638df4SBjorn Andersson static int qcom_smem_alloc_global(struct qcom_smem *smem, 4004b638df4SBjorn Andersson unsigned item, 4014b638df4SBjorn Andersson size_t size) 4024b638df4SBjorn Andersson { 4034b638df4SBjorn Andersson struct smem_global_entry *entry; 404d52e4048SChris Lew struct smem_header *header; 4054b638df4SBjorn Andersson 4064b638df4SBjorn Andersson header = smem->regions[0].virt_base; 4074b638df4SBjorn Andersson entry = &header->toc[item]; 4084b638df4SBjorn Andersson if (entry->allocated) 4094b638df4SBjorn Andersson return -EEXIST; 4104b638df4SBjorn Andersson 4114b638df4SBjorn Andersson size = ALIGN(size, 8); 4129806884dSStephen Boyd if (WARN_ON(size > le32_to_cpu(header->available))) 4134b638df4SBjorn Andersson return -ENOMEM; 4144b638df4SBjorn Andersson 4154b638df4SBjorn Andersson entry->offset = header->free_offset; 4169806884dSStephen Boyd entry->size = cpu_to_le32(size); 4174b638df4SBjorn Andersson 4184b638df4SBjorn Andersson /* 4194b638df4SBjorn Andersson * Ensure the header is consistent before we mark the item allocated, 4204b638df4SBjorn Andersson * so that remote processors will get a consistent view of the item 4214b638df4SBjorn Andersson * even though they do not take the spinlock on read. 4224b638df4SBjorn Andersson */ 4234b638df4SBjorn Andersson wmb(); 4249806884dSStephen Boyd entry->allocated = cpu_to_le32(1); 4254b638df4SBjorn Andersson 4269806884dSStephen Boyd le32_add_cpu(&header->free_offset, size); 4279806884dSStephen Boyd le32_add_cpu(&header->available, -size); 4284b638df4SBjorn Andersson 4294b638df4SBjorn Andersson return 0; 4304b638df4SBjorn Andersson } 4314b638df4SBjorn Andersson 4324b638df4SBjorn Andersson /** 4334b638df4SBjorn Andersson * qcom_smem_alloc() - allocate space for a smem item 4344b638df4SBjorn Andersson * @host: remote processor id, or -1 4354b638df4SBjorn Andersson * @item: smem item handle 4364b638df4SBjorn Andersson * @size: number of bytes to be allocated 4374b638df4SBjorn Andersson * 4384b638df4SBjorn Andersson * Allocate space for a given smem item of size @size, given that the item is 4394b638df4SBjorn Andersson * not yet allocated. 4404b638df4SBjorn Andersson */ 4414b638df4SBjorn Andersson int qcom_smem_alloc(unsigned host, unsigned item, size_t size) 4424b638df4SBjorn Andersson { 443d52e4048SChris Lew struct smem_partition_header *phdr; 4444b638df4SBjorn Andersson unsigned long flags; 4454b638df4SBjorn Andersson int ret; 4464b638df4SBjorn Andersson 4474b638df4SBjorn Andersson if (!__smem) 4484b638df4SBjorn Andersson return -EPROBE_DEFER; 4494b638df4SBjorn Andersson 4504b638df4SBjorn Andersson if (item < SMEM_ITEM_LAST_FIXED) { 4514b638df4SBjorn Andersson dev_err(__smem->dev, 4524b638df4SBjorn Andersson "Rejecting allocation of static entry %d\n", item); 4534b638df4SBjorn Andersson return -EINVAL; 4544b638df4SBjorn Andersson } 4554b638df4SBjorn Andersson 4565b394067SChris Lew if (WARN_ON(item >= __smem->item_count)) 4575b394067SChris Lew return -EINVAL; 4585b394067SChris Lew 4594b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 4604b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 4614b638df4SBjorn Andersson &flags); 4624b638df4SBjorn Andersson if (ret) 4634b638df4SBjorn Andersson return ret; 4644b638df4SBjorn Andersson 465d52e4048SChris Lew if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 466d52e4048SChris Lew phdr = __smem->partitions[host]; 467d52e4048SChris Lew ret = qcom_smem_alloc_private(__smem, phdr, item, size); 468d52e4048SChris Lew } else if (__smem->global_partition) { 469d52e4048SChris Lew phdr = __smem->global_partition; 470d52e4048SChris Lew ret = qcom_smem_alloc_private(__smem, phdr, item, size); 471d52e4048SChris Lew } else { 4724b638df4SBjorn Andersson ret = qcom_smem_alloc_global(__smem, item, size); 473d52e4048SChris Lew } 4744b638df4SBjorn Andersson 4754b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 4764b638df4SBjorn Andersson 4774b638df4SBjorn Andersson return ret; 4784b638df4SBjorn Andersson } 4794b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_alloc); 4804b638df4SBjorn Andersson 4811a03964dSStephen Boyd static void *qcom_smem_get_global(struct qcom_smem *smem, 4824b638df4SBjorn Andersson unsigned item, 4834b638df4SBjorn Andersson size_t *size) 4844b638df4SBjorn Andersson { 4854b638df4SBjorn Andersson struct smem_header *header; 4869f01b7a8SAlex Elder struct smem_region *region; 4874b638df4SBjorn Andersson struct smem_global_entry *entry; 4884b638df4SBjorn Andersson u32 aux_base; 4894b638df4SBjorn Andersson unsigned i; 4904b638df4SBjorn Andersson 4914b638df4SBjorn Andersson header = smem->regions[0].virt_base; 4924b638df4SBjorn Andersson entry = &header->toc[item]; 4934b638df4SBjorn Andersson if (!entry->allocated) 4941a03964dSStephen Boyd return ERR_PTR(-ENXIO); 4954b638df4SBjorn Andersson 4969806884dSStephen Boyd aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; 4974b638df4SBjorn Andersson 4984b638df4SBjorn Andersson for (i = 0; i < smem->num_regions; i++) { 4999f01b7a8SAlex Elder region = &smem->regions[i]; 5004b638df4SBjorn Andersson 5019f01b7a8SAlex Elder if (region->aux_base == aux_base || !aux_base) { 5024b638df4SBjorn Andersson if (size != NULL) 5039806884dSStephen Boyd *size = le32_to_cpu(entry->size); 5049f01b7a8SAlex Elder return region->virt_base + le32_to_cpu(entry->offset); 5051a03964dSStephen Boyd } 5064b638df4SBjorn Andersson } 5074b638df4SBjorn Andersson 5081a03964dSStephen Boyd return ERR_PTR(-ENOENT); 5091a03964dSStephen Boyd } 5101a03964dSStephen Boyd 5111a03964dSStephen Boyd static void *qcom_smem_get_private(struct qcom_smem *smem, 512d52e4048SChris Lew struct smem_partition_header *phdr, 513d52e4048SChris Lew size_t cacheline, 5144b638df4SBjorn Andersson unsigned item, 5154b638df4SBjorn Andersson size_t *size) 5164b638df4SBjorn Andersson { 5179806884dSStephen Boyd struct smem_private_entry *e, *end; 518c7c1dc35SBjorn Andersson 51901f14154SBjorn Andersson e = phdr_to_first_uncached_entry(phdr); 52001f14154SBjorn Andersson end = phdr_to_last_uncached_entry(phdr); 5214b638df4SBjorn Andersson 5229806884dSStephen Boyd while (e < end) { 523c7c1dc35SBjorn Andersson if (e->canary != SMEM_PRIVATE_CANARY) 524c7c1dc35SBjorn Andersson goto invalid_canary; 5254b638df4SBjorn Andersson 5269806884dSStephen Boyd if (le16_to_cpu(e->item) == item) { 5274b638df4SBjorn Andersson if (size != NULL) 5289806884dSStephen Boyd *size = le32_to_cpu(e->size) - 5299806884dSStephen Boyd le16_to_cpu(e->padding_data); 5304b638df4SBjorn Andersson 53101f14154SBjorn Andersson return uncached_entry_to_item(e); 5324b638df4SBjorn Andersson } 5334b638df4SBjorn Andersson 53401f14154SBjorn Andersson e = uncached_entry_next(e); 5354b638df4SBjorn Andersson } 5364b638df4SBjorn Andersson 537c7c1dc35SBjorn Andersson /* Item was not found in the uncached list, search the cached list */ 538c7c1dc35SBjorn Andersson 539c7c1dc35SBjorn Andersson e = phdr_to_first_cached_entry(phdr, cacheline); 540c7c1dc35SBjorn Andersson end = phdr_to_last_cached_entry(phdr); 541c7c1dc35SBjorn Andersson 542c7c1dc35SBjorn Andersson while (e > end) { 543c7c1dc35SBjorn Andersson if (e->canary != SMEM_PRIVATE_CANARY) 544c7c1dc35SBjorn Andersson goto invalid_canary; 545c7c1dc35SBjorn Andersson 546c7c1dc35SBjorn Andersson if (le16_to_cpu(e->item) == item) { 547c7c1dc35SBjorn Andersson if (size != NULL) 548c7c1dc35SBjorn Andersson *size = le32_to_cpu(e->size) - 549c7c1dc35SBjorn Andersson le16_to_cpu(e->padding_data); 550c7c1dc35SBjorn Andersson 551c7c1dc35SBjorn Andersson return cached_entry_to_item(e); 552c7c1dc35SBjorn Andersson } 553c7c1dc35SBjorn Andersson 554c7c1dc35SBjorn Andersson e = cached_entry_next(e, cacheline); 555c7c1dc35SBjorn Andersson } 556c7c1dc35SBjorn Andersson 5571a03964dSStephen Boyd return ERR_PTR(-ENOENT); 558c7c1dc35SBjorn Andersson 559c7c1dc35SBjorn Andersson invalid_canary: 56004a512feSAlex Elder dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", 56104a512feSAlex Elder le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); 562c7c1dc35SBjorn Andersson 563c7c1dc35SBjorn Andersson return ERR_PTR(-EINVAL); 5644b638df4SBjorn Andersson } 5654b638df4SBjorn Andersson 5664b638df4SBjorn Andersson /** 5674b638df4SBjorn Andersson * qcom_smem_get() - resolve ptr of size of a smem item 5684b638df4SBjorn Andersson * @host: the remote processor, or -1 5694b638df4SBjorn Andersson * @item: smem item handle 5704b638df4SBjorn Andersson * @size: pointer to be filled out with size of the item 5714b638df4SBjorn Andersson * 5721a03964dSStephen Boyd * Looks up smem item and returns pointer to it. Size of smem 5731a03964dSStephen Boyd * item is returned in @size. 5744b638df4SBjorn Andersson */ 5751a03964dSStephen Boyd void *qcom_smem_get(unsigned host, unsigned item, size_t *size) 5764b638df4SBjorn Andersson { 577d52e4048SChris Lew struct smem_partition_header *phdr; 5784b638df4SBjorn Andersson unsigned long flags; 579d52e4048SChris Lew size_t cacheln; 5804b638df4SBjorn Andersson int ret; 5811a03964dSStephen Boyd void *ptr = ERR_PTR(-EPROBE_DEFER); 5824b638df4SBjorn Andersson 5834b638df4SBjorn Andersson if (!__smem) 5841a03964dSStephen Boyd return ptr; 5854b638df4SBjorn Andersson 5865b394067SChris Lew if (WARN_ON(item >= __smem->item_count)) 5875b394067SChris Lew return ERR_PTR(-EINVAL); 5885b394067SChris Lew 5894b638df4SBjorn Andersson ret = hwspin_lock_timeout_irqsave(__smem->hwlock, 5904b638df4SBjorn Andersson HWSPINLOCK_TIMEOUT, 5914b638df4SBjorn Andersson &flags); 5924b638df4SBjorn Andersson if (ret) 5931a03964dSStephen Boyd return ERR_PTR(ret); 5944b638df4SBjorn Andersson 595d52e4048SChris Lew if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 596d52e4048SChris Lew phdr = __smem->partitions[host]; 597d52e4048SChris Lew cacheln = __smem->cacheline[host]; 598d52e4048SChris Lew ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); 599d52e4048SChris Lew } else if (__smem->global_partition) { 600d52e4048SChris Lew phdr = __smem->global_partition; 601d52e4048SChris Lew cacheln = __smem->global_cacheline; 602d52e4048SChris Lew ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); 603d52e4048SChris Lew } else { 6041a03964dSStephen Boyd ptr = qcom_smem_get_global(__smem, item, size); 605d52e4048SChris Lew } 6064b638df4SBjorn Andersson 6074b638df4SBjorn Andersson hwspin_unlock_irqrestore(__smem->hwlock, &flags); 6081a03964dSStephen Boyd 6091a03964dSStephen Boyd return ptr; 6104b638df4SBjorn Andersson 6114b638df4SBjorn Andersson } 6124b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get); 6134b638df4SBjorn Andersson 6144b638df4SBjorn Andersson /** 6154b638df4SBjorn Andersson * qcom_smem_get_free_space() - retrieve amount of free space in a partition 6164b638df4SBjorn Andersson * @host: the remote processor identifying a partition, or -1 6174b638df4SBjorn Andersson * 6184b638df4SBjorn Andersson * To be used by smem clients as a quick way to determine if any new 6194b638df4SBjorn Andersson * allocations has been made. 6204b638df4SBjorn Andersson */ 6214b638df4SBjorn Andersson int qcom_smem_get_free_space(unsigned host) 6224b638df4SBjorn Andersson { 6234b638df4SBjorn Andersson struct smem_partition_header *phdr; 6244b638df4SBjorn Andersson struct smem_header *header; 6254b638df4SBjorn Andersson unsigned ret; 6264b638df4SBjorn Andersson 6274b638df4SBjorn Andersson if (!__smem) 6284b638df4SBjorn Andersson return -EPROBE_DEFER; 6294b638df4SBjorn Andersson 6304b638df4SBjorn Andersson if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { 6314b638df4SBjorn Andersson phdr = __smem->partitions[host]; 6329806884dSStephen Boyd ret = le32_to_cpu(phdr->offset_free_cached) - 6339806884dSStephen Boyd le32_to_cpu(phdr->offset_free_uncached); 634d52e4048SChris Lew } else if (__smem->global_partition) { 635d52e4048SChris Lew phdr = __smem->global_partition; 636d52e4048SChris Lew ret = le32_to_cpu(phdr->offset_free_cached) - 637d52e4048SChris Lew le32_to_cpu(phdr->offset_free_uncached); 6384b638df4SBjorn Andersson } else { 6394b638df4SBjorn Andersson header = __smem->regions[0].virt_base; 6409806884dSStephen Boyd ret = le32_to_cpu(header->available); 6414b638df4SBjorn Andersson } 6424b638df4SBjorn Andersson 6434b638df4SBjorn Andersson return ret; 6444b638df4SBjorn Andersson } 6454b638df4SBjorn Andersson EXPORT_SYMBOL(qcom_smem_get_free_space); 6464b638df4SBjorn Andersson 6476d361c1dSAlex Elder /** 6486d361c1dSAlex Elder * qcom_smem_virt_to_phys() - return the physical address associated 6496d361c1dSAlex Elder * with an smem item pointer (previously returned by qcom_smem_get() 6506d361c1dSAlex Elder * @p: the virtual address to convert 6516d361c1dSAlex Elder * 6526d361c1dSAlex Elder * Returns 0 if the pointer provided is not within any smem region. 6536d361c1dSAlex Elder */ 6546d361c1dSAlex Elder phys_addr_t qcom_smem_virt_to_phys(void *p) 6556d361c1dSAlex Elder { 6566d361c1dSAlex Elder unsigned i; 6576d361c1dSAlex Elder 6586d361c1dSAlex Elder for (i = 0; i < __smem->num_regions; i++) { 6596d361c1dSAlex Elder struct smem_region *region = &__smem->regions[i]; 6606d361c1dSAlex Elder 6616d361c1dSAlex Elder if (p < region->virt_base) 6626d361c1dSAlex Elder continue; 6636d361c1dSAlex Elder if (p < region->virt_base + region->size) { 6646d361c1dSAlex Elder u64 offset = p - region->virt_base; 6656d361c1dSAlex Elder 6666d361c1dSAlex Elder return (phys_addr_t)region->aux_base + offset; 6676d361c1dSAlex Elder } 6686d361c1dSAlex Elder } 6696d361c1dSAlex Elder 6706d361c1dSAlex Elder return 0; 6716d361c1dSAlex Elder } 6726d361c1dSAlex Elder EXPORT_SYMBOL(qcom_smem_virt_to_phys); 6736d361c1dSAlex Elder 6744b638df4SBjorn Andersson static int qcom_smem_get_sbl_version(struct qcom_smem *smem) 6754b638df4SBjorn Andersson { 676dcc0967dSChris Lew struct smem_header *header; 6779806884dSStephen Boyd __le32 *versions; 6784b638df4SBjorn Andersson 679dcc0967dSChris Lew header = smem->regions[0].virt_base; 680dcc0967dSChris Lew versions = header->version; 6814b638df4SBjorn Andersson 6829806884dSStephen Boyd return le32_to_cpu(versions[SMEM_MASTER_SBL_VERSION_INDEX]); 6834b638df4SBjorn Andersson } 6844b638df4SBjorn Andersson 685d52e4048SChris Lew static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem) 6864b638df4SBjorn Andersson { 6874b638df4SBjorn Andersson struct smem_ptable *ptable; 688d52e4048SChris Lew u32 version; 6894b638df4SBjorn Andersson 6904b638df4SBjorn Andersson ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; 6919806884dSStephen Boyd if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) 692d52e4048SChris Lew return ERR_PTR(-ENOENT); 6934b638df4SBjorn Andersson 6949806884dSStephen Boyd version = le32_to_cpu(ptable->version); 6959806884dSStephen Boyd if (version != 1) { 6964b638df4SBjorn Andersson dev_err(smem->dev, 6979806884dSStephen Boyd "Unsupported partition header version %d\n", version); 698d52e4048SChris Lew return ERR_PTR(-EINVAL); 699d52e4048SChris Lew } 700d52e4048SChris Lew return ptable; 701d52e4048SChris Lew } 702d52e4048SChris Lew 7035b394067SChris Lew static u32 qcom_smem_get_item_count(struct qcom_smem *smem) 7045b394067SChris Lew { 7055b394067SChris Lew struct smem_ptable *ptable; 7065b394067SChris Lew struct smem_info *info; 7075b394067SChris Lew 7085b394067SChris Lew ptable = qcom_smem_get_ptable(smem); 7095b394067SChris Lew if (IS_ERR_OR_NULL(ptable)) 7105b394067SChris Lew return SMEM_ITEM_COUNT; 7115b394067SChris Lew 7125b394067SChris Lew info = (struct smem_info *)&ptable->entry[ptable->num_entries]; 7135b394067SChris Lew if (memcmp(info->magic, SMEM_INFO_MAGIC, sizeof(info->magic))) 7145b394067SChris Lew return SMEM_ITEM_COUNT; 7155b394067SChris Lew 7165b394067SChris Lew return le16_to_cpu(info->num_items); 7175b394067SChris Lew } 7185b394067SChris Lew 719ada79289SAlex Elder /* 720ada79289SAlex Elder * Validate the partition header for a partition whose partition 721ada79289SAlex Elder * table entry is supplied. Returns a pointer to its header if 722ada79289SAlex Elder * valid, or a null pointer otherwise. 723ada79289SAlex Elder */ 724ada79289SAlex Elder static struct smem_partition_header * 725ada79289SAlex Elder qcom_smem_partition_header(struct qcom_smem *smem, 7267d019344SAlex Elder struct smem_ptable_entry *entry, u16 host0, u16 host1) 727ada79289SAlex Elder { 728ada79289SAlex Elder struct smem_partition_header *header; 729190b216cSAlex Elder u32 size; 730ada79289SAlex Elder 731ada79289SAlex Elder header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); 732ada79289SAlex Elder 733ada79289SAlex Elder if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { 734ada79289SAlex Elder dev_err(smem->dev, "bad partition magic %02x %02x %02x %02x\n", 735ada79289SAlex Elder header->magic[0], header->magic[1], 736ada79289SAlex Elder header->magic[2], header->magic[3]); 737ada79289SAlex Elder return NULL; 738ada79289SAlex Elder } 739ada79289SAlex Elder 7407d019344SAlex Elder if (host0 != le16_to_cpu(header->host0)) { 7417d019344SAlex Elder dev_err(smem->dev, "bad host0 (%hu != %hu)\n", 7427d019344SAlex Elder host0, le16_to_cpu(header->host0)); 7437d019344SAlex Elder return NULL; 7447d019344SAlex Elder } 7457d019344SAlex Elder if (host1 != le16_to_cpu(header->host1)) { 7467d019344SAlex Elder dev_err(smem->dev, "bad host1 (%hu != %hu)\n", 7477d019344SAlex Elder host1, le16_to_cpu(header->host1)); 7487d019344SAlex Elder return NULL; 7497d019344SAlex Elder } 7507d019344SAlex Elder 751190b216cSAlex Elder size = le32_to_cpu(header->size); 752190b216cSAlex Elder if (size != le32_to_cpu(entry->size)) { 753190b216cSAlex Elder dev_err(smem->dev, "bad partition size (%u != %u)\n", 754190b216cSAlex Elder size, le32_to_cpu(entry->size)); 755190b216cSAlex Elder return NULL; 756190b216cSAlex Elder } 757190b216cSAlex Elder 758380dc4afSAlex Elder if (le32_to_cpu(header->offset_free_uncached) > size) { 759380dc4afSAlex Elder dev_err(smem->dev, "bad partition free uncached (%u > %u)\n", 760380dc4afSAlex Elder le32_to_cpu(header->offset_free_uncached), size); 761380dc4afSAlex Elder return NULL; 762380dc4afSAlex Elder } 763380dc4afSAlex Elder 764ada79289SAlex Elder return header; 765ada79289SAlex Elder } 766ada79289SAlex Elder 767d52e4048SChris Lew static int qcom_smem_set_global_partition(struct qcom_smem *smem) 768d52e4048SChris Lew { 769d52e4048SChris Lew struct smem_partition_header *header; 7708fa1a214SAlex Elder struct smem_ptable_entry *entry; 771d52e4048SChris Lew struct smem_ptable *ptable; 7728fa1a214SAlex Elder bool found = false; 773d52e4048SChris Lew int i; 774d52e4048SChris Lew 7750b65c59eSBjorn Andersson if (smem->global_partition) { 7760b65c59eSBjorn Andersson dev_err(smem->dev, "Already found the global partition\n"); 7770b65c59eSBjorn Andersson return -EINVAL; 7780b65c59eSBjorn Andersson } 7790b65c59eSBjorn Andersson 780d52e4048SChris Lew ptable = qcom_smem_get_ptable(smem); 781d52e4048SChris Lew if (IS_ERR(ptable)) 782d52e4048SChris Lew return PTR_ERR(ptable); 783d52e4048SChris Lew 784d52e4048SChris Lew for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { 785d52e4048SChris Lew entry = &ptable->entry[i]; 786eba75702SAlex Elder if (!le32_to_cpu(entry->offset)) 787eba75702SAlex Elder continue; 788eba75702SAlex Elder if (!le32_to_cpu(entry->size)) 789eba75702SAlex Elder continue; 790eba75702SAlex Elder 79133fdbc4eSAlex Elder if (le16_to_cpu(entry->host0) != SMEM_GLOBAL_HOST) 79233fdbc4eSAlex Elder continue; 79333fdbc4eSAlex Elder 79433fdbc4eSAlex Elder if (le16_to_cpu(entry->host1) == SMEM_GLOBAL_HOST) { 7958fa1a214SAlex Elder found = true; 796d52e4048SChris Lew break; 797d52e4048SChris Lew } 7988fa1a214SAlex Elder } 799d52e4048SChris Lew 8008fa1a214SAlex Elder if (!found) { 801d52e4048SChris Lew dev_err(smem->dev, "Missing entry for global partition\n"); 8024b638df4SBjorn Andersson return -EINVAL; 8034b638df4SBjorn Andersson } 8044b638df4SBjorn Andersson 8057d019344SAlex Elder header = qcom_smem_partition_header(smem, entry, 8067d019344SAlex Elder SMEM_GLOBAL_HOST, SMEM_GLOBAL_HOST); 807ada79289SAlex Elder if (!header) 808ada79289SAlex Elder return -EINVAL; 809ada79289SAlex Elder 810d52e4048SChris Lew smem->global_partition = header; 811d52e4048SChris Lew smem->global_cacheline = le32_to_cpu(entry->cacheline); 812d52e4048SChris Lew 813d52e4048SChris Lew return 0; 814d52e4048SChris Lew } 815d52e4048SChris Lew 81613a920aeSAlex Elder static int 81713a920aeSAlex Elder qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) 818d52e4048SChris Lew { 819d52e4048SChris Lew struct smem_partition_header *header; 820d52e4048SChris Lew struct smem_ptable_entry *entry; 821d52e4048SChris Lew struct smem_ptable *ptable; 822d52e4048SChris Lew unsigned int remote_host; 82313a920aeSAlex Elder u16 host0, host1; 824d52e4048SChris Lew int i; 825d52e4048SChris Lew 826d52e4048SChris Lew ptable = qcom_smem_get_ptable(smem); 827d52e4048SChris Lew if (IS_ERR(ptable)) 828d52e4048SChris Lew return PTR_ERR(ptable); 829d52e4048SChris Lew 8309806884dSStephen Boyd for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { 8314b638df4SBjorn Andersson entry = &ptable->entry[i]; 8329806884dSStephen Boyd if (!le32_to_cpu(entry->offset)) 8334b638df4SBjorn Andersson continue; 8349806884dSStephen Boyd if (!le32_to_cpu(entry->size)) 8354b638df4SBjorn Andersson continue; 8364b638df4SBjorn Andersson 837eba75702SAlex Elder host0 = le16_to_cpu(entry->host0); 838eba75702SAlex Elder host1 = le16_to_cpu(entry->host1); 8399806884dSStephen Boyd if (host0 == local_host) 8409806884dSStephen Boyd remote_host = host1; 841eb68cf09SAlex Elder else if (host1 == local_host) 8429806884dSStephen Boyd remote_host = host0; 843eb68cf09SAlex Elder else 844eb68cf09SAlex Elder continue; 8454b638df4SBjorn Andersson 8464b638df4SBjorn Andersson if (remote_host >= SMEM_HOST_COUNT) { 84713a920aeSAlex Elder dev_err(smem->dev, "bad host %hu\n", remote_host); 8484b638df4SBjorn Andersson return -EINVAL; 8494b638df4SBjorn Andersson } 8504b638df4SBjorn Andersson 8514b638df4SBjorn Andersson if (smem->partitions[remote_host]) { 85213a920aeSAlex Elder dev_err(smem->dev, "duplicate host %hu\n", remote_host); 8534b638df4SBjorn Andersson return -EINVAL; 8544b638df4SBjorn Andersson } 8554b638df4SBjorn Andersson 8567d019344SAlex Elder header = qcom_smem_partition_header(smem, entry, host0, host1); 857ada79289SAlex Elder if (!header) 858ada79289SAlex Elder return -EINVAL; 859ada79289SAlex Elder 8604b638df4SBjorn Andersson smem->partitions[remote_host] = header; 861c7c1dc35SBjorn Andersson smem->cacheline[remote_host] = le32_to_cpu(entry->cacheline); 8624b638df4SBjorn Andersson } 8634b638df4SBjorn Andersson 8644b638df4SBjorn Andersson return 0; 8654b638df4SBjorn Andersson } 8664b638df4SBjorn Andersson 867d0bfd7c9SStephen Boyd static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, 868d0bfd7c9SStephen Boyd const char *name, int i) 8694b638df4SBjorn Andersson { 870d0bfd7c9SStephen Boyd struct device_node *np; 871d0bfd7c9SStephen Boyd struct resource r; 872100d26e8SAlex Elder resource_size_t size; 873d0bfd7c9SStephen Boyd int ret; 8744b638df4SBjorn Andersson 875d0bfd7c9SStephen Boyd np = of_parse_phandle(dev->of_node, name, 0); 876d0bfd7c9SStephen Boyd if (!np) { 877d0bfd7c9SStephen Boyd dev_err(dev, "No %s specified\n", name); 878d0bfd7c9SStephen Boyd return -EINVAL; 8794b638df4SBjorn Andersson } 8804b638df4SBjorn Andersson 881d0bfd7c9SStephen Boyd ret = of_address_to_resource(np, 0, &r); 882d0bfd7c9SStephen Boyd of_node_put(np); 883d0bfd7c9SStephen Boyd if (ret) 884d0bfd7c9SStephen Boyd return ret; 885100d26e8SAlex Elder size = resource_size(&r); 886d0bfd7c9SStephen Boyd 887100d26e8SAlex Elder smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, size); 888d0bfd7c9SStephen Boyd if (!smem->regions[i].virt_base) 889d0bfd7c9SStephen Boyd return -ENOMEM; 890100d26e8SAlex Elder smem->regions[i].aux_base = (u32)r.start; 891100d26e8SAlex Elder smem->regions[i].size = size; 892d0bfd7c9SStephen Boyd 893d0bfd7c9SStephen Boyd return 0; 8944b638df4SBjorn Andersson } 8954b638df4SBjorn Andersson 8964b638df4SBjorn Andersson static int qcom_smem_probe(struct platform_device *pdev) 8974b638df4SBjorn Andersson { 8984b638df4SBjorn Andersson struct smem_header *header; 8994b638df4SBjorn Andersson struct qcom_smem *smem; 9004b638df4SBjorn Andersson size_t array_size; 901d0bfd7c9SStephen Boyd int num_regions; 9024b638df4SBjorn Andersson int hwlock_id; 9034b638df4SBjorn Andersson u32 version; 9044b638df4SBjorn Andersson int ret; 9054b638df4SBjorn Andersson 906d0bfd7c9SStephen Boyd num_regions = 1; 907d0bfd7c9SStephen Boyd if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL)) 908d0bfd7c9SStephen Boyd num_regions++; 9094b638df4SBjorn Andersson 9104b638df4SBjorn Andersson array_size = num_regions * sizeof(struct smem_region); 9114b638df4SBjorn Andersson smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); 9124b638df4SBjorn Andersson if (!smem) 9134b638df4SBjorn Andersson return -ENOMEM; 9144b638df4SBjorn Andersson 9154b638df4SBjorn Andersson smem->dev = &pdev->dev; 9164b638df4SBjorn Andersson smem->num_regions = num_regions; 9174b638df4SBjorn Andersson 918d0bfd7c9SStephen Boyd ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); 9194b638df4SBjorn Andersson if (ret) 9204b638df4SBjorn Andersson return ret; 9214b638df4SBjorn Andersson 922d0bfd7c9SStephen Boyd if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev, 923d0bfd7c9SStephen Boyd "qcom,rpm-msg-ram", 1))) 924d0bfd7c9SStephen Boyd return ret; 9254b638df4SBjorn Andersson 9264b638df4SBjorn Andersson header = smem->regions[0].virt_base; 9279806884dSStephen Boyd if (le32_to_cpu(header->initialized) != 1 || 9289806884dSStephen Boyd le32_to_cpu(header->reserved)) { 9294b638df4SBjorn Andersson dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); 9304b638df4SBjorn Andersson return -EINVAL; 9314b638df4SBjorn Andersson } 9324b638df4SBjorn Andersson 9334b638df4SBjorn Andersson version = qcom_smem_get_sbl_version(smem); 934d52e4048SChris Lew switch (version >> 16) { 935d52e4048SChris Lew case SMEM_GLOBAL_PART_VERSION: 936d52e4048SChris Lew ret = qcom_smem_set_global_partition(smem); 937d52e4048SChris Lew if (ret < 0) 938d52e4048SChris Lew return ret; 9395b394067SChris Lew smem->item_count = qcom_smem_get_item_count(smem); 9405b394067SChris Lew break; 941d52e4048SChris Lew case SMEM_GLOBAL_HEAP_VERSION: 9425b394067SChris Lew smem->item_count = SMEM_ITEM_COUNT; 943d52e4048SChris Lew break; 944d52e4048SChris Lew default: 9454b638df4SBjorn Andersson dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); 9464b638df4SBjorn Andersson return -EINVAL; 9474b638df4SBjorn Andersson } 9484b638df4SBjorn Andersson 94913a920aeSAlex Elder BUILD_BUG_ON(SMEM_HOST_APPS >= SMEM_HOST_COUNT); 9504b638df4SBjorn Andersson ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); 951d52e4048SChris Lew if (ret < 0 && ret != -ENOENT) 9524b638df4SBjorn Andersson return ret; 9534b638df4SBjorn Andersson 9544b638df4SBjorn Andersson hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); 9554b638df4SBjorn Andersson if (hwlock_id < 0) { 956951a5af9SStephen Boyd if (hwlock_id != -EPROBE_DEFER) 9574b638df4SBjorn Andersson dev_err(&pdev->dev, "failed to retrieve hwlock\n"); 9584b638df4SBjorn Andersson return hwlock_id; 9594b638df4SBjorn Andersson } 9604b638df4SBjorn Andersson 9614b638df4SBjorn Andersson smem->hwlock = hwspin_lock_request_specific(hwlock_id); 9624b638df4SBjorn Andersson if (!smem->hwlock) 9634b638df4SBjorn Andersson return -ENXIO; 9644b638df4SBjorn Andersson 9654b638df4SBjorn Andersson __smem = smem; 9664b638df4SBjorn Andersson 967*efb448d0SImran Khan smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo", 968*efb448d0SImran Khan PLATFORM_DEVID_NONE, NULL, 969*efb448d0SImran Khan 0); 970*efb448d0SImran Khan if (IS_ERR(smem->socinfo)) 971*efb448d0SImran Khan dev_dbg(&pdev->dev, "failed to register socinfo device\n"); 972*efb448d0SImran Khan 9734b638df4SBjorn Andersson return 0; 9744b638df4SBjorn Andersson } 9754b638df4SBjorn Andersson 9764b638df4SBjorn Andersson static int qcom_smem_remove(struct platform_device *pdev) 9774b638df4SBjorn Andersson { 978*efb448d0SImran Khan platform_device_unregister(__smem->socinfo); 979*efb448d0SImran Khan 9804b638df4SBjorn Andersson hwspin_lock_free(__smem->hwlock); 981f8c67df7SStephen Boyd __smem = NULL; 9824b638df4SBjorn Andersson 9834b638df4SBjorn Andersson return 0; 9844b638df4SBjorn Andersson } 9854b638df4SBjorn Andersson 9864b638df4SBjorn Andersson static const struct of_device_id qcom_smem_of_match[] = { 9874b638df4SBjorn Andersson { .compatible = "qcom,smem" }, 9884b638df4SBjorn Andersson {} 9894b638df4SBjorn Andersson }; 9904b638df4SBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smem_of_match); 9914b638df4SBjorn Andersson 9924b638df4SBjorn Andersson static struct platform_driver qcom_smem_driver = { 9934b638df4SBjorn Andersson .probe = qcom_smem_probe, 9944b638df4SBjorn Andersson .remove = qcom_smem_remove, 9954b638df4SBjorn Andersson .driver = { 9964b638df4SBjorn Andersson .name = "qcom-smem", 9974b638df4SBjorn Andersson .of_match_table = qcom_smem_of_match, 9984b638df4SBjorn Andersson .suppress_bind_attrs = true, 9994b638df4SBjorn Andersson }, 10004b638df4SBjorn Andersson }; 10014b638df4SBjorn Andersson 10024b638df4SBjorn Andersson static int __init qcom_smem_init(void) 10034b638df4SBjorn Andersson { 10044b638df4SBjorn Andersson return platform_driver_register(&qcom_smem_driver); 10054b638df4SBjorn Andersson } 10064b638df4SBjorn Andersson arch_initcall(qcom_smem_init); 10074b638df4SBjorn Andersson 10084b638df4SBjorn Andersson static void __exit qcom_smem_exit(void) 10094b638df4SBjorn Andersson { 10104b638df4SBjorn Andersson platform_driver_unregister(&qcom_smem_driver); 10114b638df4SBjorn Andersson } 10124b638df4SBjorn Andersson module_exit(qcom_smem_exit) 10134b638df4SBjorn Andersson 10144b638df4SBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 10154b638df4SBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); 10164b638df4SBjorn Andersson MODULE_LICENSE("GPL v2"); 1017